From fe934644acadfe9d7fcbf870948264a5f3c41c61 Mon Sep 17 00:00:00 2001 From: alexkar598 <> Date: Mon, 12 Aug 2019 07:47:43 -0400 Subject: [PATCH] Actually commits the line ending changes --- README.md | 328 +- _maps/boxstation.dm | 2 +- _maps/deltastation.dm | 2 +- _maps/metastation.dm | 2 +- _maps/pubbystation.dm | 2 +- _maps/runtimestation.dm | 2 +- code/__DEFINES/_protect.dm | 18 +- code/__DEFINES/_readme.dm | 26 +- code/__DEFINES/admin.dm | 178 +- code/__DEFINES/atmospherics.dm | 624 +- code/__DEFINES/bsql.config.dm | 12 +- code/__DEFINES/bsql.dm | 270 +- code/__DEFINES/combat.dm | 390 +- code/__DEFINES/configuration.dm | 18 +- code/__DEFINES/forensics.dm | 2 +- code/__DEFINES/hud.dm | 22 +- code/__DEFINES/interaction_flags.dm | 44 +- code/__DEFINES/jobs.dm | 188 +- code/__DEFINES/machines.dm | 216 +- code/__DEFINES/medal.dm | 64 +- code/__DEFINES/move_force.dm | 40 +- code/__DEFINES/movespeed_modification.dm | 128 +- code/__DEFINES/networks.dm | 6 +- code/__DEFINES/preferences.dm | 166 +- code/__DEFINES/radio.dm | 230 +- code/__DEFINES/reagents_specific_heat.dm | 6 +- code/__DEFINES/research.dm | 154 +- code/__DEFINES/sight.dm | 70 +- code/__DEFINES/sound.dm | 150 +- code/__DEFINES/stat.dm | 44 +- code/__DEFINES/tgs.config.dm | 22 +- code/__DEFINES/tgs.dm | 516 +- code/__DEFINES/tools.dm | 30 +- code/__DEFINES/wires.dm | 100 +- code/__HELPERS/_lists.dm | 1178 +- code/__HELPERS/_string_lists.dm | 82 +- code/__HELPERS/files.dm | 146 +- code/__HELPERS/game.dm | 1146 +- code/__HELPERS/global_lists.dm | 142 +- code/__HELPERS/icons.dm | 2404 +- code/__HELPERS/mobs.dm | 950 +- code/__HELPERS/mouse_control.dm | 104 +- code/__HELPERS/names.dm | 508 +- code/__HELPERS/qdel.dm | 20 +- code/__HELPERS/radio.dm | 28 +- code/__HELPERS/sanitize_values.dm | 146 +- code/__HELPERS/text.dm | 1642 +- code/__HELPERS/time.dm | 190 +- code/__HELPERS/type2type.dm | 1242 +- code/__HELPERS/unused.dm | 78 +- code/_compile_options.dm | 114 +- code/_globalvars/bitfields.dm | 370 +- code/_globalvars/configuration.dm | 70 +- code/_globalvars/game_modes.dm | 22 +- code/_globalvars/genetics.dm | 18 +- code/_globalvars/lists/flavor_misc.dm | 386 +- code/_globalvars/lists/mapping.dm | 104 +- code/_globalvars/lists/mobs.dm | 144 +- code/_globalvars/lists/names.dm | 102 +- code/_globalvars/lists/objects.dm | 82 +- code/_globalvars/logging.dm | 144 +- code/_globalvars/misc.dm | 58 +- code/_js/byjax.dm | 96 +- code/_js/menus.dm | 72 +- code/_onclick/adjacent.dm | 210 +- code/_onclick/click.dm | 1040 +- code/_onclick/cyborg.dm | 352 +- code/_onclick/drag_drop.dm | 290 +- code/_onclick/hud/_defines.dm | 358 +- code/_onclick/hud/alien.dm | 246 +- code/_onclick/hud/alien_larva.dm | 60 +- code/_onclick/hud/credits.dm | 138 +- code/_onclick/hud/fullscreen.dm | 372 +- code/_onclick/hud/ghost.dm | 178 +- code/_onclick/hud/hud.dm | 588 +- code/_onclick/hud/human.dm | 884 +- code/_onclick/hud/monkey.dm | 302 +- code/_onclick/hud/robot.dm | 544 +- code/_onclick/hud/screen_objects.dm | 1434 +- code/_onclick/item_attack.dm | 304 +- code/_onclick/observer.dm | 172 +- code/_onclick/other_mobs.dm | 516 +- code/_onclick/overmind.dm | 68 +- .../controllers/configuration/config_entry.dm | 392 +- .../configuration/configuration.dm | 794 +- .../configuration/entries/comms.dm | 66 +- .../configuration/entries/dbconfig.dm | 102 +- .../configuration/entries/game_options.dm | 760 +- .../configuration/entries/general.dm | 976 +- code/controllers/failsafe.dm | 204 +- code/controllers/globals.dm | 110 +- code/controllers/subsystem/atoms.dm | 306 +- code/controllers/subsystem/blackbox.dm | 672 +- code/controllers/subsystem/dbcore.dm | 804 +- 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 | 522 +- code/controllers/subsystem/spacedrift.dm | 118 +- code/datums/actions/beam_rifle.dm | 24 +- code/datums/actions/ninja.dm | 102 +- code/datums/ai_laws.dm | 946 +- code/datums/browser.dm | 934 +- code/datums/components/README.md | 266 +- code/datums/components/_component.dm | 628 +- code/datums/components/decals/blood.dm | 78 +- code/datums/components/forensics.dm | 368 +- code/datums/components/jousting.dm | 148 +- code/datums/components/lockon_aiming.dm | 478 +- code/datums/components/nanites.dm | 632 +- code/datums/components/ntnet_interface.dm | 132 +- code/datums/components/riding.dm | 676 +- code/datums/components/slippery.dm | 38 +- code/datums/components/spill.dm | 114 +- .../components/storage/concrete/_concrete.dm | 402 +- .../storage/concrete/bag_of_holding.dm | 82 +- .../components/storage/concrete/bluespace.dm | 46 +- .../components/storage/concrete/implant.dm | 36 +- .../components/storage/concrete/pockets.dm | 192 +- .../components/storage/concrete/rped.dm | 66 +- .../components/storage/concrete/stack.dm | 134 +- code/datums/components/storage/storage.dm | 1620 +- code/datums/components/wet_floor.dm | 414 +- code/datums/datacore.dm | 676 +- code/datums/datum.dm | 328 +- code/datums/datumvars.dm | 2836 +-- code/datums/diseases/advance/advance.dm | 966 +- code/datums/diseases/advance/presets.dm | 82 +- .../datums/diseases/advance/symptoms/beard.dm | 86 +- .../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 | 242 +- .../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 | 932 +- .../diseases/advance/symptoms/itching.dm | 106 +- .../diseases/advance/symptoms/oxygen.dm | 138 +- .../diseases/advance/symptoms/shedding.dm | 110 +- .../diseases/advance/symptoms/shivering.dm | 116 +- .../diseases/advance/symptoms/sneeze.dm | 104 +- .../diseases/advance/symptoms/symptoms.dm | 152 +- .../diseases/advance/symptoms/voice_change.dm | 170 +- .../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 | 74 +- code/datums/diseases/beesease.dm | 76 +- code/datums/diseases/brainrot.dm | 118 +- code/datums/diseases/cold.dm | 106 +- 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 | 114 +- 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 | 1304 +- code/datums/forced_movement.dm | 186 +- code/datums/helper_datums/events.dm | 108 +- code/datums/helper_datums/getrev.dm | 258 +- code/datums/helper_datums/teleport.dm | 332 +- code/datums/holocall.dm | 752 +- code/datums/hud.dm | 272 +- code/datums/map_config.dm | 286 +- code/datums/mind.dm | 1554 +- code/datums/mood_events/drug_events.dm | 120 +- code/datums/mood_events/mood_event.dm | 46 +- code/datums/mood_events/needs_events.dm | 196 +- code/datums/numbered_display.dm | 20 +- code/datums/position_point_vector.dm | 450 +- code/datums/recipe.dm | 240 +- code/datums/spawners_menu.dm | 116 +- code/datums/verbs.dm | 204 +- code/datums/wires/_wires.dm | 600 +- code/datums/wires/airalarm.dm | 148 +- code/datums/wires/airlock.dm | 288 +- code/datums/wires/apc.dm | 108 +- code/datums/wires/autolathe.dm | 94 +- code/datums/wires/explosive.dm | 244 +- code/datums/wires/mulebot.dm | 60 +- code/datums/wires/particle_accelerator.dm | 94 +- code/datums/wires/radio.dm | 50 +- code/datums/wires/robot.dm | 172 +- code/datums/wires/suit_storage_unit.dm | 90 +- code/datums/wires/syndicatebomb.dm | 182 +- code/datums/wires/vending.dm | 118 +- code/datums/world_topic.dm | 378 +- code/game/area/Space_Station_13_areas.dm | 2192 +- code/game/area/ai_monitored.dm | 60 +- code/game/atoms.dm | 1486 +- code/game/atoms_movable.dm | 1840 +- code/game/communications.dm | 398 +- code/game/gamemodes/brother/traitor_bro.dm | 144 +- code/game/gamemodes/changeling/changeling.dm | 276 +- .../game/gamemodes/changeling/traitor_chan.dm | 166 +- code/game/gamemodes/events.dm | 132 +- code/game/gamemodes/extended/extended.dm | 58 +- code/game/gamemodes/game_mode.dm | 1184 +- code/game/gamemodes/meteor/meteor.dm | 120 +- 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 | 44 +- code/game/gamemodes/traitor/double_agents.dm | 166 +- code/game/gamemodes/traitor/traitor.dm | 200 +- code/game/gamemodes/wizard/wizard.dm | 148 +- code/game/machinery/Beacon.dm | 96 +- code/game/machinery/PDApainter.dm | 298 +- code/game/machinery/ai_slipper.dm | 96 +- code/game/machinery/airlock_control.dm | 326 +- code/game/machinery/autolathe.dm | 782 +- code/game/machinery/buttons.dm | 578 +- code/game/machinery/camera/camera_assembly.dm | 570 +- code/game/machinery/camera/motion.dm | 224 +- code/game/machinery/camera/presets.dm | 280 +- code/game/machinery/camera/tracking.dm | 302 +- code/game/machinery/cell_charger.dm | 264 +- code/game/machinery/cloning.dm | 1110 +- code/game/machinery/computer/Operating.dm | 382 +- code/game/machinery/computer/aifixer.dm | 314 +- code/game/machinery/computer/atmos_alert.dm | 176 +- code/game/machinery/computer/atmos_control.dm | 642 +- .../game/machinery/computer/buildandrepair.dm | 286 +- code/game/machinery/computer/camera.dm | 620 +- code/game/machinery/computer/card.dm | 1248 +- .../game/machinery/computer/communications.dm | 1496 +- code/game/machinery/computer/crew.dm | 388 +- code/game/machinery/computer/law.dm | 178 +- code/game/machinery/computer/medical.dm | 1204 +- code/game/machinery/computer/pod.dm | 266 +- code/game/machinery/computer/robot.dm | 366 +- code/game/machinery/computer/station_alert.dm | 174 +- code/game/machinery/constructable_frame.dm | 556 +- code/game/machinery/doors/alarmlock.dm | 84 +- code/game/machinery/doors/brigdoors.dm | 518 +- code/game/machinery/doors/door.dm | 778 +- code/game/machinery/doors/firedoor.dm | 924 +- code/game/machinery/doors/poddoor.dm | 182 +- code/game/machinery/doors/shutters.dm | 26 +- code/game/machinery/doors/unpowered.dm | 48 +- code/game/machinery/doors/windowdoor.dm | 1080 +- code/game/machinery/doppler_array.dm | 306 +- .../embedded_controller/access_controller.dm | 624 +- .../embedded_controller/airlock_controller.dm | 628 +- .../embedded_controller_base.dm | 174 +- .../simple_vent_controller.dm | 144 +- code/game/machinery/firealarm.dm | 656 +- code/game/machinery/flasher.dm | 410 +- code/game/machinery/hologram.dm | 1404 +- code/game/machinery/igniter.dm | 274 +- code/game/machinery/lightswitch.dm | 126 +- code/game/machinery/magnet.dm | 760 +- code/game/machinery/mass_driver.dm | 80 +- code/game/machinery/navbeacon.dm | 428 +- code/game/machinery/pipe/construction.dm | 470 +- code/game/machinery/pipe/pipe_dispenser.dm | 440 +- .../machinery/porta_turret/portable_turret.dm | 2192 +- code/game/machinery/recharger.dm | 358 +- code/game/machinery/rechargestation.dm | 214 +- code/game/machinery/recycler.dm | 410 +- code/game/machinery/status_display.dm | 742 +- code/game/machinery/suit_storage_unit.dm | 898 +- code/game/machinery/syndicatebeacon.dm | 270 +- .../machinery/telecomms/computers/message.dm | 950 +- .../telecomms/machines/message_server.dm | 432 +- .../machinery/telecomms/telecomunications.dm | 320 +- code/game/machinery/washing_machine.dm | 614 +- code/game/machinery/wishgranter.dm | 86 +- code/game/mecha/combat/combat.dm | 26 +- code/game/mecha/combat/durand.dm | 44 +- code/game/mecha/combat/gygax.dm | 136 +- code/game/mecha/combat/honker.dm | 364 +- code/game/mecha/combat/marauder.dm | 192 +- code/game/mecha/combat/phazon.dm | 60 +- code/game/mecha/combat/reticence.dm | 56 +- code/game/mecha/equipment/mecha_equipment.dm | 340 +- code/game/mecha/equipment/weapons/weapons.dm | 984 +- code/game/mecha/mech_fabricator.dm | 848 +- code/game/mecha/mecha.dm | 2344 +- code/game/mecha/mecha_construction_paths.dm | 3880 ++-- code/game/mecha/mecha_control_console.dm | 258 +- code/game/mecha/mecha_parts.dm | 698 +- code/game/mecha/mecha_wreckage.dm | 542 +- code/game/mecha/medical/medical.dm | 36 +- code/game/mecha/medical/odysseus.dm | 62 +- code/game/mecha/working/ripley.dm | 468 +- code/game/mecha/working/working.dm | 4 +- code/game/objects/effects/bump_teleporter.dm | 74 +- code/game/objects/effects/decals/cleanable.dm | 190 +- .../effects/decals/cleanable/aliens.dm | 158 +- .../effects/decals/cleanable/humans.dm | 424 +- .../objects/effects/decals/cleanable/misc.dm | 404 +- code/game/objects/effects/decals/crayon.dm | 66 +- code/game/objects/effects/decals/decal.dm | 96 +- code/game/objects/effects/decals/misc.dm | 62 +- code/game/objects/effects/decals/remains.dm | 66 +- code/game/objects/effects/forcefields.dm | 74 +- code/game/objects/effects/landmarks.dm | 880 +- code/game/objects/effects/mines.dm | 354 +- code/game/objects/effects/misc.dm | 188 +- code/game/objects/effects/portals.dm | 486 +- code/game/objects/effects/proximity.dm | 230 +- .../objects/effects/spawners/bombspawner.dm | 134 +- .../game/objects/effects/spawners/lootdrop.dm | 716 +- code/game/objects/effects/spiders.dm | 462 +- code/game/objects/effects/step_triggers.dm | 400 +- .../temporary_visuals/projectiles/impact.dm | 76 +- .../temporary_visuals/projectiles/muzzle.dm | 60 +- .../projectiles/projectile_effects.dm | 120 +- .../temporary_visuals/projectiles/tracer.dm | 136 +- code/game/objects/empulse.dm | 62 +- code/game/objects/items.dm | 1594 +- code/game/objects/items/AI_modules.dm | 1248 +- code/game/objects/items/airlock_painter.dm | 254 +- code/game/objects/items/bodybag.dm | 182 +- code/game/objects/items/candle.dm | 160 +- code/game/objects/items/cards_ids.dm | 1130 +- code/game/objects/items/cigs_lighters.dm | 1850 +- code/game/objects/items/cosmetics.dm | 454 +- code/game/objects/items/devices/PDA/PDA.dm | 2050 +- code/game/objects/items/devices/PDA/cart.dm | 1478 +- 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 | 618 +- code/game/objects/items/devices/flashlight.dm | 1120 +- code/game/objects/items/devices/gps.dm | 426 +- .../game/objects/items/devices/instruments.dm | 526 +- .../objects/items/devices/laserpointer.dm | 402 +- .../objects/items/devices/lightreplacer.dm | 532 +- code/game/objects/items/devices/multitool.dm | 342 +- code/game/objects/items/devices/paicard.dm | 334 +- code/game/objects/items/devices/powersink.dm | 310 +- .../items/devices/radio/electropack.dm | 288 +- .../items/devices/radio/encryptionkey.dm | 264 +- .../objects/items/devices/radio/headset.dm | 688 +- .../objects/items/devices/radio/intercom.dm | 300 +- .../game/objects/items/devices/radio/radio.dm | 832 +- code/game/objects/items/devices/scanners.dm | 1600 +- .../objects/items/devices/taperecorder.dm | 582 +- .../objects/items/devices/transfer_valve.dm | 474 +- code/game/objects/items/dice.dm | 412 +- code/game/objects/items/dna_injector.dm | 956 +- code/game/objects/items/extinguisher.dm | 492 +- .../objects/items/grenades/chem_grenade.dm | 1166 +- code/game/objects/items/grenades/emgrenade.dm | 20 +- code/game/objects/items/grenades/flashbang.dm | 72 +- .../game/objects/items/grenades/ghettobomb.dm | 120 +- code/game/objects/items/grenades/grenade.dm | 268 +- code/game/objects/items/grenades/smokebomb.dm | 60 +- .../objects/items/grenades/spawnergrenade.dm | 126 +- .../objects/items/grenades/syndieminibomb.dm | 100 +- code/game/objects/items/handcuffs.dm | 800 +- code/game/objects/items/hot_potato.dm | 348 +- code/game/objects/items/implants/implant.dm | 228 +- .../objects/items/implants/implantcase.dm | 166 +- .../objects/items/implants/implantchair.dm | 394 +- code/game/objects/items/implants/implanter.dm | 130 +- .../game/objects/items/implants/implantpad.dm | 156 +- .../objects/items/implants/implantuplink.dm | 46 +- code/game/objects/items/kitchen.dm | 382 +- code/game/objects/items/latexballoon.dm | 116 +- code/game/objects/items/manuals.dm | 908 +- code/game/objects/items/melee/energy.dm | 468 +- code/game/objects/items/paiwire.dm | 26 +- code/game/objects/items/robot/robot_items.dm | 1486 +- code/game/objects/items/robot/robot_parts.dm | 842 +- .../objects/items/robot/robot_upgrades.dm | 1328 +- code/game/objects/items/scrolls.dm | 146 +- code/game/objects/items/shields.dm | 546 +- code/game/objects/items/shooting_range.dm | 192 +- code/game/objects/items/singularityhammer.dm | 226 +- code/game/objects/items/stacks/bscrystal.dm | 180 +- code/game/objects/items/stacks/medical.dm | 302 +- code/game/objects/items/stacks/rods.dm | 190 +- .../game/objects/items/stacks/sheets/glass.dm | 678 +- .../objects/items/stacks/sheets/leather.dm | 494 +- .../game/objects/items/stacks/sheets/light.dm | 70 +- .../objects/items/stacks/sheets/sheets.dm | 34 +- code/game/objects/items/storage/backpack.dm | 1220 +- code/game/objects/items/storage/bags.dm | 786 +- code/game/objects/items/storage/belt.dm | 1370 +- code/game/objects/items/storage/boxes.dm | 2178 +- code/game/objects/items/storage/briefcase.dm | 98 +- code/game/objects/items/storage/fancy.dm | 704 +- code/game/objects/items/storage/firstaid.dm | 716 +- code/game/objects/items/storage/lockbox.dm | 374 +- code/game/objects/items/storage/secure.dm | 376 +- code/game/objects/items/storage/storage.dm | 90 +- code/game/objects/items/storage/toolbox.dm | 606 +- code/game/objects/items/storage/wallets.dm | 170 +- code/game/objects/items/stunbaton.dm | 428 +- code/game/objects/items/tanks/jetpack.dm | 508 +- code/game/objects/items/teleportation.dm | 472 +- code/game/objects/items/tools/crowbar.dm | 174 +- code/game/objects/items/tools/screwdriver.dm | 282 +- code/game/objects/items/tools/wirecutters.dm | 252 +- code/game/objects/items/tools/wrench.dm | 218 +- code/game/objects/items/trash.dm | 176 +- code/game/objects/items/vending_items.dm | 102 +- code/game/objects/items/weaponry.dm | 1358 +- code/game/objects/objs.dm | 510 +- code/game/objects/structures.dm | 226 +- code/game/objects/structures/ai_core.dm | 686 +- code/game/objects/structures/bedsheet_bin.dm | 826 +- .../structures/crates_lockers/closets.dm | 1012 +- .../crates_lockers/closets/fitness.dm | 130 +- .../crates_lockers/closets/gimmick.dm | 214 +- .../crates_lockers/closets/job_closets.dm | 622 +- .../crates_lockers/closets/l3closet.dm | 108 +- .../crates_lockers/closets/secure/bar.dm | 24 +- .../crates_lockers/closets/secure/cargo.dm | 48 +- .../closets/secure/engineering.dm | 198 +- .../crates_lockers/closets/secure/freezer.dm | 220 +- .../closets/secure/hydroponics.dm | 24 +- .../crates_lockers/closets/secure/medical.dm | 222 +- .../crates_lockers/closets/secure/personal.dm | 106 +- .../closets/secure/scientist.dm | 62 +- .../closets/secure/secure_closets.dm | 24 +- .../crates_lockers/closets/secure/security.dm | 606 +- .../crates_lockers/closets/syndicate.dm | 238 +- .../crates_lockers/closets/utility_closets.dm | 350 +- .../crates_lockers/closets/wardrobe.dm | 408 +- .../structures/crates_lockers/crates.dm | 422 +- .../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 | 216 +- code/game/objects/structures/displaycase.dm | 692 +- code/game/objects/structures/dresser.dm | 110 +- code/game/objects/structures/electricchair.dm | 92 +- code/game/objects/structures/extinguisher.dm | 308 +- code/game/objects/structures/flora.dm | 880 +- code/game/objects/structures/hivebot.dm | 72 +- code/game/objects/structures/kitchen_spike.dm | 304 +- code/game/objects/structures/ladders.dm | 462 +- code/game/objects/structures/manned_turret.dm | 432 +- code/game/objects/structures/mineral_doors.dm | 696 +- code/game/objects/structures/mirror.dm | 488 +- code/game/objects/structures/mop_bucket.dm | 60 +- code/game/objects/structures/morgue.dm | 780 +- code/game/objects/structures/musician.dm | 772 +- code/game/objects/structures/noticeboard.dm | 264 +- code/game/objects/structures/safe.dm | 422 +- code/game/objects/structures/tables_racks.dm | 1150 +- .../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 | 724 +- code/game/shuttle_engines.dm | 312 +- code/game/turfs/openspace/openspace.dm | 216 +- code/game/world.dm | 678 +- code/modules/NTNet/services/_service.dm | 76 +- code/modules/admin/admin.dm | 1800 +- code/modules/admin/admin_ranks.dm | 612 +- code/modules/admin/chat_commands.dm | 240 +- code/modules/admin/create_mob.dm | 76 +- 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 | 620 +- code/modules/admin/topic.dm | 3960 ++-- code/modules/admin/verbs/BrokenInhands.dm | 70 +- code/modules/admin/verbs/adminhelp.dm | 1362 +- code/modules/admin/verbs/adminjump.dm | 314 +- code/modules/admin/verbs/adminsay.dm | 46 +- code/modules/admin/verbs/atmosdebug.dm | 82 +- code/modules/admin/verbs/cinematic.dm | 20 +- code/modules/admin/verbs/deadsay.dm | 84 +- code/modules/admin/verbs/fps.dm | 52 +- code/modules/admin/verbs/getlogs.dm | 68 +- code/modules/admin/verbs/mapping.dm | 752 +- code/modules/admin/verbs/massmodvar.dm | 530 +- code/modules/admin/verbs/modifyvariables.dm | 1288 +- code/modules/admin/verbs/onlyone.dm | 60 +- code/modules/admin/verbs/playsound.dm | 360 +- code/modules/admin/verbs/possess.dm | 116 +- code/modules/admin/verbs/pray.dm | 150 +- code/modules/admin/verbs/randomverbs.dm | 2434 +-- code/modules/admin/verbs/shuttlepanel.dm | 152 +- code/modules/admin/verbs/tripAI.dm | 40 +- code/modules/admin/whitelist.dm | 46 +- code/modules/antagonists/_common/antag_hud.dm | 104 +- .../antagonists/_common/antag_spawner.dm | 562 +- .../changeling/changeling_power.dm | 170 +- .../antagonists/changeling/powers/absorb.dm | 290 +- .../changeling/powers/digitalcamo.dm | 48 +- .../changeling/powers/fakedeath.dm | 148 +- .../changeling/powers/fleshmend.dm | 42 +- .../antagonists/changeling/powers/hivemind.dm | 234 +- .../changeling/powers/humanform.dm | 68 +- .../changeling/powers/lesserform.dm | 34 +- .../changeling/powers/mimic_voice.dm | 54 +- .../antagonists/changeling/powers/panacea.dm | 82 +- .../antagonists/changeling/powers/shriek.dm | 90 +- .../antagonists/changeling/powers/spiders.dm | 28 +- .../changeling/powers/tiny_prick.dm | 500 +- .../changeling/powers/transform.dm | 324 +- .../nukeop/equipment/pinpointer.dm | 180 +- .../antagonists/wizard/equipment/artefact.dm | 944 +- .../antagonists/wizard/equipment/spellbook.dm | 1488 +- code/modules/assembly/assembly.dm | 252 +- code/modules/assembly/flash.dm | 594 +- code/modules/assembly/helpers.dm | 30 +- code/modules/assembly/holder.dm | 332 +- code/modules/assembly/igniter.dm | 86 +- code/modules/assembly/infrared.dm | 480 +- code/modules/assembly/mousetrap.dm | 284 +- code/modules/assembly/proximity.dm | 318 +- code/modules/assembly/shock_kit.dm | 78 +- code/modules/assembly/signaler.dm | 480 +- code/modules/assembly/timer.dm | 266 +- code/modules/assembly/voice.dm | 202 +- .../atmospherics/gasmixtures/gas_mixture.dm | 918 +- .../atmospherics/machinery/atmosmachinery.dm | 678 +- .../binary_devices/binary_devices.dm | 42 +- .../components/binary_devices/circulator.dm | 380 +- .../components/binary_devices/dp_vent_pump.dm | 488 +- .../trinary_devices/trinary_devices.dm | 96 +- .../components/unary_devices/cryo.dm | 906 +- .../atmospherics/machinery/other/meter.dm | 294 +- .../machinery/pipes/layermanifold.dm | 276 +- .../portable/portable_atmospherics.dm | 326 +- .../atmospherics/machinery/portable/pump.dm | 310 +- .../awaymissions/bluespaceartillery.dm | 110 +- code/modules/awaymissions/exile.dm | 18 +- code/modules/awaymissions/gateway.dm | 506 +- .../awaymissions/mission_code/Academy.dm | 780 +- .../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/bsql/core/connection.dm | 134 +- code/modules/bsql/core/library.dm | 86 +- code/modules/bsql/core/operation.dm | 94 +- code/modules/bsql/core/query.dm | 70 +- code/modules/bsql/includes.dm | 8 +- code/modules/cargo/bounty_console.dm | 192 +- code/modules/cargo/console.dm | 482 +- code/modules/cargo/expressconsole.dm | 424 +- code/modules/cargo/gondolapod.dm | 146 +- code/modules/client/client_defines.dm | 164 +- code/modules/client/preferences.dm | 3482 +-- code/modules/client/preferences_toggles.dm | 912 +- code/modules/client/verbs/etips.dm | 40 +- code/modules/clothing/clothing.dm | 636 +- code/modules/clothing/glasses/_glasses.dm | 940 +- code/modules/clothing/glasses/hud.dm | 424 +- code/modules/clothing/gloves/_gloves.dm | 86 +- code/modules/clothing/gloves/boxing.dm | 38 +- code/modules/clothing/gloves/color.dm | 406 +- code/modules/clothing/gloves/miscellaneous.dm | 158 +- code/modules/clothing/head/_head.dm | 60 +- code/modules/clothing/head/collectable.dm | 310 +- code/modules/clothing/head/hardhat.dm | 312 +- code/modules/clothing/head/jobs.dm | 540 +- code/modules/clothing/head/misc.dm | 814 +- code/modules/clothing/head/misc_special.dm | 658 +- code/modules/clothing/head/soft_caps.dm | 262 +- code/modules/clothing/masks/_masks.dm | 144 +- code/modules/clothing/masks/boxing.dm | 132 +- code/modules/clothing/masks/breath.dm | 82 +- code/modules/clothing/masks/gasmask.dm | 398 +- code/modules/clothing/masks/miscellaneous.dm | 672 +- code/modules/clothing/shoes/_shoes.dm | 180 +- code/modules/clothing/shoes/colour.dm | 248 +- code/modules/clothing/shoes/magboots.dm | 130 +- .../clothing/spacesuits/_spacesuits.dm | 90 +- .../clothing/spacesuits/miscellaneous.dm | 798 +- code/modules/clothing/spacesuits/syndi.dm | 326 +- code/modules/clothing/suits/_suits.dm | 64 +- code/modules/clothing/suits/bio.dm | 176 +- code/modules/clothing/suits/jobs.dm | 312 +- code/modules/clothing/suits/labcoat.dm | 102 +- code/modules/clothing/suits/miscellaneous.dm | 1294 +- code/modules/clothing/suits/utility.dm | 298 +- code/modules/clothing/suits/wiz_robe.dm | 456 +- code/modules/clothing/under/_under.dm | 352 +- code/modules/clothing/under/color.dm | 480 +- code/modules/clothing/under/jobs/civilian.dm | 790 +- .../clothing/under/jobs/engineering.dm | 162 +- code/modules/clothing/under/jobs/medsci.dm | 420 +- code/modules/clothing/under/jobs/security.dm | 426 +- code/modules/clothing/under/miscellaneous.dm | 1652 +- code/modules/clothing/under/pants.dm | 158 +- code/modules/clothing/under/shorts.dm | 74 +- code/modules/detectivework/detective_work.dm | 204 +- code/modules/detectivework/evidence.dm | 178 +- .../detectivework/footprints_and_rag.dm | 98 +- code/modules/detectivework/scanner.dm | 432 +- code/modules/events/anomaly.dm | 102 +- code/modules/events/anomaly_bluespace.dm | 26 +- code/modules/events/anomaly_flux.dm | 30 +- code/modules/events/anomaly_grav.dm | 26 +- code/modules/events/anomaly_vortex.dm | 28 +- code/modules/events/blob.dm | 60 +- code/modules/events/brand_intelligence.dm | 156 +- code/modules/events/carp_migration.dm | 76 +- .../modules/events/communications_blackout.dm | 52 +- code/modules/events/disease_outbreak.dm | 150 +- code/modules/events/dust.dm | 60 +- code/modules/events/electrical_storm.dm | 66 +- code/modules/events/false_alarm.dm | 124 +- code/modules/events/high_priority_bounty.dm | 40 +- code/modules/events/holiday/halloween.dm | 122 +- code/modules/events/immovable_rod.dm | 332 +- code/modules/events/ion_storm.dm | 1134 +- code/modules/events/mass_hallucination.dm | 74 +- code/modules/events/meteor_wave.dm | 152 +- code/modules/events/prison_break.dm | 118 +- code/modules/events/processor_overload.dm | 78 +- code/modules/events/radiation_storm.dm | 38 +- code/modules/events/spider_infestation.dm | 78 +- .../events/spontaneous_appendicitis.dm | 64 +- code/modules/events/vent_clog.dm | 228 +- code/modules/fields/timestop.dm | 368 +- code/modules/flufftext/Dreaming.dm | 138 +- code/modules/food_and_drinks/pizzabox.dm | 686 +- code/modules/holodeck/holo_effect.dm | 220 +- code/modules/holodeck/items.dm | 456 +- code/modules/hydroponics/biogenerator.dm | 640 +- code/modules/hydroponics/grown.dm | 340 +- code/modules/hydroponics/growninedible.dm | 122 +- code/modules/hydroponics/hydroitemdefines.dm | 414 +- code/modules/hydroponics/hydroponics.dm | 1876 +- code/modules/hydroponics/seed_extractor.dm | 394 +- code/modules/hydroponics/seeds.dm | 788 +- code/modules/jobs/access.dm | 756 +- code/modules/jobs/job_types/assistant.dm | 74 +- code/modules/jobs/job_types/captain.dm | 132 +- code/modules/jobs/jobs.dm | 310 +- 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 | 702 +- code/modules/library/lib_machines.dm | 1206 +- code/modules/library/random_books.dm | 174 +- code/modules/mapping/ruins.dm | 284 +- .../space_management/multiz_helpers.dm | 94 +- .../space_management/space_reservation.dm | 148 +- code/modules/mining/equipment/mining_tools.dm | 266 +- code/modules/mining/laborcamp/laborshuttle.dm | 52 +- code/modules/mining/laborcamp/laborstacker.dm | 352 +- code/modules/mining/machine_processing.dm | 422 +- code/modules/mining/machine_stacking.dm | 244 +- code/modules/mining/machine_unloading.dm | 60 +- code/modules/mining/mint.dm | 210 +- code/modules/mining/money_bag.dm | 54 +- code/modules/mining/ores_coins.dm | 962 +- code/modules/mining/satchel_ore_boxdm.dm | 178 +- code/modules/mob/camera/camera.dm | 54 +- code/modules/mob/dead/dead.dm | 266 +- code/modules/mob/dead/new_player/login.dm | 68 +- code/modules/mob/dead/new_player/logout.dm | 12 +- .../modules/mob/dead/new_player/new_player.dm | 1094 +- code/modules/mob/dead/new_player/poll.dm | 1238 +- .../mob/dead/new_player/preferences_setup.dm | 102 +- .../mob/dead/new_player/sprite_accessories.dm | 3786 ++-- code/modules/mob/dead/observer/login.dm | 40 +- code/modules/mob/dead/observer/logout.dm | 32 +- .../mob/dead/observer/observer_movement.dm | 4 +- code/modules/mob/dead/observer/say.dm | 76 +- code/modules/mob/death.dm | 28 +- code/modules/mob/inventory.dm | 994 +- code/modules/mob/living/carbon/alien/alien.dm | 294 +- .../mob/living/carbon/alien/alien_defense.dm | 256 +- code/modules/mob/living/carbon/alien/death.dm | 28 +- .../carbon/alien/humanoid/caste/drone.dm | 84 +- .../carbon/alien/humanoid/caste/hunter.dm | 190 +- .../carbon/alien/humanoid/caste/sentinel.dm | 32 +- .../mob/living/carbon/alien/humanoid/death.dm | 42 +- .../living/carbon/alien/humanoid/inventory.dm | 10 +- .../mob/living/carbon/alien/humanoid/life.dm | 36 +- .../mob/living/carbon/alien/humanoid/queen.dm | 314 +- .../carbon/alien/humanoid/update_icons.dm | 186 +- .../mob/living/carbon/alien/larva/death.dm | 44 +- .../living/carbon/alien/larva/inventory.dm | 6 +- .../mob/living/carbon/alien/larva/larva.dm | 138 +- .../mob/living/carbon/alien/larva/life.dm | 64 +- .../mob/living/carbon/alien/larva/powers.dm | 126 +- .../living/carbon/alien/larva/update_icons.dm | 62 +- code/modules/mob/living/carbon/alien/life.dm | 108 +- code/modules/mob/living/carbon/alien/login.dm | 8 +- .../modules/mob/living/carbon/alien/logout.dm | 8 +- code/modules/mob/living/carbon/alien/say.dm | 46 +- .../modules/mob/living/carbon/alien/screen.dm | 64 +- .../carbon/alien/special/alien_embryo.dm | 274 +- .../living/carbon/alien/special/facehugger.dm | 540 +- .../mob/living/carbon/carbon_defense.dm | 786 +- .../mob/living/carbon/carbon_defines.dm | 130 +- code/modules/mob/living/carbon/emote.dm | 194 +- code/modules/mob/living/carbon/human/death.dm | 120 +- code/modules/mob/living/carbon/human/dummy.dm | 98 +- code/modules/mob/living/carbon/human/emote.dm | 352 +- code/modules/mob/living/carbon/human/human.dm | 2150 +- .../mob/living/carbon/human/human_defense.dm | 1798 +- .../mob/living/carbon/human/human_defines.dm | 116 +- .../mob/living/carbon/human/human_helpers.dm | 356 +- .../mob/living/carbon/human/human_movement.dm | 152 +- .../mob/living/carbon/human/inventory.dm | 542 +- code/modules/mob/living/carbon/human/say.dm | 222 +- .../carbon/human/species_types/abductors.dm | 38 +- .../carbon/human/species_types/android.dm | 48 +- .../carbon/human/species_types/angel.dm | 286 +- .../carbon/human/species_types/corporate.dm | 40 +- .../carbon/human/species_types/felinid.dm | 262 +- .../carbon/human/species_types/flypeople.dm | 72 +- .../carbon/human/species_types/golems.dm | 2258 +- .../carbon/human/species_types/humans.dm | 28 +- .../carbon/human/species_types/jellypeople.dm | 1472 +- .../human/species_types/lizardpeople.dm | 188 +- .../carbon/human/species_types/plasmamen.dm | 280 +- .../carbon/human/species_types/podpeople.dm | 140 +- .../human/species_types/shadowpeople.dm | 440 +- .../carbon/human/species_types/skeletons.dm | 42 +- .../carbon/human/species_types/synths.dm | 268 +- .../carbon/human/species_types/zombies.dm | 188 +- .../mob/living/carbon/human/update_icons.dm | 1350 +- code/modules/mob/living/carbon/inventory.dm | 286 +- code/modules/mob/living/carbon/life.dm | 1356 +- .../modules/mob/living/carbon/monkey/death.dm | 18 +- .../mob/living/carbon/monkey/inventory.dm | 68 +- code/modules/mob/living/carbon/monkey/life.dm | 340 +- .../mob/living/carbon/monkey/monkey.dm | 342 +- .../mob/living/carbon/monkey/update_icons.dm | 152 +- .../modules/mob/living/carbon/update_icons.dm | 578 +- code/modules/mob/living/damage_procs.dm | 582 +- code/modules/mob/living/death.dm | 184 +- code/modules/mob/living/emote.dm | 1044 +- code/modules/mob/living/inhand_holder.dm | 168 +- code/modules/mob/living/living_defense.dm | 818 +- code/modules/mob/living/living_defines.dm | 236 +- code/modules/mob/living/living_movement.dm | 96 +- code/modules/mob/living/login.dm | 58 +- code/modules/mob/living/logout.dm | 8 +- code/modules/mob/living/say.dm | 810 +- code/modules/mob/living/silicon/ai/ai.dm | 2084 +- code/modules/mob/living/silicon/ai/death.dm | 112 +- code/modules/mob/living/silicon/ai/examine.dm | 44 +- .../living/silicon/ai/freelook/cameranet.dm | 408 +- .../mob/living/silicon/ai/freelook/chunk.dm | 284 +- .../mob/living/silicon/ai/freelook/eye.dm | 416 +- .../mob/living/silicon/ai/freelook/read_me.dm | 100 +- code/modules/mob/living/silicon/ai/laws.dm | 52 +- 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 | 362 +- .../mob/living/silicon/ai/vox_sounds.dm | 1956 +- code/modules/mob/living/silicon/death.dm | 26 +- code/modules/mob/living/silicon/laws.dm | 186 +- code/modules/mob/living/silicon/login.dm | 30 +- code/modules/mob/living/silicon/pai/death.dm | 24 +- .../mob/living/silicon/pai/personality.dm | 122 +- code/modules/mob/living/silicon/pai/say.dm | 16 +- .../mob/living/silicon/pai/software.dm | 1318 +- .../modules/mob/living/silicon/robot/death.dm | 68 +- .../modules/mob/living/silicon/robot/emote.dm | 144 +- .../mob/living/silicon/robot/examine.dm | 102 +- .../mob/living/silicon/robot/inventory.dm | 430 +- code/modules/mob/living/silicon/robot/laws.dm | 166 +- code/modules/mob/living/silicon/robot/life.dm | 206 +- .../modules/mob/living/silicon/robot/login.dm | 10 +- .../mob/living/silicon/robot/robot_modules.dm | 1378 +- .../living/silicon/robot/robot_movement.dm | 34 +- code/modules/mob/living/silicon/robot/say.dm | 4 +- code/modules/mob/living/silicon/say.dm | 108 +- code/modules/mob/living/silicon/silicon.dm | 838 +- .../mob/living/simple_animal/bot/bot.dm | 2040 +- .../mob/living/simple_animal/bot/ed209bot.dm | 1146 +- .../mob/living/simple_animal/bot/medbot.dm | 1118 +- .../mob/living/simple_animal/bot/secbot.dm | 880 +- .../mob/living/simple_animal/constructs.dm | 962 +- .../mob/living/simple_animal/corpse.dm | 442 +- .../mob/living/simple_animal/friendly/cat.dm | 586 +- .../mob/living/simple_animal/friendly/crab.dm | 156 +- .../simple_animal/friendly/farm_animals.dm | 704 +- .../living/simple_animal/friendly/lizard.dm | 82 +- .../mob/living/simple_animal/hostile/carp.dm | 350 +- .../living/simple_animal/hostile/faithless.dm | 88 +- .../simple_animal/hostile/giant_spider.dm | 1084 +- .../living/simple_animal/hostile/hivebot.dm | 196 +- .../mob/living/simple_animal/hostile/mimic.dm | 548 +- .../living/simple_animal/hostile/mushroom.dm | 382 +- .../living/simple_animal/hostile/pirate.dm | 182 +- .../simple_animal/hostile/retaliate/bat.dm | 80 +- .../simple_animal/hostile/retaliate/clown.dm | 552 +- .../hostile/retaliate/retaliate.dm | 106 +- .../living/simple_animal/hostile/russian.dm | 150 +- .../living/simple_animal/hostile/syndicate.dm | 632 +- .../mob/living/simple_animal/parrot.dm | 2034 +- .../modules/mob/living/simple_animal/shade.dm | 128 +- code/modules/mob/login.dm | 136 +- code/modules/mob/logout.dm | 34 +- code/modules/mob/mob_helpers.dm | 1018 +- code/modules/mob/mob_movement.dm | 794 +- code/modules/mob/mob_movespeed.dm | 246 +- code/modules/mob/mob_transformation_simple.dm | 120 +- code/modules/mob/say.dm | 242 +- code/modules/mob/transform_procs.dm | 1276 +- code/modules/mob/update_icons.dm | 140 +- code/modules/paperwork/clipboard.dm | 256 +- code/modules/paperwork/filingcabinet.dm | 448 +- code/modules/paperwork/folders.dm | 234 +- code/modules/paperwork/handlabeler.dm | 238 +- code/modules/paperwork/paperbin.dm | 348 +- code/modules/paperwork/pen.dm | 456 +- code/modules/paperwork/photocopier.dm | 670 +- code/modules/paperwork/stamps.dm | 142 +- code/modules/paperwork/ticketmachine.dm | 402 +- code/modules/photography/_pictures.dm | 354 +- code/modules/photography/camera/camera.dm | 458 +- .../camera/camera_image_capturing.dm | 176 +- code/modules/photography/camera/film.dm | 28 +- code/modules/photography/camera/other.dm | 28 +- .../photography/camera/silicon_camera.dm | 198 +- code/modules/photography/photos/album.dm | 150 +- code/modules/photography/photos/frame.dm | 332 +- code/modules/photography/photos/photo.dm | 186 +- code/modules/power/cable.dm | 1708 +- code/modules/power/cell.dm | 714 +- code/modules/power/generator.dm | 468 +- code/modules/power/gravitygenerator.dm | 820 +- code/modules/power/monitor.dm | 242 +- code/modules/power/port_gen.dm | 578 +- code/modules/power/singularity/collector.dm | 480 +- .../power/singularity/containment_field.dm | 270 +- code/modules/power/singularity/emitter.dm | 1032 +- .../power/singularity/field_generator.dm | 712 +- code/modules/power/singularity/generator.dm | 70 +- code/modules/power/singularity/investigate.dm | 6 +- .../particle_accelerator/particle.dm | 136 +- .../particle_accelerator.dm | 346 +- .../particle_accelerator/particle_control.dm | 670 +- .../particle_accelerator/particle_emitter.dm | 94 +- code/modules/power/tracker.dm | 186 +- .../mapGenerators/lavaland.dm | 72 +- .../mapGenerators/repair.dm | 222 +- .../projectiles/ammunition/_ammunition.dm | 160 +- .../modules/projectiles/ammunition/_firing.dm | 126 +- .../projectiles/ammunition/ballistic/lmg.dm | 46 +- .../ammunition/ballistic/pistol.dm | 100 +- .../ammunition/ballistic/revolver.dm | 80 +- .../projectiles/ammunition/ballistic/rifle.dm | 56 +- .../ammunition/ballistic/shotgun.dm | 278 +- .../projectiles/ammunition/ballistic/smg.dm | 50 +- .../ammunition/ballistic/sniper.dm | 40 +- .../ammunition/caseless/_caseless.dm | 32 +- .../projectiles/ammunition/caseless/foam.dm | 130 +- .../projectiles/ammunition/caseless/misc.dm | 26 +- .../projectiles/ammunition/caseless/rocket.dm | 38 +- .../projectiles/ammunition/energy/_energy.dm | 20 +- .../projectiles/ammunition/energy/ebow.dm | 24 +- .../projectiles/ammunition/energy/gravity.dm | 58 +- .../projectiles/ammunition/energy/laser.dm | 140 +- .../projectiles/ammunition/energy/lmg.dm | 12 +- .../projectiles/ammunition/energy/plasma.dm | 22 +- .../projectiles/ammunition/energy/portal.dm | 42 +- .../projectiles/ammunition/energy/special.dm | 142 +- .../projectiles/ammunition/energy/stun.dm | 52 +- .../projectiles/ammunition/special/magic.dm | 134 +- .../projectiles/ammunition/special/syringe.dm | 122 +- .../boxes_magazines/_box_magazine.dm | 296 +- .../boxes_magazines/external/grenade.dm | 16 +- .../boxes_magazines/external/lmg.dm | 44 +- .../boxes_magazines/external/pistol.dm | 118 +- .../boxes_magazines/external/rechargable.dm | 28 +- .../boxes_magazines/external/rifle.dm | 44 +- .../boxes_magazines/external/shotgun.dm | 72 +- .../boxes_magazines/external/smg.dm | 152 +- .../boxes_magazines/external/sniper.dm | 54 +- .../boxes_magazines/external/toy.dm | 118 +- .../boxes_magazines/internal/_cylinder.dm | 98 +- .../boxes_magazines/internal/_internal.dm | 16 +- .../boxes_magazines/internal/grenade.dm | 34 +- .../boxes_magazines/internal/misc.dm | 10 +- .../boxes_magazines/internal/revolver.dm | 44 +- .../boxes_magazines/internal/rifle.dm | 30 +- .../boxes_magazines/internal/shotgun.dm | 76 +- .../boxes_magazines/internal/toy.dm | 14 +- code/modules/projectiles/guns/energy.dm | 450 +- code/modules/projectiles/guns/energy/laser.dm | 280 +- .../projectiles/guns/energy/mounted.dm | 52 +- code/modules/projectiles/guns/energy/pulse.dm | 156 +- .../projectiles/guns/energy/special.dm | 690 +- code/modules/projectiles/guns/energy/stun.dm | 96 +- code/modules/projectiles/guns/magic.dm | 186 +- code/modules/projectiles/guns/magic/wand.dm | 430 +- .../projectiles/guns/misc/beam_rifle.dm | 1150 +- .../modules/projectiles/guns/misc/chem_gun.dm | 92 +- .../projectiles/guns/misc/grenade_launcher.dm | 92 +- code/modules/projectiles/guns/misc/medbeam.dm | 270 +- .../projectiles/guns/misc/syringe_gun.dm | 246 +- code/modules/projectiles/projectile.dm | 1384 +- code/modules/projectiles/projectile/beams.dm | 368 +- .../modules/projectiles/projectile/bullets.dm | 18 +- .../projectile/bullets/_incendiary.dm | 34 +- .../projectile/bullets/dart_syringe.dm | 76 +- .../projectile/bullets/dnainjector.dm | 48 +- .../projectiles/projectile/bullets/grenade.dm | 24 +- .../projectiles/projectile/bullets/lmg.dm | 88 +- .../projectiles/projectile/bullets/pistol.dm | 72 +- .../projectile/bullets/revolver.dm | 124 +- .../projectiles/projectile/bullets/rifle.dm | 32 +- .../projectiles/projectile/bullets/shotgun.dm | 188 +- .../projectiles/projectile/bullets/smg.dm | 42 +- .../projectiles/projectile/bullets/sniper.dm | 90 +- .../projectiles/projectile/bullets/special.dm | 64 +- .../projectiles/projectile/energy/_energy.dm | 14 +- .../projectiles/projectile/energy/ebow.dm | 34 +- .../projectiles/projectile/energy/misc.dm | 38 +- .../projectiles/projectile/energy/stun.dm | 76 +- .../projectiles/projectile/energy/tesla.dm | 58 +- .../projectiles/projectile/magic/spellcard.dm | 12 +- .../projectile/reusable/_reusable.dm | 40 +- .../projectile/reusable/foam_dart.dm | 82 +- .../projectiles/projectile/special/curse.dm | 104 +- .../projectiles/projectile/special/floral.dm | 30 +- .../projectiles/projectile/special/gravity.dm | 204 +- .../projectile/special/hallucination.dm | 460 +- .../projectiles/projectile/special/ion.dm | 34 +- .../projectiles/projectile/special/meteor.dm | 38 +- .../projectile/special/mindflayer.dm | 18 +- .../projectile/special/neurotoxin.dm | 24 +- .../projectiles/projectile/special/plasma.dm | 80 +- .../projectiles/projectile/special/rocket.dm | 148 +- .../projectile/special/temperature.dm | 58 +- .../projectile/special/wormhole.dm | 58 +- code/modules/reagents/reagent_containers.dm | 246 +- .../reagents/reagent_containers/borghydro.dm | 512 +- .../reagents/reagent_containers/dropper.dm | 216 +- .../reagents/reagent_containers/glass.dm | 810 +- .../reagents/reagent_containers/hypospray.dm | 410 +- .../reagents/reagent_containers/pill.dm | 488 +- .../reagents/reagent_containers/spray.dm | 746 +- code/modules/reagents/reagent_dispenser.dm | 434 +- code/modules/recycling/sortingmachinery.dm | 394 +- code/modules/research/designs.dm | 176 +- .../research/designs/bluespace_designs.dm | 130 +- .../research/designs/electronics_designs.dm | 222 +- .../research/designs/mining_designs.dm | 266 +- code/modules/research/designs/misc_designs.dm | 912 +- code/modules/research/destructive_analyzer.dm | 308 +- code/modules/research/experimentor.dm | 1368 +- .../modules/research/machinery/_production.dm | 732 +- .../research/machinery/circuit_imprinter.dm | 64 +- .../departmental_circuit_imprinter.dm | 26 +- .../machinery/departmental_protolathe.dm | 86 +- .../machinery/departmental_techfab.dm | 82 +- code/modules/research/machinery/protolathe.dm | 52 +- code/modules/research/machinery/techfab.dm | 72 +- .../nanites/nanite_chamber_computer.dm | 318 +- .../research/nanites/public_chamber.dm | 366 +- code/modules/research/rdconsole.dm | 2278 +- code/modules/research/rdmachines.dm | 204 +- code/modules/research/research_disk.dm | 44 +- .../research/techweb/__techweb_helpers.dm | 68 +- code/modules/research/techweb/_techweb.dm | 748 +- .../modules/research/techweb/_techweb_node.dm | 194 +- .../security_levels/keycard_authentication.dm | 286 +- .../security_levels/security_levels.dm | 236 +- code/modules/shuttle/arrivals.dm | 412 +- code/modules/shuttle/computer.dm | 152 +- code/modules/shuttle/ferry.dm | 80 +- code/modules/shuttle/monastery.dm | 14 +- 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/emplosion.dm | 34 +- .../spells/spell_types/ethereal_jaunt.dm | 208 +- code/modules/spells/spell_types/explosion.dm | 30 +- code/modules/spells/spell_types/genetic.dm | 86 +- .../spells/spell_types/inflict_handler.dm | 120 +- code/modules/spells/spell_types/knock.dm | 62 +- code/modules/spells/spell_types/mime.dm | 476 +- .../spells/spell_types/mind_transfer.dm | 196 +- code/modules/spells/spell_types/projectile.dm | 258 +- .../spells/spell_types/rightandwrong.dm | 386 +- code/modules/spells/spell_types/trigger.dm | 58 +- code/modules/surgery/core_removal.dm | 84 +- code/modules/surgery/eye_surgery.dm | 98 +- code/modules/surgery/helpers.dm | 358 +- code/modules/surgery/implant_removal.dm | 126 +- code/modules/surgery/limb_augmentation.dm | 140 +- code/modules/surgery/lipoplasty.dm | 124 +- code/modules/surgery/organs/helpers.dm | 58 +- code/modules/surgery/organs/liver.dm | 222 +- code/modules/surgery/organs/stomach.dm | 254 +- code/modules/surgery/plastic_surgery.dm | 102 +- code/modules/surgery/surgery_step.dm | 314 +- code/modules/surgery/tools.dm | 754 +- code/modules/tgs/core/_definitions.dm | 4 +- code/modules/tgs/core/core.dm | 328 +- code/modules/tgs/core/datum.dm | 164 +- code/modules/tgs/includes.dm | 20 +- code/modules/tgs/v3210/api.dm | 506 +- code/modules/tgs/v3210/commands.dm | 156 +- code/modules/tgs/v4/api.dm | 684 +- code/modules/tgui/tgui.dm | 774 +- code/modules/unit_tests/_unit_tests.dm | 28 +- code/modules/unit_tests/anchored_mobs.dm | 18 +- code/modules/unit_tests/component_tests.dm | 22 +- code/modules/unit_tests/spawn_humans.dm | 14 +- code/modules/unit_tests/unit_test.dm | 154 +- code/modules/uplink/uplink_devices.dm | 152 +- code/modules/vehicles/lavaboat.dm | 142 +- code/modules/vehicles/ridden.dm | 198 +- code/modules/vehicles/vehicle_actions.dm | 386 +- code/modules/vehicles/vehicle_key.dm | 30 +- code/modules/vending/wardrobes.dm | 812 +- config/admins.txt | 284 +- config/antag_rep.txt | 10 +- config/comms.txt | 32 +- html/archivedchangelog.html | 18100 ++++++++-------- html/changelog.html | 2036 +- html/create_object.html | 258 +- interface/interface.dm | 450 +- strings/names/adjectives.txt | 748 +- strings/names/ai.txt | 290 +- strings/names/carp.txt | 60 +- strings/names/clown.txt | 244 +- strings/names/death_commando.txt | 182 +- strings/names/first.txt | 2790 +-- strings/names/first_female.txt | 1540 +- strings/names/first_male.txt | 1332 +- strings/names/golem.txt | 314 +- strings/names/last.txt | 1138 +- strings/names/lizard_female.txt | 324 +- strings/names/lizard_male.txt | 654 +- strings/names/mime.txt | 46 +- strings/names/ninjaname.txt | 86 +- strings/names/ninjatitle.txt | 90 +- strings/names/plasmaman.txt | 234 +- strings/names/posibrain.txt | 92 +- strings/names/verbs.txt | 1262 +- strings/names/wizardfirst.txt | 72 +- strings/names/wizardsecond.txt | 78 +- tgui/LICENSE.md | 40 +- tgui/README.md | 16 +- tools/CreditsTool/remappings.txt | 40 +- tools/Redirector/Configurations.dm | 106 +- tools/Redirector/Redirector.dm | 174 +- tools/Redirector/config.txt | 24 +- tools/Redirector/textprocs.dm | 304 +- tools/Runtime Condenser/Input.txt | 9792 ++++----- tools/Runtime Condenser/Output.txt | 876 +- ...dnessTestForDM.csproj.FileListAbsolute.txt | 36 +- tools/UnstandardnessTestForDM/readme.txt | 8 +- tools/travis/travis_config.txt | 18 +- .../diseases/advance/symptoms/confusion.dm | 106 +- .../datums/diseases/advance/symptoms/heal.dm | 130 +- .../code/game/machinery/computer/atmos_sim.dm | 376 +- .../code/game/objects/items/airlock_scaner.dm | 96 +- yogstation/code/modules/admin/topic.dm | 200 +- .../code/modules/clothing/glasses/_glasses.dm | 54 +- .../code/modules/surgery/organs/lungs.dm | 28 +- yogstation/code/modules/xenoarch/loot/guns.dm | 350 +- 1073 files changed, 206120 insertions(+), 206120 deletions(-) diff --git a/README.md b/README.md index 5c6b8cdd9323..06fd1e6d172a 100644 --- a/README.md +++ b/README.md @@ -1,164 +1,164 @@ -## Yogstation codebase - -[![Build Status](https://travis-ci.org/yogstation13/Yogstation-TG.png)](https://travis-ci.org/yogstation13/Yogstation-TG) -[![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) - -[**Website**](https://yogstation.net) - -[**Code**](https://github.com/yogstation13/yogstation) - -[**Wiki**](https://wiki.yogstation.net) - -[**Discord**](https://discord.gg/0keg6hQH05Ha8OfO) - - -## DOWNLOADING - -There are a number of ways to download the source code. Some are described here, an alternative all-inclusive guide is also located at https://wiki.yogstation.net/wiki/Downloading_the_source_code - -Option 1: -Follow this: https://wiki.yogstation.net/wiki/Setting_up_git - -Option 2: Download the source code as a zip by clicking the ZIP button in the -code tab of https://github.com/yogstation13/Yogstation-TG -(note: this will use a lot of bandwidth if you wish to update and is a lot of -hassle if you want to make any changes at all, so it's not recommended.) - -## INSTALLATION - -First-time installation should be fairly straightforward. First, you'll need -BYOND installed. You can get it from https://www.byond.com/download. Once you've done -that, extract the game files to wherever you want to keep them. This is a -sourcecode-only release, so the next step is to compile the server files. -Open yogstation.dme by double-clicking it, open the Build menu, and click -compile. This'll take a little while, and if everything's done right you'll get -a message like this: - -``` -saving yogstation.dmb (DEBUG mode) -yogstation.dmb - 0 errors, 0 warnings -``` - -If you see any errors or warnings, something has gone wrong - possibly a corrupt -download or the files extracted wrong. If problems persist, ask for assistance -in irc://irc.rizon.net/coderbus or in #development-public on discord. - -Once that's done, open up the config folder. You'll want to edit config.txt to -set the probabilities for different gamemodes in Secret and to set your server -location so that all your players don't get disconnected at the end of each -round. It's recommended you don't turn on the gamemodes with probability 0, -except Extended, as they have various issues and aren't currently being tested, -so they may have unknown and bizarre bugs. Extended is essentially no mode, and -isn't in the Secret rotation by default as it's just not very fun. - -You'll also want to edit config/admins.txt to remove the default admins and add -your own. "Game Master" is the highest level of access, and probably the one -you'll want to use for now. You can set up your own ranks and find out more in -config/admin_ranks.txt - -The format is - -``` -byondkey = Rank -``` - -where the admin rank must be properly capitalised. - -This codebase also depends on a native library called rust-g. A precompiled -Windows DLL is included in this repository, but Linux users will need to build -and install it themselves. Directions can be found at the [rust-g -repo](https://github.com/tgstation/rust-g). - -Finally, to start the server, run Dream Daemon and enter the path to your -compiled yogstation.dmb file. Make sure to set the port to the one you -specified in the config.txt, and set the Security box to 'Safe'. Then press GO -and the server should start up and be ready to join. It is also recommended that -you set up the SQL backend (see below). - -## UPDATING - -To update an existing installation, first back up your /config and /data folders -as these store your server configuration, player preferences and banlist. - -Then, extract the new files (preferably into a clean directory, but updating in -place should work fine), copy your /config and /data folders back into the new -install, overwriting when prompted except if we've specified otherwise, and -recompile the game. Once you start the server up again, you should be running -the new version. - -## HOSTING - -If you'd like a more robust server hosting option for tgstation and its -derivatives. Check out /tg/station's server tools suite at -https://github.com/tgstation/tgstation-server - -## MAPS - -Yogstation currently comes equipped with five maps. - -* [BoxStation (default)](https://wiki.yogstation.net/wiki/Maps#Boxstation_.28YogBox.29) -* [MetaStation](https://wiki.yogstation.net/wiki/Maps#MetaStation_.28YogsMeta.29) -* [DeltaStation](https://wiki.yogstation.net/wiki/Maps#DeltaStation_.28YogsDelta.29) -* [PubbyStation](https://wiki.yogstation.net/wiki/Maps#PubbyStation_.28YogsPubby.29) -* [MinskyStation](https://wiki.yogstation.net/wiki/Maps#MinskyStation) - - -All maps have their own code file that is in the base of the _maps directory. Maps are loaded dynamically when the game starts. Follow this guideline when adding your own map, to your fork, for easy compatibility. - -The map that will be loaded for the upcoming round is determined by reading data/next_map.json, which is a copy of the json files found in the _maps tree. If this file does not exist, the default map from config/maps.txt will be loaded. Failing that, BoxStation will be loaded. If you want to set a specific map to load next round you can use the Change Map verb in game before restarting the server or copy a json from _maps to data/next_map.json before starting the server. Also, for debugging purposes, ticking a corresponding map's code file in Dream Maker will force that map to load every round. - -If you are hosting a server, and want randomly picked maps to be played each round, you can enable map rotation in [config.txt](config/config.txt) and then set the maps to be picked in the [maps.txt](config/maps.txt) file. - -Anytime you want to make changes to a map it's imperative you use the [Map Merging tools](https://tgstation13.org/wiki/Map_Merger) - -## AWAY MISSIONS - -Yogstation supports loading away missions however they are disabled by default. - -Map files for away missions are located in the _maps/RandomZLevels directory. Each away mission includes it's own code definitions located in /code/modules/awaymissions/mission_code. These files must be included and compiled with the server beforehand otherwise the server will crash upon trying to load away missions that lack their code. - -To enable an away mission open `config/awaymissionconfig.txt` and uncomment one of the .dmm lines by removing the #. If more than one away mission is uncommented then the away mission loader will randomly select one the enabled ones to load. - -## SQL SETUP - -The SQL backend requires a Mariadb server running 10.2 or later. Mysql is not supported but Mariadb is a drop in replacement for mysql. SQL is required for the library, stats tracking, admin notes, and job-only bans, among other features, mostly related to server administration. Your server details go in /config/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql and /SQL/tgstation_schema_prefix.sql depending on if you want table prefixes. More detailed setup instructions are located here: https://www.tgstation13.org/wiki/Downloading_the_source_code#Setting_up_the_database - -If you are hosting a testing server on windows you can use a standalone version of MariaDB pre load with a blank (but initialized) tgdb database. Find them here: https://tgstation13.download/database/ Just unzip and run for a working (but insecure) database server. Includes a zipped copy of the data folder for easy resetting back to square one. - -## WEB/CDN RESOURCE DELIVERY - -Web delivery of game resources makes it quicker for players to join and reduces some of the stress on the game server. - -1. Edit compile_options.dm to set the `PRELOAD_RSC` define to `0` -1. Add a url to config/external_rsc_urls pointing to a .zip file containing the .rsc. - * If you keep up to date with /tg/ you could reuse /tg/'s rsc cdn at http://tgstation13.download/byond/tgstation.zip. Otherwise you can use cdn services like CDN77 or cloudflare (requires adding a page rule to enable caching of the zip), or roll your own cdn using route 53 and vps providers. - * Regardless even offloading the rsc to a website without a CDN will be a massive improvement over the in game system for transferring files. - -## IRC BOT SETUP - -Included in the repository is a python3 compatible IRC bot capable of relaying adminhelps to a specified -IRC channel/server, see the /tools/minibot folder for more - -## CONTRIBUTING - -Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md) - -## LICENSE - -All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.html). - -All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). -(Including tools unless their readme specifies otherwise.) - -See LICENSE and GPLv3.txt for more details. - -tgui clientside is licensed as a subproject under the MIT license. -Font Awesome font files, used by tgui, are licensed under the SIL Open Font License v1.1 -tgui assets are licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/). -The TGS3 API is licensed as a subproject under the MIT license. - -See tgui/LICENSE.md for the MIT license. -See tgui/assets/fonts/SIL-OFL-1.1-LICENSE.md for the SIL Open Font License. -See the footers of code/\_\_DEFINES/server\_tools.dm, code/modules/server\_tools/st\_commands.dm, and code/modules/server\_tools/st\_inteface.dm for the MIT license. - -All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated. +## Yogstation codebase + +[![Build Status](https://travis-ci.org/yogstation13/Yogstation-TG.png)](https://travis-ci.org/yogstation13/Yogstation-TG) +[![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) + +[**Website**](https://yogstation.net) + +[**Code**](https://github.com/yogstation13/yogstation) + +[**Wiki**](https://wiki.yogstation.net) + +[**Discord**](https://discord.gg/0keg6hQH05Ha8OfO) + + +## DOWNLOADING + +There are a number of ways to download the source code. Some are described here, an alternative all-inclusive guide is also located at https://wiki.yogstation.net/wiki/Downloading_the_source_code + +Option 1: +Follow this: https://wiki.yogstation.net/wiki/Setting_up_git + +Option 2: Download the source code as a zip by clicking the ZIP button in the +code tab of https://github.com/yogstation13/Yogstation-TG +(note: this will use a lot of bandwidth if you wish to update and is a lot of +hassle if you want to make any changes at all, so it's not recommended.) + +## INSTALLATION + +First-time installation should be fairly straightforward. First, you'll need +BYOND installed. You can get it from https://www.byond.com/download. Once you've done +that, extract the game files to wherever you want to keep them. This is a +sourcecode-only release, so the next step is to compile the server files. +Open yogstation.dme by double-clicking it, open the Build menu, and click +compile. This'll take a little while, and if everything's done right you'll get +a message like this: + +``` +saving yogstation.dmb (DEBUG mode) +yogstation.dmb - 0 errors, 0 warnings +``` + +If you see any errors or warnings, something has gone wrong - possibly a corrupt +download or the files extracted wrong. If problems persist, ask for assistance +in irc://irc.rizon.net/coderbus or in #development-public on discord. + +Once that's done, open up the config folder. You'll want to edit config.txt to +set the probabilities for different gamemodes in Secret and to set your server +location so that all your players don't get disconnected at the end of each +round. It's recommended you don't turn on the gamemodes with probability 0, +except Extended, as they have various issues and aren't currently being tested, +so they may have unknown and bizarre bugs. Extended is essentially no mode, and +isn't in the Secret rotation by default as it's just not very fun. + +You'll also want to edit config/admins.txt to remove the default admins and add +your own. "Game Master" is the highest level of access, and probably the one +you'll want to use for now. You can set up your own ranks and find out more in +config/admin_ranks.txt + +The format is + +``` +byondkey = Rank +``` + +where the admin rank must be properly capitalised. + +This codebase also depends on a native library called rust-g. A precompiled +Windows DLL is included in this repository, but Linux users will need to build +and install it themselves. Directions can be found at the [rust-g +repo](https://github.com/tgstation/rust-g). + +Finally, to start the server, run Dream Daemon and enter the path to your +compiled yogstation.dmb file. Make sure to set the port to the one you +specified in the config.txt, and set the Security box to 'Safe'. Then press GO +and the server should start up and be ready to join. It is also recommended that +you set up the SQL backend (see below). + +## UPDATING + +To update an existing installation, first back up your /config and /data folders +as these store your server configuration, player preferences and banlist. + +Then, extract the new files (preferably into a clean directory, but updating in +place should work fine), copy your /config and /data folders back into the new +install, overwriting when prompted except if we've specified otherwise, and +recompile the game. Once you start the server up again, you should be running +the new version. + +## HOSTING + +If you'd like a more robust server hosting option for tgstation and its +derivatives. Check out /tg/station's server tools suite at +https://github.com/tgstation/tgstation-server + +## MAPS + +Yogstation currently comes equipped with five maps. + +* [BoxStation (default)](https://wiki.yogstation.net/wiki/Maps#Boxstation_.28YogBox.29) +* [MetaStation](https://wiki.yogstation.net/wiki/Maps#MetaStation_.28YogsMeta.29) +* [DeltaStation](https://wiki.yogstation.net/wiki/Maps#DeltaStation_.28YogsDelta.29) +* [PubbyStation](https://wiki.yogstation.net/wiki/Maps#PubbyStation_.28YogsPubby.29) +* [MinskyStation](https://wiki.yogstation.net/wiki/Maps#MinskyStation) + + +All maps have their own code file that is in the base of the _maps directory. Maps are loaded dynamically when the game starts. Follow this guideline when adding your own map, to your fork, for easy compatibility. + +The map that will be loaded for the upcoming round is determined by reading data/next_map.json, which is a copy of the json files found in the _maps tree. If this file does not exist, the default map from config/maps.txt will be loaded. Failing that, BoxStation will be loaded. If you want to set a specific map to load next round you can use the Change Map verb in game before restarting the server or copy a json from _maps to data/next_map.json before starting the server. Also, for debugging purposes, ticking a corresponding map's code file in Dream Maker will force that map to load every round. + +If you are hosting a server, and want randomly picked maps to be played each round, you can enable map rotation in [config.txt](config/config.txt) and then set the maps to be picked in the [maps.txt](config/maps.txt) file. + +Anytime you want to make changes to a map it's imperative you use the [Map Merging tools](https://tgstation13.org/wiki/Map_Merger) + +## AWAY MISSIONS + +Yogstation supports loading away missions however they are disabled by default. + +Map files for away missions are located in the _maps/RandomZLevels directory. Each away mission includes it's own code definitions located in /code/modules/awaymissions/mission_code. These files must be included and compiled with the server beforehand otherwise the server will crash upon trying to load away missions that lack their code. + +To enable an away mission open `config/awaymissionconfig.txt` and uncomment one of the .dmm lines by removing the #. If more than one away mission is uncommented then the away mission loader will randomly select one the enabled ones to load. + +## SQL SETUP + +The SQL backend requires a Mariadb server running 10.2 or later. Mysql is not supported but Mariadb is a drop in replacement for mysql. SQL is required for the library, stats tracking, admin notes, and job-only bans, among other features, mostly related to server administration. Your server details go in /config/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql and /SQL/tgstation_schema_prefix.sql depending on if you want table prefixes. More detailed setup instructions are located here: https://www.tgstation13.org/wiki/Downloading_the_source_code#Setting_up_the_database + +If you are hosting a testing server on windows you can use a standalone version of MariaDB pre load with a blank (but initialized) tgdb database. Find them here: https://tgstation13.download/database/ Just unzip and run for a working (but insecure) database server. Includes a zipped copy of the data folder for easy resetting back to square one. + +## WEB/CDN RESOURCE DELIVERY + +Web delivery of game resources makes it quicker for players to join and reduces some of the stress on the game server. + +1. Edit compile_options.dm to set the `PRELOAD_RSC` define to `0` +1. Add a url to config/external_rsc_urls pointing to a .zip file containing the .rsc. + * If you keep up to date with /tg/ you could reuse /tg/'s rsc cdn at http://tgstation13.download/byond/tgstation.zip. Otherwise you can use cdn services like CDN77 or cloudflare (requires adding a page rule to enable caching of the zip), or roll your own cdn using route 53 and vps providers. + * Regardless even offloading the rsc to a website without a CDN will be a massive improvement over the in game system for transferring files. + +## IRC BOT SETUP + +Included in the repository is a python3 compatible IRC bot capable of relaying adminhelps to a specified +IRC channel/server, see the /tools/minibot folder for more + +## CONTRIBUTING + +Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md) + +## LICENSE + +All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.html). + +All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). +(Including tools unless their readme specifies otherwise.) + +See LICENSE and GPLv3.txt for more details. + +tgui clientside is licensed as a subproject under the MIT license. +Font Awesome font files, used by tgui, are licensed under the SIL Open Font License v1.1 +tgui assets are licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/). +The TGS3 API is licensed as a subproject under the MIT license. + +See tgui/LICENSE.md for the MIT license. +See tgui/assets/fonts/SIL-OFL-1.1-LICENSE.md for the SIL Open Font License. +See the footers of code/\_\_DEFINES/server\_tools.dm, code/modules/server\_tools/st\_commands.dm, and code/modules/server\_tools/st\_inteface.dm for the MIT license. + +All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated. diff --git a/_maps/boxstation.dm b/_maps/boxstation.dm index b54b01be55fb..527299fd4cc0 100644 --- a/_maps/boxstation.dm +++ b/_maps/boxstation.dm @@ -1 +1 @@ -#define FORCE_MAP "_maps/boxstation.json" +#define FORCE_MAP "_maps/boxstation.json" diff --git a/_maps/deltastation.dm b/_maps/deltastation.dm index 2fc0ef9179d4..f64b528da6cd 100644 --- a/_maps/deltastation.dm +++ b/_maps/deltastation.dm @@ -1 +1 @@ -#define FORCE_MAP "_maps/deltastation.json" +#define FORCE_MAP "_maps/deltastation.json" diff --git a/_maps/metastation.dm b/_maps/metastation.dm index ae97e34581e0..d64d7a4900c3 100644 --- a/_maps/metastation.dm +++ b/_maps/metastation.dm @@ -1 +1 @@ -#define FORCE_MAP "_maps/metastation.json" +#define FORCE_MAP "_maps/metastation.json" diff --git a/_maps/pubbystation.dm b/_maps/pubbystation.dm index 3d1142310421..b01ae7ecbbfd 100644 --- a/_maps/pubbystation.dm +++ b/_maps/pubbystation.dm @@ -1 +1 @@ -#define FORCE_MAP "_maps/pubbystation.json" +#define FORCE_MAP "_maps/pubbystation.json" diff --git a/_maps/runtimestation.dm b/_maps/runtimestation.dm index c8a7477687b0..352d4c1afcdc 100644 --- a/_maps/runtimestation.dm +++ b/_maps/runtimestation.dm @@ -1 +1 @@ -#define FORCE_MAP "_maps/runtimestation.json" +#define FORCE_MAP "_maps/runtimestation.json" diff --git a/code/__DEFINES/_protect.dm b/code/__DEFINES/_protect.dm index f710ca77e355..2bfb98415367 100644 --- a/code/__DEFINES/_protect.dm +++ b/code/__DEFINES/_protect.dm @@ -1,10 +1,10 @@ -#define GENERAL_PROTECT_DATUM(Path)\ -##Path/can_vv_get(var_name){\ - return FALSE;\ -}\ -##Path/vv_edit_var(var_name, var_value){\ - return FALSE;\ -}\ -##Path/CanProcCall(procname){\ - return FALSE;\ +#define GENERAL_PROTECT_DATUM(Path)\ +##Path/can_vv_get(var_name){\ + return FALSE;\ +}\ +##Path/vv_edit_var(var_name, var_value){\ + return FALSE;\ +}\ +##Path/CanProcCall(procname){\ + return FALSE;\ } \ No newline at end of file diff --git a/code/__DEFINES/_readme.dm b/code/__DEFINES/_readme.dm index 85a5dfe7e7c5..42ad52286fee 100644 --- a/code/__DEFINES/_readme.dm +++ b/code/__DEFINES/_readme.dm @@ -1,14 +1,14 @@ -/* - This folder is full of #define statements. They are similar to constants, - but must come before any code that references them, and they do not take up - memory the way constants do. - - The values in this folder are NOT options. They are not for hosts to play with. - Some of the values are arbitrary and only need to be different from similar constants; - for example, the genetic mutation numbers in genetics.dm mean nothing, but MUST be distinct. - - It is wise not to touch them unless you understand what they do, where they're used, - and most importantly, - how to undo your changes if you screw it up. - - Sayu +/* + This folder is full of #define statements. They are similar to constants, + but must come before any code that references them, and they do not take up + memory the way constants do. + + The values in this folder are NOT options. They are not for hosts to play with. + Some of the values are arbitrary and only need to be different from similar constants; + for example, the genetic mutation numbers in genetics.dm mean nothing, but MUST be distinct. + + It is wise not to touch them unless you understand what they do, where they're used, + and most importantly, + how to undo your changes if you screw it up. + - Sayu */ \ No newline at end of file diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index 46007854fe59..a94139b43bf3 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -1,90 +1,90 @@ -//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 (1<<15)-1 //the sum of all other rank permissions, used for +EVERYTHING - -#define ADMIN_QUE(user) "(?)" -#define ADMIN_FLW(user) "(FLW)" -#define ADMIN_PP(user) "(PP)" -#define ADMIN_VV(atom) "(VV)" -#define ADMIN_SM(user) "(SM)" -#define ADMIN_TP(user) "(TP)" -#define ADMIN_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 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 - -#define STICKYBAN_DB_CACHE_TIME 10 SECONDS -#define STICKYBAN_ROGUE_CHECK_TIME 5 - - -#define POLICY_POLYMORPH "polymorph" //Shown to vicitm of staff of change and related effects. +//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 (1<<15)-1 //the sum of all other rank permissions, used for +EVERYTHING + +#define ADMIN_QUE(user) "(?)" +#define ADMIN_FLW(user) "(FLW)" +#define ADMIN_PP(user) "(PP)" +#define ADMIN_VV(atom) "(VV)" +#define ADMIN_SM(user) "(SM)" +#define ADMIN_TP(user) "(TP)" +#define ADMIN_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 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 + +#define STICKYBAN_DB_CACHE_TIME 10 SECONDS +#define STICKYBAN_ROGUE_CHECK_TIME 5 + + +#define POLICY_POLYMORPH "polymorph" //Shown to vicitm of staff of change and related effects. #define POLICY_VERB_HEADER "policy_verb_header" //Shown on top of policy verb window \ No newline at end of file diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index 3c804b09a188..ee50a7d3cb69 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -1,312 +1,312 @@ -//LISTMOS -//indices of values in gas lists. -#define MOLES 1 -#define ARCHIVE 2 -#define GAS_META 3 -#define META_GAS_SPECIFIC_HEAT 1 -#define META_GAS_NAME 2 -#define META_GAS_MOLES_VISIBLE 3 -#define META_GAS_OVERLAY 4 -#define META_GAS_DANGER 5 -#define META_GAS_ID 6 -#define META_GAS_FUSION_POWER 7 -//ATMOS -//stuff you should probably leave well alone! -#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 + 50) // The limit the human body can take before it starts taking damage from heat. -#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 4 -#define LOW_PRESSURE_DAMAGE 4 //The amount of damage someone takes when in a low pressure area (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value). - -#define 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. -#define VOLUME_PUMP_LEAK_AMOUNT 0.1 //10% of an overclocked volume pump leaks into the air -//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 KITCHEN_COLDROOM_ATMOS "o2=33;n2=124;TEMP=193.15" //-80°C kitchen coldroom; higher amount of mol to reach about 101.3 kpA -#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 PIPING_LAYER_SHIFT(T, PipingLayer) \ - if(T.dir & (NORTH|SOUTH)) { \ - T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ - } \ - if(T.dir & (EAST|WEST)) { \ - T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y;\ - } - -#define PIPING_LAYER_DOUBLE_SHIFT(T, PipingLayer) \ - T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ - T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y; - -#define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) - -#define ADD_GAS(gas_id, out_list)\ - var/list/tmp_gaslist = GLOB.gaslist_cache[gas_id]; out_list[gas_id] = tmp_gaslist.Copy(); - -#define ASSERT_GAS(gas_id, gas_mixture) if (!gas_mixture.gases[gas_id]) { ADD_GAS(gas_id, gas_mixture.gases) }; - -//prefer this to gas_mixture/total_moles in performance critical areas -#define TOTAL_MOLES(cached_gases, out_var)\ - out_var = 0;\ - for(var/total_moles_id in cached_gases){\ - out_var += cached_gases[total_moles_id][MOLES];\ - } -#ifdef TESTING -GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) -#define CALCULATE_ADJACENT_TURFS(T) if (SSadjacent_air.queue[T]) { GLOB.atmos_adjacent_savings[1] += 1 } else { GLOB.atmos_adjacent_savings[2] += 1; SSadjacent_air.queue[T] = 1 } -#else -#define CALCULATE_ADJACENT_TURFS(T) SSadjacent_air.queue[T] = 1 -#endif - -GLOBAL_LIST_INIT(pipe_paint_colors, list( - "amethyst" = rgb(130,43,255), //supplymain - "blue" = rgb(0,0,255), - "brown" = rgb(178,100,56), - "cyan" = rgb(0,255,249), - "dark" = rgb(69,69,69), - "green" = rgb(30,255,0), - "grey" = rgb(255,255,255), - "orange" = rgb(255,129,25), - "purple" = rgb(128,0,182), - "red" = rgb(255,0,0), - "violet" = rgb(64,0,128), - "yellow" = rgb(255,198,0) -)) - -#define MIASMA_CORPSE_MOLES 0.02 -#define MIASMA_GIBS_MOLES 0.005 -#define MIASMA_HYGIENE_MOLES 0.002 +//LISTMOS +//indices of values in gas lists. +#define MOLES 1 +#define ARCHIVE 2 +#define GAS_META 3 +#define META_GAS_SPECIFIC_HEAT 1 +#define META_GAS_NAME 2 +#define META_GAS_MOLES_VISIBLE 3 +#define META_GAS_OVERLAY 4 +#define META_GAS_DANGER 5 +#define META_GAS_ID 6 +#define META_GAS_FUSION_POWER 7 +//ATMOS +//stuff you should probably leave well alone! +#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 + 50) // The limit the human body can take before it starts taking damage from heat. +#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 4 +#define LOW_PRESSURE_DAMAGE 4 //The amount of damage someone takes when in a low pressure area (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value). + +#define 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. +#define VOLUME_PUMP_LEAK_AMOUNT 0.1 //10% of an overclocked volume pump leaks into the air +//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 KITCHEN_COLDROOM_ATMOS "o2=33;n2=124;TEMP=193.15" //-80°C kitchen coldroom; higher amount of mol to reach about 101.3 kpA +#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 PIPING_LAYER_SHIFT(T, PipingLayer) \ + if(T.dir & (NORTH|SOUTH)) { \ + T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ + } \ + if(T.dir & (EAST|WEST)) { \ + T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y;\ + } + +#define PIPING_LAYER_DOUBLE_SHIFT(T, PipingLayer) \ + T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ + T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y; + +#define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) + +#define ADD_GAS(gas_id, out_list)\ + var/list/tmp_gaslist = GLOB.gaslist_cache[gas_id]; out_list[gas_id] = tmp_gaslist.Copy(); + +#define ASSERT_GAS(gas_id, gas_mixture) if (!gas_mixture.gases[gas_id]) { ADD_GAS(gas_id, gas_mixture.gases) }; + +//prefer this to gas_mixture/total_moles in performance critical areas +#define TOTAL_MOLES(cached_gases, out_var)\ + out_var = 0;\ + for(var/total_moles_id in cached_gases){\ + out_var += cached_gases[total_moles_id][MOLES];\ + } +#ifdef TESTING +GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) +#define CALCULATE_ADJACENT_TURFS(T) if (SSadjacent_air.queue[T]) { GLOB.atmos_adjacent_savings[1] += 1 } else { GLOB.atmos_adjacent_savings[2] += 1; SSadjacent_air.queue[T] = 1 } +#else +#define CALCULATE_ADJACENT_TURFS(T) SSadjacent_air.queue[T] = 1 +#endif + +GLOBAL_LIST_INIT(pipe_paint_colors, list( + "amethyst" = rgb(130,43,255), //supplymain + "blue" = rgb(0,0,255), + "brown" = rgb(178,100,56), + "cyan" = rgb(0,255,249), + "dark" = rgb(69,69,69), + "green" = rgb(30,255,0), + "grey" = rgb(255,255,255), + "orange" = rgb(255,129,25), + "purple" = rgb(128,0,182), + "red" = rgb(255,0,0), + "violet" = rgb(64,0,128), + "yellow" = rgb(255,198,0) +)) + +#define MIASMA_CORPSE_MOLES 0.02 +#define MIASMA_GIBS_MOLES 0.005 +#define MIASMA_HYGIENE_MOLES 0.002 diff --git a/code/__DEFINES/bsql.config.dm b/code/__DEFINES/bsql.config.dm index ce1964c217cc..3f2e8c4d70eb 100644 --- a/code/__DEFINES/bsql.config.dm +++ b/code/__DEFINES/bsql.config.dm @@ -1,6 +1,6 @@ -#define BSQL_EXTERNAL_CONFIGURATION -#define BSQL_DEL_PROC(path) ##path/Destroy() -#define BSQL_DEL_CALL(obj) qdel(##obj) -#define BSQL_IS_DELETED(obj) (QDELETED(obj)) -#define BSQL_PROTECT_DATUM(path) GENERAL_PROTECT_DATUM(##path) -#define BSQL_ERROR(message) SSdbcore.ReportError(message) +#define BSQL_EXTERNAL_CONFIGURATION +#define BSQL_DEL_PROC(path) ##path/Destroy() +#define BSQL_DEL_CALL(obj) qdel(##obj) +#define BSQL_IS_DELETED(obj) (QDELETED(obj)) +#define BSQL_PROTECT_DATUM(path) GENERAL_PROTECT_DATUM(##path) +#define BSQL_ERROR(message) SSdbcore.ReportError(message) diff --git a/code/__DEFINES/bsql.dm b/code/__DEFINES/bsql.dm index e5a11f9a32f3..8f2040449a6a 100644 --- a/code/__DEFINES/bsql.dm +++ b/code/__DEFINES/bsql.dm @@ -1,135 +1,135 @@ -//BSQL - DMAPI -#define BSQL_VERSION "v1.3.0.0" - -//types of connections -#define BSQL_CONNECTION_TYPE_MARIADB "MySql" -#define BSQL_CONNECTION_TYPE_SQLSERVER "SqlServer" - -#define BSQL_DEFAULT_TIMEOUT 5 -#define BSQL_DEFAULT_THREAD_LIMIT 50 - -//Call this before rebooting or shutting down your world to clean up gracefully. This invalidates all active connection and operation datums -/world/proc/BSQL_Shutdown() - return - -/* -Called whenever a library call is made with verbose information, override and do with as you please - message: English debug message -*/ -/world/proc/BSQL_Debug(msg) - return - -/* -Create a new database connection, does not perform the actual connect - connection_type: The BSQL connection_type to use - asyncTimeout: The timeout to use for normal operations, 0 for infinite, defaults to BSQL_DEFAULT_TIMEOUT - blockingTimeout: The timeout to use for blocking operations, must be less than or equal to asyncTimeout, 0 for infinite, defaults to asyncTimeout - threadLimit: The limit of additional threads BSQL will run simultaneously, defaults to BSQL_DEFAULT_THREAD_LIMIT -*/ -/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit) - return ..() - -/* -Starts an operation to connect to a database. Should only have 1 successful call - ipaddress: The ip/hostname of the target server - port: The port of the target server - username: The username to login to the target server - password: The password for the target server - database: Optional database to connect to. Must be used when trying to do database operations, `USE x` is not sufficient - Returns: A /datum/BSQL_Operation representing the connection or null if an error occurred -*/ -/datum/BSQL_Connection/proc/BeginConnect(ipaddress, port, username, password, database) - return - -/* -Properly quotes a string for use by the database. The connection must be open for this proc to succeed - str: The string to quote - Returns: The string quoted on success, null on error -*/ -/datum/BSQL_Connection/proc/Quote(str) - return - -/* -Starts an operation for a query - query: The text of the query. Only one query allowed per invocation, no semicolons - Returns: A /datum/BSQL_Operation/Query representing the running query and subsequent result set or null if an error occurred - - Note for MariaDB: The underlying connection is pooled. In order to use connection state based properties (i.e. LAST_INSERT_ID()) you can guarantee multiple queries will use the same connection by running BSQL_DEL_CALL(query) on the finished /datum/BSQL_Operation/Query and then creating the next one with another call to BeginQuery() with no sleeps in between -*/ -/datum/BSQL_Connection/proc/BeginQuery(query) - return - -/* -Checks if the operation is complete. This, in some cases must be called multiple times with false return before a result is present regardless of timespan. For best performance check it once per tick - - Returns: TRUE if the operation is complete, FALSE if it's not, null on error -*/ -/datum/BSQL_Operation/proc/IsComplete() - return - -/* -Blocks the entire game until the given operation completes. IsComplete should not be checked after calling this to avoid potential side effects. - -Returns: TRUE on success, FALSE if the operation wait time exceeded the connection's blockingTimeout setting -*/ -/datum/BSQL_Operation/proc/WaitForCompletion() - return - -/* -Get the error message associated with an operation. Should not be used while IsComplete() returns FALSE - - Returns: The error message, if any. null otherwise -*/ -/datum/BSQL_Operation/proc/GetError() - return - -/* -Get the error code associated with an operation. Should not be used while IsComplete() returns FALSE - - Returns: The error code, if any. null otherwise -*/ -/datum/BSQL_Operation/proc/GetErrorCode() - return - -/* -Gets an associated list of column name -> value representation of the most recent row in the query. Only valid if IsComplete() returns TRUE. If this returns null and no errors are present there are no more results in the query. Important to note that once IsComplete() returns TRUE it must not be called again without checking this or the row values may be lost - - Returns: An associated list of column name -> value for the row. Values will always be either strings or null -*/ -/datum/BSQL_Operation/Query/proc/CurrentRow() - return - - -/* -Code configuration options below - -Define this to avoid modifying this file but the following defines must be declared somewhere else before BSQL/includes.dm is included -*/ -#ifndef BSQL_EXTERNAL_CONFIGURATION - -//Modify this if you disagree with byond's GC schemes. Ensure this is called for all connections and operations when they are deleted or they will leak native resources until /world/proc/BSQL_Shutdown() is called -#define BSQL_DEL_PROC(path) ##path/Del() - -//The equivalent of calling del() in your codebase -#define BSQL_DEL_CALL(obj) del(##obj) - -//Returns TRUE if an object is delete -#define BSQL_IS_DELETED(obj) (obj == null) - -//Modify this to add protections to the connection and query datums -#define BSQL_PROTECT_DATUM(path) - -//Modify this to change up error handling for the library -#define BSQL_ERROR(message) CRASH("BSQL: [##message]") - -#endif - -/* -Copyright 2018 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. -*/ +//BSQL - DMAPI +#define BSQL_VERSION "v1.3.0.0" + +//types of connections +#define BSQL_CONNECTION_TYPE_MARIADB "MySql" +#define BSQL_CONNECTION_TYPE_SQLSERVER "SqlServer" + +#define BSQL_DEFAULT_TIMEOUT 5 +#define BSQL_DEFAULT_THREAD_LIMIT 50 + +//Call this before rebooting or shutting down your world to clean up gracefully. This invalidates all active connection and operation datums +/world/proc/BSQL_Shutdown() + return + +/* +Called whenever a library call is made with verbose information, override and do with as you please + message: English debug message +*/ +/world/proc/BSQL_Debug(msg) + return + +/* +Create a new database connection, does not perform the actual connect + connection_type: The BSQL connection_type to use + asyncTimeout: The timeout to use for normal operations, 0 for infinite, defaults to BSQL_DEFAULT_TIMEOUT + blockingTimeout: The timeout to use for blocking operations, must be less than or equal to asyncTimeout, 0 for infinite, defaults to asyncTimeout + threadLimit: The limit of additional threads BSQL will run simultaneously, defaults to BSQL_DEFAULT_THREAD_LIMIT +*/ +/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit) + return ..() + +/* +Starts an operation to connect to a database. Should only have 1 successful call + ipaddress: The ip/hostname of the target server + port: The port of the target server + username: The username to login to the target server + password: The password for the target server + database: Optional database to connect to. Must be used when trying to do database operations, `USE x` is not sufficient + Returns: A /datum/BSQL_Operation representing the connection or null if an error occurred +*/ +/datum/BSQL_Connection/proc/BeginConnect(ipaddress, port, username, password, database) + return + +/* +Properly quotes a string for use by the database. The connection must be open for this proc to succeed + str: The string to quote + Returns: The string quoted on success, null on error +*/ +/datum/BSQL_Connection/proc/Quote(str) + return + +/* +Starts an operation for a query + query: The text of the query. Only one query allowed per invocation, no semicolons + Returns: A /datum/BSQL_Operation/Query representing the running query and subsequent result set or null if an error occurred + + Note for MariaDB: The underlying connection is pooled. In order to use connection state based properties (i.e. LAST_INSERT_ID()) you can guarantee multiple queries will use the same connection by running BSQL_DEL_CALL(query) on the finished /datum/BSQL_Operation/Query and then creating the next one with another call to BeginQuery() with no sleeps in between +*/ +/datum/BSQL_Connection/proc/BeginQuery(query) + return + +/* +Checks if the operation is complete. This, in some cases must be called multiple times with false return before a result is present regardless of timespan. For best performance check it once per tick + + Returns: TRUE if the operation is complete, FALSE if it's not, null on error +*/ +/datum/BSQL_Operation/proc/IsComplete() + return + +/* +Blocks the entire game until the given operation completes. IsComplete should not be checked after calling this to avoid potential side effects. + +Returns: TRUE on success, FALSE if the operation wait time exceeded the connection's blockingTimeout setting +*/ +/datum/BSQL_Operation/proc/WaitForCompletion() + return + +/* +Get the error message associated with an operation. Should not be used while IsComplete() returns FALSE + + Returns: The error message, if any. null otherwise +*/ +/datum/BSQL_Operation/proc/GetError() + return + +/* +Get the error code associated with an operation. Should not be used while IsComplete() returns FALSE + + Returns: The error code, if any. null otherwise +*/ +/datum/BSQL_Operation/proc/GetErrorCode() + return + +/* +Gets an associated list of column name -> value representation of the most recent row in the query. Only valid if IsComplete() returns TRUE. If this returns null and no errors are present there are no more results in the query. Important to note that once IsComplete() returns TRUE it must not be called again without checking this or the row values may be lost + + Returns: An associated list of column name -> value for the row. Values will always be either strings or null +*/ +/datum/BSQL_Operation/Query/proc/CurrentRow() + return + + +/* +Code configuration options below + +Define this to avoid modifying this file but the following defines must be declared somewhere else before BSQL/includes.dm is included +*/ +#ifndef BSQL_EXTERNAL_CONFIGURATION + +//Modify this if you disagree with byond's GC schemes. Ensure this is called for all connections and operations when they are deleted or they will leak native resources until /world/proc/BSQL_Shutdown() is called +#define BSQL_DEL_PROC(path) ##path/Del() + +//The equivalent of calling del() in your codebase +#define BSQL_DEL_CALL(obj) del(##obj) + +//Returns TRUE if an object is delete +#define BSQL_IS_DELETED(obj) (obj == null) + +//Modify this to add protections to the connection and query datums +#define BSQL_PROTECT_DATUM(path) + +//Modify this to change up error handling for the library +#define BSQL_ERROR(message) CRASH("BSQL: [##message]") + +#endif + +/* +Copyright 2018 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/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 7236c3ca41a4..68a879998a79 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -1,195 +1,195 @@ -/*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 MANUAL_SUICIDE_NONLETHAL (1<<6) //when the suicide is conditionally lethal - -#define EFFECT_STUN "stun" -#define EFFECT_KNOCKDOWN "knockdown" -#define EFFECT_UNCONSCIOUS "unconscious" -#define EFFECT_PARALYZE "paralyze" -#define EFFECT_IMMOBILIZE "immobilize" -#define EFFECT_IRRADIATE "irradiate" -#define EFFECT_STUTTER "stutter" -#define EFFECT_SLUR "slur" -#define EFFECT_EYE_BLUR "eye_blur" -#define EFFECT_DROWSY "drowsy" -#define EFFECT_JITTER "jitter" - -//Bitflags defining which status effects could be or are inflicted on a mob -#define CANSTUN (1<<0) -#define CANKNOCKDOWN (1<<1) -#define CANUNCONSCIOUS (1<<2) -#define CANPUSH (1<<3) -#define GODMODE (1<<4) - -//Health Defines -#define HEALTH_THRESHOLD_CRIT 0 -#define HEALTH_THRESHOLD_FULLCRIT -30 -#define HEALTH_THRESHOLD_DEAD -100 - -#define HEALTH_THRESHOLD_NEARDEATH -90 //Not used mechanically, but to determine if someone is so close to death they hear the other side - -//Actual combat defines - -//click cooldowns, in tenths of a second, used for various combat actions -#define CLICK_CD_MELEE 8 -#define CLICK_CD_RANGE 4 -#define CLICK_CD_RAPID 2 -#define CLICK_CD_CLICK_ABILITY 6 -#define CLICK_CD_BREAKOUT 100 -#define CLICK_CD_HANDCUFFED 10 -#define CLICK_CD_RESIST 20 -#define CLICK_CD_GRABBING 10 - -//Cuff resist speeds -#define FAST_CUFFBREAK 1 -#define INSTANT_CUFFBREAK 2 - -//Grab levels -#define GRAB_PASSIVE 0 -#define GRAB_AGGRESSIVE 1 -#define GRAB_NECK 2 -#define GRAB_KILL 3 - -//Grab breakout odds -#define BASE_GRAB_RESIST_CHANCE 30 - -//slowdown when in softcrit. Note that crawling slowdown will also apply at the same time! -#define SOFTCRIT_ADD_SLOWDOWN 2 -//slowdown when crawling -#define CRAWLING_ADD_SLOWDOWN 4 - -//Attack types for checking shields/hit reactions -#define MELEE_ATTACK 1 -#define UNARMED_ATTACK 2 -#define PROJECTILE_ATTACK 3 -#define THROWN_PROJECTILE_ATTACK 4 -#define LEAP_ATTACK 5 - -//attack visual effects -#define ATTACK_EFFECT_PUNCH "punch" -#define ATTACK_EFFECT_KICK "kick" -#define ATTACK_EFFECT_SMASH "smash" -#define ATTACK_EFFECT_CLAW "claw" -#define ATTACK_EFFECT_SLASH "slash" -#define ATTACK_EFFECT_DISARM "disarm" -#define ATTACK_EFFECT_BITE "bite" -#define ATTACK_EFFECT_MECHFIRE "mech_fire" -#define ATTACK_EFFECT_MECHTOXIN "mech_toxin" -#define ATTACK_EFFECT_BOOP "boop" //Honk - -//intent defines -#define INTENT_HELP "help" -#define INTENT_GRAB "grab" -#define INTENT_DISARM "disarm" -#define INTENT_HARM "harm" -//NOTE: INTENT_HOTKEY_* defines are not actual intents! -//they are here to support hotkeys -#define INTENT_HOTKEY_LEFT "left" -#define INTENT_HOTKEY_RIGHT "right" - -//the define for visible message range in combat -#define COMBAT_MESSAGE_RANGE 3 - -//Combat object defines - -//Embedded objects -#define EMBEDDED_PAIN_CHANCE 15 //Chance for embedded objects to cause pain (damage user) -#define EMBEDDED_ITEM_FALLOUT 5 //Chance for embedded object to fall out (causing pain but removing the object) -#define EMBED_CHANCE 45 //Chance for an object to embed into somebody when thrown (if it's sharp) -#define EMBEDDED_PAIN_MULTIPLIER 2 //Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) -#define EMBEDDED_FALL_PAIN_MULTIPLIER 5 //Coefficient of multiplication for the damage the item does when it falls out (this*item.w_class) -#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4 //Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) -#define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) -#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) -#define EMBEDDED_UNSAFE_REMOVAL_TIME 30 //A Time in ticks, total removal time = (this*item.w_class) - -//Gun weapon weight -#define WEAPON_LIGHT 1 -#define WEAPON_MEDIUM 2 -#define WEAPON_HEAVY 3 -//Gun trigger guards -#define TRIGGER_GUARD_ALLOW_ALL -1 -#define TRIGGER_GUARD_NONE 0 -#define TRIGGER_GUARD_NORMAL 1 -//Gun bolt types -#define BOLT_TYPE_STANDARD 1 -#define BOLT_TYPE_OPEN 2 -#define BOLT_TYPE_NO_BOLT 3 -#define BOLT_TYPE_LOCKING 4 -//Sawn off nerfs -#define SAWN_OFF_ACC_PENALTY 25 -#define SAWN_OFF_RECOIL 1 - -//Projectile Reflect -#define REFLECT_NORMAL (1<<0) -#define REFLECT_FAKEPROJECTILE (1<<1) - -//Object/Item sharpness -#define IS_BLUNT 0 -#define IS_SHARP 1 -#define IS_SHARP_ACCURATE 2 - -//His Grace. -#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. -#define HIS_GRACE_PECKISH 20 //Slightly hungry. -#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20. -#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped. -#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30. -#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive. -#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets. - -#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill. - -#define EXPLODE_NONE 0 //Don't even ask me why we need this. -#define EXPLODE_DEVASTATE 1 -#define EXPLODE_HEAVY 2 -#define EXPLODE_LIGHT 3 - -#define EMP_HEAVY 1 -#define EMP_LIGHT 2 - -#define GRENADE_CLUMSY_FUMBLE 1 -#define GRENADE_NONCLUMSY_FUMBLE 2 -#define GRENADE_NO_FUMBLE 3 - -#define BODY_ZONE_HEAD "head" -#define BODY_ZONE_CHEST "chest" -#define BODY_ZONE_L_ARM "l_arm" -#define BODY_ZONE_R_ARM "r_arm" -#define BODY_ZONE_L_LEG "l_leg" -#define BODY_ZONE_R_LEG "r_leg" - -#define BODY_ZONE_PRECISE_EYES "eyes" -#define BODY_ZONE_PRECISE_MOUTH "mouth" -#define BODY_ZONE_PRECISE_GROIN "groin" -#define BODY_ZONE_PRECISE_L_HAND "l_hand" -#define BODY_ZONE_PRECISE_R_HAND "r_hand" -#define BODY_ZONE_PRECISE_L_FOOT "l_foot" -#define BODY_ZONE_PRECISE_R_FOOT "r_foot" - -//We will round to this value in damage calculations. -#define DAMAGE_PRECISION 0.1 - -//bullet_act() return values -#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting. -#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. -#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. -#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. +/*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 MANUAL_SUICIDE_NONLETHAL (1<<6) //when the suicide is conditionally lethal + +#define EFFECT_STUN "stun" +#define EFFECT_KNOCKDOWN "knockdown" +#define EFFECT_UNCONSCIOUS "unconscious" +#define EFFECT_PARALYZE "paralyze" +#define EFFECT_IMMOBILIZE "immobilize" +#define EFFECT_IRRADIATE "irradiate" +#define EFFECT_STUTTER "stutter" +#define EFFECT_SLUR "slur" +#define EFFECT_EYE_BLUR "eye_blur" +#define EFFECT_DROWSY "drowsy" +#define EFFECT_JITTER "jitter" + +//Bitflags defining which status effects could be or are inflicted on a mob +#define CANSTUN (1<<0) +#define CANKNOCKDOWN (1<<1) +#define CANUNCONSCIOUS (1<<2) +#define CANPUSH (1<<3) +#define GODMODE (1<<4) + +//Health Defines +#define HEALTH_THRESHOLD_CRIT 0 +#define HEALTH_THRESHOLD_FULLCRIT -30 +#define HEALTH_THRESHOLD_DEAD -100 + +#define HEALTH_THRESHOLD_NEARDEATH -90 //Not used mechanically, but to determine if someone is so close to death they hear the other side + +//Actual combat defines + +//click cooldowns, in tenths of a second, used for various combat actions +#define CLICK_CD_MELEE 8 +#define CLICK_CD_RANGE 4 +#define CLICK_CD_RAPID 2 +#define CLICK_CD_CLICK_ABILITY 6 +#define CLICK_CD_BREAKOUT 100 +#define CLICK_CD_HANDCUFFED 10 +#define CLICK_CD_RESIST 20 +#define CLICK_CD_GRABBING 10 + +//Cuff resist speeds +#define FAST_CUFFBREAK 1 +#define INSTANT_CUFFBREAK 2 + +//Grab levels +#define GRAB_PASSIVE 0 +#define GRAB_AGGRESSIVE 1 +#define GRAB_NECK 2 +#define GRAB_KILL 3 + +//Grab breakout odds +#define BASE_GRAB_RESIST_CHANCE 30 + +//slowdown when in softcrit. Note that crawling slowdown will also apply at the same time! +#define SOFTCRIT_ADD_SLOWDOWN 2 +//slowdown when crawling +#define CRAWLING_ADD_SLOWDOWN 4 + +//Attack types for checking shields/hit reactions +#define MELEE_ATTACK 1 +#define UNARMED_ATTACK 2 +#define PROJECTILE_ATTACK 3 +#define THROWN_PROJECTILE_ATTACK 4 +#define LEAP_ATTACK 5 + +//attack visual effects +#define ATTACK_EFFECT_PUNCH "punch" +#define ATTACK_EFFECT_KICK "kick" +#define ATTACK_EFFECT_SMASH "smash" +#define ATTACK_EFFECT_CLAW "claw" +#define ATTACK_EFFECT_SLASH "slash" +#define ATTACK_EFFECT_DISARM "disarm" +#define ATTACK_EFFECT_BITE "bite" +#define ATTACK_EFFECT_MECHFIRE "mech_fire" +#define ATTACK_EFFECT_MECHTOXIN "mech_toxin" +#define ATTACK_EFFECT_BOOP "boop" //Honk + +//intent defines +#define INTENT_HELP "help" +#define INTENT_GRAB "grab" +#define INTENT_DISARM "disarm" +#define INTENT_HARM "harm" +//NOTE: INTENT_HOTKEY_* defines are not actual intents! +//they are here to support hotkeys +#define INTENT_HOTKEY_LEFT "left" +#define INTENT_HOTKEY_RIGHT "right" + +//the define for visible message range in combat +#define COMBAT_MESSAGE_RANGE 3 + +//Combat object defines + +//Embedded objects +#define EMBEDDED_PAIN_CHANCE 15 //Chance for embedded objects to cause pain (damage user) +#define EMBEDDED_ITEM_FALLOUT 5 //Chance for embedded object to fall out (causing pain but removing the object) +#define EMBED_CHANCE 45 //Chance for an object to embed into somebody when thrown (if it's sharp) +#define EMBEDDED_PAIN_MULTIPLIER 2 //Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) +#define EMBEDDED_FALL_PAIN_MULTIPLIER 5 //Coefficient of multiplication for the damage the item does when it falls out (this*item.w_class) +#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4 //Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) +#define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) +#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) +#define EMBEDDED_UNSAFE_REMOVAL_TIME 30 //A Time in ticks, total removal time = (this*item.w_class) + +//Gun weapon weight +#define WEAPON_LIGHT 1 +#define WEAPON_MEDIUM 2 +#define WEAPON_HEAVY 3 +//Gun trigger guards +#define TRIGGER_GUARD_ALLOW_ALL -1 +#define TRIGGER_GUARD_NONE 0 +#define TRIGGER_GUARD_NORMAL 1 +//Gun bolt types +#define BOLT_TYPE_STANDARD 1 +#define BOLT_TYPE_OPEN 2 +#define BOLT_TYPE_NO_BOLT 3 +#define BOLT_TYPE_LOCKING 4 +//Sawn off nerfs +#define SAWN_OFF_ACC_PENALTY 25 +#define SAWN_OFF_RECOIL 1 + +//Projectile Reflect +#define REFLECT_NORMAL (1<<0) +#define REFLECT_FAKEPROJECTILE (1<<1) + +//Object/Item sharpness +#define IS_BLUNT 0 +#define IS_SHARP 1 +#define IS_SHARP_ACCURATE 2 + +//His Grace. +#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. +#define HIS_GRACE_PECKISH 20 //Slightly hungry. +#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20. +#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped. +#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30. +#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive. +#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets. + +#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill. + +#define EXPLODE_NONE 0 //Don't even ask me why we need this. +#define EXPLODE_DEVASTATE 1 +#define EXPLODE_HEAVY 2 +#define EXPLODE_LIGHT 3 + +#define EMP_HEAVY 1 +#define EMP_LIGHT 2 + +#define GRENADE_CLUMSY_FUMBLE 1 +#define GRENADE_NONCLUMSY_FUMBLE 2 +#define GRENADE_NO_FUMBLE 3 + +#define BODY_ZONE_HEAD "head" +#define BODY_ZONE_CHEST "chest" +#define BODY_ZONE_L_ARM "l_arm" +#define BODY_ZONE_R_ARM "r_arm" +#define BODY_ZONE_L_LEG "l_leg" +#define BODY_ZONE_R_LEG "r_leg" + +#define BODY_ZONE_PRECISE_EYES "eyes" +#define BODY_ZONE_PRECISE_MOUTH "mouth" +#define BODY_ZONE_PRECISE_GROIN "groin" +#define BODY_ZONE_PRECISE_L_HAND "l_hand" +#define BODY_ZONE_PRECISE_R_HAND "r_hand" +#define BODY_ZONE_PRECISE_L_FOOT "l_foot" +#define BODY_ZONE_PRECISE_R_FOOT "r_foot" + +//We will round to this value in damage calculations. +#define DAMAGE_PRECISION 0.1 + +//bullet_act() return values +#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting. +#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. +#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. +#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. diff --git a/code/__DEFINES/configuration.dm b/code/__DEFINES/configuration.dm index c4ef8e660682..3034876e3633 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/forensics.dm b/code/__DEFINES/forensics.dm index f6121482dab9..8d0647cb1ed8 100644 --- a/code/__DEFINES/forensics.dm +++ b/code/__DEFINES/forensics.dm @@ -1 +1 @@ -#define HAS_BLOOD_DNA(thing) (length(thing.GetComponent(/datum/component/forensics)?.blood_DNA)) +#define HAS_BLOOD_DNA(thing) (length(thing.GetComponent(/datum/component/forensics)?.blood_DNA)) diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 7bf8bbd25a1f..1ccbe74ced62 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -1,11 +1,11 @@ -//HUD styles. Index order defines how they are cycled in F12. -#define HUD_STYLE_STANDARD 1 //Standard hud -#define HUD_STYLE_REDUCED 2 //Reduced hud (just hands and intent switcher) -#define HUD_STYLE_NOHUD 3 //No hud (for screenshots) - -#define HUD_VERSIONS 3 //Used in show_hud(); Please ensure this is the same as the maximum index. - -//1:1 HUD layout stuff -#define UI_BOXCRAFT "EAST-4:22,SOUTH+1:6" -#define UI_BOXAREA "EAST-4:6,SOUTH+1:6" -#define UI_BOXLANG "EAST-5:22,SOUTH+1:6" +//HUD styles. Index order defines how they are cycled in F12. +#define HUD_STYLE_STANDARD 1 //Standard hud +#define HUD_STYLE_REDUCED 2 //Reduced hud (just hands and intent switcher) +#define HUD_STYLE_NOHUD 3 //No hud (for screenshots) + +#define HUD_VERSIONS 3 //Used in show_hud(); Please ensure this is the same as the maximum index. + +//1:1 HUD layout stuff +#define UI_BOXCRAFT "EAST-4:22,SOUTH+1:6" +#define UI_BOXAREA "EAST-4:6,SOUTH+1:6" +#define UI_BOXLANG "EAST-5:22,SOUTH+1:6" diff --git a/code/__DEFINES/interaction_flags.dm b/code/__DEFINES/interaction_flags.dm index a117e71d518e..dc3242c91072 100644 --- a/code/__DEFINES/interaction_flags.dm +++ b/code/__DEFINES/interaction_flags.dm @@ -1,22 +1,22 @@ - -#define INTERACT_ATOM_REQUIRES_ANCHORED (1<<0) //whether can_interact() checks for anchored. only works on movables. -#define INTERACT_ATOM_ATTACK_HAND (1<<1) //calls try_interact() on attack_hand() and returns that. -#define INTERACT_ATOM_UI_INTERACT (1<<2) //automatically calls and returns ui_interact() on interact(). -#define INTERACT_ATOM_REQUIRES_DEXTERITY (1<<3) //user must be dextrous -#define INTERACT_ATOM_IGNORE_INCAPACITATED (1<<4) //ignores incapacitated check -#define INTERACT_ATOM_IGNORE_RESTRAINED (1<<5) //incapacitated check ignores restrained -#define INTERACT_ATOM_CHECK_GRAB (1<<6) //incapacitated check checks grab -#define INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND (1<<7) //prevents leaving fingerprints automatically on attack_hand -#define INTERACT_ATOM_NO_FINGERPRINT_INTERACT (1<<8) //adds hiddenprints instead of fingerprints on interact - -#define INTERACT_ITEM_ATTACK_HAND_PICKUP (1<<0) //attempt pickup on attack_hand for items - -#define INTERACT_MACHINE_OPEN (1<<0) //can_interact() while open -#define INTERACT_MACHINE_OFFLINE (1<<1) //can_interact() while offline -#define INTERACT_MACHINE_WIRES_IF_OPEN (1<<2) //try to interact with wires if open -#define INTERACT_MACHINE_ALLOW_SILICON (1<<3) //let silicons interact -#define INTERACT_MACHINE_OPEN_SILICON (1<<4) //let silicons interact while open -#define INTERACT_MACHINE_REQUIRES_SILICON (1<<5) //must be silicon to interact -#define INTERACT_MACHINE_SET_MACHINE (1<<6) //MACHINES HAVE THIS BY DEFAULT, SOMEONE SHOULD RUN THROUGH MACHINES AND REMOVE IT FROM THINGS LIKE LIGHT SWITCHES WHEN POSSIBLE!!-------------------------- - //This flag determines if a machine set_machine's the user when the user uses it, making updateUsrDialog make the user re-call interact() on it. - //THIS FLAG IS ON ALL MACHINES BY DEFAULT, NEEDS TO BE RE-EVALUATED LATER!! + +#define INTERACT_ATOM_REQUIRES_ANCHORED (1<<0) //whether can_interact() checks for anchored. only works on movables. +#define INTERACT_ATOM_ATTACK_HAND (1<<1) //calls try_interact() on attack_hand() and returns that. +#define INTERACT_ATOM_UI_INTERACT (1<<2) //automatically calls and returns ui_interact() on interact(). +#define INTERACT_ATOM_REQUIRES_DEXTERITY (1<<3) //user must be dextrous +#define INTERACT_ATOM_IGNORE_INCAPACITATED (1<<4) //ignores incapacitated check +#define INTERACT_ATOM_IGNORE_RESTRAINED (1<<5) //incapacitated check ignores restrained +#define INTERACT_ATOM_CHECK_GRAB (1<<6) //incapacitated check checks grab +#define INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND (1<<7) //prevents leaving fingerprints automatically on attack_hand +#define INTERACT_ATOM_NO_FINGERPRINT_INTERACT (1<<8) //adds hiddenprints instead of fingerprints on interact + +#define INTERACT_ITEM_ATTACK_HAND_PICKUP (1<<0) //attempt pickup on attack_hand for items + +#define INTERACT_MACHINE_OPEN (1<<0) //can_interact() while open +#define INTERACT_MACHINE_OFFLINE (1<<1) //can_interact() while offline +#define INTERACT_MACHINE_WIRES_IF_OPEN (1<<2) //try to interact with wires if open +#define INTERACT_MACHINE_ALLOW_SILICON (1<<3) //let silicons interact +#define INTERACT_MACHINE_OPEN_SILICON (1<<4) //let silicons interact while open +#define INTERACT_MACHINE_REQUIRES_SILICON (1<<5) //must be silicon to interact +#define INTERACT_MACHINE_SET_MACHINE (1<<6) //MACHINES HAVE THIS BY DEFAULT, SOMEONE SHOULD RUN THROUGH MACHINES AND REMOVE IT FROM THINGS LIKE LIGHT SWITCHES WHEN POSSIBLE!!-------------------------- + //This flag determines if a machine set_machine's the user when the user uses it, making updateUsrDialog make the user re-call interact() on it. + //THIS FLAG IS ON ALL MACHINES BY DEFAULT, NEEDS TO BE RE-EVALUATED LATER!! diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index e90c94a84944..d94dc3a9f45c 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -1,94 +1,94 @@ - -#define ENGSEC (1<<0) - -#define CAPTAIN (1<<0) -#define HOS (1<<1) -#define WARDEN (1<<2) -#define DETECTIVE (1<<3) -#define OFFICER (1<<4) -#define CHIEF (1<<5) -#define ENGINEER (1<<6) -#define ATMOSTECH (1<<7) -#define ROBOTICIST (1<<8) -#define AI_JF (1<<9) -#define CYBORG (1<<10) - - -#define MEDSCI (1<<1) - -#define RD_JF (1<<0) -#define SCIENTIST (1<<1) -#define CHEMIST (1<<2) -#define CMO_JF (1<<3) -#define DOCTOR (1<<4) -#define GENETICIST (1<<5) -#define VIROLOGIST (1<<6) - - -#define 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 DEFAULT_RELIGION "Christianity" -#define DEFAULT_DEITY "Space Jesus" - -#define JOB_DISPLAY_ORDER_DEFAULT 0 - -#define JOB_DISPLAY_ORDER_ASSISTANT 1 //yogs start - this list has been changed to include the yogs jobs. -#define JOB_DISPLAY_ORDER_CAPTAIN 2 -#define JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL 3 -#define JOB_DISPLAY_ORDER_QUARTERMASTER 4 -#define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 5 -#define JOB_DISPLAY_ORDER_SHAFT_MINER 6 -#define JOB_DISPLAY_ORDER_BARTENDER 7 -#define JOB_DISPLAY_ORDER_COOK 8 -#define JOB_DISPLAY_ORDER_BOTANIST 9 -#define JOB_DISPLAY_ORDER_JANITOR 10 -#define JOB_DISPLAY_ORDER_CLOWN 11 -#define JOB_DISPLAY_ORDER_MIME 12 -#define JOB_DISPLAY_ORDER_CURATOR 13 -#define JOB_DISPLAY_ORDER_LAWYER 14 -#define JOB_DISPLAY_ORDER_TOURIST 15 -#define JOB_DISPLAY_ORDER_CLERK 16 -#define JOB_DISPLAY_ORDER_CHAPLAIN 17 -#define JOB_DISPLAY_ORDER_CHIEF_ENGINEER 18 -#define JOB_DISPLAY_ORDER_STATION_ENGINEER 19 -#define JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN 20 -#define JOB_DISPLAY_ORDER_SIGNAL_TECHNICIAN 21 -#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 22 -#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 23 -#define JOB_DISPLAY_ORDER_CHEMIST 24 -#define JOB_DISPLAY_ORDER_GENETICIST 25 -#define JOB_DISPLAY_ORDER_VIROLOGIST 26 -#define JOB_DISPLAY_ORDER_MINING_MEDIC 27 -#define JOB_DISPLAY_ORDER_PARAMEDIC 28 -#define JOB_DISPLAY_ORDER_PSYCHIATRIST 29 -#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 30 -#define JOB_DISPLAY_ORDER_SCIENTIST 31 -#define JOB_DISPLAY_ORDER_ROBOTICIST 32 -#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 33 -#define JOB_DISPLAY_ORDER_WARDEN 34 -#define JOB_DISPLAY_ORDER_DETECTIVE 35 -#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 36 -#define JOB_DISPLAY_ORDER_AI 37 -#define JOB_DISPLAY_ORDER_CYBORG 38 //yogs end - this list was changed to include the yogs jobs + +#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 DEFAULT_RELIGION "Christianity" +#define DEFAULT_DEITY "Space Jesus" + +#define JOB_DISPLAY_ORDER_DEFAULT 0 + +#define JOB_DISPLAY_ORDER_ASSISTANT 1 //yogs start - this list has been changed to include the yogs jobs. +#define JOB_DISPLAY_ORDER_CAPTAIN 2 +#define JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL 3 +#define JOB_DISPLAY_ORDER_QUARTERMASTER 4 +#define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 5 +#define JOB_DISPLAY_ORDER_SHAFT_MINER 6 +#define JOB_DISPLAY_ORDER_BARTENDER 7 +#define JOB_DISPLAY_ORDER_COOK 8 +#define JOB_DISPLAY_ORDER_BOTANIST 9 +#define JOB_DISPLAY_ORDER_JANITOR 10 +#define JOB_DISPLAY_ORDER_CLOWN 11 +#define JOB_DISPLAY_ORDER_MIME 12 +#define JOB_DISPLAY_ORDER_CURATOR 13 +#define JOB_DISPLAY_ORDER_LAWYER 14 +#define JOB_DISPLAY_ORDER_TOURIST 15 +#define JOB_DISPLAY_ORDER_CLERK 16 +#define JOB_DISPLAY_ORDER_CHAPLAIN 17 +#define JOB_DISPLAY_ORDER_CHIEF_ENGINEER 18 +#define JOB_DISPLAY_ORDER_STATION_ENGINEER 19 +#define JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN 20 +#define JOB_DISPLAY_ORDER_SIGNAL_TECHNICIAN 21 +#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 22 +#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 23 +#define JOB_DISPLAY_ORDER_CHEMIST 24 +#define JOB_DISPLAY_ORDER_GENETICIST 25 +#define JOB_DISPLAY_ORDER_VIROLOGIST 26 +#define JOB_DISPLAY_ORDER_MINING_MEDIC 27 +#define JOB_DISPLAY_ORDER_PARAMEDIC 28 +#define JOB_DISPLAY_ORDER_PSYCHIATRIST 29 +#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 30 +#define JOB_DISPLAY_ORDER_SCIENTIST 31 +#define JOB_DISPLAY_ORDER_ROBOTICIST 32 +#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 33 +#define JOB_DISPLAY_ORDER_WARDEN 34 +#define JOB_DISPLAY_ORDER_DETECTIVE 35 +#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 36 +#define JOB_DISPLAY_ORDER_AI 37 +#define JOB_DISPLAY_ORDER_CYBORG 38 //yogs end - this list was changed to include the yogs jobs diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm index 2672bfd5777b..7f0e99afe4c5 100644 --- a/code/__DEFINES/machines.dm +++ b/code/__DEFINES/machines.dm @@ -1,108 +1,108 @@ -// 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 NANITE_COMPILER (1<<8) //Prints nanite disks -//Note: More than one of these can be added to a design but imprinter and lathe designs are incompatable. - -//Modular computer/NTNet defines - -//Modular computer part defines -#define MC_CPU "CPU" -#define MC_HDD "HDD" -#define MC_SDD "SDD" -#define MC_CARD "CARD" -#define MC_NET "NET" -#define MC_PRINT "PRINT" -#define MC_CELL "CELL" -#define MC_CHARGE "CHARGE" -#define MC_AI "AI" - -//NTNet stuff, for modular computers - // NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc) -#define NTNET_SOFTWAREDOWNLOAD 1 // Downloads of software from NTNet -#define NTNET_PEERTOPEER 2 // P2P transfers of files between devices -#define NTNET_COMMUNICATION 3 // Communication (messaging) -#define NTNET_SYSTEMCONTROL 4 // Control of various systems, RCon, air alarm control, etc. - -//NTNet transfer speeds, used when downloading/uploading a file/program. -#define NTNETSPEED_LOWSIGNAL 0.5 // GQ/s transfer speed when the device is wirelessly connected and on Low signal -#define NTNETSPEED_HIGHSIGNAL 1 // GQ/s transfer speed when the device is wirelessly connected and on High signal -#define NTNETSPEED_ETHERNET 2 // GQ/s transfer speed when the device is using wired connection - -//Caps for NTNet logging. Less than 10 would make logging useless anyway, more than 500 may make the log browser too laggy. Defaults to 100 unless user changes it. -#define MAX_NTNET_LOGS 300 -#define MIN_NTNET_LOGS 10 - -//Program bitflags -#define PROGRAM_ALL (~0) -#define PROGRAM_CONSOLE (1<<0) -#define PROGRAM_LAPTOP (1<<1) -#define PROGRAM_TABLET (1<<2) -//Program states -#define PROGRAM_STATE_KILLED 0 -#define PROGRAM_STATE_BACKGROUND 1 -#define PROGRAM_STATE_ACTIVE 2 - -#define FIREDOOR_OPEN 1 -#define FIREDOOR_CLOSED 2 - - - -// These are used by supermatter and supermatter monitor program, mostly for UI updating purposes. Higher should always be worse! -#define SUPERMATTER_ERROR -1 // Unknown status, shouldn't happen but just in case. -#define SUPERMATTER_INACTIVE 0 // No or minimal energy -#define SUPERMATTER_NORMAL 1 // Normal operation -#define SUPERMATTER_NOTIFY 2 // Ambient temp > 80% of CRITICAL_TEMPERATURE -#define SUPERMATTER_WARNING 3 // Ambient temp > CRITICAL_TEMPERATURE OR integrity damaged -#define SUPERMATTER_DANGER 4 // Integrity < 50% -#define SUPERMATTER_EMERGENCY 5 // Integrity < 25% -#define SUPERMATTER_DELAMINATING 6 // Pretty obvious. - -//Nuclear bomb stuff -#define NUKESTATE_INTACT 5 -#define NUKESTATE_UNSCREWED 4 -#define NUKESTATE_PANEL_REMOVED 3 -#define NUKESTATE_WELDED 2 -#define NUKESTATE_CORE_EXPOSED 1 -#define NUKESTATE_CORE_REMOVED 0 - -#define NUKE_OFF_LOCKED 0 -#define NUKE_OFF_UNLOCKED 1 -#define NUKE_ON_TIMING 2 -#define NUKE_ON_EXPLODING 3 - -#define MACHINE_NOT_ELECTRIFIED 0 -#define MACHINE_ELECTRIFIED_PERMANENT -1 -#define MACHINE_DEFAULT_ELECTRIFY_TIME 30 - -//cloning defines. These are flags. -#define CLONING_SUCCESS (1<<0) -#define CLONING_DELETE_RECORD (1<<1) +// 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 NANITE_COMPILER (1<<8) //Prints nanite disks +//Note: More than one of these can be added to a design but imprinter and lathe designs are incompatable. + +//Modular computer/NTNet defines + +//Modular computer part defines +#define MC_CPU "CPU" +#define MC_HDD "HDD" +#define MC_SDD "SDD" +#define MC_CARD "CARD" +#define MC_NET "NET" +#define MC_PRINT "PRINT" +#define MC_CELL "CELL" +#define MC_CHARGE "CHARGE" +#define MC_AI "AI" + +//NTNet stuff, for modular computers + // NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc) +#define NTNET_SOFTWAREDOWNLOAD 1 // Downloads of software from NTNet +#define NTNET_PEERTOPEER 2 // P2P transfers of files between devices +#define NTNET_COMMUNICATION 3 // Communication (messaging) +#define NTNET_SYSTEMCONTROL 4 // Control of various systems, RCon, air alarm control, etc. + +//NTNet transfer speeds, used when downloading/uploading a file/program. +#define NTNETSPEED_LOWSIGNAL 0.5 // GQ/s transfer speed when the device is wirelessly connected and on Low signal +#define NTNETSPEED_HIGHSIGNAL 1 // GQ/s transfer speed when the device is wirelessly connected and on High signal +#define NTNETSPEED_ETHERNET 2 // GQ/s transfer speed when the device is using wired connection + +//Caps for NTNet logging. Less than 10 would make logging useless anyway, more than 500 may make the log browser too laggy. Defaults to 100 unless user changes it. +#define MAX_NTNET_LOGS 300 +#define MIN_NTNET_LOGS 10 + +//Program bitflags +#define PROGRAM_ALL (~0) +#define PROGRAM_CONSOLE (1<<0) +#define PROGRAM_LAPTOP (1<<1) +#define PROGRAM_TABLET (1<<2) +//Program states +#define PROGRAM_STATE_KILLED 0 +#define PROGRAM_STATE_BACKGROUND 1 +#define PROGRAM_STATE_ACTIVE 2 + +#define FIREDOOR_OPEN 1 +#define FIREDOOR_CLOSED 2 + + + +// These are used by supermatter and supermatter monitor program, mostly for UI updating purposes. Higher should always be worse! +#define SUPERMATTER_ERROR -1 // Unknown status, shouldn't happen but just in case. +#define SUPERMATTER_INACTIVE 0 // No or minimal energy +#define SUPERMATTER_NORMAL 1 // Normal operation +#define SUPERMATTER_NOTIFY 2 // Ambient temp > 80% of CRITICAL_TEMPERATURE +#define SUPERMATTER_WARNING 3 // Ambient temp > CRITICAL_TEMPERATURE OR integrity damaged +#define SUPERMATTER_DANGER 4 // Integrity < 50% +#define SUPERMATTER_EMERGENCY 5 // Integrity < 25% +#define SUPERMATTER_DELAMINATING 6 // Pretty obvious. + +//Nuclear bomb stuff +#define NUKESTATE_INTACT 5 +#define NUKESTATE_UNSCREWED 4 +#define NUKESTATE_PANEL_REMOVED 3 +#define NUKESTATE_WELDED 2 +#define NUKESTATE_CORE_EXPOSED 1 +#define NUKESTATE_CORE_REMOVED 0 + +#define NUKE_OFF_LOCKED 0 +#define NUKE_OFF_UNLOCKED 1 +#define NUKE_ON_TIMING 2 +#define NUKE_ON_EXPLODING 3 + +#define MACHINE_NOT_ELECTRIFIED 0 +#define MACHINE_ELECTRIFIED_PERMANENT -1 +#define MACHINE_DEFAULT_ELECTRIFY_TIME 30 + +//cloning defines. These are flags. +#define CLONING_SUCCESS (1<<0) +#define CLONING_DELETE_RECORD (1<<1) diff --git a/code/__DEFINES/medal.dm b/code/__DEFINES/medal.dm index bdaa25b843ec..b39f33a71bc0 100644 --- a/code/__DEFINES/medal.dm +++ b/code/__DEFINES/medal.dm @@ -1,32 +1,32 @@ -// 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" -#define MEDAL_RODSUPLEX "Feat of Strength" -#define MEDAL_CLOWNCARKING "Round and Full" -#define MEDAL_THANKSALOT "The Best Driver" +// 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" +#define MEDAL_RODSUPLEX "Feat of Strength" +#define MEDAL_CLOWNCARKING "Round and Full" +#define MEDAL_THANKSALOT "The Best Driver" diff --git a/code/__DEFINES/move_force.dm b/code/__DEFINES/move_force.dm index 5ab615643af3..1f8819b0c857 100644 --- a/code/__DEFINES/move_force.dm +++ b/code/__DEFINES/move_force.dm @@ -1,20 +1,20 @@ -//Defaults -#define MOVE_FORCE_DEFAULT 1000 -#define MOVE_RESIST_DEFAULT 1000 -#define PULL_FORCE_DEFAULT 1000 - -//Factors/modifiers -#define MOVE_FORCE_PULL_RATIO 1 //Same move force to pull objects -#define MOVE_FORCE_PUSH_RATIO 1 //Same move force to normally push -#define MOVE_FORCE_FORCEPUSH_RATIO 2 //2x move force to forcefully push -#define MOVE_FORCE_CRUSH_RATIO 3 //3x move force to do things like crush objects -#define MOVE_FORCE_THROW_RATIO 1 //Same force throw as resist to throw objects - -#define MOVE_FORCE_OVERPOWERING (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 10) -#define MOVE_FORCE_EXTREMELY_STRONG (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 3) -#define MOVE_FORCE_VERY_STRONG ((MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO) - 1) -#define MOVE_FORCE_STRONG (MOVE_FORCE_DEFAULT * 2) -#define MOVE_FORCE_NORMAL MOVE_FORCE_DEFAULT -#define MOVE_FORCE_WEAK (MOVE_FORCE_DEFAULT / 2) -#define MOVE_FORCE_VERY_WEAK ((MOVE_FORCE_DEFAULT / MOVE_FORCE_CRUSH_RATIO) + 1) -#define MOVE_FORCE_EXTREMELY_WEAK (MOVE_FORCE_DEFAULT / (MOVE_FORCE_CRUSH_RATIO * 3)) +//Defaults +#define MOVE_FORCE_DEFAULT 1000 +#define MOVE_RESIST_DEFAULT 1000 +#define PULL_FORCE_DEFAULT 1000 + +//Factors/modifiers +#define MOVE_FORCE_PULL_RATIO 1 //Same move force to pull objects +#define MOVE_FORCE_PUSH_RATIO 1 //Same move force to normally push +#define MOVE_FORCE_FORCEPUSH_RATIO 2 //2x move force to forcefully push +#define MOVE_FORCE_CRUSH_RATIO 3 //3x move force to do things like crush objects +#define MOVE_FORCE_THROW_RATIO 1 //Same force throw as resist to throw objects + +#define MOVE_FORCE_OVERPOWERING (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 10) +#define MOVE_FORCE_EXTREMELY_STRONG (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 3) +#define MOVE_FORCE_VERY_STRONG ((MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO) - 1) +#define MOVE_FORCE_STRONG (MOVE_FORCE_DEFAULT * 2) +#define MOVE_FORCE_NORMAL MOVE_FORCE_DEFAULT +#define MOVE_FORCE_WEAK (MOVE_FORCE_DEFAULT / 2) +#define MOVE_FORCE_VERY_WEAK ((MOVE_FORCE_DEFAULT / MOVE_FORCE_CRUSH_RATIO) + 1) +#define MOVE_FORCE_EXTREMELY_WEAK (MOVE_FORCE_DEFAULT / (MOVE_FORCE_CRUSH_RATIO * 3)) diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm index 963a0d7a8160..bf03de671da8 100644 --- a/code/__DEFINES/movespeed_modification.dm +++ b/code/__DEFINES/movespeed_modification.dm @@ -1,64 +1,64 @@ -#define MOVESPEED_DATA_INDEX_PRIORITY 1 -#define MOVESPEED_DATA_INDEX_FLAGS 2 -#define MOVESPEED_DATA_INDEX_MULTIPLICATIVE_SLOWDOWN 3 -#define MOVESPEED_DATA_INDEX_MOVETYPE 4 -#define MOVESPEED_DATA_INDEX_BL_MOVETYPE 5 -#define MOVESPEED_DATA_INDEX_CONFLICT 6 - -#define MOVESPEED_DATA_INDEX_MAX 6 - -//flags -#define IGNORE_NOSLOW (1 << 0) - -//conflict types - -#define MOVE_CONFLICT_JETPACK "JETPACK" - -//ids - -#define MOVESPEED_ID_MOB_WALK_RUN_CONFIG_SPEED "MOB_WALK_RUN" -#define MOVESPEED_ID_CONFIG_SPEEDMOD "MOB_CONFIG_MODIFIER" - -#define MOVESPEED_ID_SLIME_REAGENTMOD "SLIME_REAGENT_MODIFIER" -#define MOVESPEED_ID_SLIME_HEALTHMOD "SLIME_HEALTH_MODIFIER" -#define MOVESPEED_ID_SLIME_TEMPMOD "SLIME_TEMPERATURE_MODIFIER" - -#define MOVESPEED_ID_SLIME_STATUS "SLIME_STATUS" - -#define MOVESPEED_ID_TARANTULA_WEB "TARANTULA_WEB" - -#define MOVESPEED_ID_LIVING_TURF_SPEEDMOD "LIVING_TURF_SPEEDMOD" - -#define MOVESPEED_ID_CARBON_SOFTCRIT "CARBON_SOFTCRIT" -#define MOVESPEED_ID_CARBON_OLDSPEED "CARBON_DEPRECATED_SPEED" -#define MOVESPEED_ID_CARBON_CRAWLING "CARBON_CRAWLING" - -#define MOVESPEED_ID_DNA_VAULT "DNA_VAULT" - -#define MOVESPEED_ID_YELLOW_ORB "YELLOW_ORB" - -#define MOVESPEED_ID_TARFOOT "TARFOOT" - -#define MOVESPEED_ID_SEPIA "SEPIA" - -#define MOVESPEED_ID_MONKEY_REAGENT_SPEEDMOD "MONKEY_REAGENT_SPEEDMOD" -#define MOVESPEED_ID_MONKEY_TEMPERATURE_SPEEDMOD "MONKEY_TEMPERATURE_SPEEDMOD" -#define MOVESPEED_ID_MONKEY_HEALTH_SPEEDMOD "MONKEY_HEALTH_SPEEDMOD" - -#define MOVESPEED_ID_CHANGELING_MUSCLES "CHANGELING_MUSCLES" - -#define MOVESPEED_ID_SIMPLEMOB_VARSPEED "SIMPLEMOB_VARSPEED_MODIFIER" -#define MOVESPEED_ID_ADMIN_VAREDIT "ADMIN_VAREDIT_MODIFIER" - -#define MOVESPEED_ID_PAI_SPACEWALK_SPEEDMOD "PAI_SPACEWALK_MODIFIER" - -#define MOVESPEED_ID_SANITY "MOOD_SANITY" - -#define MOVESPEED_ID_SPECIES "SPECIES_SPEED_MOD" -#define MOVESPEED_ID_SNAIL_CRAWL "SNAIL_CRAWL_SPEED_MOD" - -#define MOVESPEED_ID_CYBER_THRUSTER "CYBER_IMPLANT_THRUSTER" -#define MOVESPEED_ID_JETPACK "JETPACK" - -#define MOVESPEED_ID_SLAUGHTER "SLAUGHTER" -#define MOVESPEED_ID_DIE_OF_FATE "DIE_OF_FATE" +#define MOVESPEED_DATA_INDEX_PRIORITY 1 +#define MOVESPEED_DATA_INDEX_FLAGS 2 +#define MOVESPEED_DATA_INDEX_MULTIPLICATIVE_SLOWDOWN 3 +#define MOVESPEED_DATA_INDEX_MOVETYPE 4 +#define MOVESPEED_DATA_INDEX_BL_MOVETYPE 5 +#define MOVESPEED_DATA_INDEX_CONFLICT 6 + +#define MOVESPEED_DATA_INDEX_MAX 6 + +//flags +#define IGNORE_NOSLOW (1 << 0) + +//conflict types + +#define MOVE_CONFLICT_JETPACK "JETPACK" + +//ids + +#define MOVESPEED_ID_MOB_WALK_RUN_CONFIG_SPEED "MOB_WALK_RUN" +#define MOVESPEED_ID_CONFIG_SPEEDMOD "MOB_CONFIG_MODIFIER" + +#define MOVESPEED_ID_SLIME_REAGENTMOD "SLIME_REAGENT_MODIFIER" +#define MOVESPEED_ID_SLIME_HEALTHMOD "SLIME_HEALTH_MODIFIER" +#define MOVESPEED_ID_SLIME_TEMPMOD "SLIME_TEMPERATURE_MODIFIER" + +#define MOVESPEED_ID_SLIME_STATUS "SLIME_STATUS" + +#define MOVESPEED_ID_TARANTULA_WEB "TARANTULA_WEB" + +#define MOVESPEED_ID_LIVING_TURF_SPEEDMOD "LIVING_TURF_SPEEDMOD" + +#define MOVESPEED_ID_CARBON_SOFTCRIT "CARBON_SOFTCRIT" +#define MOVESPEED_ID_CARBON_OLDSPEED "CARBON_DEPRECATED_SPEED" +#define MOVESPEED_ID_CARBON_CRAWLING "CARBON_CRAWLING" + +#define MOVESPEED_ID_DNA_VAULT "DNA_VAULT" + +#define MOVESPEED_ID_YELLOW_ORB "YELLOW_ORB" + +#define MOVESPEED_ID_TARFOOT "TARFOOT" + +#define MOVESPEED_ID_SEPIA "SEPIA" + +#define MOVESPEED_ID_MONKEY_REAGENT_SPEEDMOD "MONKEY_REAGENT_SPEEDMOD" +#define MOVESPEED_ID_MONKEY_TEMPERATURE_SPEEDMOD "MONKEY_TEMPERATURE_SPEEDMOD" +#define MOVESPEED_ID_MONKEY_HEALTH_SPEEDMOD "MONKEY_HEALTH_SPEEDMOD" + +#define MOVESPEED_ID_CHANGELING_MUSCLES "CHANGELING_MUSCLES" + +#define MOVESPEED_ID_SIMPLEMOB_VARSPEED "SIMPLEMOB_VARSPEED_MODIFIER" +#define MOVESPEED_ID_ADMIN_VAREDIT "ADMIN_VAREDIT_MODIFIER" + +#define MOVESPEED_ID_PAI_SPACEWALK_SPEEDMOD "PAI_SPACEWALK_MODIFIER" + +#define MOVESPEED_ID_SANITY "MOOD_SANITY" + +#define MOVESPEED_ID_SPECIES "SPECIES_SPEED_MOD" +#define MOVESPEED_ID_SNAIL_CRAWL "SNAIL_CRAWL_SPEED_MOD" + +#define MOVESPEED_ID_CYBER_THRUSTER "CYBER_IMPLANT_THRUSTER" +#define MOVESPEED_ID_JETPACK "JETPACK" + +#define MOVESPEED_ID_SLAUGHTER "SLAUGHTER" +#define MOVESPEED_ID_DIE_OF_FATE "DIE_OF_FATE" diff --git a/code/__DEFINES/networks.dm b/code/__DEFINES/networks.dm index 09b9e4cadfc4..115c1653493c 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 5379c291296c..6c3c3b746842 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 DEADMIN_ALWAYS (1<<15) -#define DEADMIN_ANTAGONIST (1<<16) -#define DEADMIN_POSITION_HEAD (1<<17) -#define DEADMIN_POSITION_SECURITY (1<<18) -#define DEADMIN_POSITION_SILICON (1<<19) - -#define 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_BANKCARD (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_BANKCARD) - -#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 + +//Preference toggles +#define SOUND_ADMINHELP (1<<0) +#define SOUND_MIDI (1<<1) +#define SOUND_AMBIENCE (1<<2) +#define SOUND_LOBBY (1<<3) +#define MEMBER_PUBLIC (1<<4) +#define INTENT_STYLE (1<<5) +#define MIDROUND_ANTAG (1<<6) +#define SOUND_INSTRUMENTS (1<<7) +#define SOUND_SHIP_AMBIENCE (1<<8) +#define SOUND_PRAYERS (1<<9) +#define ANNOUNCE_LOGIN (1<<10) +#define SOUND_ANNOUNCEMENTS (1<<11) +#define DISABLE_DEATHRATTLE (1<<12) +#define DISABLE_ARRIVALRATTLE (1<<13) +#define COMBOHUD_LIGHTING (1<<14) + +#define DEADMIN_ALWAYS (1<<15) +#define DEADMIN_ANTAGONIST (1<<16) +#define DEADMIN_POSITION_HEAD (1<<17) +#define DEADMIN_POSITION_SECURITY (1<<18) +#define DEADMIN_POSITION_SILICON (1<<19) + +#define 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_BANKCARD (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_BANKCARD) + +#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 \ No newline at end of file diff --git a/code/__DEFINES/radio.dm b/code/__DEFINES/radio.dm index bfba80a80cb9..157a85d1814c 100644 --- a/code/__DEFINES/radio.dm +++ b/code/__DEFINES/radio.dm @@ -1,115 +1,115 @@ -// Radios use a large variety of predefined frequencies. - -//say based modes like binary are in living/say.dm - -#define RADIO_CHANNEL_COMMON "Common" -#define RADIO_KEY_COMMON ";" - -#define RADIO_CHANNEL_SECURITY "Security" -#define RADIO_KEY_SECURITY "s" -#define RADIO_TOKEN_SECURITY ":s" - -#define RADIO_CHANNEL_ENGINEERING "Engineering" -#define RADIO_KEY_ENGINEERING "e" -#define RADIO_TOKEN_ENGINEERING ":e" - -#define RADIO_CHANNEL_COMMAND "Command" -#define RADIO_KEY_COMMAND "c" -#define RADIO_TOKEN_COMMAND ":c" - -#define RADIO_CHANNEL_SCIENCE "Science" -#define RADIO_KEY_SCIENCE "n" -#define RADIO_TOKEN_SCIENCE ":n" - -#define RADIO_CHANNEL_MEDICAL "Medical" -#define RADIO_KEY_MEDICAL "m" -#define RADIO_TOKEN_MEDICAL ":m" - -#define RADIO_CHANNEL_SUPPLY "Supply" -#define RADIO_KEY_SUPPLY "u" -#define RADIO_TOKEN_SUPPLY ":u" - -#define RADIO_CHANNEL_SERVICE "Service" -#define RADIO_KEY_SERVICE "v" -#define RADIO_TOKEN_SERVICE ":v" - -#define RADIO_CHANNEL_AI_PRIVATE "AI Private" -#define RADIO_KEY_AI_PRIVATE "o" -#define RADIO_TOKEN_AI_PRIVATE ":o" - - -#define RADIO_CHANNEL_SYNDICATE "Syndicate" -#define RADIO_KEY_SYNDICATE "t" -#define RADIO_TOKEN_SYNDICATE ":t" - -#define RADIO_CHANNEL_CENTCOM "CentCom" -#define RADIO_KEY_CENTCOM "y" -#define RADIO_TOKEN_CENTCOM ":y" - -#define RADIO_CHANNEL_CTF_RED "Red Team" -#define RADIO_CHANNEL_CTF_BLUE "Blue Team" - - -#define MIN_FREE_FREQ 1201 // ------------------------------------------------- -// Frequencies are always odd numbers and range from 1201 to 1599. - -#define FREQ_SYNDICATE 1213 // Nuke op comms frequency, dark brown -#define FREQ_CTF_RED 1215 // CTF red team comms frequency, red -#define FREQ_CTF_BLUE 1217 // CTF blue team comms frequency, blue -#define FREQ_CENTCOM 1337 // CentCom comms frequency, gray -#define FREQ_SUPPLY 1347 // Supply comms frequency, light brown -#define FREQ_SERVICE 1349 // Service comms frequency, green -#define FREQ_SCIENCE 1351 // Science comms frequency, plum -#define FREQ_COMMAND 1353 // Command comms frequency, gold -#define FREQ_MEDICAL 1355 // Medical comms frequency, soft blue -#define FREQ_ENGINEERING 1357 // Engineering comms frequency, orange -#define FREQ_SECURITY 1359 // Security comms frequency, red - -#define FREQ_STATUS_DISPLAYS 1435 -#define FREQ_ATMOS_ALARMS 1437 // air alarms <-> alert computers -#define FREQ_ATMOS_CONTROL 1439 // air alarms <-> vents and scrubbers - -#define MIN_FREQ 1441 // ------------------------------------------------------ -// Only the 1441 to 1489 range is freely available for general conversation. -// This represents 1/8th of the available spectrum. - -#define FREQ_ATMOS_STORAGE 1441 -#define FREQ_NAV_BEACON 1445 -#define FREQ_AI_PRIVATE 1447 // AI private comms frequency, magenta -#define FREQ_PRESSURE_PLATE 1447 -#define FREQ_AIRLOCK_CONTROL 1449 -#define FREQ_ELECTROPACK 1449 -#define FREQ_MAGNETS 1449 -#define FREQ_LOCATOR_IMPLANT 1451 -#define FREQ_SIGNALER 1457 // the default for new signalers -#define FREQ_COMMON 1459 // Common comms frequency, dark green - -#define MAX_FREQ 1489 // ------------------------------------------------------ - -#define MAX_FREE_FREQ 1599 // ------------------------------------------------- - -// Transmission types. -#define TRANSMISSION_WIRE 0 // some sort of wired connection, not used -#define TRANSMISSION_RADIO 1 // electromagnetic radiation (default) -#define TRANSMISSION_SUBSPACE 2 // subspace transmission (headsets only) -#define TRANSMISSION_SUPERSPACE 3 // reaches independent (CentCom) radios only - -// Filter types, used as an optimization to avoid unnecessary proc calls. -#define RADIO_TO_AIRALARM "to_airalarm" -#define RADIO_FROM_AIRALARM "from_airalarm" -#define RADIO_SIGNALER "signaler" -#define RADIO_ATMOSIA "atmosia" -#define RADIO_AIRLOCK "airlock" -#define RADIO_MAGNETS "magnets" - -#define DEFAULT_SIGNALER_CODE 30 - -//Requests Console -#define REQ_NO_NEW_MESSAGE 0 -#define REQ_NORMAL_MESSAGE_PRIORITY 1 -#define REQ_HIGH_MESSAGE_PRIORITY 2 -#define REQ_EXTREME_MESSAGE_PRIORITY 3 - -#define REQ_DEP_TYPE_ASSISTANCE (1<<0) -#define REQ_DEP_TYPE_SUPPLIES (1<<1) -#define REQ_DEP_TYPE_INFORMATION (1<<2) +// Radios use a large variety of predefined frequencies. + +//say based modes like binary are in living/say.dm + +#define RADIO_CHANNEL_COMMON "Common" +#define RADIO_KEY_COMMON ";" + +#define RADIO_CHANNEL_SECURITY "Security" +#define RADIO_KEY_SECURITY "s" +#define RADIO_TOKEN_SECURITY ":s" + +#define RADIO_CHANNEL_ENGINEERING "Engineering" +#define RADIO_KEY_ENGINEERING "e" +#define RADIO_TOKEN_ENGINEERING ":e" + +#define RADIO_CHANNEL_COMMAND "Command" +#define RADIO_KEY_COMMAND "c" +#define RADIO_TOKEN_COMMAND ":c" + +#define RADIO_CHANNEL_SCIENCE "Science" +#define RADIO_KEY_SCIENCE "n" +#define RADIO_TOKEN_SCIENCE ":n" + +#define RADIO_CHANNEL_MEDICAL "Medical" +#define RADIO_KEY_MEDICAL "m" +#define RADIO_TOKEN_MEDICAL ":m" + +#define RADIO_CHANNEL_SUPPLY "Supply" +#define RADIO_KEY_SUPPLY "u" +#define RADIO_TOKEN_SUPPLY ":u" + +#define RADIO_CHANNEL_SERVICE "Service" +#define RADIO_KEY_SERVICE "v" +#define RADIO_TOKEN_SERVICE ":v" + +#define RADIO_CHANNEL_AI_PRIVATE "AI Private" +#define RADIO_KEY_AI_PRIVATE "o" +#define RADIO_TOKEN_AI_PRIVATE ":o" + + +#define RADIO_CHANNEL_SYNDICATE "Syndicate" +#define RADIO_KEY_SYNDICATE "t" +#define RADIO_TOKEN_SYNDICATE ":t" + +#define RADIO_CHANNEL_CENTCOM "CentCom" +#define RADIO_KEY_CENTCOM "y" +#define RADIO_TOKEN_CENTCOM ":y" + +#define RADIO_CHANNEL_CTF_RED "Red Team" +#define RADIO_CHANNEL_CTF_BLUE "Blue Team" + + +#define MIN_FREE_FREQ 1201 // ------------------------------------------------- +// Frequencies are always odd numbers and range from 1201 to 1599. + +#define FREQ_SYNDICATE 1213 // Nuke op comms frequency, dark brown +#define FREQ_CTF_RED 1215 // CTF red team comms frequency, red +#define FREQ_CTF_BLUE 1217 // CTF blue team comms frequency, blue +#define FREQ_CENTCOM 1337 // CentCom comms frequency, gray +#define FREQ_SUPPLY 1347 // Supply comms frequency, light brown +#define FREQ_SERVICE 1349 // Service comms frequency, green +#define FREQ_SCIENCE 1351 // Science comms frequency, plum +#define FREQ_COMMAND 1353 // Command comms frequency, gold +#define FREQ_MEDICAL 1355 // Medical comms frequency, soft blue +#define FREQ_ENGINEERING 1357 // Engineering comms frequency, orange +#define FREQ_SECURITY 1359 // Security comms frequency, red + +#define FREQ_STATUS_DISPLAYS 1435 +#define FREQ_ATMOS_ALARMS 1437 // air alarms <-> alert computers +#define FREQ_ATMOS_CONTROL 1439 // air alarms <-> vents and scrubbers + +#define MIN_FREQ 1441 // ------------------------------------------------------ +// Only the 1441 to 1489 range is freely available for general conversation. +// This represents 1/8th of the available spectrum. + +#define FREQ_ATMOS_STORAGE 1441 +#define FREQ_NAV_BEACON 1445 +#define FREQ_AI_PRIVATE 1447 // AI private comms frequency, magenta +#define FREQ_PRESSURE_PLATE 1447 +#define FREQ_AIRLOCK_CONTROL 1449 +#define FREQ_ELECTROPACK 1449 +#define FREQ_MAGNETS 1449 +#define FREQ_LOCATOR_IMPLANT 1451 +#define FREQ_SIGNALER 1457 // the default for new signalers +#define FREQ_COMMON 1459 // Common comms frequency, dark green + +#define MAX_FREQ 1489 // ------------------------------------------------------ + +#define MAX_FREE_FREQ 1599 // ------------------------------------------------- + +// Transmission types. +#define TRANSMISSION_WIRE 0 // some sort of wired connection, not used +#define TRANSMISSION_RADIO 1 // electromagnetic radiation (default) +#define TRANSMISSION_SUBSPACE 2 // subspace transmission (headsets only) +#define TRANSMISSION_SUPERSPACE 3 // reaches independent (CentCom) radios only + +// Filter types, used as an optimization to avoid unnecessary proc calls. +#define RADIO_TO_AIRALARM "to_airalarm" +#define RADIO_FROM_AIRALARM "from_airalarm" +#define RADIO_SIGNALER "signaler" +#define RADIO_ATMOSIA "atmosia" +#define RADIO_AIRLOCK "airlock" +#define RADIO_MAGNETS "magnets" + +#define DEFAULT_SIGNALER_CODE 30 + +//Requests Console +#define REQ_NO_NEW_MESSAGE 0 +#define REQ_NORMAL_MESSAGE_PRIORITY 1 +#define REQ_HIGH_MESSAGE_PRIORITY 2 +#define REQ_EXTREME_MESSAGE_PRIORITY 3 + +#define REQ_DEP_TYPE_ASSISTANCE (1<<0) +#define REQ_DEP_TYPE_SUPPLIES (1<<1) +#define REQ_DEP_TYPE_INFORMATION (1<<2) diff --git a/code/__DEFINES/reagents_specific_heat.dm b/code/__DEFINES/reagents_specific_heat.dm index a721b98cc64b..90a379d7de5e 100644 --- a/code/__DEFINES/reagents_specific_heat.dm +++ b/code/__DEFINES/reagents_specific_heat.dm @@ -1,3 +1,3 @@ -#define SPECIFIC_HEAT_DEFAULT 200 - -#define SPECIFIC_HEAT_PLASMA 500 +#define SPECIFIC_HEAT_DEFAULT 200 + +#define SPECIFIC_HEAT_PLASMA 500 diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index 64daba3d33d5..3be66e2b6767 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -1,77 +1,77 @@ - -#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 TOXINS_RESEARCH_MAX 70000 // This is the maximum amount of research points a toxins bomb can get. -#define TOXINS_RESEARCH_LAMBDA 3940 // This determines how easy it is for a toxins bomb to reach the max research cap. + +#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 TOXINS_RESEARCH_MAX 70000 // This is the maximum amount of research points a toxins bomb can get. +#define TOXINS_RESEARCH_LAMBDA 3940 // This determines how easy it is for a toxins bomb to reach the max research cap. diff --git a/code/__DEFINES/sight.dm b/code/__DEFINES/sight.dm index 6ab3092d1d04..5cac2900823b 100644 --- a/code/__DEFINES/sight.dm +++ b/code/__DEFINES/sight.dm @@ -1,35 +1,35 @@ -#define SEE_INVISIBLE_MINIMUM 5 - -#define INVISIBILITY_LIGHTING 20 - -#define SEE_INVISIBLE_LIVING 25 - -//#define SEE_INVISIBLE_LEVEL_ONE 35 //currently unused -//#define INVISIBILITY_LEVEL_ONE 35 //currently unused - -//#define SEE_INVISIBLE_LEVEL_TWO 45 //currently unused -//#define INVISIBILITY_LEVEL_TWO 45 //currently unused - -#define INVISIBILITY_OBSERVER 60 -#define SEE_INVISIBLE_OBSERVER 60 - -#define INVISIBILITY_MAXIMUM 100 //the maximum allowed for "real" objects - -#define INVISIBILITY_ABSTRACT 101 //only used for abstract objects (e.g. spacevine_controller), things that are not really there. - -#define BORGMESON (1<<0) -#define BORGTHERM (1<<1) -#define BORGXRAY (1<<2) -#define BORGMATERIAL (1<<3) - -//for clothing visor toggles, these determine which vars to toggle -#define VISOR_FLASHPROTECT (1<<0) -#define VISOR_TINT (1<<1) -#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses -#define VISOR_DARKNESSVIEW (1<<3) -#define VISOR_INVISVIEW (1<<4) - -//for whether AI eyes see static, and whether it is mouse-opaque or not -#define USE_STATIC_NONE 0 -#define USE_STATIC_TRANSPARENT 1 -#define USE_STATIC_OPAQUE 2 +#define SEE_INVISIBLE_MINIMUM 5 + +#define INVISIBILITY_LIGHTING 20 + +#define SEE_INVISIBLE_LIVING 25 + +//#define SEE_INVISIBLE_LEVEL_ONE 35 //currently unused +//#define INVISIBILITY_LEVEL_ONE 35 //currently unused + +//#define SEE_INVISIBLE_LEVEL_TWO 45 //currently unused +//#define INVISIBILITY_LEVEL_TWO 45 //currently unused + +#define INVISIBILITY_OBSERVER 60 +#define SEE_INVISIBLE_OBSERVER 60 + +#define INVISIBILITY_MAXIMUM 100 //the maximum allowed for "real" objects + +#define INVISIBILITY_ABSTRACT 101 //only used for abstract objects (e.g. spacevine_controller), things that are not really there. + +#define BORGMESON (1<<0) +#define BORGTHERM (1<<1) +#define BORGXRAY (1<<2) +#define BORGMATERIAL (1<<3) + +//for clothing visor toggles, these determine which vars to toggle +#define VISOR_FLASHPROTECT (1<<0) +#define VISOR_TINT (1<<1) +#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses +#define VISOR_DARKNESSVIEW (1<<3) +#define VISOR_INVISVIEW (1<<4) + +//for whether AI eyes see static, and whether it is mouse-opaque or not +#define USE_STATIC_NONE 0 +#define USE_STATIC_TRANSPARENT 1 +#define USE_STATIC_OPAQUE 2 diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index 8c62bc636c34..64e60afb1288 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -1,75 +1,75 @@ -//max channel is 1024. Only go lower from here, because byond tends to pick the first availiable channel to play sounds on -#define CHANNEL_LOBBYMUSIC 1024 -#define CHANNEL_ADMIN 1023 -#define CHANNEL_VOX 1022 -#define CHANNEL_JUKEBOX 1021 -#define CHANNEL_JUSTICAR_ARK 1020 -#define CHANNEL_HEARTBEAT 1019 //sound channel for heartbeats -#define CHANNEL_AMBIENCE 1018 -#define CHANNEL_BUZZ 1017 -#define CHANNEL_BICYCLE 1016 - -//THIS SHOULD ALWAYS BE THE LOWEST ONE! -//KEEP IT UPDATED - -#define CHANNEL_HIGHEST_AVAILABLE 1015 - - -#define SOUND_MINIMUM_PRESSURE 10 -#define FALLOFF_SOUNDS 1 - - -//Ambience types - -#define GENERIC list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg',\ - 'sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg',\ - 'sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg',\ - 'sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg',\ - 'sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg',\ - 'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg','sound/ambience/ambigen15.ogg') - -#define HOLY list('sound/ambience/ambicha1.ogg','sound/ambience/ambicha2.ogg','sound/ambience/ambicha3.ogg',\ - 'sound/ambience/ambicha4.ogg', 'sound/ambience/ambiholy.ogg', 'sound/ambience/ambiholy2.ogg',\ - 'sound/ambience/ambiholy3.ogg') - -#define HIGHSEC list('sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg') - -#define RUINS list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ - 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ - 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ - 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambitech3.ogg',\ - 'sound/ambience/ambimystery.ogg', 'sound/ambience/ambimaint1.ogg') - -#define ENGINEERING list('sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg',\ - 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg') - -#define MINING list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ - 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ - 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ - 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint1.ogg', 'sound/ambience/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_JUSTICAR_ARK 1020 +#define CHANNEL_HEARTBEAT 1019 //sound channel for heartbeats +#define CHANNEL_AMBIENCE 1018 +#define CHANNEL_BUZZ 1017 +#define CHANNEL_BICYCLE 1016 + +//THIS SHOULD ALWAYS BE THE LOWEST ONE! +//KEEP IT UPDATED + +#define CHANNEL_HIGHEST_AVAILABLE 1015 + + +#define SOUND_MINIMUM_PRESSURE 10 +#define FALLOFF_SOUNDS 1 + + +//Ambience types + +#define GENERIC list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg',\ + 'sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg',\ + 'sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg',\ + 'sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg',\ + 'sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg',\ + 'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg','sound/ambience/ambigen15.ogg') + +#define HOLY list('sound/ambience/ambicha1.ogg','sound/ambience/ambicha2.ogg','sound/ambience/ambicha3.ogg',\ + 'sound/ambience/ambicha4.ogg', 'sound/ambience/ambiholy.ogg', 'sound/ambience/ambiholy2.ogg',\ + 'sound/ambience/ambiholy3.ogg') + +#define HIGHSEC list('sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg') + +#define RUINS list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ + 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ + 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ + 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambitech3.ogg',\ + 'sound/ambience/ambimystery.ogg', 'sound/ambience/ambimaint1.ogg') + +#define ENGINEERING list('sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg',\ + 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg') + +#define MINING list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ + 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ + 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ + 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint1.ogg', 'sound/ambience/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 b975aa1f89b4..2dade0825ff6 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 32243f7ad407..9f4f63a1fcc7 100644 --- a/code/__DEFINES/tgs.config.dm +++ b/code/__DEFINES/tgs.config.dm @@ -1,11 +1,11 @@ -#define TGS_EXTERNAL_CONFIGURATION -#define TGS_V3_API -#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name) -#define TGS_READ_GLOBAL(Name) GLOB.##Name -#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value -#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "[html_encode(##message)]") -#define TGS_INFO_LOG(message) log_world("TGS: Info: [##message]") -#define TGS_ERROR_LOG(message) log_world("TGS: Error: [##message]") -#define TGS_NOTIFY_ADMINS(event) message_admins(##event) -#define TGS_CLIENT_COUNT GLOB.clients.len -#define TGS_PROTECT_DATUM(Path) GENERAL_PROTECT_DATUM(##Path) +#define TGS_EXTERNAL_CONFIGURATION +#define TGS_V3_API +#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name) +#define TGS_READ_GLOBAL(Name) GLOB.##Name +#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value +#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "[html_encode(##message)]") +#define TGS_INFO_LOG(message) log_world("TGS: Info: [##message]") +#define TGS_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 45a7e46ce3b6..37dc08d241ac 100644 --- a/code/__DEFINES/tgs.dm +++ b/code/__DEFINES/tgs.dm @@ -1,258 +1,258 @@ -//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 version of tgstation-server -/datum/tgs_version - var/suite //The suite version, can be >=3 - - //this group of variables can be null to represent a wild card - var/major //The major version - var/minor //The minor version - var/patch //The patch version - - var/raw_parameter //The unparsed parameter - var/deprefixed_parameter //The version only bit of raw_parameter - -//if the tgs_version is a wildcard version -/datum/tgs_version/proc/Wildcard() - return - -//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 supported /datum/tgs_version of the API -/world/proc/TgsMaximumAPIVersion() - return - -/world/proc/TgsMinimumAPIVersion() - 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 - -//Gets the current /datum/tgs_version of the server tools running the server -/world/proc/TgsVersion() - 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 version of tgstation-server +/datum/tgs_version + var/suite //The suite version, can be >=3 + + //this group of variables can be null to represent a wild card + var/major //The major version + var/minor //The minor version + var/patch //The patch version + + var/raw_parameter //The unparsed parameter + var/deprefixed_parameter //The version only bit of raw_parameter + +//if the tgs_version is a wildcard version +/datum/tgs_version/proc/Wildcard() + return + +//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 supported /datum/tgs_version of the API +/world/proc/TgsMaximumAPIVersion() + return + +/world/proc/TgsMinimumAPIVersion() + 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 + +//Gets the current /datum/tgs_version of the server tools running the server +/world/proc/TgsVersion() + 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. +*/ diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm index 878d35608d7c..f37116f060f6 100644 --- a/code/__DEFINES/tools.dm +++ b/code/__DEFINES/tools.dm @@ -1,15 +1,15 @@ -// 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" - - -// 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" + + +// 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 960d0479bee8..34063c1787bc 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 47659a55f969..64c014e8bf8f 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -1,589 +1,589 @@ -/* - * 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() // Yogs -- Allows for noninteger weights - 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/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 512 -#error Remie said that lummox was adding a way to get a lists -#error contents via list.values, if that is true remove this -#error otherwise, update the version and bug lummox -#endif -//Flattens a keyed list into a list of it's contents -/proc/flatten_list(list/key_list) - if(!islist(key_list)) - return null - . = list() - for(var/key in key_list) - . |= key_list[key] - -/proc/make_associative(list/flat_list) - . = list() - for(var/thing in flat_list) - .[thing] = TRUE - -//Picks from the list, with some safeties, and returns the "default" arg if it fails -#define DEFAULTPICK(L, default) ((islist(L) && length(L)) ? pick(L) : default) - -/* Definining a counter as a series of key -> numeric value entries - - * All these procs modify in place. -*/ - -/proc/counterlist_scale(list/L, scalar) - var/list/out = list() - for(var/key in L) - out[key] = L[key] * scalar - . = out - -/proc/counterlist_sum(list/L) - . = 0 - for(var/key in L) - . += L[key] - -/proc/counterlist_normalise(list/L) - var/avg = counterlist_sum(L) - if(avg != 0) - . = counterlist_scale(L, 1 / avg) - else - . = L - -/proc/counterlist_combine(list/L1, list/L2) - for(var/key in L2) - var/other_value = L2[key] - if(key in L1) - L1[key] += other_value - else - L1[key] = other_value - -/proc/assoc_list_strip_value(list/input) - var/list/ret = list() - for(var/key in input) - ret += key - return ret - -/proc/compare_list(list/l,list/d) - if(!islist(l) || !islist(d)) - return FALSE - - if(l.len != d.len) - return FALSE - - for(var/i in 1 to l.len) - if(l[i] != d[i]) - return FALSE - - return TRUE +/* + * Holds procs to help with list operations + * Contains groups: + * Misc + * Sorting + */ + +/* + * Misc + */ + +#define LAZYINITLIST(L) if (!L) L = list() +#define UNSETEMPTY(L) if (L && !length(L)) L = null +#define 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() // Yogs -- Allows for noninteger weights + 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/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 512 +#error Remie said that lummox was adding a way to get a lists +#error contents via list.values, if that is true remove this +#error otherwise, update the version and bug lummox +#endif +//Flattens a keyed list into a list of it's contents +/proc/flatten_list(list/key_list) + if(!islist(key_list)) + return null + . = list() + for(var/key in key_list) + . |= key_list[key] + +/proc/make_associative(list/flat_list) + . = list() + for(var/thing in flat_list) + .[thing] = TRUE + +//Picks from the list, with some safeties, and returns the "default" arg if it fails +#define DEFAULTPICK(L, default) ((islist(L) && length(L)) ? pick(L) : default) + +/* Definining a counter as a series of key -> numeric value entries + + * All these procs modify in place. +*/ + +/proc/counterlist_scale(list/L, scalar) + var/list/out = list() + for(var/key in L) + out[key] = L[key] * scalar + . = out + +/proc/counterlist_sum(list/L) + . = 0 + for(var/key in L) + . += L[key] + +/proc/counterlist_normalise(list/L) + var/avg = counterlist_sum(L) + if(avg != 0) + . = counterlist_scale(L, 1 / avg) + else + . = L + +/proc/counterlist_combine(list/L1, list/L2) + for(var/key in L2) + var/other_value = L2[key] + if(key in L1) + L1[key] += other_value + else + L1[key] = other_value + +/proc/assoc_list_strip_value(list/input) + var/list/ret = list() + for(var/key in input) + ret += key + return ret + +/proc/compare_list(list/l,list/d) + if(!islist(l) || !islist(d)) + return FALSE + + if(l.len != d.len) + return FALSE + + for(var/i in 1 to l.len) + if(l[i] != d[i]) + return FALSE + + return TRUE diff --git a/code/__HELPERS/_string_lists.dm b/code/__HELPERS/_string_lists.dm index f2f57bb15db6..766ea9e5c22a 100644 --- a/code/__HELPERS/_string_lists.dm +++ b/code/__HELPERS/_string_lists.dm @@ -1,41 +1,41 @@ -#define pick_list(FILE, KEY) (pick(strings(FILE, KEY))) -#define pick_list_replacements(FILE, KEY) (strings_replacement(FILE, KEY)) -#define json_load(FILE) (json_decode(file2text(FILE))) - -GLOBAL_LIST(string_cache) -GLOBAL_VAR(string_filename_current_key) - - -/proc/strings_replacement(filename, key, directory = "strings") - load_strings_file(filename, directory) - - if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) - var/response = pick(GLOB.string_cache[filename][key]) - var/regex/r = regex("@pick\\((\\D+?)\\)", "g") - response = r.Replace(response, /proc/strings_subkey_lookup) - return response - else - CRASH("strings list not found: [directory]/[filename], index=[key]") - -/proc/strings(filename as text, key as text, directory = "strings") - load_strings_file(filename, directory) - if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) - return GLOB.string_cache[filename][key] - else - CRASH("strings list not found: [directory]/[filename], index=[key]") - -/proc/strings_subkey_lookup(match, group1) - return pick_list(GLOB.string_filename_current_key, group1) - -/proc/load_strings_file(filename, directory = "strings") - GLOB.string_filename_current_key = filename - if(filename in GLOB.string_cache) - return //no work to do - - if(!GLOB.string_cache) - GLOB.string_cache = new - - if(fexists("[directory]/[filename]")) - GLOB.string_cache[filename] = json_load("[directory]/[filename]") - else - CRASH("file not found: [directory]/[filename]") +#define pick_list(FILE, KEY) (pick(strings(FILE, KEY))) +#define pick_list_replacements(FILE, KEY) (strings_replacement(FILE, KEY)) +#define json_load(FILE) (json_decode(file2text(FILE))) + +GLOBAL_LIST(string_cache) +GLOBAL_VAR(string_filename_current_key) + + +/proc/strings_replacement(filename, key, directory = "strings") + load_strings_file(filename, directory) + + if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) + var/response = pick(GLOB.string_cache[filename][key]) + var/regex/r = regex("@pick\\((\\D+?)\\)", "g") + response = r.Replace(response, /proc/strings_subkey_lookup) + return response + else + CRASH("strings list not found: [directory]/[filename], index=[key]") + +/proc/strings(filename as text, key as text, directory = "strings") + load_strings_file(filename, directory) + if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) + return GLOB.string_cache[filename][key] + else + CRASH("strings list not found: [directory]/[filename], index=[key]") + +/proc/strings_subkey_lookup(match, group1) + return pick_list(GLOB.string_filename_current_key, group1) + +/proc/load_strings_file(filename, directory = "strings") + GLOB.string_filename_current_key = filename + if(filename in GLOB.string_cache) + return //no work to do + + if(!GLOB.string_cache) + GLOB.string_cache = new + + if(fexists("[directory]/[filename]")) + GLOB.string_cache[filename] = json_load("[directory]/[filename]") + else + CRASH("file not found: [directory]/[filename]") diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index 5d6106cf9181..384c96ee7210 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")) - 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")) + 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 6ea305475a09..6161753e49aa 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -1,573 +1,573 @@ -//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 - for(var/mob/M in view(R, T)) - processing_list += M - for(var/obj/O in view(R, T)) - processing_list += O - T.luminosity = lum - - while(processing_list.len) // recursive_hear_check inlined here - var/atom/A = processing_list[1] - if(A.flags_1 & HEAR_1) - . += A - 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/list/candidates = list() - - for(var/mob/dead/observer/G in GLOB.player_list) - candidates += G - - return pollCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category, flashwindow, candidates) - -/proc/pollCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE, list/group = null) - var/time_passed = world.time - if (!Question) - Question = "Would you like to be a special role?" - var/list/result = list() - for(var/m in group) - var/mob/M = m - if(!M.key || !M.client || (ignore_category && GLOB.poll_ignore[ignore_category] && M.ckey in GLOB.poll_ignore[ignore_category])) - continue - if(be_special_flag) - if(!(M.client.prefs) || !(be_special_flag in M.client.prefs.be_special)) - continue - if(gametypeCheck) - if(!gametypeCheck.age_check(M.client)) - continue - if(jobbanType) - if(is_banned_from(M.ckey, list(jobbanType, ROLE_SYNDICATE)) || QDELETED(M)) - continue - - showCandidatePollWindow(M, poll_time, Question, result, ignore_category, time_passed, flashwindow) - sleep(poll_time) - - //Check all our candidates, to make sure they didn't log off or get deleted during the wait period. - for(var/mob/M in result) - if(!M.key || !M.client) - result -= M - - listclearnulls(result) - - return result - -/proc/pollCandidatesForMob(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, mob/M, ignore_category = null) - var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) - if(!M || QDELETED(M) || !M.loc) - return list() - return L - -/proc/pollCandidatesForMobs(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, list/mobs, ignore_category = null) - var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) - var/i=1 - for(var/v in mobs) - var/atom/A = v - if(!A || QDELETED(A) || !A.loc) - mobs.Cut(i,i+1) - else - ++i - return L - -/proc/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() - new_character.key = G_found.key - - return new_character - -/proc/send_to_playing_players(thing) //sends a whatever to all playing players; use instead of to_chat(world, where needed) - for(var/M in GLOB.player_list) - if(M && !isnewplayer(M)) - to_chat(M, thing) - -/proc/window_flash(client/C, ignorepref = FALSE) - if(ismob(C)) - var/mob/M = C - if(M.client) - C = M.client - if(!C || (!C.prefs.windowflashing && !ignorepref)) - return - winset(C, "mainwindow", "flash=5") - -//Recursively checks if an item is inside a given type, even through layers of storage. Returns the atom if it finds it. -/proc/recursive_loc_check(atom/movable/target, type) - var/atom/A = target - if(istype(A, type)) - return A - - while(!istype(A.loc, type)) - if(!A.loc) - return - A = A.loc - - return A.loc - -/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank) - if(!SSticker.IsRoundInProgress() || QDELETED(character)) - return - var/area/A = get_area(character) - deadchat_broadcast(" has arrived at the station at [A.name].", "[character.real_name] ([rank])", follow_target = character, message_type=DEADCHAT_ARRIVALRATTLE) - if((!GLOB.announcement_systems.len) || (!character.mind)) - return - if((character.mind.assigned_role == "Cyborg") || (character.mind.assigned_role == character.mind.special_role)) - return - - var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) - announcer.announce("ARRIVAL", character.real_name, rank, list()) //make the list empty to make it announce it in common - -/proc/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 + for(var/mob/M in view(R, T)) + processing_list += M + for(var/obj/O in view(R, T)) + processing_list += O + T.luminosity = lum + + while(processing_list.len) // recursive_hear_check inlined here + var/atom/A = processing_list[1] + if(A.flags_1 & HEAR_1) + . += A + 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/list/candidates = list() + + for(var/mob/dead/observer/G in GLOB.player_list) + candidates += G + + return pollCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category, flashwindow, candidates) + +/proc/pollCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE, list/group = null) + var/time_passed = world.time + if (!Question) + Question = "Would you like to be a special role?" + var/list/result = list() + for(var/m in group) + var/mob/M = m + if(!M.key || !M.client || (ignore_category && GLOB.poll_ignore[ignore_category] && M.ckey in GLOB.poll_ignore[ignore_category])) + continue + if(be_special_flag) + if(!(M.client.prefs) || !(be_special_flag in M.client.prefs.be_special)) + continue + if(gametypeCheck) + if(!gametypeCheck.age_check(M.client)) + continue + if(jobbanType) + if(is_banned_from(M.ckey, list(jobbanType, ROLE_SYNDICATE)) || QDELETED(M)) + continue + + showCandidatePollWindow(M, poll_time, Question, result, ignore_category, time_passed, flashwindow) + sleep(poll_time) + + //Check all our candidates, to make sure they didn't log off or get deleted during the wait period. + for(var/mob/M in result) + if(!M.key || !M.client) + result -= M + + listclearnulls(result) + + return result + +/proc/pollCandidatesForMob(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, mob/M, ignore_category = null) + var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) + if(!M || QDELETED(M) || !M.loc) + return list() + return L + +/proc/pollCandidatesForMobs(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, list/mobs, ignore_category = null) + var/list/L = pollGhostCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category) + var/i=1 + for(var/v in mobs) + var/atom/A = v + if(!A || QDELETED(A) || !A.loc) + mobs.Cut(i,i+1) + else + ++i + return L + +/proc/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() + new_character.key = G_found.key + + return new_character + +/proc/send_to_playing_players(thing) //sends a whatever to all playing players; use instead of to_chat(world, where needed) + for(var/M in GLOB.player_list) + if(M && !isnewplayer(M)) + to_chat(M, thing) + +/proc/window_flash(client/C, ignorepref = FALSE) + if(ismob(C)) + var/mob/M = C + if(M.client) + C = M.client + if(!C || (!C.prefs.windowflashing && !ignorepref)) + return + winset(C, "mainwindow", "flash=5") + +//Recursively checks if an item is inside a given type, even through layers of storage. Returns the atom if it finds it. +/proc/recursive_loc_check(atom/movable/target, type) + var/atom/A = target + if(istype(A, type)) + return A + + while(!istype(A.loc, type)) + if(!A.loc) + return + A = A.loc + + return A.loc + +/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank) + if(!SSticker.IsRoundInProgress() || QDELETED(character)) + return + var/area/A = get_area(character) + deadchat_broadcast(" has arrived at the station at [A.name].", "[character.real_name] ([rank])", follow_target = character, message_type=DEADCHAT_ARRIVALRATTLE) + if((!GLOB.announcement_systems.len) || (!character.mind)) + return + if((character.mind.assigned_role == "Cyborg") || (character.mind.assigned_role == character.mind.special_role)) + return + + var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) + announcer.announce("ARRIVAL", character.real_name, rank, list()) //make the list empty to make it announce it in common + +/proc/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 a108082be66c..ca4f5f7b8009 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -1,71 +1,71 @@ -////////////////////////// -/////Initial Building///// -////////////////////////// - -/proc/make_datum_references_lists() - //hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.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, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - //undershirt - init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - //socks - init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) - //bodypart accessories (blizzard intensifies) - init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/lizard, GLOB.animated_tails_list_lizard) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/human, GLOB.animated_tails_list_human) - init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/horns,GLOB.horns_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.ears_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open, GLOB.wings_open_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines_animated, GLOB.animated_spines_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.r_wings_list,roundstart = TRUE) - init_sprite_accessory_subtypes(/datum/sprite_accessory/caps, GLOB.caps_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) - - - //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 - - GLOB.emote_list = init_emote_list() - - - init_subtypes(/datum/crafting_recipe, GLOB.crafting_recipes) - -//creates every subtype of prototype (excluding prototype) and adds it to list L. -//if no list/L is provided, one is created. -/proc/init_subtypes(prototype, list/L) - if(!istype(L)) - L = list() - for(var/path in subtypesof(prototype)) - L += new path() - return L - -//returns a list of paths to every subtype of prototype (excluding prototype) -//if no list/L is provided, one is created. -/proc/init_paths(prototype, list/L) - if(!istype(L)) - L = list() - for(var/path in subtypesof(prototype)) - L+= path - return L +////////////////////////// +/////Initial Building///// +////////////////////////// + +/proc/make_datum_references_lists() + //hair + init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.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, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + //undershirt + init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + //socks + init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) + //bodypart accessories (blizzard intensifies) + init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/lizard, GLOB.animated_tails_list_lizard) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/human, GLOB.animated_tails_list_human) + init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/horns,GLOB.horns_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.ears_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open, GLOB.wings_open_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines_animated, GLOB.animated_spines_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.r_wings_list,roundstart = TRUE) + init_sprite_accessory_subtypes(/datum/sprite_accessory/caps, GLOB.caps_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) + + + //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 + + GLOB.emote_list = init_emote_list() + + + init_subtypes(/datum/crafting_recipe, GLOB.crafting_recipes) + +//creates every subtype of prototype (excluding prototype) and adds it to list L. +//if no list/L is provided, one is created. +/proc/init_subtypes(prototype, list/L) + if(!istype(L)) + L = list() + for(var/path in subtypesof(prototype)) + L += new path() + return L + +//returns a list of paths to every subtype of prototype (excluding prototype) +//if no list/L is provided, one is created. +/proc/init_paths(prototype, list/L) + if(!istype(L)) + L = list() + for(var/path in subtypesof(prototype)) + L+= path + return L diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 7b71eea02047..6a1287a8f870 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1,1202 +1,1202 @@ -/* -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=1)//If safety is on, a new icon is not created. - var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon. - flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish. - flat_icon.ChangeOpacity(0.5)//Make it half transparent. - var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect. - flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect. - return flat_icon - -//What the mob looks like as animated static -//By vg's ComicIronic -/proc/getStaticIcon(icon/A, safety = TRUE) - var/icon/flat_icon = safety ? A : new(A) - flat_icon.Blend(rgb(255,255,255)) - flat_icon.BecomeAlphaMask() - var/icon/static_icon = icon('icons/effects/effects.dmi', "static_base") - static_icon.AddAlphaMask(flat_icon) - return static_icon - -//What the mob looks like as a pitch black outline -//By vg's ComicIronic -/proc/getBlankIcon(icon/A, safety=1) - var/icon/flat_icon = safety ? A : new(A) - flat_icon.Blend(rgb(255,255,255)) - flat_icon.BecomeAlphaMask() - var/icon/blank_icon = new/icon('icons/effects/effects.dmi', "blank_base") - blank_icon.AddAlphaMask(flat_icon) - return blank_icon - - -//Dwarf fortress style icons based on letters (defaults to the first letter of the Atom's name) -//By vg's ComicIronic -/proc/getLetterImage(atom/A, letter= "", uppercase = 0) - if(!A) - return - - var/icon/atom_icon = new(A.icon, A.icon_state) - - if(!letter) - letter = 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=1)//If safety is on, a new icon is not created. + var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon. + flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish. + flat_icon.ChangeOpacity(0.5)//Make it half transparent. + var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect. + flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect. + return flat_icon + +//What the mob looks like as animated static +//By vg's ComicIronic +/proc/getStaticIcon(icon/A, safety = TRUE) + var/icon/flat_icon = safety ? A : new(A) + flat_icon.Blend(rgb(255,255,255)) + flat_icon.BecomeAlphaMask() + var/icon/static_icon = icon('icons/effects/effects.dmi', "static_base") + static_icon.AddAlphaMask(flat_icon) + return static_icon + +//What the mob looks like as a pitch black outline +//By vg's ComicIronic +/proc/getBlankIcon(icon/A, safety=1) + var/icon/flat_icon = safety ? A : new(A) + flat_icon.Blend(rgb(255,255,255)) + flat_icon.BecomeAlphaMask() + var/icon/blank_icon = new/icon('icons/effects/effects.dmi', "blank_base") + blank_icon.AddAlphaMask(flat_icon) + return blank_icon + + +//Dwarf fortress style icons based on letters (defaults to the first letter of the Atom's name) +//By vg's ComicIronic +/proc/getLetterImage(atom/A, letter= "", uppercase = 0) + if(!A) + return + + var/icon/atom_icon = new(A.icon, A.icon_state) + + if(!letter) + letter = 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 422f21d66cac..1dac6714c962 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -1,475 +1,475 @@ -/proc/random_blood_type() - return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") - -/proc/random_eye_color() - switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino")) - if("brown") - return "630" - if("hazel") - return "542" - if("grey") - return pick("666","777","888","999","aaa","bbb","ccc") - if("blue") - return "36c" - if("green") - return "060" - if("amber") - return "fc0" - if("albino") - return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9") - else - return "000" - -/proc/random_underwear(gender) - if(!GLOB.underwear_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - switch(gender) - if(MALE) - return pick(GLOB.underwear_m) - if(FEMALE) - return pick(GLOB.underwear_f) - else - return pick(GLOB.underwear_list) - -/proc/random_undershirt(gender) - if(!GLOB.undershirt_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - switch(gender) - if(MALE) - return pick(GLOB.undershirt_m) - if(FEMALE) - return pick(GLOB.undershirt_f) - else - return pick(GLOB.undershirt_list) - -/proc/random_socks() - if(!GLOB.socks_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) - return pick(GLOB.socks_list) - -/proc/random_features() - if(!GLOB.tails_list_human.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) - if(!GLOB.tails_list_lizard.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) - if(!GLOB.snouts_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) - if(!GLOB.horns_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list) - if(!GLOB.ears_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list) - if(!GLOB.frills_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) - if(!GLOB.spines_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) - if(!GLOB.legs_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) - if(!GLOB.body_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) - if(!GLOB.wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) - if(!GLOB.moth_wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) - - //For now we will always return none for tail_human and ears. - return(list("mcolor" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),"ethcolor" = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)], "tail_lizard" = pick(GLOB.tails_list_lizard), "tail_human" = "None", "wings" = "None", "snout" = pick(GLOB.snouts_list), "horns" = pick(GLOB.horns_list), "ears" = "None", "frills" = pick(GLOB.frills_list), "spines" = pick(GLOB.spines_list), "body_markings" = pick(GLOB.body_markings_list), "legs" = "Normal Legs", "caps" = pick(GLOB.caps_list), "moth_wings" = pick(GLOB.moth_wings_list))) - -/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_ethereal_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(ethereal_name()) - - if(!findname(.)) - break - -/proc/random_unique_moth_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) - - if(!findname(.)) - break - -/proc/random_skin_tone() - return pick(GLOB.skin_tones) - -GLOBAL_LIST_INIT(skin_tones, 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) - 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 || user.get_active_held_item() != holding || user.incapacitated() || (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 || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) - . = 0 - break - - if(isliving(user)) - var/mob/living/L = user - if(L.IsStun() || L.IsParalyzed()) - . = 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, list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks, required_mobility_flags = MOBILITY_STAND) - 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 - var/mob/living/L - if(isliving(user)) - L = user - . = 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 - - if(L && !CHECK_MULTIPLE_BITFIELDS(L.mobility_flags, required_mobility_flags)) - . = 0 - break - - for(var/atom/target in targets) - if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) - . = 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 - var/atom/X - for(var/j in 1 to amount) - X = new spawn_type(arglist(new_args)) - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - return X //return the last mob spawned - -/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE) - var/turf/T = get_turf(target) - var/step_count = 0 - if(!T) - CRASH("attempt to spawn atom type: [spawn_type] in nullspace") - - var/list/spawned_mobs = new(amount) - - for(var/j in 1 to amount) - var/atom/movable/X - - if (istype(spawn_type, /list)) - var/mob_type = pick(spawn_type) - X = new mob_type(T) - else - X = new spawn_type(T) - - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - - spawned_mobs[j] = X - - if(always_max_walk || prob(walk_chance)) - if(always_max_walk) - step_count = max_walk - else - step_count = rand(1, max_walk) - - for(var/i in 1 to step_count) - step(X, pick(NORTH, SOUTH, EAST, WEST)) - - return spawned_mobs - -// Displays a message in deadchat, sent by source. Source is not linkified, message is, to avoid stuff like character names to be linkified. -// Automatically gives the class deadsay to the whole message (message + source) -/proc/deadchat_broadcast(message, source=null, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) - message = "[source][message]" - for(var/mob/M in GLOB.player_list) - var/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, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + switch(gender) + if(MALE) + return pick(GLOB.underwear_m) + if(FEMALE) + return pick(GLOB.underwear_f) + else + return pick(GLOB.underwear_list) + +/proc/random_undershirt(gender) + if(!GLOB.undershirt_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + switch(gender) + if(MALE) + return pick(GLOB.undershirt_m) + if(FEMALE) + return pick(GLOB.undershirt_f) + else + return pick(GLOB.undershirt_list) + +/proc/random_socks() + if(!GLOB.socks_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) + return pick(GLOB.socks_list) + +/proc/random_features() + if(!GLOB.tails_list_human.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) + if(!GLOB.tails_list_lizard.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) + if(!GLOB.snouts_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) + if(!GLOB.horns_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list) + if(!GLOB.ears_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list) + if(!GLOB.frills_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) + if(!GLOB.spines_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) + if(!GLOB.legs_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) + if(!GLOB.body_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) + if(!GLOB.wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) + if(!GLOB.moth_wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) + + //For now we will always return none for tail_human and ears. + return(list("mcolor" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),"ethcolor" = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)], "tail_lizard" = pick(GLOB.tails_list_lizard), "tail_human" = "None", "wings" = "None", "snout" = pick(GLOB.snouts_list), "horns" = pick(GLOB.horns_list), "ears" = "None", "frills" = pick(GLOB.frills_list), "spines" = pick(GLOB.spines_list), "body_markings" = pick(GLOB.body_markings_list), "legs" = "Normal Legs", "caps" = pick(GLOB.caps_list), "moth_wings" = pick(GLOB.moth_wings_list))) + +/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_ethereal_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(ethereal_name()) + + if(!findname(.)) + break + +/proc/random_unique_moth_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) + + if(!findname(.)) + break + +/proc/random_skin_tone() + return pick(GLOB.skin_tones) + +GLOBAL_LIST_INIT(skin_tones, 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) + 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 || user.get_active_held_item() != holding || user.incapacitated() || (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 || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) + . = 0 + break + + if(isliving(user)) + var/mob/living/L = user + if(L.IsStun() || L.IsParalyzed()) + . = 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, list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks, required_mobility_flags = MOBILITY_STAND) + 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 + var/mob/living/L + if(isliving(user)) + L = user + . = 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 + + if(L && !CHECK_MULTIPLE_BITFIELDS(L.mobility_flags, required_mobility_flags)) + . = 0 + break + + for(var/atom/target in targets) + if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) + . = 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 + var/atom/X + for(var/j in 1 to amount) + X = new spawn_type(arglist(new_args)) + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + return X //return the last mob spawned + +/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE) + var/turf/T = get_turf(target) + var/step_count = 0 + if(!T) + CRASH("attempt to spawn atom type: [spawn_type] in nullspace") + + var/list/spawned_mobs = new(amount) + + for(var/j in 1 to amount) + var/atom/movable/X + + if (istype(spawn_type, /list)) + var/mob_type = pick(spawn_type) + X = new mob_type(T) + else + X = new spawn_type(T) + + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + + spawned_mobs[j] = X + + if(always_max_walk || prob(walk_chance)) + if(always_max_walk) + step_count = max_walk + else + step_count = rand(1, max_walk) + + for(var/i in 1 to step_count) + step(X, pick(NORTH, SOUTH, EAST, WEST)) + + return spawned_mobs + +// Displays a message in deadchat, sent by source. Source is not linkified, message is, to avoid stuff like character names to be linkified. +// Automatically gives the class deadsay to the whole message (message + source) +/proc/deadchat_broadcast(message, source=null, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) + message = "[source][message]" + for(var/mob/M in GLOB.player_list) + var/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/mouse_control.dm b/code/__HELPERS/mouse_control.dm index cdd839ce178b..784496ed200c 100644 --- a/code/__HELPERS/mouse_control.dm +++ b/code/__HELPERS/mouse_control.dm @@ -1,52 +1,52 @@ -/proc/mouse_angle_from_client(client/client) - var/list/mouse_control = params2list(client.mouseParams) - if(mouse_control["screen-loc"] && client) - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32) - var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32) - var/list/screenview = getviewsize(client.view) - var/screenviewX = screenview[1] * world.icon_size - var/screenviewY = screenview[2] * world.icon_size - var/ox = round(screenviewX/2) - client.pixel_x //"origin" x - var/oy = round(screenviewY/2) - client.pixel_y //"origin" y - var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox)) - return angle - -//Wow, specific name! -/proc/mouse_absolute_datum_map_position_from_client(client/client) - if(!isloc(client.mob.loc)) - return - var/list/mouse_control = params2list(client.mouseParams) - var/atom/A = client.eye - var/turf/T = get_turf(A) - var/cx = T.x - var/cy = T.y - var/cz = T.z - if(mouse_control["screen-loc"]) - var/x = 0 - var/y = 0 - var/z = 0 - var/p_x = 0 - var/p_y = 0 - //Split screen-loc up into X+Pixel_X and Y+Pixel_Y - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - //Split X+Pixel_X up into list(X, Pixel_X) - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - //Split Y+Pixel_Y up into list(Y, Pixel_Y) - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/sx = text2num(screen_loc_X[1]) - var/sy = text2num(screen_loc_Y[1]) - //Get the resolution of the client's current screen size. - var/list/screenview = getviewsize(client.view) - var/svx = screenview[1] - var/svy = screenview[2] - var/cox = round((svx - 1) / 2) - var/coy = round((svy - 1) / 2) - x = cx + (sx - 1 - cox) - y = cy + (sy - 1 - coy) - z = cz - p_x = text2num(screen_loc_X[2]) - p_y = text2num(screen_loc_Y[2]) - return new /datum/position(x, y, z, p_x, p_y) +/proc/mouse_angle_from_client(client/client) + var/list/mouse_control = params2list(client.mouseParams) + if(mouse_control["screen-loc"] && client) + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32) + var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32) + var/list/screenview = getviewsize(client.view) + var/screenviewX = screenview[1] * world.icon_size + var/screenviewY = screenview[2] * world.icon_size + var/ox = round(screenviewX/2) - client.pixel_x //"origin" x + var/oy = round(screenviewY/2) - client.pixel_y //"origin" y + var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox)) + return angle + +//Wow, specific name! +/proc/mouse_absolute_datum_map_position_from_client(client/client) + if(!isloc(client.mob.loc)) + return + var/list/mouse_control = params2list(client.mouseParams) + var/atom/A = client.eye + var/turf/T = get_turf(A) + var/cx = T.x + var/cy = T.y + var/cz = T.z + if(mouse_control["screen-loc"]) + var/x = 0 + var/y = 0 + var/z = 0 + var/p_x = 0 + var/p_y = 0 + //Split screen-loc up into X+Pixel_X and Y+Pixel_Y + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + //Split X+Pixel_X up into list(X, Pixel_X) + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + //Split Y+Pixel_Y up into list(Y, Pixel_Y) + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/sx = text2num(screen_loc_X[1]) + var/sy = text2num(screen_loc_Y[1]) + //Get the resolution of the client's current screen size. + var/list/screenview = getviewsize(client.view) + var/svx = screenview[1] + var/svy = screenview[2] + var/cox = round((svx - 1) / 2) + var/coy = round((svy - 1) / 2) + x = cx + (sx - 1 - cox) + y = cy + (sy - 1 - coy) + z = cz + p_x = text2num(screen_loc_X[2]) + p_y = text2num(screen_loc_Y[2]) + return new /datum/position(x, y, z, p_x, p_y) diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index 226b85bc6efa..e1848b21a290 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -1,254 +1,254 @@ -/proc/lizard_name(gender) - if(gender == MALE) - return "[pick(GLOB.lizard_names_male)]-[pick(GLOB.lizard_names_male)]" - else - return "[pick(GLOB.lizard_names_female)]-[pick(GLOB.lizard_names_female)]" - -/proc/ethereal_name() - var/tempname = "[pick(GLOB.ethereal_names)] [random_capital_letter()]" - if(prob(65)) - tempname += random_capital_letter() - return tempname - -/proc/plasmaman_name() - return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" - -/proc/moth_name() - return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" - -/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/ethereal_name() + var/tempname = "[pick(GLOB.ethereal_names)] [random_capital_letter()]" + if(prob(65)) + tempname += random_capital_letter() + return tempname + +/proc/plasmaman_name() + return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" + +/proc/moth_name() + return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" + +/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 9bd0d4c95ff7..0d2bf8915293 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 84b354b6cd55..b3a89c245a83 100644 --- a/code/__HELPERS/radio.dm +++ b/code/__HELPERS/radio.dm @@ -1,14 +1,14 @@ -// 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]" +// 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]" diff --git a/code/__HELPERS/sanitize_values.dm b/code/__HELPERS/sanitize_values.dm index 8c6d4fe9f4ca..ed51438b988f 100644 --- a/code/__HELPERS/sanitize_values.dm +++ b/code/__HELPERS/sanitize_values.dm @@ -1,74 +1,74 @@ -//general stuff -/proc/sanitize_integer(number, min=0, max=1, default=0) - if(isnum(number)) - number = round(number) - if(min <= number && number <= max) - return number - return default - -/proc/sanitize_text(text, default="") - if(istext(text)) - return text - return default - -/proc/sanitize_inlist(value, list/List, default) - if(value in List) - return value - if(default) - return default - if(List && List.len) - return pick(List) - - - -//more specialised stuff -/proc/sanitize_gender(gender,neuter=0,plural=1, default="male") - switch(gender) - if(MALE, FEMALE) - return gender - if(NEUTER) - if(neuter) - return gender - else - return default - if(PLURAL) - if(plural) - return gender - else - return default - return default - -/proc/sanitize_hexcolor(color, desired_format=3, include_crunch=0, default) - var/crunch = include_crunch ? "#" : "" - if(!istext(color)) - color = "" - - var/start = 1 + (text2ascii(color,1)==35) - var/len = length(color) - var/step_size = 1 + ((len+1)-start != desired_format) - - . = "" - for(var/i=start, i<=len, i+=step_size) - var/ascii = text2ascii(color,i) - switch(ascii) - if(48 to 57) - . += ascii2text(ascii) //numbers 0 to 9 - if(97 to 102) - . += ascii2text(ascii) //letters a to f - if(65 to 70) - . += ascii2text(ascii+32) //letters A to F - translates to lowercase - else - break - - if(length(.) != desired_format) - if(default) - return default - return crunch + repeat_string(desired_format, "0") - - return crunch + . - -/proc/sanitize_ooccolor(color) - var/list/HSL = rgb2hsl(hex2num(copytext(color,2,4)),hex2num(copytext(color,4,6)),hex2num(copytext(color,6,8))) - HSL[3] = min(HSL[3],0.4) - var/list/RGB = hsl2rgb(arglist(HSL)) +//general stuff +/proc/sanitize_integer(number, min=0, max=1, default=0) + if(isnum(number)) + number = round(number) + if(min <= number && number <= max) + return number + return default + +/proc/sanitize_text(text, default="") + if(istext(text)) + return text + return default + +/proc/sanitize_inlist(value, list/List, default) + if(value in List) + return value + if(default) + return default + if(List && List.len) + return pick(List) + + + +//more specialised stuff +/proc/sanitize_gender(gender,neuter=0,plural=1, default="male") + switch(gender) + if(MALE, FEMALE) + return gender + if(NEUTER) + if(neuter) + return gender + else + return default + if(PLURAL) + if(plural) + return gender + else + return default + return default + +/proc/sanitize_hexcolor(color, desired_format=3, include_crunch=0, default) + var/crunch = include_crunch ? "#" : "" + if(!istext(color)) + color = "" + + var/start = 1 + (text2ascii(color,1)==35) + var/len = length(color) + var/step_size = 1 + ((len+1)-start != desired_format) + + . = "" + for(var/i=start, i<=len, i+=step_size) + var/ascii = text2ascii(color,i) + switch(ascii) + if(48 to 57) + . += ascii2text(ascii) //numbers 0 to 9 + if(97 to 102) + . += ascii2text(ascii) //letters a to f + if(65 to 70) + . += ascii2text(ascii+32) //letters A to F - translates to lowercase + else + break + + if(length(.) != desired_format) + if(default) + return default + return crunch + repeat_string(desired_format, "0") + + return crunch + . + +/proc/sanitize_ooccolor(color) + var/list/HSL = rgb2hsl(hex2num(copytext(color,2,4)),hex2num(copytext(color,4,6)),hex2num(copytext(color,6,8))) + HSL[3] = min(HSL[3],0.4) + var/list/RGB = hsl2rgb(arglist(HSL)) return "#[num2hex(RGB[1],2)][num2hex(RGB[2],2)][num2hex(RGB[3],2)]" \ No newline at end of file diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 3ab97a8634e8..bc3b579154b4 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1,821 +1,821 @@ -/* - * 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(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(lentext(text) != lentext(compare)) - return 0 - for(var/i = 1, i < lentext(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 <= lentext(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 <= lentext(string), pos++) - var/let = copytext(string, pos, (pos + 1) % lentext(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 = lentext(buffer) - for(var/pos = lentext(buffer), pos >= 0, pos--) - var/let = copytext(buffer, pos, (pos + 1) % lentext(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 = lentext(punctbuffer), pos >= 0, pos--) - var/punct = copytext(punctbuffer, pos, (pos + 1) % lentext(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 || lentext(buffer) > 280 || lentext(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/random_capital_letter() - return uppertext(pick(GLOB.alphabet)) - -/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 - - -/proc/readable_corrupted_text(text) - var/list/corruption_options = list("..", "£%", "~~\"", "!!", "*", "^", "$!", "-", "}", "?") - var/corrupted_text = "" - - // Have every letter have a chance of creating corruption on either side - // Small chance of letters being removed in place of corruption - still overall readable - for(var/letter_index = 1; letter_index <= length(text); letter_index++) - var/letter = text[letter_index] - - if (prob(15)) - corrupted_text += pick(corruption_options) - - if (prob(95)) - corrupted_text += letter - else - corrupted_text += pick(corruption_options) - - if (prob(15)) - corrupted_text += pick(corruption_options) - - return corrupted_text - -#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) -#define is_digit(X) ((length(X) == 1) && (length(text2num(X)) == 1)) +/* + * 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(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(lentext(text) != lentext(compare)) + return 0 + for(var/i = 1, i < lentext(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 <= lentext(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 <= lentext(string), pos++) + var/let = copytext(string, pos, (pos + 1) % lentext(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 = lentext(buffer) + for(var/pos = lentext(buffer), pos >= 0, pos--) + var/let = copytext(buffer, pos, (pos + 1) % lentext(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 = lentext(punctbuffer), pos >= 0, pos--) + var/punct = copytext(punctbuffer, pos, (pos + 1) % lentext(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 || lentext(buffer) > 280 || lentext(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/random_capital_letter() + return uppertext(pick(GLOB.alphabet)) + +/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 + + +/proc/readable_corrupted_text(text) + var/list/corruption_options = list("..", "£%", "~~\"", "!!", "*", "^", "$!", "-", "}", "?") + var/corrupted_text = "" + + // Have every letter have a chance of creating corruption on either side + // Small chance of letters being removed in place of corruption - still overall readable + for(var/letter_index = 1; letter_index <= length(text); letter_index++) + var/letter = text[letter_index] + + if (prob(15)) + corrupted_text += pick(corruption_options) + + if (prob(95)) + corrupted_text += letter + else + corrupted_text += pick(corruption_options) + + if (prob(15)) + corrupted_text += pick(corruption_options) + + return corrupted_text + +#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) +#define is_digit(X) ((length(X) == 1) && (length(text2num(X)) == 1)) diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index a7508699a9e1..a55884a697fe 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -1,95 +1,95 @@ -//Returns the world time in english -/proc/worldtime2text() - return gameTimestamp("hh:mm:ss", world.time) - -/proc/time_stamp(format = "hh:mm:ss", show_ds) - var/time_string = time2text(world.timeofday, format) - return show_ds ? "[time_string]:[world.timeofday % 10]" : time_string - -/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) - if(!wtime) - wtime = world.time - return time2text(wtime - GLOB.timezoneOffset, format) - -/proc/station_time(display_only = FALSE, wtime=world.time) - return ((((wtime - SSticker.round_start_time) * SSticker.station_time_rate_multiplier) + SSticker.gametime_offset) % 864000) - (display_only? GLOB.timezoneOffset : 0) - -/proc/station_time_timestamp(format = "hh:mm:ss", wtime) - return time2text(station_time(TRUE, wtime), format) - -/proc/station_time_debug(force_set) - if(isnum(force_set)) - SSticker.gametime_offset = force_set - return - SSticker.gametime_offset = rand(0, 864000) //hours in day * minutes in hour * seconds in minute * deciseconds in second - if(prob(50)) - SSticker.gametime_offset = FLOOR(SSticker.gametime_offset, 3600) - else - SSticker.gametime_offset = CEILING(SSticker.gametime_offset, 3600) - -/* Returns 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)) +//Returns the world time in english +/proc/worldtime2text() + return gameTimestamp("hh:mm:ss", world.time) + +/proc/time_stamp(format = "hh:mm:ss", show_ds) + var/time_string = time2text(world.timeofday, format) + return show_ds ? "[time_string]:[world.timeofday % 10]" : time_string + +/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) + if(!wtime) + wtime = world.time + return time2text(wtime - GLOB.timezoneOffset, format) + +/proc/station_time(display_only = FALSE, wtime=world.time) + return ((((wtime - SSticker.round_start_time) * SSticker.station_time_rate_multiplier) + SSticker.gametime_offset) % 864000) - (display_only? GLOB.timezoneOffset : 0) + +/proc/station_time_timestamp(format = "hh:mm:ss", wtime) + return time2text(station_time(TRUE, wtime), format) + +/proc/station_time_debug(force_set) + if(isnum(force_set)) + SSticker.gametime_offset = force_set + return + SSticker.gametime_offset = rand(0, 864000) //hours in day * minutes in hour * seconds in minute * deciseconds in second + if(prob(50)) + SSticker.gametime_offset = FLOOR(SSticker.gametime_offset, 3600) + else + SSticker.gametime_offset = CEILING(SSticker.gametime_offset, 3600) + +/* Returns 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)) diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index 3a9b2f561121..8796df70e7ea 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -1,621 +1,621 @@ -/* - * 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/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/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/__HELPERS/unused.dm b/code/__HELPERS/unused.dm index 8b590eb2bd23..fce25720b751 100644 --- a/code/__HELPERS/unused.dm +++ b/code/__HELPERS/unused.dm @@ -1,39 +1,39 @@ - - -/datum/projectile_data - var/src_x - var/src_y - var/time - var/distance - var/power_x - var/power_y - var/dest_x - var/dest_y - -/datum/projectile_data/New(var/src_x, var/src_y, var/time, var/distance, \ - var/power_x, var/power_y, var/dest_x, var/dest_y) - src.src_x = src_x - src.src_y = src_y - src.time = time - src.distance = distance - src.power_x = power_x - src.power_y = power_y - src.dest_x = dest_x - src.dest_y = dest_y - -/proc/projectile_trajectory(src_x, src_y, rotation, angle, power) - - // returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle], - // rotated at [rotation] and with the power of [power] - // Thanks to VistaPOWA for this function - - var/power_x = power * cos(angle) - var/power_y = power * sin(angle) - var/time = 2* power_y / 10 //10 = g - - var/distance = time * power_x - - var/dest_x = src_x + distance*sin(rotation); - var/dest_y = src_y + distance*cos(rotation); - - return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y) + + +/datum/projectile_data + var/src_x + var/src_y + var/time + var/distance + var/power_x + var/power_y + var/dest_x + var/dest_y + +/datum/projectile_data/New(var/src_x, var/src_y, var/time, var/distance, \ + var/power_x, var/power_y, var/dest_x, var/dest_y) + src.src_x = src_x + src.src_y = src_y + src.time = time + src.distance = distance + src.power_x = power_x + src.power_y = power_y + src.dest_x = dest_x + src.dest_y = dest_y + +/proc/projectile_trajectory(src_x, src_y, rotation, angle, power) + + // returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle], + // rotated at [rotation] and with the power of [power] + // Thanks to VistaPOWA for this function + + var/power_x = power * cos(angle) + var/power_y = power * sin(angle) + var/time = 2* power_y / 10 //10 = g + + var/distance = time * power_x + + var/dest_x = src_x + distance*sin(rotation); + var/dest_y = src_y + distance*cos(rotation); + + return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 3ec2510cc78a..af948086df5f 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 cdb2a47312c4..0797e80d317d 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -1,185 +1,185 @@ -GLOBAL_LIST_INIT(bitfields, list( - "appearance_flags" = list( - "LONG_GLIDE" = LONG_GLIDE, - "RESET_COLOR" = RESET_COLOR, - "RESET_ALPHA" = RESET_ALPHA, - "RESET_TRANSFORM" = RESET_TRANSFORM, - "NO_CLIENT_COLOR" = NO_CLIENT_COLOR, - "KEEP_TOGETHER" = KEEP_TOGETHER, - "KEEP_APART" = KEEP_APART, - "PLANE_MASTER" = PLANE_MASTER, - "TILE_BOUND" = TILE_BOUND, - "PIXEL_SCALE" = PIXEL_SCALE - ), - "sight" = list( - "SEE_INFRA" = SEE_INFRA, - "SEE_SELF" = SEE_SELF, - "SEE_MOBS" = SEE_MOBS, - "SEE_OBJS" = SEE_OBJS, - "SEE_TURFS" = SEE_TURFS, - "SEE_PIXELS" = SEE_PIXELS, - "SEE_THRU" = SEE_THRU, - "SEE_BLACKNESS" = SEE_BLACKNESS, - "BLIND" = BLIND - ), - "obj_flags" = list( - "EMAGGED" = EMAGGED, - "IN_USE" = IN_USE, - "CAN_BE_HIT" = CAN_BE_HIT, - "BEING_SHOCKED" = BEING_SHOCKED, - "DANGEROUS_POSSESSION" = DANGEROUS_POSSESSION, - "ON_BLUEPRINTS" = ON_BLUEPRINTS, - "UNIQUE_RENAME" = UNIQUE_RENAME, - "USES_TGUI" = USES_TGUI, - "FROZEN" = FROZEN, - ), - "datum_flags" = list( - "DF_USE_TAG" = DF_USE_TAG, - "DF_VAR_EDITED" = DF_VAR_EDITED, - "DF_ISPROCESSING" = DF_ISPROCESSING, - ), - "item_flags" = list( - "BEING_REMOVED" = BEING_REMOVED, - "IN_INVENTORY" = IN_INVENTORY, - "FORCE_STRING_OVERRIDE" = FORCE_STRING_OVERRIDE, - "NEEDS_PERMIT" = NEEDS_PERMIT, - "SLOWS_WHILE_IN_HAND" = SLOWS_WHILE_IN_HAND, - "NO_MAT_REDEMPTION" = NO_MAT_REDEMPTION, - "DROPDEL" = DROPDEL, - "NOBLUDGEON" = NOBLUDGEON, - "ABSTRACT" = ABSTRACT, - "IN_STORAGE" = IN_STORAGE, - ), - "admin_flags" = list( - "BUILDMODE" = R_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, - "PASSDOOR" = PASSDOOR //yogs - ), - "movement_type" = list( - "GROUND" = GROUND, - "FLYING" = FLYING, - "VENTCRAWLING" = VENTCRAWLING, - "FLOATING" = FLOATING, - "UNSTOPPABLE" = UNSTOPPABLE - ), - "resistance_flags" = list( - "LAVA_PROOF" = LAVA_PROOF, - "FIRE_PROOF" = FIRE_PROOF, - "FLAMMABLE" = FLAMMABLE, - "ON_FIRE" = ON_FIRE, - "UNACIDABLE" = UNACIDABLE, - "ACID_PROOF" = ACID_PROOF, - "INDESTRUCTIBLE" = INDESTRUCTIBLE, - "FREEZE_PROOF" = FREEZE_PROOF - ), - "flags_1" = list( - "NOJAUNT_1" = NOJAUNT_1, - "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, - "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, - "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, - "MASKINTERNALS" = MASKINTERNALS, - "NOSLIP" = NOSLIP, - "THICKMATERIAL" = THICKMATERIAL, - "VOICEBOX_TOGGLABLE" = VOICEBOX_TOGGLABLE, - "VOICEBOX_DISABLED" = VOICEBOX_DISABLED, - "SHOWEROKAY" = SHOWEROKAY, - "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, - ), - "car_traits" = list( - "CAN_KIDNAP" = CAN_KIDNAP, - ), - "mobility_flags" = list( - "MOVE" = MOBILITY_MOVE, - "STAND" = MOBILITY_STAND, - "PICKUP" = MOBILITY_PICKUP, - "USE" = MOBILITY_USE, - "UI" = MOBILITY_UI, - "STORAGE" = MOBILITY_STORAGE, - "PULL" = MOBILITY_PULL, - ), - "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 - ) - )) +GLOBAL_LIST_INIT(bitfields, list( + "appearance_flags" = list( + "LONG_GLIDE" = LONG_GLIDE, + "RESET_COLOR" = RESET_COLOR, + "RESET_ALPHA" = RESET_ALPHA, + "RESET_TRANSFORM" = RESET_TRANSFORM, + "NO_CLIENT_COLOR" = NO_CLIENT_COLOR, + "KEEP_TOGETHER" = KEEP_TOGETHER, + "KEEP_APART" = KEEP_APART, + "PLANE_MASTER" = PLANE_MASTER, + "TILE_BOUND" = TILE_BOUND, + "PIXEL_SCALE" = PIXEL_SCALE + ), + "sight" = list( + "SEE_INFRA" = SEE_INFRA, + "SEE_SELF" = SEE_SELF, + "SEE_MOBS" = SEE_MOBS, + "SEE_OBJS" = SEE_OBJS, + "SEE_TURFS" = SEE_TURFS, + "SEE_PIXELS" = SEE_PIXELS, + "SEE_THRU" = SEE_THRU, + "SEE_BLACKNESS" = SEE_BLACKNESS, + "BLIND" = BLIND + ), + "obj_flags" = list( + "EMAGGED" = EMAGGED, + "IN_USE" = IN_USE, + "CAN_BE_HIT" = CAN_BE_HIT, + "BEING_SHOCKED" = BEING_SHOCKED, + "DANGEROUS_POSSESSION" = DANGEROUS_POSSESSION, + "ON_BLUEPRINTS" = ON_BLUEPRINTS, + "UNIQUE_RENAME" = UNIQUE_RENAME, + "USES_TGUI" = USES_TGUI, + "FROZEN" = FROZEN, + ), + "datum_flags" = list( + "DF_USE_TAG" = DF_USE_TAG, + "DF_VAR_EDITED" = DF_VAR_EDITED, + "DF_ISPROCESSING" = DF_ISPROCESSING, + ), + "item_flags" = list( + "BEING_REMOVED" = BEING_REMOVED, + "IN_INVENTORY" = IN_INVENTORY, + "FORCE_STRING_OVERRIDE" = FORCE_STRING_OVERRIDE, + "NEEDS_PERMIT" = NEEDS_PERMIT, + "SLOWS_WHILE_IN_HAND" = SLOWS_WHILE_IN_HAND, + "NO_MAT_REDEMPTION" = NO_MAT_REDEMPTION, + "DROPDEL" = DROPDEL, + "NOBLUDGEON" = NOBLUDGEON, + "ABSTRACT" = ABSTRACT, + "IN_STORAGE" = IN_STORAGE, + ), + "admin_flags" = list( + "BUILDMODE" = R_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, + "PASSDOOR" = PASSDOOR //yogs + ), + "movement_type" = list( + "GROUND" = GROUND, + "FLYING" = FLYING, + "VENTCRAWLING" = VENTCRAWLING, + "FLOATING" = FLOATING, + "UNSTOPPABLE" = UNSTOPPABLE + ), + "resistance_flags" = list( + "LAVA_PROOF" = LAVA_PROOF, + "FIRE_PROOF" = FIRE_PROOF, + "FLAMMABLE" = FLAMMABLE, + "ON_FIRE" = ON_FIRE, + "UNACIDABLE" = UNACIDABLE, + "ACID_PROOF" = ACID_PROOF, + "INDESTRUCTIBLE" = INDESTRUCTIBLE, + "FREEZE_PROOF" = FREEZE_PROOF + ), + "flags_1" = list( + "NOJAUNT_1" = NOJAUNT_1, + "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, + "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, + "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, + "MASKINTERNALS" = MASKINTERNALS, + "NOSLIP" = NOSLIP, + "THICKMATERIAL" = THICKMATERIAL, + "VOICEBOX_TOGGLABLE" = VOICEBOX_TOGGLABLE, + "VOICEBOX_DISABLED" = VOICEBOX_DISABLED, + "SHOWEROKAY" = SHOWEROKAY, + "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, + ), + "car_traits" = list( + "CAN_KIDNAP" = CAN_KIDNAP, + ), + "mobility_flags" = list( + "MOVE" = MOBILITY_MOVE, + "STAND" = MOBILITY_STAND, + "PICKUP" = MOBILITY_PICKUP, + "USE" = MOBILITY_USE, + "UI" = MOBILITY_UI, + "STORAGE" = MOBILITY_STORAGE, + "PULL" = MOBILITY_PULL, + ), + "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 + ) + )) diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm index 71e28ad39532..c2f220485942 100644 --- a/code/_globalvars/configuration.dm +++ b/code/_globalvars/configuration.dm @@ -1,36 +1,36 @@ -GLOBAL_REAL(config, /datum/controller/configuration) - -GLOBAL_DATUM(revdata, /datum/getrev) - -GLOBAL_VAR(host) -GLOBAL_VAR(station_name) -GLOBAL_VAR_INIT(game_version, "/tg/ Station 13") -GLOBAL_VAR_INIT(changelog_hash, "") -GLOBAL_VAR_INIT(hub_visibility, FALSE) - -GLOBAL_VAR_INIT(ooc_allowed, TRUE) // used with admin verbs to disable ooc - not a config option apparently -GLOBAL_VAR_INIT(dooc_allowed, TRUE) -GLOBAL_VAR_INIT(enter_allowed, TRUE) -GLOBAL_VAR_INIT(shuttle_frozen, FALSE) -GLOBAL_VAR_INIT(shuttle_left, FALSE) -GLOBAL_VAR_INIT(tinted_weldhelh, TRUE) - - -// Debug is used exactly once (in living.dm) but is commented out in a lot of places. It is not set anywhere and only checked. -// Debug2 is used in conjunction with a lot of admin verbs and therefore is actually legit. -GLOBAL_VAR_INIT(Debug, FALSE) // global debug switch -GLOBAL_VAR_INIT(Debug2, FALSE) - -//This was a define, but I changed it to a variable so it can be changed in-game.(kept the all-caps definition because... code...) -Errorage -//Protecting these because the proper way to edit them is with the config/secrets -GLOBAL_VAR_INIT(MAX_EX_DEVESTATION_RANGE, 3) -GLOBAL_PROTECT(MAX_EX_DEVESTATION_RANGE) -GLOBAL_VAR_INIT(MAX_EX_HEAVY_RANGE, 7) -GLOBAL_PROTECT(MAX_EX_HEAVY_RANGE) -GLOBAL_VAR_INIT(MAX_EX_LIGHT_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_LIGHT_RANGE) -GLOBAL_VAR_INIT(MAX_EX_FLASH_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_FLASH_RANGE) -GLOBAL_VAR_INIT(MAX_EX_FLAME_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_FLAME_RANGE) +GLOBAL_REAL(config, /datum/controller/configuration) + +GLOBAL_DATUM(revdata, /datum/getrev) + +GLOBAL_VAR(host) +GLOBAL_VAR(station_name) +GLOBAL_VAR_INIT(game_version, "/tg/ Station 13") +GLOBAL_VAR_INIT(changelog_hash, "") +GLOBAL_VAR_INIT(hub_visibility, FALSE) + +GLOBAL_VAR_INIT(ooc_allowed, TRUE) // used with admin verbs to disable ooc - not a config option apparently +GLOBAL_VAR_INIT(dooc_allowed, TRUE) +GLOBAL_VAR_INIT(enter_allowed, TRUE) +GLOBAL_VAR_INIT(shuttle_frozen, FALSE) +GLOBAL_VAR_INIT(shuttle_left, FALSE) +GLOBAL_VAR_INIT(tinted_weldhelh, TRUE) + + +// Debug is used exactly once (in living.dm) but is commented out in a lot of places. It is not set anywhere and only checked. +// Debug2 is used in conjunction with a lot of admin verbs and therefore is actually legit. +GLOBAL_VAR_INIT(Debug, FALSE) // global debug switch +GLOBAL_VAR_INIT(Debug2, FALSE) + +//This was a define, but I changed it to a variable so it can be changed in-game.(kept the all-caps definition because... code...) -Errorage +//Protecting these because the proper way to edit them is with the config/secrets +GLOBAL_VAR_INIT(MAX_EX_DEVESTATION_RANGE, 3) +GLOBAL_PROTECT(MAX_EX_DEVESTATION_RANGE) +GLOBAL_VAR_INIT(MAX_EX_HEAVY_RANGE, 7) +GLOBAL_PROTECT(MAX_EX_HEAVY_RANGE) +GLOBAL_VAR_INIT(MAX_EX_LIGHT_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_LIGHT_RANGE) +GLOBAL_VAR_INIT(MAX_EX_FLASH_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_FLASH_RANGE) +GLOBAL_VAR_INIT(MAX_EX_FLAME_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_FLAME_RANGE) GLOBAL_VAR_INIT(DYN_EX_SCALE, 0.5) \ No newline at end of file diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm index f6022cdd590b..01a34c2ab788 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 bdf53f3d71c7..c5c424977cd6 100644 --- a/code/_globalvars/genetics.dm +++ b/code/_globalvars/genetics.dm @@ -1,9 +1,9 @@ -//faster than having to constantly loop for them -GLOBAL_LIST_EMPTY_TYPED(all_mutations, /datum/mutation/human) //type = initialized mutation -GLOBAL_LIST_EMPTY(full_sequences) //type = correct sequence -GLOBAL_LIST_EMPTY(bad_mutations) //bad initialized mutations -GLOBAL_LIST_EMPTY(good_mutations) //good initialized mutations -GLOBAL_LIST_EMPTY(not_good_mutations) //neutral initialized mutations -GLOBAL_LIST_EMPTY(alias_mutations) //alias = type - -GLOBAL_LIST_EMPTY(mutation_recipes) +//faster than having to constantly loop for them +GLOBAL_LIST_EMPTY_TYPED(all_mutations, /datum/mutation/human) //type = initialized mutation +GLOBAL_LIST_EMPTY(full_sequences) //type = correct sequence +GLOBAL_LIST_EMPTY(bad_mutations) //bad initialized mutations +GLOBAL_LIST_EMPTY(good_mutations) //good initialized mutations +GLOBAL_LIST_EMPTY(not_good_mutations) //neutral initialized mutations +GLOBAL_LIST_EMPTY(alias_mutations) //alias = type + +GLOBAL_LIST_EMPTY(mutation_recipes) diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm index 20554d476f81..0c6b7a7662e0 100644 --- a/code/_globalvars/lists/flavor_misc.dm +++ b/code/_globalvars/lists/flavor_misc.dm @@ -1,193 +1,193 @@ -//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(underwear_list) //stores /datum/sprite_accessory/underwear indexed by name -GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name -GLOBAL_LIST_EMPTY(underwear_f) //stores only underwear name - //Undershirts -GLOBAL_LIST_EMPTY(undershirt_list) //stores /datum/sprite_accessory/undershirt indexed by name -GLOBAL_LIST_EMPTY(undershirt_m) //stores only undershirt name -GLOBAL_LIST_EMPTY(undershirt_f) //stores only undershirt name - //Socks -GLOBAL_LIST_EMPTY(socks_list) //stores /datum/sprite_accessory/socks indexed by name - //Lizard Bits (all datum lists indexed by name) -GLOBAL_LIST_EMPTY(body_markings_list) -GLOBAL_LIST_EMPTY(tails_list_lizard) -GLOBAL_LIST_EMPTY(animated_tails_list_lizard) -GLOBAL_LIST_EMPTY(snouts_list) -GLOBAL_LIST_EMPTY(horns_list) -GLOBAL_LIST_EMPTY(frills_list) -GLOBAL_LIST_EMPTY(spines_list) -GLOBAL_LIST_EMPTY(legs_list) -GLOBAL_LIST_EMPTY(animated_spines_list) - - //Mutant Human bits -GLOBAL_LIST_EMPTY(tails_list_human) -GLOBAL_LIST_EMPTY(animated_tails_list_human) -GLOBAL_LIST_EMPTY(ears_list) -GLOBAL_LIST_EMPTY(wings_list) -GLOBAL_LIST_EMPTY(wings_open_list) -GLOBAL_LIST_EMPTY(r_wings_list) -GLOBAL_LIST_EMPTY(moth_wings_list) -GLOBAL_LIST_EMPTY(caps_list) - -GLOBAL_LIST_INIT(color_list_ethereal, list("F Class(Green)" = "97ee63", "F2 Class (Light Green)" = "00fa9a", "F3 Class (Dark Green)" = "37835b", "M Class (Red)" = "9c3030", "M1 Class (Purple)" = "ee82ee", "G Class (Yellow)" = "fbdf56", "O Class (Blue)" = "3399ff", "A Class (Cyan)" = "00ffff")) - -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", - "Banned", - "Bliss", - "Blue", - "Clown", - "Database", - "Dorf", - "Firewall", - "Fuzzy", - "Gentoo", - "Glitchman", - "Gondola", - "Goon", - "Hades", - "Heartline", - "Helios", - "House", - "Inverted", - "Matrix", - "Monochrome", - "Murica", - "Nanotrasen", - "Not Malf", - "President", - "Random", - "Rainbow", - "Red", - "Red October", - "Static", - "Syndicat Meow", - "Text", - "Too Deep", - "Triumvirate", - "Triumvirate-M", - "Weird")) - -/proc/resolve_ai_icon(input) - if(!input || !(input in GLOB.ai_core_display_screens)) - return "ai" - else - if(input == "Random") - input = pick(GLOB.ai_core_display_screens - "Random") - return "ai-[lowertext(input)]" - -GLOBAL_LIST_INIT(security_depts_prefs, 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)) - -//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 Testing Range -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", - "Testing Range", "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(underwear_list) //stores /datum/sprite_accessory/underwear indexed by name +GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name +GLOBAL_LIST_EMPTY(underwear_f) //stores only underwear name + //Undershirts +GLOBAL_LIST_EMPTY(undershirt_list) //stores /datum/sprite_accessory/undershirt indexed by name +GLOBAL_LIST_EMPTY(undershirt_m) //stores only undershirt name +GLOBAL_LIST_EMPTY(undershirt_f) //stores only undershirt name + //Socks +GLOBAL_LIST_EMPTY(socks_list) //stores /datum/sprite_accessory/socks indexed by name + //Lizard Bits (all datum lists indexed by name) +GLOBAL_LIST_EMPTY(body_markings_list) +GLOBAL_LIST_EMPTY(tails_list_lizard) +GLOBAL_LIST_EMPTY(animated_tails_list_lizard) +GLOBAL_LIST_EMPTY(snouts_list) +GLOBAL_LIST_EMPTY(horns_list) +GLOBAL_LIST_EMPTY(frills_list) +GLOBAL_LIST_EMPTY(spines_list) +GLOBAL_LIST_EMPTY(legs_list) +GLOBAL_LIST_EMPTY(animated_spines_list) + + //Mutant Human bits +GLOBAL_LIST_EMPTY(tails_list_human) +GLOBAL_LIST_EMPTY(animated_tails_list_human) +GLOBAL_LIST_EMPTY(ears_list) +GLOBAL_LIST_EMPTY(wings_list) +GLOBAL_LIST_EMPTY(wings_open_list) +GLOBAL_LIST_EMPTY(r_wings_list) +GLOBAL_LIST_EMPTY(moth_wings_list) +GLOBAL_LIST_EMPTY(caps_list) + +GLOBAL_LIST_INIT(color_list_ethereal, list("F Class(Green)" = "97ee63", "F2 Class (Light Green)" = "00fa9a", "F3 Class (Dark Green)" = "37835b", "M Class (Red)" = "9c3030", "M1 Class (Purple)" = "ee82ee", "G Class (Yellow)" = "fbdf56", "O Class (Blue)" = "3399ff", "A Class (Cyan)" = "00ffff")) + +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", + "Banned", + "Bliss", + "Blue", + "Clown", + "Database", + "Dorf", + "Firewall", + "Fuzzy", + "Gentoo", + "Glitchman", + "Gondola", + "Goon", + "Hades", + "Heartline", + "Helios", + "House", + "Inverted", + "Matrix", + "Monochrome", + "Murica", + "Nanotrasen", + "Not Malf", + "President", + "Random", + "Rainbow", + "Red", + "Red October", + "Static", + "Syndicat Meow", + "Text", + "Too Deep", + "Triumvirate", + "Triumvirate-M", + "Weird")) + +/proc/resolve_ai_icon(input) + if(!input || !(input in GLOB.ai_core_display_screens)) + return "ai" + else + if(input == "Random") + input = pick(GLOB.ai_core_display_screens - "Random") + return "ai-[lowertext(input)]" + +GLOBAL_LIST_INIT(security_depts_prefs, 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)) + +//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 Testing Range +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", + "Testing Range", "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 24415cdd700f..d21389df0e70 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) //handles clockwork portal+eminence teleport destinations -GLOBAL_LIST_EMPTY(jobspawn_overrides) //These will take precedence over normal spawnpoints if created. -GLOBAL_LIST_EMPTY(stationroom_landmarks) //yogs - list of all spawns for stationrooms - -GLOBAL_LIST_EMPTY(wizardstart) -GLOBAL_LIST_EMPTY(nukeop_start) -GLOBAL_LIST_EMPTY(nukeop_leader_start) -GLOBAL_LIST_EMPTY(newplayer_start) -GLOBAL_LIST_EMPTY(prisonwarp) //admin prisoners go to these -GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here (ninja energy net) -GLOBAL_LIST_EMPTY(xeno_spawn)//aliens, morphs and nightmares spawn at these -GLOBAL_LIST_EMPTY(tdome1) -GLOBAL_LIST_EMPTY(tdome2) -GLOBAL_LIST_EMPTY(tdomeobserve) -GLOBAL_LIST_EMPTY(tdomeadmin) -GLOBAL_LIST_EMPTY(prisonwarped) //list of players already warped -GLOBAL_LIST_EMPTY(blobstart) //stationloving objects, blobs, santa, respawning devils -GLOBAL_LIST_EMPTY(secequipment) //sec equipment lockers that scale with the number of sec players -GLOBAL_LIST_EMPTY(deathsquadspawn) -GLOBAL_LIST_EMPTY(emergencyresponseteamspawn) -GLOBAL_LIST_EMPTY(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_INIT(cardinals, list(NORTH, SOUTH, EAST, WEST)) +GLOBAL_LIST_INIT(cardinals_multiz, list(NORTH, SOUTH, EAST, WEST, UP, DOWN)) +GLOBAL_LIST_INIT(diagonals, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) +GLOBAL_LIST_INIT(corners_multiz, list(UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(diagonals_multiz, list( + NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, + UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, + DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(alldirs_multiz, list( + NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, + UP, UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, + DOWN, DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) + +GLOBAL_LIST_EMPTY(landmarks_list) //list of all landmarks created +GLOBAL_LIST_EMPTY(start_landmarks_list) //list of all spawn points created +GLOBAL_LIST_EMPTY(department_security_spawns) //list of all department security spawns + +GLOBAL_LIST_EMPTY(generic_event_spawns) //handles clockwork portal+eminence teleport destinations +GLOBAL_LIST_EMPTY(jobspawn_overrides) //These will take precedence over normal spawnpoints if created. +GLOBAL_LIST_EMPTY(stationroom_landmarks) //yogs - list of all spawns for stationrooms + +GLOBAL_LIST_EMPTY(wizardstart) +GLOBAL_LIST_EMPTY(nukeop_start) +GLOBAL_LIST_EMPTY(nukeop_leader_start) +GLOBAL_LIST_EMPTY(newplayer_start) +GLOBAL_LIST_EMPTY(prisonwarp) //admin prisoners go to these +GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here (ninja energy net) +GLOBAL_LIST_EMPTY(xeno_spawn)//aliens, morphs and nightmares spawn at these +GLOBAL_LIST_EMPTY(tdome1) +GLOBAL_LIST_EMPTY(tdome2) +GLOBAL_LIST_EMPTY(tdomeobserve) +GLOBAL_LIST_EMPTY(tdomeadmin) +GLOBAL_LIST_EMPTY(prisonwarped) //list of players already warped +GLOBAL_LIST_EMPTY(blobstart) //stationloving objects, blobs, santa, respawning devils +GLOBAL_LIST_EMPTY(secequipment) //sec equipment lockers that scale with the number of sec players +GLOBAL_LIST_EMPTY(deathsquadspawn) +GLOBAL_LIST_EMPTY(emergencyresponseteamspawn) +GLOBAL_LIST_EMPTY(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) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 19e6c21f684e..5a32f60309e6 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -1,72 +1,72 @@ -GLOBAL_LIST_EMPTY(clients) //all clients -GLOBAL_LIST_EMPTY(admins) //all clients whom are admins -GLOBAL_PROTECT(admins) -GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb. - -GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client -GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for stealthmins - -//Since it didn't really belong in any other category, I'm putting this here -//This is for procs to replace all the goddamn 'in world's that are chilling around the code - -GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**. -GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless -GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob -GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player -GLOBAL_LIST_EMPTY(suicided_mob_list) //contains a list of all mobs that suicided, including their associated ghosts. -GLOBAL_LIST_EMPTY(drones_list) -GLOBAL_LIST_EMPTY(dead_mob_list) //all dead mobs, including clientless. Excludes /mob/dead/new_player -GLOBAL_LIST_EMPTY(joined_player_list) //all clients that have joined the game at round-start or as a latejoin. -GLOBAL_LIST_EMPTY(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(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(emote_list) - -/proc/update_config_movespeed_type_lookup(update_mobs = TRUE) - var/list/mob_types = list() - var/list/entry_value = CONFIG_GET(keyed_list/multiplicative_movespeed) - for(var/path in entry_value) - var/value = entry_value[path] - if(!value) - continue - for(var/subpath in typesof(path)) - mob_types[subpath] = value - GLOB.mob_config_movespeed_type_lookup = mob_types - if(update_mobs) - update_mob_config_movespeeds() - -/proc/update_mob_config_movespeeds() - for(var/i in GLOB.mob_list) - var/mob/M = i - M.update_config_movespeed() - -/proc/init_emote_list() - . = list() - for(var/path in subtypesof(/datum/emote)) - var/datum/emote/E = new path() - if(!.[E.key]) - .[E.key] = list(E) - else - .[E.key] += E - - if(!.[E.key_third_person]) - .[E.key_third_person] = list(E) - else - .[E.key_third_person] |= E +GLOBAL_LIST_EMPTY(clients) //all clients +GLOBAL_LIST_EMPTY(admins) //all clients whom are admins +GLOBAL_PROTECT(admins) +GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb. + +GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client +GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for stealthmins + +//Since it didn't really belong in any other category, I'm putting this here +//This is for procs to replace all the goddamn 'in world's that are chilling around the code + +GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**. +GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless +GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob +GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player +GLOBAL_LIST_EMPTY(suicided_mob_list) //contains a list of all mobs that suicided, including their associated ghosts. +GLOBAL_LIST_EMPTY(drones_list) +GLOBAL_LIST_EMPTY(dead_mob_list) //all dead mobs, including clientless. Excludes /mob/dead/new_player +GLOBAL_LIST_EMPTY(joined_player_list) //all clients that have joined the game at round-start or as a latejoin. +GLOBAL_LIST_EMPTY(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(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(emote_list) + +/proc/update_config_movespeed_type_lookup(update_mobs = TRUE) + var/list/mob_types = list() + var/list/entry_value = CONFIG_GET(keyed_list/multiplicative_movespeed) + for(var/path in entry_value) + var/value = entry_value[path] + if(!value) + continue + for(var/subpath in typesof(path)) + mob_types[subpath] = value + GLOB.mob_config_movespeed_type_lookup = mob_types + if(update_mobs) + update_mob_config_movespeeds() + +/proc/update_mob_config_movespeeds() + for(var/i in GLOB.mob_list) + var/mob/M = i + M.update_config_movespeed() + +/proc/init_emote_list() + . = list() + for(var/path in subtypesof(/datum/emote)) + var/datum/emote/E = new path() + if(!.[E.key]) + .[E.key] = list(E) + else + .[E.key] += E + + if(!.[E.key_third_person]) + .[E.key_third_person] = list(E) + else + .[E.key_third_person] |= E diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index d769fd6614ae..f7231351538d 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -1,51 +1,51 @@ -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(ethereal_names, world.file2list("strings/names/ethereal.txt")) -GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt")) -GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt")) -GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt")) -GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt")) - -GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) -GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt")) -GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt")) -GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt")) -GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt")) -//loaded on startup because of " -//would include in rsc if ' was used - -/* -List of configurable names in preferences and their metadata -"id" = list( - "pref_name" = "name", //pref label - "qdesc" = "name", //popup question text - "allow_numbers" = FALSE, // numbers allowed in the name - "group" = "whatever", // group (these will be grouped together on pref ui ,order still follows the list so they need to be concurrent to be grouped) - "allow_null" = FALSE // if empty name is entered it's replaced with default value - ), -*/ -GLOBAL_LIST_INIT(preferences_custom_names, list( - "human" = list("pref_name" = "Backup Human", "qdesc" = "backup human name, used in the event you are assigned a command role as another species", "allow_numbers" = FALSE , "group" = "backup_human", "allow_null" = FALSE), - "clown" = list("pref_name" = "Clown" , "qdesc" = "clown name", "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), - "mime" = list("pref_name" = "Mime", "qdesc" = "mime name" , "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), - "cyborg" = list("pref_name" = "Cyborg", "qdesc" = "cyborg name (Leave empty to use default naming scheme)", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = TRUE), - "ai" = list("pref_name" = "AI", "qdesc" = "ai name", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = FALSE), - "religion" = list("pref_name" = "Chaplain religion", "qdesc" = "religion" , "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE), - "deity" = list("pref_name" = "Chaplain deity", "qdesc" = "deity", "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE) - )) +GLOBAL_LIST_INIT(ai_names, world.file2list("strings/names/ai.txt")) +GLOBAL_LIST_INIT(wizard_first, world.file2list("strings/names/wizardfirst.txt")) +GLOBAL_LIST_INIT(wizard_second, world.file2list("strings/names/wizardsecond.txt")) +GLOBAL_LIST_INIT(ninja_titles, world.file2list("strings/names/ninjatitle.txt")) +GLOBAL_LIST_INIT(ninja_names, world.file2list("strings/names/ninjaname.txt")) +GLOBAL_LIST_INIT(commando_names, world.file2list("strings/names/death_commando.txt")) +GLOBAL_LIST_INIT(first_names_male, world.file2list("strings/names/first_male.txt")) +GLOBAL_LIST_INIT(first_names_female, world.file2list("strings/names/first_female.txt")) +GLOBAL_LIST_INIT(last_names, world.file2list("strings/names/last.txt")) +GLOBAL_LIST_INIT(lizard_names_male, world.file2list("strings/names/lizard_male.txt")) +GLOBAL_LIST_INIT(lizard_names_female, world.file2list("strings/names/lizard_female.txt")) +GLOBAL_LIST_INIT(clown_names, world.file2list("strings/names/clown.txt")) +GLOBAL_LIST_INIT(mime_names, world.file2list("strings/names/mime.txt")) +GLOBAL_LIST_INIT(carp_names, world.file2list("strings/names/carp.txt")) +GLOBAL_LIST_INIT(golem_names, world.file2list("strings/names/golem.txt")) +GLOBAL_LIST_INIT(moth_first, world.file2list("strings/names/moth_first.txt")) +GLOBAL_LIST_INIT(moth_last, world.file2list("strings/names/moth_last.txt")) +GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt")) +GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt")) +GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt")) +GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt")) +GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt")) +GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt")) + +GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) +GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt")) +GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt")) +GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt")) +GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt")) +//loaded on startup because of " +//would include in rsc if ' was used + +/* +List of configurable names in preferences and their metadata +"id" = list( + "pref_name" = "name", //pref label + "qdesc" = "name", //popup question text + "allow_numbers" = FALSE, // numbers allowed in the name + "group" = "whatever", // group (these will be grouped together on pref ui ,order still follows the list so they need to be concurrent to be grouped) + "allow_null" = FALSE // if empty name is entered it's replaced with default value + ), +*/ +GLOBAL_LIST_INIT(preferences_custom_names, list( + "human" = list("pref_name" = "Backup Human", "qdesc" = "backup human name, used in the event you are assigned a command role as another species", "allow_numbers" = FALSE , "group" = "backup_human", "allow_null" = FALSE), + "clown" = list("pref_name" = "Clown" , "qdesc" = "clown name", "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), + "mime" = list("pref_name" = "Mime", "qdesc" = "mime name" , "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), + "cyborg" = list("pref_name" = "Cyborg", "qdesc" = "cyborg name (Leave empty to use default naming scheme)", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = TRUE), + "ai" = list("pref_name" = "AI", "qdesc" = "ai name", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = FALSE), + "religion" = list("pref_name" = "Chaplain religion", "qdesc" = "religion" , "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE), + "deity" = list("pref_name" = "Chaplain deity", "qdesc" = "deity", "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE) + )) diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 14c752613bff..c49e27bb5611 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -1,41 +1,41 @@ -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(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(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 181f855119ec..b5facbaf9a29 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -1,72 +1,72 @@ -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_ntsl_log) -GLOBAL_PROTECT(world_ntsl_log) -GLOBAL_VAR(world_manifest_log) -GLOBAL_PROTECT(world_manifest_log) -GLOBAL_VAR(query_debug_log) -GLOBAL_PROTECT(query_debug_log) -GLOBAL_VAR(world_job_debug_log) -GLOBAL_PROTECT(world_job_debug_log) -GLOBAL_VAR(world_mecha_log) -GLOBAL_PROTECT(world_mecha_log) -GLOBAL_VAR(world_virus_log) -GLOBAL_PROTECT(world_virus_log) -GLOBAL_VAR(world_asset_log) -GLOBAL_PROTECT(world_asset_log) -GLOBAL_VAR(world_cloning_log) -GLOBAL_PROTECT(world_cloning_log) -GLOBAL_VAR(world_map_error_log) -GLOBAL_PROTECT(world_map_error_log) -GLOBAL_VAR(world_paper_log) -GLOBAL_PROTECT(world_paper_log) - -GLOBAL_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_ntsl_log) +GLOBAL_PROTECT(world_ntsl_log) +GLOBAL_VAR(world_manifest_log) +GLOBAL_PROTECT(world_manifest_log) +GLOBAL_VAR(query_debug_log) +GLOBAL_PROTECT(query_debug_log) +GLOBAL_VAR(world_job_debug_log) +GLOBAL_PROTECT(world_job_debug_log) +GLOBAL_VAR(world_mecha_log) +GLOBAL_PROTECT(world_mecha_log) +GLOBAL_VAR(world_virus_log) +GLOBAL_PROTECT(world_virus_log) +GLOBAL_VAR(world_asset_log) +GLOBAL_PROTECT(world_asset_log) +GLOBAL_VAR(world_cloning_log) +GLOBAL_PROTECT(world_cloning_log) +GLOBAL_VAR(world_map_error_log) +GLOBAL_PROTECT(world_map_error_log) +GLOBAL_VAR(world_paper_log) +GLOBAL_PROTECT(world_paper_log) + +GLOBAL_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 2957c33dbc14..edd22ec242fe 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -1,29 +1,29 @@ -GLOBAL_VAR_INIT(admin_notice, "") // Admin notice that all clients see when joining the server - -GLOBAL_VAR_INIT(timezoneOffset, 0) // The difference betwen midnight (of the host computer) and 0 world.ticks. - - // For FTP requests. (i.e. downloading runtime logs.) - // However it'd be ok to use for accessing attack logs and such too, which are even laggier. -GLOBAL_VAR_INIT(fileaccess_timer, 0) - -GLOBAL_VAR_INIT(TAB, "    ") - -GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) - -GLOBAL_VAR_INIT(CELLRATE, 0.002) // conversion ratio between a watt-tick and kilojoule -GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second) - -GLOBAL_LIST_EMPTY(powernets) - -GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes - -GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details - -// All 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. + + // 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_VAR_INIT(TAB, "    ") + +GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) + +GLOBAL_VAR_INIT(CELLRATE, 0.002) // conversion ratio between a watt-tick and kilojoule +GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second) + +GLOBAL_LIST_EMPTY(powernets) + +GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes + +GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details + +// All 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 9d96bbc412f7..7ac9bbff1899 100644 --- a/code/_js/byjax.dm +++ b/code/_js/byjax.dm @@ -1,48 +1,48 @@ -//this function places received data into element with specified id. -#define js_byjax {" - -function replaceContent() { - var args = Array.prototype.slice.call(arguments); - var id = args\[0\]; - var content = args\[1\]; - var callback = null; - if(args\[2\]){ - callback = args\[2\]; - if(args\[3\]){ - args = args.slice(3); - } - } - var parent = document.getElementById(id); - if(typeof(parent)!=='undefined' && parent!=null){ - parent.innerHTML = content?content:''; - } - if(callback && window\[callback\]){ - window\[callback\].apply(null,args); - } -} -"} - -/* -sends data to control_id:replaceContent - -receiver - mob -control_id - window id (for windows opened with browse(), it'll be "windowname.browser") -target_element - HTML element id -new_content - HTML content -callback - js function that will be called after the data is sent -callback_args - arguments for callback function - -Be sure to include required js functions in your page, or it'll raise an exception. -*/ -/proc/send_byjax(receiver, control_id, target_element, new_content=null, callback=null, list/callback_args=null) - if(receiver && target_element && control_id) // && winexists(receiver, control_id)) - var/list/argums = list(target_element, new_content) - if(callback) - argums += callback - if(callback_args) - argums += callback_args - argums = list2params(argums) - - receiver << output(argums,"[control_id]:replaceContent") - return - +//this function places received data into element with specified id. +#define js_byjax {" + +function replaceContent() { + var args = Array.prototype.slice.call(arguments); + var id = args\[0\]; + var content = args\[1\]; + var callback = null; + if(args\[2\]){ + callback = args\[2\]; + if(args\[3\]){ + args = args.slice(3); + } + } + var parent = document.getElementById(id); + if(typeof(parent)!=='undefined' && parent!=null){ + parent.innerHTML = content?content:''; + } + if(callback && window\[callback\]){ + window\[callback\].apply(null,args); + } +} +"} + +/* +sends data to control_id:replaceContent + +receiver - mob +control_id - window id (for windows opened with browse(), it'll be "windowname.browser") +target_element - HTML element id +new_content - HTML content +callback - js function that will be called after the data is sent +callback_args - arguments for callback function + +Be sure to include required js functions in your page, or it'll raise an exception. +*/ +/proc/send_byjax(receiver, control_id, target_element, new_content=null, callback=null, list/callback_args=null) + if(receiver && target_element && control_id) // && winexists(receiver, control_id)) + var/list/argums = list(target_element, new_content) + if(callback) + argums += callback + if(callback_args) + argums += callback_args + argums = list2params(argums) + + receiver << output(argums,"[control_id]:replaceContent") + return + diff --git a/code/_js/menus.dm b/code/_js/menus.dm index 76a92469edb6..9b2fc40e6801 100644 --- a/code/_js/menus.dm +++ b/code/_js/menus.dm @@ -1,37 +1,37 @@ -#define js_dropdowns {" -function dropdowns() { - var divs = document.getElementsByTagName('div'); - var headers = new Array(); - var links = new Array(); - for(var i=0;i=0) { - elem.className = elem.className.replace('visible','hidden'); - this.className = this.className.replace('open','closed'); - this.innerHTML = this.innerHTML.replace('-','+'); - } - else { - elem.className = elem.className.replace('hidden','visible'); - this.className = this.className.replace('closed','open'); - this.innerHTML = this.innerHTML.replace('+','-'); - } - return false; - } - })(links\[i\]); - } - } -} +#define js_dropdowns {" +function dropdowns() { + var divs = document.getElementsByTagName('div'); + var headers = new Array(); + var links = new Array(); + for(var i=0;i=0) { + elem.className = elem.className.replace('visible','hidden'); + this.className = this.className.replace('open','closed'); + this.innerHTML = this.innerHTML.replace('-','+'); + } + else { + elem.className = elem.className.replace('hidden','visible'); + this.className = this.className.replace('closed','open'); + this.innerHTML = this.innerHTML.replace('+','-'); + } + return false; + } + })(links\[i\]); + } + } +} "} \ No newline at end of file diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm index f50e2e2b29fc..c0f1b81582a1 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,target = neighbor, mover = 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,target = neighbor, mover = 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 da648bc08868..436080154e68 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 + 1 - - if(check_click_intercept(params,A)) - return - - if(notransform) - return - - if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, params) & COMSIG_MOB_CANCEL_CLICKON) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(incapacitated(ignore_restraints = 1)) - return - - face_atom(A) - - if(next_move > world.time) // in the year 2000... - return - - if(!modifiers["catcher"] && A.IsObscured()) - return - - if(ismecha(loc)) - var/obj/mecha/M = loc - return M.click_action(A,src,params) - - if(restrained()) - changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow - RestrainedClickOn(A) - return - - if(in_throw_mode) - throw_item(A) - return - - var/obj/item/W = get_active_held_item() - - if(W == A) - W.attack_self(src) - update_inv_hands() - return - - //These are always reachable. - //User itself, current loc, and user inventory - if(A in DirectAccess()) - if(W) - W.melee_attack_chain(src, A, params) - else - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - UnarmedAttack(A) - return - - //Can't reach anything else in lockers or other weirdness - if(!loc.AllowClick()) - return - - //Standard reach turf to turf or reaching inside storage - if(CanReach(A,W)) - if(W) - W.melee_attack_chain(src, A, params) - else - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - UnarmedAttack(A,1) - else - if(W) - W.afterattack(A,src,0,params) - else - RangedAttack(A,params) - -//Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it -/atom/proc/IsObscured() - 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)) - next += target.loc - - checking = next - return FALSE - -/atom/movable/proc/DirectAccess() - return list(src, loc) - -/mob/DirectAccess(atom/target) - return ..() + contents - -/mob/living/DirectAccess(atom/target) - return ..() + GetAllContents() - -/atom/proc/AllowClick() - return FALSE - -/turf/AllowClick() - return TRUE - -/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach) - if(!here || !there) - return - switch(reach) - if(0) - return FALSE - if(1) - return FALSE //here.Adjacent(there) - if(2 to INFINITY) - var/obj/dummy = new(get_turf(here)) - dummy.pass_flags |= PASSTABLE - dummy.invisibility = INVISIBILITY_ABSTRACT - for(var/i in 1 to reach) //Limit it to that many tries - var/turf/T = get_step(dummy, get_dir(dummy, there)) - if(dummy.CanReach(there)) - qdel(dummy) - return TRUE - if(!dummy.Move(T)) //we're blocked! - qdel(dummy) - return - qdel(dummy) - -// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click) -/mob/proc/DblClickOn(atom/A, params) - return - - -/* - Translates into 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) - if(user.client && user.client.eye == user || user.client.eye == user.loc) - 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) - var/result = SEND_SIGNAL(src, COMSIG_ALT_CLICK_ON, A) //yogs start - has to be like this, otherwise stuff breaks - if(!result) - A.AltClick(src) - return //yogs end - -/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) - var/turf/T = get_turf(src) - if(T && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.statpanel = T.name - -// Use this instead of /mob/proc/AltClickOn(atom/A) where you only want turf content listing without additional atom alt-click interaction -/atom/proc/AltClickNoInteract(mob/user, atom/A) - var/turf/T = get_turf(A) - if(T && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.statpanel = T.name - -/mob/proc/TurfAdjacent(turf/T) - return T.Adjacent(src) - -/* - Control+Shift click - Unused except for AI -*/ -/mob/proc/CtrlShiftClickOn(atom/A) - A.CtrlShiftClick(src) - return - -/mob/proc/ShiftMiddleClickOn(atom/A) - src.pointed(A) - return - -/atom/proc/CtrlShiftClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) - return - -/* - Misc helpers - - 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 = ran_zone(zone_selected) - 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 + 1 + + if(check_click_intercept(params,A)) + return + + if(notransform) + return + + if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, params) & COMSIG_MOB_CANCEL_CLICKON) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) // alt and alt-gr (rightalt) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(incapacitated(ignore_restraints = 1)) + return + + face_atom(A) + + if(next_move > world.time) // in the year 2000... + return + + if(!modifiers["catcher"] && A.IsObscured()) + return + + if(ismecha(loc)) + var/obj/mecha/M = loc + return M.click_action(A,src,params) + + if(restrained()) + changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow + RestrainedClickOn(A) + return + + if(in_throw_mode) + throw_item(A) + return + + var/obj/item/W = get_active_held_item() + + if(W == A) + W.attack_self(src) + update_inv_hands() + return + + //These are always reachable. + //User itself, current loc, and user inventory + if(A in DirectAccess()) + if(W) + W.melee_attack_chain(src, A, params) + else + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + UnarmedAttack(A) + return + + //Can't reach anything else in lockers or other weirdness + if(!loc.AllowClick()) + return + + //Standard reach turf to turf or reaching inside storage + if(CanReach(A,W)) + if(W) + W.melee_attack_chain(src, A, params) + else + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + UnarmedAttack(A,1) + else + if(W) + W.afterattack(A,src,0,params) + else + RangedAttack(A,params) + +//Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it +/atom/proc/IsObscured() + 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)) + next += target.loc + + checking = next + return FALSE + +/atom/movable/proc/DirectAccess() + return list(src, loc) + +/mob/DirectAccess(atom/target) + return ..() + contents + +/mob/living/DirectAccess(atom/target) + return ..() + GetAllContents() + +/atom/proc/AllowClick() + return FALSE + +/turf/AllowClick() + return TRUE + +/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach) + if(!here || !there) + return + switch(reach) + if(0) + return FALSE + if(1) + return FALSE //here.Adjacent(there) + if(2 to INFINITY) + var/obj/dummy = new(get_turf(here)) + dummy.pass_flags |= PASSTABLE + dummy.invisibility = INVISIBILITY_ABSTRACT + for(var/i in 1 to reach) //Limit it to that many tries + var/turf/T = get_step(dummy, get_dir(dummy, there)) + if(dummy.CanReach(there)) + qdel(dummy) + return TRUE + if(!dummy.Move(T)) //we're blocked! + qdel(dummy) + return + qdel(dummy) + +// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click) +/mob/proc/DblClickOn(atom/A, params) + return + + +/* + Translates into 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) + if(user.client && user.client.eye == user || user.client.eye == user.loc) + 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) + var/result = SEND_SIGNAL(src, COMSIG_ALT_CLICK_ON, A) //yogs start - has to be like this, otherwise stuff breaks + if(!result) + A.AltClick(src) + return //yogs end + +/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) + var/turf/T = get_turf(src) + if(T && user.TurfAdjacent(T)) + user.listed_turf = T + user.client.statpanel = T.name + +// Use this instead of /mob/proc/AltClickOn(atom/A) where you only want turf content listing without additional atom alt-click interaction +/atom/proc/AltClickNoInteract(mob/user, atom/A) + var/turf/T = get_turf(A) + if(T && user.TurfAdjacent(T)) + user.listed_turf = T + user.client.statpanel = T.name + +/mob/proc/TurfAdjacent(turf/T) + return T.Adjacent(src) + +/* + Control+Shift click + Unused except for AI +*/ +/mob/proc/CtrlShiftClickOn(atom/A) + A.CtrlShiftClick(src) + return + +/mob/proc/ShiftMiddleClickOn(atom/A) + src.pointed(A) + return + +/atom/proc/CtrlShiftClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) + return + +/* + Misc helpers + + 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 = ran_zone(zone_selected) + 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 0f7aae4c897a..eb9d8b76089e 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -1,176 +1,176 @@ -/* - Cyborg ClickOn() - - Cyborgs have no range restriction on attack_robot(), because it is basically an AI click. - However, they do have a range restriction on item use, so they cannot do without the - adjacency code. -*/ - -/mob/living/silicon/robot/ClickOn(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 || IsParalyzed() || 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 && 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) - A.BorgAltClick(src) - -/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden - CtrlShiftClick(user) - -/obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlShiftClick() - else - ..() - - -/atom/proc/BorgShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden - ShiftClick(user) - -/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIShiftClick() - else - ..() - - -/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overridden - CtrlClick(user) - -/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/atom/proc/BorgAltClick(mob/living/silicon/robot/user) - AltClick(user) - return - -/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIAltClick() - else - ..() - -/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIAltClick() - else - ..() - -/* - As with AI, these are not used in click code, - because the code for robots is specific, not generic. - - If you would like to add advanced features to robot - clicks, you can do so here, but you will have to - change attack_robot() above to the proper function -*/ -/mob/living/silicon/robot/UnarmedAttack(atom/A) - A.attack_robot(src) -/mob/living/silicon/robot/RangedAttack(atom/A) - A.attack_robot(src) - -/atom/proc/attack_robot(mob/user) - attack_ai(user) - return +/* + Cyborg ClickOn() + + Cyborgs have no range restriction on attack_robot(), because it is basically an AI click. + However, they do have a range restriction on item use, so they cannot do without the + adjacency code. +*/ + +/mob/living/silicon/robot/ClickOn(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 || IsParalyzed() || 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 && 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) + A.BorgAltClick(src) + +/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden + CtrlShiftClick(user) + +/obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlShiftClick() + else + ..() + + +/atom/proc/BorgShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden + ShiftClick(user) + +/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIShiftClick() + else + ..() + + +/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overridden + CtrlClick(user) + +/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/atom/proc/BorgAltClick(mob/living/silicon/robot/user) + AltClick(user) + return + +/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIAltClick() + else + ..() + +/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIAltClick() + else + ..() + +/* + As with AI, these are not used in click code, + because the code for robots is specific, not generic. + + If you would like to add advanced features to robot + clicks, you can do so here, but you will have to + change attack_robot() above to the proper function +*/ +/mob/living/silicon/robot/UnarmedAttack(atom/A) + A.attack_robot(src) +/mob/living/silicon/robot/RangedAttack(atom/A) + A.attack_robot(src) + +/atom/proc/attack_robot(mob/user) + attack_ai(user) + return diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index c64f0d7a3eac..29bd0b51784d 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -1,146 +1,146 @@ -/* - 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) - ..() - -/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) + ..() + +/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 9e93caa30124..ebbd0eacf476 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -1,179 +1,179 @@ -/* - 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:16, SOUTH:5" //borgs -#define ui_borg_lamp "CENTER-4:16, SOUTH:5" //borgs -#define ui_borg_thrusters "CENTER-5:16, 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 "CENTER+4:21,SOUTH+1:5" //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_above_movement "EAST-2:26,SOUTH+1:7" -#define ui_above_intent "EAST-3:24, SOUTH+1:7" -#define ui_movi "EAST-2:26,SOUTH:5" -#define ui_acti "EAST-3:24,SOUTH:5" -#define ui_zonesel "EAST-1:28,SOUTH:5" -#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12) -#define ui_crafting "EAST-4:22,SOUTH:5" -#define ui_building "EAST-4:22,SOUTH:21" -#define ui_language_menu "EAST-4:6,SOUTH:21" - -#define ui_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:17" -#define ui_mood "EAST-1:28,CENTER-3:10" - -//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 - -//slimes -#define ui_slime_health "EAST,CENTER:15" //same as borgs, constructs 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" - -// pAI - -#define ui_pai_software "SOUTH:6,WEST" -#define ui_pai_shell "SOUTH:6,WEST+1" -#define ui_pai_chassis "SOUTH:6,WEST+2" -#define ui_pai_rest "SOUTH:6,WEST+3" -#define ui_pai_light "SOUTH:6,WEST+4" -#define ui_pai_newscaster "SOUTH:6,WEST+5" -#define ui_pai_host_monitor "SOUTH:6,WEST+6" -#define ui_pai_crew_manifest "SOUTH:6,WEST+7" -#define ui_pai_state_laws "SOUTH:6,WEST+8" -#define ui_pai_pda_send "SOUTH:6,WEST+9" -#define ui_pai_pda_log "SOUTH:6,WEST+10" -#define ui_pai_take_picture "SOUTH:6,WEST+12" -#define ui_pai_view_images "SOUTH:6,WEST+13" - -//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:16, SOUTH:5" //borgs +#define ui_borg_lamp "CENTER-4:16, SOUTH:5" //borgs +#define ui_borg_thrusters "CENTER-5:16, 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 "CENTER+4:21,SOUTH+1:5" //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_above_movement "EAST-2:26,SOUTH+1:7" +#define ui_above_intent "EAST-3:24, SOUTH+1:7" +#define ui_movi "EAST-2:26,SOUTH:5" +#define ui_acti "EAST-3:24,SOUTH:5" +#define ui_zonesel "EAST-1:28,SOUTH:5" +#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12) +#define ui_crafting "EAST-4:22,SOUTH:5" +#define ui_building "EAST-4:22,SOUTH:21" +#define ui_language_menu "EAST-4:6,SOUTH:21" + +#define ui_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:17" +#define ui_mood "EAST-1:28,CENTER-3:10" + +//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 + +//slimes +#define ui_slime_health "EAST,CENTER:15" //same as borgs, constructs 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" + +// pAI + +#define ui_pai_software "SOUTH:6,WEST" +#define ui_pai_shell "SOUTH:6,WEST+1" +#define ui_pai_chassis "SOUTH:6,WEST+2" +#define ui_pai_rest "SOUTH:6,WEST+3" +#define ui_pai_light "SOUTH:6,WEST+4" +#define ui_pai_newscaster "SOUTH:6,WEST+5" +#define ui_pai_host_monitor "SOUTH:6,WEST+6" +#define ui_pai_crew_manifest "SOUTH:6,WEST+7" +#define ui_pai_state_laws "SOUTH:6,WEST+8" +#define ui_pai_pda_send "SOUTH:6,WEST+9" +#define ui_pai_pda_log "SOUTH:6,WEST+10" +#define ui_pai_take_picture "SOUTH:6,WEST+12" +#define ui_pai_view_images "SOUTH:6,WEST+13" + +//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/alien.dm b/code/_onclick/hud/alien.dm index 92b091494225..5ca0bd3c827c 100644 --- a/code/_onclick/hud/alien.dm +++ b/code/_onclick/hud/alien.dm @@ -1,123 +1,123 @@ -/obj/screen/alien - icon = 'icons/mob/screen_alien.dmi' - -/obj/screen/alien/leap - name = "toggle leap" - icon_state = "leap_off" - -/obj/screen/alien/leap/Click() - if(isalienhunter(usr)) - var/mob/living/carbon/alien/humanoid/hunter/AH = usr - AH.toggle_leap() - -/obj/screen/alien/plasma_display - icon = 'icons/mob/screen_gen.dmi' - icon_state = "power_display2" - name = "plasma stored" - screen_loc = ui_alienplasmadisplay - - -/obj/screen/alien/alien_queen_finder - icon = 'icons/mob/screen_alien.dmi' - icon_state = "queen_finder" - name = "queen sense" - desc = "Allows you to sense the general direction of your Queen." - screen_loc = ui_alien_queen_finder - -/datum/hud/alien - ui_style = 'icons/mob/screen_alien.dmi' - -/datum/hud/alien/New(mob/living/carbon/alien/humanoid/owner) - ..() - - var/obj/screen/using - -//equippable shit - -//hands - build_hand_slots() - -//begin buttons - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_1" - using.screen_loc = ui_swaphand_position(owner,1) - 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) - static_inventory += using - - using = new /obj/screen/act_intent/alien() - using.icon_state = mymob.a_intent - static_inventory += using - action_intent = using - - if(isalienhunter(mymob)) - var/mob/living/carbon/alien/humanoid/hunter/H = mymob - H.leap_icon = new /obj/screen/alien/leap() - H.leap_icon.screen_loc = ui_alien_storage_r - static_inventory += H.leap_icon - - using = new/obj/screen/language_menu - using.screen_loc = ui_alien_language_menu - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - static_inventory += using - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_above_movement - hotkeybuttons += using - - throw_icon = new /obj/screen/throw_catch() - throw_icon.icon = ui_style - throw_icon.screen_loc = ui_drop_throw - hotkeybuttons += throw_icon - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon(mymob) - pull_icon.screen_loc = ui_above_movement - static_inventory += pull_icon - -//begin indicators - - healths = new /obj/screen/healths/alien() - infodisplay += healths - - alien_plasma_display = new /obj/screen/alien/plasma_display() - infodisplay += alien_plasma_display - - if(!isalienqueen(mymob)) - alien_queen_finder = new /obj/screen/alien/alien_queen_finder - infodisplay += alien_queen_finder - - zone_select = new /obj/screen/zone_sel/alien() - zone_select.update_icon(mymob) - 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() - -/datum/hud/alien/persistent_inventory_update() - if(!mymob) - return - var/mob/living/carbon/alien/humanoid/H = mymob - if(hud_version != HUD_STYLE_NOHUD) - for(var/obj/item/I in H.held_items) - I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) - H.client.screen += I - else - for(var/obj/item/I in H.held_items) - I.screen_loc = null - H.client.screen -= I +/obj/screen/alien + icon = 'icons/mob/screen_alien.dmi' + +/obj/screen/alien/leap + name = "toggle leap" + icon_state = "leap_off" + +/obj/screen/alien/leap/Click() + if(isalienhunter(usr)) + var/mob/living/carbon/alien/humanoid/hunter/AH = usr + AH.toggle_leap() + +/obj/screen/alien/plasma_display + icon = 'icons/mob/screen_gen.dmi' + icon_state = "power_display2" + name = "plasma stored" + screen_loc = ui_alienplasmadisplay + + +/obj/screen/alien/alien_queen_finder + icon = 'icons/mob/screen_alien.dmi' + icon_state = "queen_finder" + name = "queen sense" + desc = "Allows you to sense the general direction of your Queen." + screen_loc = ui_alien_queen_finder + +/datum/hud/alien + ui_style = 'icons/mob/screen_alien.dmi' + +/datum/hud/alien/New(mob/living/carbon/alien/humanoid/owner) + ..() + + var/obj/screen/using + +//equippable shit + +//hands + build_hand_slots() + +//begin buttons + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_1" + using.screen_loc = ui_swaphand_position(owner,1) + 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) + static_inventory += using + + using = new /obj/screen/act_intent/alien() + using.icon_state = mymob.a_intent + static_inventory += using + action_intent = using + + if(isalienhunter(mymob)) + var/mob/living/carbon/alien/humanoid/hunter/H = mymob + H.leap_icon = new /obj/screen/alien/leap() + H.leap_icon.screen_loc = ui_alien_storage_r + static_inventory += H.leap_icon + + using = new/obj/screen/language_menu + using.screen_loc = ui_alien_language_menu + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + static_inventory += using + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_above_movement + hotkeybuttons += using + + throw_icon = new /obj/screen/throw_catch() + throw_icon.icon = ui_style + throw_icon.screen_loc = ui_drop_throw + hotkeybuttons += throw_icon + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon(mymob) + pull_icon.screen_loc = ui_above_movement + static_inventory += pull_icon + +//begin indicators + + healths = new /obj/screen/healths/alien() + infodisplay += healths + + alien_plasma_display = new /obj/screen/alien/plasma_display() + infodisplay += alien_plasma_display + + if(!isalienqueen(mymob)) + alien_queen_finder = new /obj/screen/alien/alien_queen_finder + infodisplay += alien_queen_finder + + zone_select = new /obj/screen/zone_sel/alien() + zone_select.update_icon(mymob) + 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() + +/datum/hud/alien/persistent_inventory_update() + if(!mymob) + return + var/mob/living/carbon/alien/humanoid/H = mymob + if(hud_version != HUD_STYLE_NOHUD) + for(var/obj/item/I in H.held_items) + I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) + H.client.screen += I + else + for(var/obj/item/I in H.held_items) + I.screen_loc = null + H.client.screen -= I diff --git a/code/_onclick/hud/alien_larva.dm b/code/_onclick/hud/alien_larva.dm index 8281fe8e9969..9a637269852c 100644 --- a/code/_onclick/hud/alien_larva.dm +++ b/code/_onclick/hud/alien_larva.dm @@ -1,30 +1,30 @@ -/datum/hud/larva - ui_style = 'icons/mob/screen_alien.dmi' - -/datum/hud/larva/New(mob/owner) - ..() - var/obj/screen/using - - using = new /obj/screen/act_intent/alien() - using.icon_state = mymob.a_intent - static_inventory += using - action_intent = using - - healths = new /obj/screen/healths/alien() - infodisplay += healths - - alien_queen_finder = new /obj/screen/alien/alien_queen_finder() - infodisplay += alien_queen_finder - pull_icon = new /obj/screen/pull() - pull_icon.icon = 'icons/mob/screen_alien.dmi' - pull_icon.update_icon(mymob) - pull_icon.screen_loc = ui_above_movement - hotkeybuttons += pull_icon - - using = new/obj/screen/language_menu - using.screen_loc = ui_alien_language_menu - static_inventory += using - - zone_select = new /obj/screen/zone_sel/alien() - zone_select.update_icon(mymob) - static_inventory += zone_select +/datum/hud/larva + ui_style = 'icons/mob/screen_alien.dmi' + +/datum/hud/larva/New(mob/owner) + ..() + var/obj/screen/using + + using = new /obj/screen/act_intent/alien() + using.icon_state = mymob.a_intent + static_inventory += using + action_intent = using + + healths = new /obj/screen/healths/alien() + infodisplay += healths + + alien_queen_finder = new /obj/screen/alien/alien_queen_finder() + infodisplay += alien_queen_finder + pull_icon = new /obj/screen/pull() + pull_icon.icon = 'icons/mob/screen_alien.dmi' + pull_icon.update_icon(mymob) + pull_icon.screen_loc = ui_above_movement + hotkeybuttons += pull_icon + + using = new/obj/screen/language_menu + using.screen_loc = ui_alien_language_menu + static_inventory += using + + zone_select = new /obj/screen/zone_sel/alien() + zone_select.update_icon(mymob) + static_inventory += zone_select diff --git a/code/_onclick/hud/credits.dm b/code/_onclick/hud/credits.dm index 702aa1cdcb8e..2cd0ddedaa16 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 9155fcd033c9..10317125b4d0 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -1,186 +1,186 @@ - -/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/hive_mc - icon_state = "hive_mc" - layer = CURSE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/hive_eyes - icon_state = "hive_eyes" - layer = CURSE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/impaired - icon_state = "impairedoverlay" - -/obj/screen/fullscreen/flash - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "flash" - -/obj/screen/fullscreen/flash/static - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "noise" - -/obj/screen/fullscreen/high - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "druggy" - -/obj/screen/fullscreen/color_vision - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "flash" - alpha = 80 - -/obj/screen/fullscreen/color_vision/green - color = "#00ff00" - -/obj/screen/fullscreen/color_vision/red - color = "#ff0000" - -/obj/screen/fullscreen/color_vision/blue - color = "#0000ff" - -/obj/screen/fullscreen/lighting_backdrop - icon = 'icons/mob/screen_gen.dmi' - icon_state = "flash" - transform = matrix(200, 0, 0, 0, 200, 0) - plane = LIGHTING_PLANE - blend_mode = BLEND_OVERLAY - show_when_dead = TRUE - -//Provides darkness to the back of the lighting plane -/obj/screen/fullscreen/lighting_backdrop/lit - invisibility = INVISIBILITY_LIGHTING - layer = BACKGROUND_LAYER+21 - color = "#000" - show_when_dead = TRUE - -//Provides whiteness in case you don't see lights so everything is still visible -/obj/screen/fullscreen/lighting_backdrop/unlit - layer = BACKGROUND_LAYER+20 - show_when_dead = TRUE - -/obj/screen/fullscreen/see_through_darkness - icon_state = "nightvision" - plane = LIGHTING_PLANE - layer = LIGHTING_LAYER - blend_mode = BLEND_ADD - show_when_dead = TRUE + +/mob + 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/hive_mc + icon_state = "hive_mc" + layer = CURSE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/hive_eyes + icon_state = "hive_eyes" + layer = CURSE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/impaired + icon_state = "impairedoverlay" + +/obj/screen/fullscreen/flash + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "flash" + +/obj/screen/fullscreen/flash/static + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "noise" + +/obj/screen/fullscreen/high + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "druggy" + +/obj/screen/fullscreen/color_vision + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "flash" + alpha = 80 + +/obj/screen/fullscreen/color_vision/green + color = "#00ff00" + +/obj/screen/fullscreen/color_vision/red + color = "#ff0000" + +/obj/screen/fullscreen/color_vision/blue + color = "#0000ff" + +/obj/screen/fullscreen/lighting_backdrop + icon = 'icons/mob/screen_gen.dmi' + icon_state = "flash" + transform = matrix(200, 0, 0, 0, 200, 0) + plane = LIGHTING_PLANE + blend_mode = BLEND_OVERLAY + show_when_dead = TRUE + +//Provides darkness to the back of the lighting plane +/obj/screen/fullscreen/lighting_backdrop/lit + invisibility = INVISIBILITY_LIGHTING + layer = BACKGROUND_LAYER+21 + color = "#000" + show_when_dead = TRUE + +//Provides whiteness in case you don't see lights so everything is still visible +/obj/screen/fullscreen/lighting_backdrop/unlit + layer = BACKGROUND_LAYER+20 + show_when_dead = TRUE + +/obj/screen/fullscreen/see_through_darkness + icon_state = "nightvision" + plane = LIGHTING_PLANE + layer = LIGHTING_LAYER + blend_mode = BLEND_ADD + show_when_dead = TRUE diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm index 2b70392b395e..87c590bb91e6 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -1,89 +1,89 @@ -/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 - static_inventory += using - - using = new /obj/screen/ghost/orbit() - using.screen_loc = ui_ghost_orbit - static_inventory += using - - using = new /obj/screen/ghost/reenter_corpse() - using.screen_loc = ui_ghost_reenter_corpse - static_inventory += using - - using = new /obj/screen/ghost/teleport() - using.screen_loc = ui_ghost_teleport - static_inventory += using - - using = new /obj/screen/ghost/pai() - using.screen_loc = ui_ghost_pai - static_inventory += using - - using = new /obj/screen/language_menu - using.icon = ui_style - 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 + static_inventory += using + + using = new /obj/screen/ghost/orbit() + using.screen_loc = ui_ghost_orbit + static_inventory += using + + using = new /obj/screen/ghost/reenter_corpse() + using.screen_loc = ui_ghost_reenter_corpse + static_inventory += using + + using = new /obj/screen/ghost/teleport() + using.screen_loc = ui_ghost_teleport + static_inventory += using + + using = new /obj/screen/ghost/pai() + using.screen_loc = ui_ghost_pai + static_inventory += using + + using = new /obj/screen/language_menu + using.icon = ui_style + 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 1d1836716bc0..f054cb0c8eca 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -1,294 +1,294 @@ -/* - 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 - -/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 + +/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 99c774642762..d813845b6949 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -1,442 +1,442 @@ -/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/widescreen_layout = FALSE - if(owner.client?.prefs?.widescreenpref) - widescreen_layout = TRUE - - var/obj/screen/using - var/obj/screen/inventory/inv_box - - using = new/obj/screen/language_menu - using.icon = ui_style - if(!widescreen_layout) - using.screen_loc = UI_BOXLANG - static_inventory += using - - using = new /obj/screen/area_creator - using.icon = ui_style - if(!widescreen_layout) - using.screen_loc = UI_BOXAREA - static_inventory += using - - action_intent = new /obj/screen/act_intent/segmented - action_intent.icon_state = mymob.a_intent - 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 - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - 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) - 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) - 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_above_intent - hotkeybuttons += using - - using = new /obj/screen/human/toggle() - using.icon = ui_style - using.screen_loc = ui_inventory - static_inventory += using - - using = new /obj/screen/human/equip() - using.icon = ui_style - using.screen_loc = ui_equip_position(mymob) - 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 - hotkeybuttons += throw_icon - - rest_icon = new /obj/screen/rest() - rest_icon.icon = ui_style - rest_icon.screen_loc = ui_above_movement - static_inventory += rest_icon - - internals = new /obj/screen/internals() - infodisplay += internals - - healths = new /obj/screen/healths() - infodisplay += healths - - healthdoll = new /obj/screen/healthdoll() - infodisplay += healthdoll - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon(mymob) - pull_icon.screen_loc = ui_above_intent - static_inventory += pull_icon - - lingchemdisplay = new /obj/screen/ling/chems() - infodisplay += lingchemdisplay - - lingstingdisplay = new /obj/screen/ling/sting() - infodisplay += lingstingdisplay - - devilsouldisplay = new /obj/screen/devil/soul_counter - infodisplay += devilsouldisplay - - zone_select = new /obj/screen/zone_sel() - zone_select.icon = ui_style - zone_select.update_icon(mymob) - 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/widescreen_layout = FALSE + if(owner.client?.prefs?.widescreenpref) + widescreen_layout = TRUE + + var/obj/screen/using + var/obj/screen/inventory/inv_box + + using = new/obj/screen/language_menu + using.icon = ui_style + if(!widescreen_layout) + using.screen_loc = UI_BOXLANG + static_inventory += using + + using = new /obj/screen/area_creator + using.icon = ui_style + if(!widescreen_layout) + using.screen_loc = UI_BOXAREA + static_inventory += using + + action_intent = new /obj/screen/act_intent/segmented + action_intent.icon_state = mymob.a_intent + 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 + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + 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) + 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) + 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_above_intent + hotkeybuttons += using + + using = new /obj/screen/human/toggle() + using.icon = ui_style + using.screen_loc = ui_inventory + static_inventory += using + + using = new /obj/screen/human/equip() + using.icon = ui_style + using.screen_loc = ui_equip_position(mymob) + 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 + hotkeybuttons += throw_icon + + rest_icon = new /obj/screen/rest() + rest_icon.icon = ui_style + rest_icon.screen_loc = ui_above_movement + static_inventory += rest_icon + + internals = new /obj/screen/internals() + infodisplay += internals + + healths = new /obj/screen/healths() + infodisplay += healths + + healthdoll = new /obj/screen/healthdoll() + infodisplay += healthdoll + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon(mymob) + pull_icon.screen_loc = ui_above_intent + static_inventory += pull_icon + + lingchemdisplay = new /obj/screen/ling/chems() + infodisplay += lingchemdisplay + + lingstingdisplay = new /obj/screen/ling/sting() + infodisplay += lingstingdisplay + + devilsouldisplay = new /obj/screen/devil/soul_counter + infodisplay += devilsouldisplay + + zone_select = new /obj/screen/zone_sel() + zone_select.icon = ui_style + zone_select.update_icon(mymob) + 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 d3be91abe94d..1d0855d32cbd 100644 --- a/code/_onclick/hud/monkey.dm +++ b/code/_onclick/hud/monkey.dm @@ -1,151 +1,151 @@ -/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 - 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 - static_inventory += using - - using = new/obj/screen/language_menu - using.icon = ui_style - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - 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) - 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) - 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 - hotkeybuttons += throw_icon - - internals = new /obj/screen/internals() - infodisplay += internals - - healths = new /obj/screen/healths() - infodisplay += healths - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon(mymob) - pull_icon.screen_loc = ui_above_movement - static_inventory += pull_icon - - lingchemdisplay = new /obj/screen/ling/chems() - infodisplay += lingchemdisplay - - lingstingdisplay = new /obj/screen/ling/sting() - infodisplay += lingstingdisplay - - - zone_select = new /obj/screen/zone_sel() - zone_select.icon = ui_style - zone_select.update_icon(mymob) - static_inventory += zone_select - - mymob.client.screen = list() - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_above_intent - 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 + 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 + static_inventory += using + + using = new/obj/screen/language_menu + using.icon = ui_style + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + 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) + 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) + 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 + hotkeybuttons += throw_icon + + internals = new /obj/screen/internals() + infodisplay += internals + + healths = new /obj/screen/healths() + infodisplay += healths + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon(mymob) + pull_icon.screen_loc = ui_above_movement + static_inventory += pull_icon + + lingchemdisplay = new /obj/screen/ling/chems() + infodisplay += lingchemdisplay + + lingstingdisplay = new /obj/screen/ling/sting() + infodisplay += lingstingdisplay + + + zone_select = new /obj/screen/zone_sel() + zone_select.icon = ui_style + zone_select.update_icon(mymob) + static_inventory += zone_select + + mymob.client.screen = list() + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_above_intent + 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 54a81efddd12..ee18f1476807 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -1,272 +1,272 @@ -/obj/screen/robot - icon = 'icons/mob/screen_cyborg.dmi' - -/obj/screen/robot/module - name = "cyborg module" - icon_state = "nomod" - -/obj/screen/robot/Click() - if(isobserver(usr)) - return 1 - -/obj/screen/robot/module/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - if(R.module.type != /obj/item/robot_module) - R.hud_used.toggle_show_robot_modules() - return 1 - R.pick_module() - -/obj/screen/robot/module1 - name = "module1" - icon_state = "inv1" - -/obj/screen/robot/module1/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(1) - -/obj/screen/robot/module2 - name = "module2" - icon_state = "inv2" - -/obj/screen/robot/module2/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(2) - -/obj/screen/robot/module3 - name = "module3" - icon_state = "inv3" - -/obj/screen/robot/module3/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(3) - -/obj/screen/robot/radio - name = "radio" - icon_state = "radio" - -/obj/screen/robot/radio/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.radio.interact(R) - -/obj/screen/robot/store - name = "store" - icon_state = "store" - -/obj/screen/robot/store/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.uneq_active() - -/obj/screen/robot/lamp - name = "headlamp" - icon_state = "lamp0" - -/obj/screen/robot/lamp/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.control_headlamp() - -/obj/screen/robot/thrusters - name = "ion thrusters" - icon_state = "ionpulse0" - -/obj/screen/robot/thrusters/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_ionpulse() - -/datum/hud/robot - ui_style = 'icons/mob/screen_cyborg.dmi' - -/datum/hud/robot/New(mob/owner) - ..() - var/mob/living/silicon/robot/mymobR = mymob - var/obj/screen/using - - using = new/obj/screen/language_menu - using.screen_loc = ui_borg_language_menu - static_inventory += using - -//Radio - using = new /obj/screen/robot/radio() - using.screen_loc = ui_borg_radio - static_inventory += using - -//Module select - using = new /obj/screen/robot/module1() - using.screen_loc = ui_inv1 - static_inventory += using - mymobR.inv1 = using - - using = new /obj/screen/robot/module2() - using.screen_loc = ui_inv2 - static_inventory += using - mymobR.inv2 = using - - using = new /obj/screen/robot/module3() - using.screen_loc = ui_inv3 - 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 - static_inventory += using - - using = new /obj/screen/ai/image_view() - using.screen_loc = ui_borg_album - static_inventory += using - -//Sec/Med HUDs - using = new /obj/screen/ai/sensors() - using.screen_loc = ui_borg_sensor - static_inventory += using - -//Headlamp control - using = new /obj/screen/robot/lamp() - using.screen_loc = ui_borg_lamp - static_inventory += using - mymobR.lamp_button = using - -//Thrusters - using = new /obj/screen/robot/thrusters() - using.screen_loc = ui_borg_thrusters - static_inventory += using - mymobR.thruster_button = using - -//Intent - action_intent = new /obj/screen/act_intent/robot() - action_intent.icon_state = mymob.a_intent - static_inventory += action_intent - -//Health - healths = new /obj/screen/healths/robot() - 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.screen_loc = ui_borg_store - - pull_icon = new /obj/screen/pull() - pull_icon.icon = 'icons/mob/screen_cyborg.dmi' - pull_icon.update_icon(mymob) - pull_icon.screen_loc = ui_borg_pull - hotkeybuttons += pull_icon - - - zone_select = new /obj/screen/zone_sel/robot() - zone_select.update_icon(mymob) - static_inventory += zone_select - - -/datum/hud/proc/toggle_show_robot_modules() - if(!iscyborg(mymob)) - return - - var/mob/living/silicon/robot/R = mymob - - R.shown_robot_modules = !R.shown_robot_modules - update_robot_modules_display() - -/datum/hud/proc/update_robot_modules_display(mob/viewer) - if(!iscyborg(mymob)) - return - - var/mob/living/silicon/robot/R = mymob - - var/mob/screenmob = viewer || R - - if(!R.module) - return - - if(!R.client) - return - - if(R.shown_robot_modules && screenmob.hud_used.hud_shown) - //Modules display is shown - screenmob.client.screen += module_store_icon //"store" icon - - if(!R.module.modules) - to_chat(usr, "Selected module has no modules to select") - return - - if(!R.robot_modules_background) - return - - var/display_rows = CEILING(length(R.module.get_inactive_modules()) / 8, 1) - R.robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7" - screenmob.client.screen += R.robot_modules_background - - var/x = -4 //Start at CENTER-4,SOUTH+1 - var/y = 1 - - for(var/atom/movable/A in R.module.get_inactive_modules()) - //Module is not currently active - screenmob.client.screen += A - if(x < 0) - A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7" - else - A.screen_loc = "CENTER+[x]:16,SOUTH+[y]:7" - A.layer = ABOVE_HUD_LAYER - A.plane = ABOVE_HUD_PLANE - - x++ - if(x == 4) - x = -4 - y++ - - else - //Modules display is hidden - screenmob.client.screen -= module_store_icon //"store" icon - - for(var/atom/A in R.module.get_inactive_modules()) - //Module is not currently active - screenmob.client.screen -= A - R.shown_robot_modules = 0 - screenmob.client.screen -= R.robot_modules_background - -/datum/hud/robot/persistent_inventory_update(mob/viewer) - if(!mymob) - return - var/mob/living/silicon/robot/R = mymob - - var/mob/screenmob = viewer || R - - if(screenmob.hud_used) - if(screenmob.hud_used.hud_shown) - for(var/i in 1 to R.held_items.len) - var/obj/item/I = R.held_items[i] - if(I) - switch(i) - if(1) - I.screen_loc = ui_inv1 - if(2) - I.screen_loc = ui_inv2 - if(3) - I.screen_loc = ui_inv3 - else - return - screenmob.client.screen += I - else - for(var/obj/item/I in R.held_items) - screenmob.client.screen -= I +/obj/screen/robot + icon = 'icons/mob/screen_cyborg.dmi' + +/obj/screen/robot/module + name = "cyborg module" + icon_state = "nomod" + +/obj/screen/robot/Click() + if(isobserver(usr)) + return 1 + +/obj/screen/robot/module/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + if(R.module.type != /obj/item/robot_module) + R.hud_used.toggle_show_robot_modules() + return 1 + R.pick_module() + +/obj/screen/robot/module1 + name = "module1" + icon_state = "inv1" + +/obj/screen/robot/module1/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(1) + +/obj/screen/robot/module2 + name = "module2" + icon_state = "inv2" + +/obj/screen/robot/module2/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(2) + +/obj/screen/robot/module3 + name = "module3" + icon_state = "inv3" + +/obj/screen/robot/module3/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(3) + +/obj/screen/robot/radio + name = "radio" + icon_state = "radio" + +/obj/screen/robot/radio/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.radio.interact(R) + +/obj/screen/robot/store + name = "store" + icon_state = "store" + +/obj/screen/robot/store/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.uneq_active() + +/obj/screen/robot/lamp + name = "headlamp" + icon_state = "lamp0" + +/obj/screen/robot/lamp/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.control_headlamp() + +/obj/screen/robot/thrusters + name = "ion thrusters" + icon_state = "ionpulse0" + +/obj/screen/robot/thrusters/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_ionpulse() + +/datum/hud/robot + ui_style = 'icons/mob/screen_cyborg.dmi' + +/datum/hud/robot/New(mob/owner) + ..() + var/mob/living/silicon/robot/mymobR = mymob + var/obj/screen/using + + using = new/obj/screen/language_menu + using.screen_loc = ui_borg_language_menu + static_inventory += using + +//Radio + using = new /obj/screen/robot/radio() + using.screen_loc = ui_borg_radio + static_inventory += using + +//Module select + using = new /obj/screen/robot/module1() + using.screen_loc = ui_inv1 + static_inventory += using + mymobR.inv1 = using + + using = new /obj/screen/robot/module2() + using.screen_loc = ui_inv2 + static_inventory += using + mymobR.inv2 = using + + using = new /obj/screen/robot/module3() + using.screen_loc = ui_inv3 + 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 + static_inventory += using + + using = new /obj/screen/ai/image_view() + using.screen_loc = ui_borg_album + static_inventory += using + +//Sec/Med HUDs + using = new /obj/screen/ai/sensors() + using.screen_loc = ui_borg_sensor + static_inventory += using + +//Headlamp control + using = new /obj/screen/robot/lamp() + using.screen_loc = ui_borg_lamp + static_inventory += using + mymobR.lamp_button = using + +//Thrusters + using = new /obj/screen/robot/thrusters() + using.screen_loc = ui_borg_thrusters + static_inventory += using + mymobR.thruster_button = using + +//Intent + action_intent = new /obj/screen/act_intent/robot() + action_intent.icon_state = mymob.a_intent + static_inventory += action_intent + +//Health + healths = new /obj/screen/healths/robot() + 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.screen_loc = ui_borg_store + + pull_icon = new /obj/screen/pull() + pull_icon.icon = 'icons/mob/screen_cyborg.dmi' + pull_icon.update_icon(mymob) + pull_icon.screen_loc = ui_borg_pull + hotkeybuttons += pull_icon + + + zone_select = new /obj/screen/zone_sel/robot() + zone_select.update_icon(mymob) + 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 5e1ac0512cfb..884d74effae6 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -1,717 +1,717 @@ -/* - 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/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 1 - - if(usr.incapacitated()) - return 1 - if(ismecha(usr.loc)) // stops inventory actions in a mech - return 1 - - if(hud && 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 1 - -/obj/screen/inventory/MouseEntered() - ..() - add_overlays() - -/obj/screen/inventory/MouseExited() - ..() - cut_overlay(object_overlays) - object_overlays.Cut() - -/obj/screen/inventory/update_icon() - if(!icon_empty) - icon_empty = icon_state - - if(hud && 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(hud && user && slot_id) - 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_overlays() - - if(hud && hud.mymob) - 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 - if(world.time <= usr.next_move) - return 1 - if(usr.incapacitated() || isobserver(usr)) - return 1 - if (ismecha(usr.loc)) // stops inventory actions in a mech - return 1 - - if(hud.mymob.active_hand_index == held_index) - var/obj/item/I = hud.mymob.get_active_held_item() - if(I) - I.Click(location, control, params) - else - hud.mymob.swap_hand(held_index) - return 1 - -/obj/screen/close - name = "close" - layer = ABOVE_HUD_LAYER - plane = ABOVE_HUD_PLANE - icon_state = "backpack_close" - -/obj/screen/close/Initialize(mapload, new_master) - . = ..() - master = new_master - -/obj/screen/close/Click() - var/datum/component/storage/S = master - S.hide_from(usr) - return TRUE - -/obj/screen/drop - name = "drop" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_drop" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/drop/Click() - if(usr.stat == CONSCIOUS) - usr.dropItemToGround(usr.get_active_held_item()) - -/obj/screen/act_intent - name = "intent" - icon_state = "help" - screen_loc = ui_acti - -/obj/screen/act_intent/Click(location, control, params) - usr.a_intent_change(INTENT_HOTKEY_RIGHT) - -/obj/screen/act_intent/segmented/Click(location, control, params) - if(usr.client.prefs.toggles & INTENT_STYLE) - var/_x = text2num(params2list(params)["icon-x"]) - var/_y = text2num(params2list(params)["icon-y"]) - - if(_x<=16 && _y<=16) - usr.a_intent_change(INTENT_HARM) - - else if(_x<=16 && _y>=17) - usr.a_intent_change(INTENT_HELP) - - else if(_x>=17 && _y<=16) - usr.a_intent_change(INTENT_GRAB) - - else if(_x>=17 && _y>=17) - usr.a_intent_change(INTENT_DISARM) - else - return ..() - -/obj/screen/act_intent/alien - icon = 'icons/mob/screen_alien.dmi' - screen_loc = ui_movi - -/obj/screen/act_intent/robot - icon = 'icons/mob/screen_cyborg.dmi' - screen_loc = ui_borg_intents - -/obj/screen/internals - name = "toggle internals" - icon_state = "internal0" - screen_loc = ui_internal - -/obj/screen/internals/Click() - if(!iscarbon(usr)) - return - var/mob/living/carbon/C = usr - if(C.incapacitated()) - return - - if(C.internal) - C.internal = null - to_chat(C, "You are no longer running on internals.") - icon_state = "internal0" - else - if(!C.getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - if(!istype(C.wear_mask, /obj/item/clothing/mask)) - to_chat(C, "You are not wearing an internals mask!") - return 1 - else - var/obj/item/clothing/mask/M = C.wear_mask - if(M.mask_adjusted) // if mask on face but pushed down - M.adjustmask(C) // adjust it back - if( !(M.clothing_flags & MASKINTERNALS) ) - to_chat(C, "You are not wearing an internals mask!") - return - - var/obj/item/I = C.is_holding_item_of_type(/obj/item/tank) - if(I) - to_chat(C, "You are now running on internals from [I] in your [C.get_held_index_name(C.get_held_index_of_item(I))].") - C.internal = I - else if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(istype(H.s_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.s_store] on your [H.wear_suit.name].") - H.internal = H.s_store - else if(istype(H.belt, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.belt] on your belt.") - H.internal = H.belt - else if(istype(H.l_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.l_store] in your left pocket.") - H.internal = H.l_store - else if(istype(H.r_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.r_store] in your right pocket.") - H.internal = H.r_store - - //Separate so CO2 jetpacks are a little less cumbersome. - if(!C.internal && istype(C.back, /obj/item/tank)) - to_chat(C, "You are now running on internals from [C.back] on your back.") - C.internal = C.back - - if(C.internal) - icon_state = "internal1" - else - to_chat(C, "You don't have an oxygen tank!") - return - C.update_action_buttons_icon() - -/obj/screen/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(mob/user) - if(!user && hud) - user = hud.mymob - if(!user) - return - switch(user.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(mob/mymob) - if(!mymob) - return - if(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(mob/mymob) - if(!isliving(mymob)) - return - var/mob/living/L = mymob - if(!L.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/selecting = BODY_ZONE_CHEST - 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(isobserver(user)) - return - - if(choice != selecting) - selecting = choice - update_icon(usr) - return 1 - -/obj/screen/zone_sel/update_icon(mob/user) - cut_overlays() - add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[selecting]")) - user.zone_selected = selecting - -/obj/screen/zone_sel/alien - icon = 'icons/mob/screen_alien.dmi' - -/obj/screen/zone_sel/alien/update_icon(mob/user) - cut_overlays() - add_overlay(mutable_appearance('icons/mob/screen_alien.dmi', "[selecting]")) - user.zone_selected = selecting - -/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/slime - icon = 'icons/mob/screen_slime.dmi' - icon_state = "slime_health0" - screen_loc = ui_slime_health - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healthdoll - name = "health doll" - screen_loc = ui_healthdoll - -/obj/screen/healthdoll/Click() - if (ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.check_self_for_injuries() - -/obj/screen/mood - name = "mood" - icon_state = "mood5" - screen_loc = ui_mood - -/obj/screen/sanity - name = "sanity" - icon_state = "sanity3" - 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/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 1 + + if(usr.incapacitated()) + return 1 + if(ismecha(usr.loc)) // stops inventory actions in a mech + return 1 + + if(hud && 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 1 + +/obj/screen/inventory/MouseEntered() + ..() + add_overlays() + +/obj/screen/inventory/MouseExited() + ..() + cut_overlay(object_overlays) + object_overlays.Cut() + +/obj/screen/inventory/update_icon() + if(!icon_empty) + icon_empty = icon_state + + if(hud && 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(hud && user && slot_id) + 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_overlays() + + if(hud && hud.mymob) + 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 + if(world.time <= usr.next_move) + return 1 + if(usr.incapacitated() || isobserver(usr)) + return 1 + if (ismecha(usr.loc)) // stops inventory actions in a mech + return 1 + + if(hud.mymob.active_hand_index == held_index) + var/obj/item/I = hud.mymob.get_active_held_item() + if(I) + I.Click(location, control, params) + else + hud.mymob.swap_hand(held_index) + return 1 + +/obj/screen/close + name = "close" + layer = ABOVE_HUD_LAYER + plane = ABOVE_HUD_PLANE + icon_state = "backpack_close" + +/obj/screen/close/Initialize(mapload, new_master) + . = ..() + master = new_master + +/obj/screen/close/Click() + var/datum/component/storage/S = master + S.hide_from(usr) + return TRUE + +/obj/screen/drop + name = "drop" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_drop" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/drop/Click() + if(usr.stat == CONSCIOUS) + usr.dropItemToGround(usr.get_active_held_item()) + +/obj/screen/act_intent + name = "intent" + icon_state = "help" + screen_loc = ui_acti + +/obj/screen/act_intent/Click(location, control, params) + usr.a_intent_change(INTENT_HOTKEY_RIGHT) + +/obj/screen/act_intent/segmented/Click(location, control, params) + if(usr.client.prefs.toggles & INTENT_STYLE) + var/_x = text2num(params2list(params)["icon-x"]) + var/_y = text2num(params2list(params)["icon-y"]) + + if(_x<=16 && _y<=16) + usr.a_intent_change(INTENT_HARM) + + else if(_x<=16 && _y>=17) + usr.a_intent_change(INTENT_HELP) + + else if(_x>=17 && _y<=16) + usr.a_intent_change(INTENT_GRAB) + + else if(_x>=17 && _y>=17) + usr.a_intent_change(INTENT_DISARM) + else + return ..() + +/obj/screen/act_intent/alien + icon = 'icons/mob/screen_alien.dmi' + screen_loc = ui_movi + +/obj/screen/act_intent/robot + icon = 'icons/mob/screen_cyborg.dmi' + screen_loc = ui_borg_intents + +/obj/screen/internals + name = "toggle internals" + icon_state = "internal0" + screen_loc = ui_internal + +/obj/screen/internals/Click() + if(!iscarbon(usr)) + return + var/mob/living/carbon/C = usr + if(C.incapacitated()) + return + + if(C.internal) + C.internal = null + to_chat(C, "You are no longer running on internals.") + icon_state = "internal0" + else + if(!C.getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + if(!istype(C.wear_mask, /obj/item/clothing/mask)) + to_chat(C, "You are not wearing an internals mask!") + return 1 + else + var/obj/item/clothing/mask/M = C.wear_mask + if(M.mask_adjusted) // if mask on face but pushed down + M.adjustmask(C) // adjust it back + if( !(M.clothing_flags & MASKINTERNALS) ) + to_chat(C, "You are not wearing an internals mask!") + return + + var/obj/item/I = C.is_holding_item_of_type(/obj/item/tank) + if(I) + to_chat(C, "You are now running on internals from [I] in your [C.get_held_index_name(C.get_held_index_of_item(I))].") + C.internal = I + else if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(istype(H.s_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.s_store] on your [H.wear_suit.name].") + H.internal = H.s_store + else if(istype(H.belt, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.belt] on your belt.") + H.internal = H.belt + else if(istype(H.l_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.l_store] in your left pocket.") + H.internal = H.l_store + else if(istype(H.r_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.r_store] in your right pocket.") + H.internal = H.r_store + + //Separate so CO2 jetpacks are a little less cumbersome. + if(!C.internal && istype(C.back, /obj/item/tank)) + to_chat(C, "You are now running on internals from [C.back] on your back.") + C.internal = C.back + + if(C.internal) + icon_state = "internal1" + else + to_chat(C, "You don't have an oxygen tank!") + return + C.update_action_buttons_icon() + +/obj/screen/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(mob/user) + if(!user && hud) + user = hud.mymob + if(!user) + return + switch(user.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(mob/mymob) + if(!mymob) + return + if(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(mob/mymob) + if(!isliving(mymob)) + return + var/mob/living/L = mymob + if(!L.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/selecting = BODY_ZONE_CHEST + 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(isobserver(user)) + return + + if(choice != selecting) + selecting = choice + update_icon(usr) + return 1 + +/obj/screen/zone_sel/update_icon(mob/user) + cut_overlays() + add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[selecting]")) + user.zone_selected = selecting + +/obj/screen/zone_sel/alien + icon = 'icons/mob/screen_alien.dmi' + +/obj/screen/zone_sel/alien/update_icon(mob/user) + cut_overlays() + add_overlay(mutable_appearance('icons/mob/screen_alien.dmi', "[selecting]")) + user.zone_selected = selecting + +/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/slime + icon = 'icons/mob/screen_slime.dmi' + icon_state = "slime_health0" + screen_loc = ui_slime_health + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healthdoll + name = "health doll" + screen_loc = ui_healthdoll + +/obj/screen/healthdoll/Click() + if (ishuman(usr)) + var/mob/living/carbon/human/H = usr + H.check_self_for_injuries() + +/obj/screen/mood + name = "mood" + icon_state = "mood5" + screen_loc = ui_mood + +/obj/screen/sanity + name = "sanity" + icon_state = "sanity3" + 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 47e966f3ff4b..75aea985f82f 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -1,152 +1,152 @@ - -/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) - if(user.a_intent == INTENT_HARM && stat == DEAD && (butcher_results || guaranteed_butcher_results)) //can we butcher it? - var/datum/component/butchering/butchering = I.GetComponent(/datum/component/butchering) - if(butchering && butchering.butchering_enabled) - to_chat(user, "You begin to butcher [src]...") - playsound(loc, butchering.butcher_sound, 50, TRUE, -1) - if(do_mob(user, src, butchering.speed) && Adjacent(I)) - butchering.Butcher(user, src) - return 1 - else if(I.is_sharp() && !butchering) //give sharp objects butchering functionality, for consistency - I.AddComponent(/datum/component/butchering, 80 * I.toolspeed) - attackby(I, user, params) //call the attackby again to refresh and do the butchering check again - return - return I.attack(src, user) - - -/obj/item/proc/attack(mob/living/M, mob/living/user) - SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, M, user) - SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, M, user) - if(item_flags & NOBLUDGEON) - return - - if(force && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm other living beings!") - return - - if(!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) - - -//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 - 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) - send_item_attack_message(I, user) - if(I.force) - apply_damage(I.force, I.damtype) - if(I.damtype == BRUTE) - if(prob(33)) - I.add_mob_blood(src) - var/turf/location = get_turf(src) - add_splatter_floor(location) - if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood - user.add_mob_blood(src) - if(ishuman(user)) - var/mob/living/carbon/human/dirtyboy = user - dirtyboy.adjust_hygiene(-10) - 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) - return 1 + +/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) + if(user.a_intent == INTENT_HARM && stat == DEAD && (butcher_results || guaranteed_butcher_results)) //can we butcher it? + var/datum/component/butchering/butchering = I.GetComponent(/datum/component/butchering) + if(butchering && butchering.butchering_enabled) + to_chat(user, "You begin to butcher [src]...") + playsound(loc, butchering.butcher_sound, 50, TRUE, -1) + if(do_mob(user, src, butchering.speed) && Adjacent(I)) + butchering.Butcher(user, src) + return 1 + else if(I.is_sharp() && !butchering) //give sharp objects butchering functionality, for consistency + I.AddComponent(/datum/component/butchering, 80 * I.toolspeed) + attackby(I, user, params) //call the attackby again to refresh and do the butchering check again + return + return I.attack(src, user) + + +/obj/item/proc/attack(mob/living/M, mob/living/user) + SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, M, user) + SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, M, user) + if(item_flags & NOBLUDGEON) + return + + if(force && HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm other living beings!") + return + + if(!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) + + +//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 + 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) + send_item_attack_message(I, user) + if(I.force) + apply_damage(I.force, I.damtype) + if(I.damtype == BRUTE) + if(prob(33)) + I.add_mob_blood(src) + var/turf/location = get_turf(src) + add_splatter_floor(location) + if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood + user.add_mob_blood(src) + if(ishuman(user)) + var/mob/living/carbon/human/dirtyboy = user + dirtyboy.adjust_hygiene(-10) + 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) + return 1 diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index 7effa262eeb0..ac9975ee1d8b 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -1,86 +1,86 @@ -/mob/dead/observer/DblClickOn(atom/A, params) - if(check_click_intercept(params, A)) - return - - if(can_reenter_corpse && mind && mind.current) - if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it - reenter_corpse() // (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"]) - AltClickNoInteract(src, A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(world.time <= next_move) - return - // You are responsible for checking config.ghost_interaction when you override this function - // Not all of them require checking, see below - A.attack_ghost(src) - -// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you -/atom/proc/attack_ghost(mob/dead/observer/user) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_NO_ATTACK_HAND) - return TRUE - if(user.client) - if(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(atom/A, params) + if(check_click_intercept(params, A)) + return + + if(can_reenter_corpse && mind && mind.current) + if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it + reenter_corpse() // (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"]) + AltClickNoInteract(src, A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(world.time <= next_move) + return + // You are responsible for checking config.ghost_interaction when you override this function + // Not all of them require checking, see below + A.attack_ghost(src) + +// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you +/atom/proc/attack_ghost(mob/dead/observer/user) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_NO_ATTACK_HAND) + return TRUE + if(user.client) + if(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 62fbb66e05dd..7e8dbfa86068 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -1,258 +1,258 @@ -/* - 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(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 - - for(var/datum/mutation/human/HM in dna.mutations) - HM.on_ranged_attack(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(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 + + for(var/datum/mutation/human/HM in dna.mutations) + HM.on_ranged_attack(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/_onclick/overmind.dm b/code/_onclick/overmind.dm index 1e4ffd99adbc..8ace273dd8fe 100644 --- a/code/_onclick/overmind.dm +++ b/code/_onclick/overmind.dm @@ -1,35 +1,35 @@ -// Blob Overmind Controls - - -/mob/camera/blob/ClickOn(var/atom/A, var/params) //Expand blob - var/list/modifiers = params2list(params) - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - var/turf/T = get_turf(A) - if(T) - expand_blob(T) - -/mob/camera/blob/MiddleClickOn(atom/A) //Rally spores - var/turf/T = get_turf(A) - if(T) - rally_spores(T) - -/mob/camera/blob/CtrlClickOn(atom/A) //Create a shield - var/turf/T = get_turf(A) - if(T) - create_shield(T) - -/mob/camera/blob/AltClickOn(atom/A) //Remove a blob - var/turf/T = get_turf(A) - if(T) +// Blob Overmind Controls + + +/mob/camera/blob/ClickOn(var/atom/A, var/params) //Expand blob + var/list/modifiers = params2list(params) + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + var/turf/T = get_turf(A) + if(T) + expand_blob(T) + +/mob/camera/blob/MiddleClickOn(atom/A) //Rally spores + var/turf/T = get_turf(A) + if(T) + rally_spores(T) + +/mob/camera/blob/CtrlClickOn(atom/A) //Create a shield + var/turf/T = get_turf(A) + if(T) + create_shield(T) + +/mob/camera/blob/AltClickOn(atom/A) //Remove a blob + var/turf/T = get_turf(A) + if(T) remove_blob(T) \ No newline at end of file diff --git a/code/controllers/configuration/config_entry.dm b/code/controllers/configuration/config_entry.dm index 49ff1c8d498d..9e7a46e91e06 100644 --- a/code/controllers/configuration/config_entry.dm +++ b/code/controllers/configuration/config_entry.dm @@ -1,196 +1,196 @@ -#define VALUE_MODE_NUM 0 -#define VALUE_MODE_TEXT 1 -#define VALUE_MODE_FLAG 2 - -#define KEY_MODE_TEXT 0 -#define KEY_MODE_TYPE 1 - -/datum/config_entry - var/name //read-only, this is determined by the last portion of the derived entry type - var/config_entry_value - var/default //read-only, just set value directly - - var/resident_file //the file which this was loaded from, if any - var/modified = FALSE //set to TRUE if the default has been overridden by a config entry - - var/deprecated_by //the /datum/config_entry type that supercedes this one - - var/protection = NONE - var/abstract_type = /datum/config_entry //do not instantiate if type matches this - - var/vv_VAS = TRUE //Force validate and set on VV. VAS proccall guard will run regardless. - - var/dupes_allowed = FALSE - -/datum/config_entry/New() - if(type == abstract_type) - CRASH("Abstract config entry [type] instatiated!") - name = lowertext(type2top(type)) - if(islist(config_entry_value)) - var/list/L = config_entry_value - default = L.Copy() - else - default = config_entry_value - -/datum/config_entry/Destroy() - config.RemoveEntry(src) - return ..() - -/datum/config_entry/can_vv_get(var_name) - . = ..() - if(var_name == NAMEOF(src, config_entry_value) || var_name == NAMEOF(src, default)) - . &= !(protection & CONFIG_ENTRY_HIDDEN) - -/datum/config_entry/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list(NAMEOF(src, name), NAMEOF(src, vv_VAS), NAMEOF(src, default), NAMEOF(src, resident_file), NAMEOF(src, protection), NAMEOF(src, abstract_type), NAMEOF(src, modified), NAMEOF(src, dupes_allowed)) - if(var_name == NAMEOF(src, config_entry_value)) - if(protection & CONFIG_ENTRY_LOCKED) - return FALSE - if(vv_VAS) - . = ValidateAndSet("[var_value]") - if(.) - datum_flags |= DF_VAR_EDITED - return - else - return ..() - if(var_name in banned_edits) - return FALSE - return ..() - -/datum/config_entry/proc/VASProcCallGuard(str_val) - . = !((protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "ValidateAndSet" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - if(!.) - log_admin_private("Config set of [type] to [str_val] attempted by [key_name(usr)]") - -/datum/config_entry/proc/ValidateAndSet(str_val) - VASProcCallGuard(str_val) - CRASH("Invalid config entry type!") - -/datum/config_entry/proc/ValidateListEntry(key_name, key_value) - return TRUE - -/datum/config_entry/proc/DeprecationUpdate(value) - return - -/datum/config_entry/string - config_entry_value = "" - abstract_type = /datum/config_entry/string - var/auto_trim = TRUE - -/datum/config_entry/string/vv_edit_var(var_name, var_value) - return var_name != "auto_trim" && ..() - -/datum/config_entry/string/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - config_entry_value = auto_trim ? trim(str_val) : str_val - return TRUE - -/datum/config_entry/number - config_entry_value = 0 - abstract_type = /datum/config_entry/number - var/integer = TRUE - var/max_val = INFINITY - var/min_val = -INFINITY - -/datum/config_entry/number/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - var/temp = text2num(trim(str_val)) - if(!isnull(temp)) - config_entry_value = CLAMP(integer ? round(temp) : temp, min_val, max_val) - if(config_entry_value != temp && !(datum_flags & DF_VAR_EDITED)) - log_config("Changing [name] from [temp] to [config_entry_value]!") - return TRUE - return FALSE - -/datum/config_entry/number/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list("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 = " " - -/datum/config_entry/keyed_list/New() - . = ..() - if(isnull(key_mode) || isnull(value_mode)) - CRASH("Keyed list of type [type] created with null key or value mode!") - -/datum/config_entry/keyed_list/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - - str_val = trim(str_val) - var/key_pos = findtext(str_val, splitter) - var/key_name = null - var/key_value = null - - if(key_pos || value_mode == VALUE_MODE_FLAG) - key_name = lowertext(copytext(str_val, 1, key_pos)) - 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(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" && ..() +#define VALUE_MODE_NUM 0 +#define VALUE_MODE_TEXT 1 +#define VALUE_MODE_FLAG 2 + +#define KEY_MODE_TEXT 0 +#define KEY_MODE_TYPE 1 + +/datum/config_entry + var/name //read-only, this is determined by the last portion of the derived entry type + var/config_entry_value + var/default //read-only, just set value directly + + var/resident_file //the file which this was loaded from, if any + var/modified = FALSE //set to TRUE if the default has been overridden by a config entry + + var/deprecated_by //the /datum/config_entry type that supercedes this one + + var/protection = NONE + var/abstract_type = /datum/config_entry //do not instantiate if type matches this + + var/vv_VAS = TRUE //Force validate and set on VV. VAS proccall guard will run regardless. + + var/dupes_allowed = FALSE + +/datum/config_entry/New() + if(type == abstract_type) + CRASH("Abstract config entry [type] instatiated!") + name = lowertext(type2top(type)) + if(islist(config_entry_value)) + var/list/L = config_entry_value + default = L.Copy() + else + default = config_entry_value + +/datum/config_entry/Destroy() + config.RemoveEntry(src) + return ..() + +/datum/config_entry/can_vv_get(var_name) + . = ..() + if(var_name == NAMEOF(src, config_entry_value) || var_name == NAMEOF(src, default)) + . &= !(protection & CONFIG_ENTRY_HIDDEN) + +/datum/config_entry/vv_edit_var(var_name, var_value) + var/static/list/banned_edits = list(NAMEOF(src, name), NAMEOF(src, vv_VAS), NAMEOF(src, default), NAMEOF(src, resident_file), NAMEOF(src, protection), NAMEOF(src, abstract_type), NAMEOF(src, modified), NAMEOF(src, dupes_allowed)) + if(var_name == NAMEOF(src, config_entry_value)) + if(protection & CONFIG_ENTRY_LOCKED) + return FALSE + if(vv_VAS) + . = ValidateAndSet("[var_value]") + if(.) + datum_flags |= DF_VAR_EDITED + return + else + return ..() + if(var_name in banned_edits) + return FALSE + return ..() + +/datum/config_entry/proc/VASProcCallGuard(str_val) + . = !((protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "ValidateAndSet" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + if(!.) + log_admin_private("Config set of [type] to [str_val] attempted by [key_name(usr)]") + +/datum/config_entry/proc/ValidateAndSet(str_val) + VASProcCallGuard(str_val) + CRASH("Invalid config entry type!") + +/datum/config_entry/proc/ValidateListEntry(key_name, key_value) + return TRUE + +/datum/config_entry/proc/DeprecationUpdate(value) + return + +/datum/config_entry/string + config_entry_value = "" + abstract_type = /datum/config_entry/string + var/auto_trim = TRUE + +/datum/config_entry/string/vv_edit_var(var_name, var_value) + return var_name != "auto_trim" && ..() + +/datum/config_entry/string/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + config_entry_value = auto_trim ? trim(str_val) : str_val + return TRUE + +/datum/config_entry/number + config_entry_value = 0 + abstract_type = /datum/config_entry/number + var/integer = TRUE + var/max_val = INFINITY + var/min_val = -INFINITY + +/datum/config_entry/number/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + var/temp = text2num(trim(str_val)) + if(!isnull(temp)) + config_entry_value = CLAMP(integer ? round(temp) : temp, min_val, max_val) + if(config_entry_value != temp && !(datum_flags & DF_VAR_EDITED)) + log_config("Changing [name] from [temp] to [config_entry_value]!") + return TRUE + return FALSE + +/datum/config_entry/number/vv_edit_var(var_name, var_value) + var/static/list/banned_edits = list("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 = " " + +/datum/config_entry/keyed_list/New() + . = ..() + if(isnull(key_mode) || isnull(value_mode)) + CRASH("Keyed list of type [type] created with null key or value mode!") + +/datum/config_entry/keyed_list/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + + str_val = trim(str_val) + var/key_pos = findtext(str_val, splitter) + var/key_name = null + var/key_value = null + + if(key_pos || value_mode == VALUE_MODE_FLAG) + key_name = lowertext(copytext(str_val, 1, key_pos)) + 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(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" && ..() diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index ae0f78e096d2..c3edbab7d416 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -1,397 +1,397 @@ -/datum/controller/configuration - name = "Configuration" - - var/directory = "config" - - var/warned_deprecated_configs = FALSE - var/hiding_entries_by_type = TRUE //Set for readability, admins can set this to FALSE if they want to debug it - var/list/entries - var/list/entries_by_type - - var/list/maplist - var/datum/map_config/defaultmap - - var/list/modes // allowed modes - var/list/gamemode_cache - var/list/votable_modes // votable modes - var/list/mode_names - var/list/mode_reports - var/list/mode_false_report_weight - - var/motd - var/policy - -/datum/controller/configuration/proc/admin_reload() - if(IsAdminAdvancedProcCall()) - return - log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") - message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") - full_wipe() - Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - -/datum/controller/configuration/proc/Load(_directory) - if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything. - return - if(_directory) - directory = _directory - if(entries) - CRASH("/datum/controller/configuration/Load() called more than once!") - InitEntries() - LoadModes() - if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1) - var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt") - for(var/I in legacy_configs) - if(fexists("[directory]/[I]")) - log_config("No $include directives found in config.txt! Loading legacy [legacy_configs.Join("/")] files...") - for(var/J in legacy_configs) - LoadEntries(J) - break - loadmaplist(CONFIG_MAPS_FILE) - LoadMOTD() - LoadPolicy() - -/datum/controller/configuration/proc/full_wipe() - if(IsAdminAdvancedProcCall()) - return - entries_by_type.Cut() - QDEL_LIST_ASSOC_VAL(entries) - entries = null - QDEL_LIST_ASSOC_VAL(maplist) - maplist = null - QDEL_NULL(defaultmap) - -/datum/controller/configuration/Destroy() - full_wipe() - config = null - - return ..() - -/datum/controller/configuration/proc/InitEntries() - var/list/_entries = list() - entries = _entries - var/list/_entries_by_type = list() - entries_by_type = _entries_by_type - - for(var/I in typesof(/datum/config_entry)) //typesof is faster in this case - var/datum/config_entry/E = I - if(initial(E.abstract_type) == I) - continue - E = new I - var/esname = E.name - var/datum/config_entry/test = _entries[esname] - if(test) - log_config("Error: [test.type] has the same name as [E.type]: [esname]! Not initializing [E.type]!") - qdel(E) - continue - _entries[esname] = E - _entries_by_type[I] = E - -/datum/controller/configuration/proc/RemoveEntry(datum/config_entry/CE) - entries -= CE.name - entries_by_type -= CE.type - -/datum/controller/configuration/proc/LoadEntries(filename, list/stack = list()) - if(IsAdminAdvancedProcCall()) - return - - var/filename_to_test = world.system_type == MS_WINDOWS ? lowertext(filename) : filename - if(filename_to_test in stack) - log_config("Warning: Config recursion detected ([english_list(stack)]), breaking!") - return - stack = stack + filename_to_test - - log_config("Loading config file [filename]...") - var/list/lines = world.file2list("[directory]/[filename]") - var/list/_entries = entries - for(var/L in lines) - L = trim(L) - if(!L) - continue - - var/firstchar = 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) - if(!validated) - log_config("Failed to validate setting \"[value]\" for [entry]") - else - if(E.modified && !E.dupes_allowed) - log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.") - - E.resident_file = filename - - if(validated) - E.modified = TRUE - - ++. - -/datum/controller/configuration/can_vv_get(var_name) - return (var_name != NAMEOF(src, entries_by_type) || !hiding_entries_by_type) && ..() - -/datum/controller/configuration/vv_edit_var(var_name, var_value) - var/list/banned_edits = list(NAMEOF(src, entries_by_type), NAMEOF(src, entries), NAMEOF(src, directory)) - return !(var_name in banned_edits) && ..() - -/datum/controller/configuration/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Edit", src) - stat("[name]:", statclick) - -/datum/controller/configuration/proc/Get(entry_type) - var/datum/config_entry/E = entry_type - var/entry_is_abstract = initial(E.abstract_type) == entry_type - if(entry_is_abstract) - CRASH("Tried to retrieve an abstract config_entry: [entry_type]") - E = entries_by_type[entry_type] - if(!E) - CRASH("Missing config entry for [entry_type]!") - if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]") - return - return E.config_entry_value - -/datum/controller/configuration/proc/Set(entry_type, new_val) - var/datum/config_entry/E = entry_type - var/entry_is_abstract = initial(E.abstract_type) == entry_type - if(entry_is_abstract) - CRASH("Tried to set an abstract config_entry: [entry_type]") - E = entries_by_type[entry_type] - if(!E) - CRASH("Missing config entry for [entry_type]!") - if((E.protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Set" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - log_admin_private("Config rewrite of [entry_type] to [new_val] attempted by [key_name(usr)]") - return - return E.ValidateAndSet("[new_val]") - -/datum/controller/configuration/proc/LoadModes() - gamemode_cache = typecacheof(/datum/game_mode, TRUE) - modes = list() - mode_names = list() - mode_reports = list() - mode_false_report_weight = list() - votable_modes = list() - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - for(var/T in gamemode_cache) - // I wish I didn't have to instance the game modes in order to look up - // their information, but it is the only way (at least that I know of). - var/datum/game_mode/M = new T() - - if(M.config_tag) - if(!(M.config_tag in modes)) // ensure each mode is added only once - modes += M.config_tag - mode_names[M.config_tag] = M.name - probabilities[M.config_tag] = M.probability - mode_reports[M.report_type] = M.generate_report() - if(probabilities[M.config_tag]>0) - mode_false_report_weight[M.report_type] = M.false_report_weight - else - //"impossible" modes will still falsly show up occasionally, else they'll stick out like a sore thumb if an admin decides to force a disabled gamemode. - mode_false_report_weight[M.report_type] = min(1, M.false_report_weight) - if(M.votable) - votable_modes += M.config_tag - qdel(M) - votable_modes += "secret" - -/datum/controller/configuration/proc/LoadMOTD() - motd = file2text("[directory]/motd.txt") - var/tm_info = GLOB.revdata.GetTestMergeInfo() - if(motd || tm_info) - motd = motd ? "[motd]
    [tm_info]" : tm_info -/* -Policy file should be a json file with a single object. -Value is raw html. - -Possible keywords : -Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker -Mob types : /mob/living/simple_animal/hostile/carp -Antagonist types : /datum/antagonist/highlander -Species types : /datum/species/lizard -special keywords defined in _DEFINES/admin.dm - -Example config: -{ - "Assistant" : "Don't kill everyone", - "/datum/antagonist/highlander" : "Kill everyone", - "Ash Walker" : "Kill all spacemans" -} - -*/ -/datum/controller/configuration/proc/LoadPolicy() - policy = list() - var/rawpolicy = file2text("[directory]/policy.json") - if(rawpolicy) - var/parsed = json_decode(rawpolicy) - if(!parsed) - log_config("JSON parsing failure for policy.json") - else - policy = parsed - -/datum/controller/configuration/proc/loadmaplist(filename) - log_config("Loading config file [filename]...") - filename = "[directory]/[filename]" - var/list/Lines = world.file2list(filename) - - var/datum/map_config/currentmap = null - for(var/t in Lines) - if(!t) - continue - - t = trim(t) - if(length(t) == 0) - continue - else if(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 ("votable") - currentmap.votable = TRUE - if ("endmap") - LAZYINITLIST(maplist) - maplist[currentmap.map_name] = currentmap - currentmap = null - if ("disabled") - currentmap = null - else - log_config("Unknown command in map vote config: '[command]'") - - -/datum/controller/configuration/proc/pick_mode(mode_name) - // I wish I didn't have to instance the game modes in order to look up - // their information, but it is the only way (at least that I know of). - // ^ This guy didn't try hard enough - for(var/T in gamemode_cache) - var/datum/game_mode/M = T - var/ct = initial(M.config_tag) - if(ct && ct == mode_name) - return new T - return new /datum/game_mode/extended() - -/datum/controller/configuration/proc/get_runnable_modes() - var/list/datum/game_mode/runnable_modes = new - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) - var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) - var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust) - for(var/T in gamemode_cache) - var/datum/game_mode/M = new T() - if(!(M.config_tag in modes)) - qdel(M) - continue - if(probabilities[M.config_tag]<=0) - qdel(M) - continue - if(min_pop[M.config_tag]) - M.required_players = min_pop[M.config_tag] - if(max_pop[M.config_tag]) - M.maximum_players = max_pop[M.config_tag] - if(M.can_start()) - var/final_weight = probabilities[M.config_tag] - if(SSpersistence.saved_modes.len == 3 && repeated_mode_adjust.len == 3) - var/recent_round = min(SSpersistence.saved_modes.Find(M.config_tag),3) - var/adjustment = 0 - while(recent_round) - adjustment += repeated_mode_adjust[recent_round] - recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0) - final_weight *= ((100-adjustment)/100) - runnable_modes[M] = final_weight - return runnable_modes - -/datum/controller/configuration/proc/get_runnable_midround_modes(crew) - var/list/datum/game_mode/runnable_modes = new - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) - var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) - for(var/T in (gamemode_cache - SSticker.mode.type)) - var/datum/game_mode/M = new T() - if(!(M.config_tag in modes)) - qdel(M) - continue - if(probabilities[M.config_tag]<=0) - qdel(M) - continue - if(min_pop[M.config_tag]) - M.required_players = min_pop[M.config_tag] - if(max_pop[M.config_tag]) - M.maximum_players = max_pop[M.config_tag] - if(M.required_players <= crew) - if(M.maximum_players >= 0 && M.maximum_players < crew) - continue - runnable_modes[M] = probabilities[M.config_tag] - return runnable_modes +/datum/controller/configuration + name = "Configuration" + + var/directory = "config" + + var/warned_deprecated_configs = FALSE + var/hiding_entries_by_type = TRUE //Set for readability, admins can set this to FALSE if they want to debug it + var/list/entries + var/list/entries_by_type + + var/list/maplist + var/datum/map_config/defaultmap + + var/list/modes // allowed modes + var/list/gamemode_cache + var/list/votable_modes // votable modes + var/list/mode_names + var/list/mode_reports + var/list/mode_false_report_weight + + var/motd + var/policy + +/datum/controller/configuration/proc/admin_reload() + if(IsAdminAdvancedProcCall()) + return + log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") + message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") + full_wipe() + Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + +/datum/controller/configuration/proc/Load(_directory) + if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything. + return + if(_directory) + directory = _directory + if(entries) + CRASH("/datum/controller/configuration/Load() called more than once!") + InitEntries() + LoadModes() + if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1) + var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt") + for(var/I in legacy_configs) + if(fexists("[directory]/[I]")) + log_config("No $include directives found in config.txt! Loading legacy [legacy_configs.Join("/")] files...") + for(var/J in legacy_configs) + LoadEntries(J) + break + loadmaplist(CONFIG_MAPS_FILE) + LoadMOTD() + LoadPolicy() + +/datum/controller/configuration/proc/full_wipe() + if(IsAdminAdvancedProcCall()) + return + entries_by_type.Cut() + QDEL_LIST_ASSOC_VAL(entries) + entries = null + QDEL_LIST_ASSOC_VAL(maplist) + maplist = null + QDEL_NULL(defaultmap) + +/datum/controller/configuration/Destroy() + full_wipe() + config = null + + return ..() + +/datum/controller/configuration/proc/InitEntries() + var/list/_entries = list() + entries = _entries + var/list/_entries_by_type = list() + entries_by_type = _entries_by_type + + for(var/I in typesof(/datum/config_entry)) //typesof is faster in this case + var/datum/config_entry/E = I + if(initial(E.abstract_type) == I) + continue + E = new I + var/esname = E.name + var/datum/config_entry/test = _entries[esname] + if(test) + log_config("Error: [test.type] has the same name as [E.type]: [esname]! Not initializing [E.type]!") + qdel(E) + continue + _entries[esname] = E + _entries_by_type[I] = E + +/datum/controller/configuration/proc/RemoveEntry(datum/config_entry/CE) + entries -= CE.name + entries_by_type -= CE.type + +/datum/controller/configuration/proc/LoadEntries(filename, list/stack = list()) + if(IsAdminAdvancedProcCall()) + return + + var/filename_to_test = world.system_type == MS_WINDOWS ? lowertext(filename) : filename + if(filename_to_test in stack) + log_config("Warning: Config recursion detected ([english_list(stack)]), breaking!") + return + stack = stack + filename_to_test + + log_config("Loading config file [filename]...") + var/list/lines = world.file2list("[directory]/[filename]") + var/list/_entries = entries + for(var/L in lines) + L = trim(L) + if(!L) + continue + + var/firstchar = 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) + if(!validated) + log_config("Failed to validate setting \"[value]\" for [entry]") + else + if(E.modified && !E.dupes_allowed) + log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.") + + E.resident_file = filename + + if(validated) + E.modified = TRUE + + ++. + +/datum/controller/configuration/can_vv_get(var_name) + return (var_name != NAMEOF(src, entries_by_type) || !hiding_entries_by_type) && ..() + +/datum/controller/configuration/vv_edit_var(var_name, var_value) + var/list/banned_edits = list(NAMEOF(src, entries_by_type), NAMEOF(src, entries), NAMEOF(src, directory)) + return !(var_name in banned_edits) && ..() + +/datum/controller/configuration/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Edit", src) + stat("[name]:", statclick) + +/datum/controller/configuration/proc/Get(entry_type) + var/datum/config_entry/E = entry_type + var/entry_is_abstract = initial(E.abstract_type) == entry_type + if(entry_is_abstract) + CRASH("Tried to retrieve an abstract config_entry: [entry_type]") + E = entries_by_type[entry_type] + if(!E) + CRASH("Missing config entry for [entry_type]!") + if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]") + return + return E.config_entry_value + +/datum/controller/configuration/proc/Set(entry_type, new_val) + var/datum/config_entry/E = entry_type + var/entry_is_abstract = initial(E.abstract_type) == entry_type + if(entry_is_abstract) + CRASH("Tried to set an abstract config_entry: [entry_type]") + E = entries_by_type[entry_type] + if(!E) + CRASH("Missing config entry for [entry_type]!") + if((E.protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Set" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + log_admin_private("Config rewrite of [entry_type] to [new_val] attempted by [key_name(usr)]") + return + return E.ValidateAndSet("[new_val]") + +/datum/controller/configuration/proc/LoadModes() + gamemode_cache = typecacheof(/datum/game_mode, TRUE) + modes = list() + mode_names = list() + mode_reports = list() + mode_false_report_weight = list() + votable_modes = list() + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + for(var/T in gamemode_cache) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + var/datum/game_mode/M = new T() + + if(M.config_tag) + if(!(M.config_tag in modes)) // ensure each mode is added only once + modes += M.config_tag + mode_names[M.config_tag] = M.name + probabilities[M.config_tag] = M.probability + mode_reports[M.report_type] = M.generate_report() + if(probabilities[M.config_tag]>0) + mode_false_report_weight[M.report_type] = M.false_report_weight + else + //"impossible" modes will still falsly show up occasionally, else they'll stick out like a sore thumb if an admin decides to force a disabled gamemode. + mode_false_report_weight[M.report_type] = min(1, M.false_report_weight) + if(M.votable) + votable_modes += M.config_tag + qdel(M) + votable_modes += "secret" + +/datum/controller/configuration/proc/LoadMOTD() + motd = file2text("[directory]/motd.txt") + var/tm_info = GLOB.revdata.GetTestMergeInfo() + if(motd || tm_info) + motd = motd ? "[motd]
    [tm_info]" : tm_info +/* +Policy file should be a json file with a single object. +Value is raw html. + +Possible keywords : +Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker +Mob types : /mob/living/simple_animal/hostile/carp +Antagonist types : /datum/antagonist/highlander +Species types : /datum/species/lizard +special keywords defined in _DEFINES/admin.dm + +Example config: +{ + "Assistant" : "Don't kill everyone", + "/datum/antagonist/highlander" : "Kill everyone", + "Ash Walker" : "Kill all spacemans" +} + +*/ +/datum/controller/configuration/proc/LoadPolicy() + policy = list() + var/rawpolicy = file2text("[directory]/policy.json") + if(rawpolicy) + var/parsed = json_decode(rawpolicy) + if(!parsed) + log_config("JSON parsing failure for policy.json") + else + policy = parsed + +/datum/controller/configuration/proc/loadmaplist(filename) + log_config("Loading config file [filename]...") + filename = "[directory]/[filename]" + var/list/Lines = world.file2list(filename) + + var/datum/map_config/currentmap = null + for(var/t in Lines) + if(!t) + continue + + t = trim(t) + if(length(t) == 0) + continue + else if(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 ("votable") + currentmap.votable = TRUE + if ("endmap") + LAZYINITLIST(maplist) + maplist[currentmap.map_name] = currentmap + currentmap = null + if ("disabled") + currentmap = null + else + log_config("Unknown command in map vote config: '[command]'") + + +/datum/controller/configuration/proc/pick_mode(mode_name) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + // ^ This guy didn't try hard enough + for(var/T in gamemode_cache) + var/datum/game_mode/M = T + var/ct = initial(M.config_tag) + if(ct && ct == mode_name) + return new T + return new /datum/game_mode/extended() + +/datum/controller/configuration/proc/get_runnable_modes() + var/list/datum/game_mode/runnable_modes = new + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) + var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) + var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust) + for(var/T in gamemode_cache) + var/datum/game_mode/M = new T() + if(!(M.config_tag in modes)) + qdel(M) + continue + if(probabilities[M.config_tag]<=0) + qdel(M) + continue + if(min_pop[M.config_tag]) + M.required_players = min_pop[M.config_tag] + if(max_pop[M.config_tag]) + M.maximum_players = max_pop[M.config_tag] + if(M.can_start()) + var/final_weight = probabilities[M.config_tag] + if(SSpersistence.saved_modes.len == 3 && repeated_mode_adjust.len == 3) + var/recent_round = min(SSpersistence.saved_modes.Find(M.config_tag),3) + var/adjustment = 0 + while(recent_round) + adjustment += repeated_mode_adjust[recent_round] + recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0) + final_weight *= ((100-adjustment)/100) + runnable_modes[M] = final_weight + return runnable_modes + +/datum/controller/configuration/proc/get_runnable_midround_modes(crew) + var/list/datum/game_mode/runnable_modes = new + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) + var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) + for(var/T in (gamemode_cache - SSticker.mode.type)) + var/datum/game_mode/M = new T() + if(!(M.config_tag in modes)) + qdel(M) + continue + if(probabilities[M.config_tag]<=0) + qdel(M) + continue + if(min_pop[M.config_tag]) + M.required_players = min_pop[M.config_tag] + if(max_pop[M.config_tag]) + M.maximum_players = max_pop[M.config_tag] + if(M.required_players <= crew) + if(M.maximum_players >= 0 && M.maximum_players < crew) + continue + runnable_modes[M] = probabilities[M.config_tag] + return runnable_modes diff --git a/code/controllers/configuration/entries/comms.dm b/code/controllers/configuration/entries/comms.dm index 07bdaa4b97a6..89ceffc33a86 100644 --- a/code/controllers/configuration/entries/comms.dm +++ b/code/controllers/configuration/entries/comms.dm @@ -1,34 +1,34 @@ -/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 - -/datum/config_entry/string/webhook_address - protection = CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/webhook_key +/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 + +/datum/config_entry/string/webhook_address + protection = CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/webhook_key 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 b03419524144..3dd4f4b0bd91 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 6a1e0b1d851c..65971910efaf 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -1,380 +1,380 @@ -/datum/config_entry/number_list/repeated_mode_adjust - -/datum/config_entry/keyed_list/probability - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name) - return key_name in config.modes - -/datum/config_entry/keyed_list/max_pop - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/keyed_list/max_pop/ValidateListEntry(key_name) - return key_name in config.modes - -/datum/config_entry/keyed_list/min_pop - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/keyed_list/min_pop/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/keyed_list/continuous // which roundtypes continue if all antagonists die - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/continuous/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/keyed_list/midround_antag // which roundtypes use the midround antagonist system - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/midround_antag/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/number/damage_multiplier - config_entry_value = 1 - integer = FALSE - -/datum/config_entry/number/minimal_access_threshold //If the number of players is larger than this threshold, minimal access will be turned on. - min_val = 0 - -/datum/config_entry/flag/jobs_have_minimal_access //determines whether jobs use minimal access or expanded access. - -/datum/config_entry/flag/assistants_have_maint_access - -/datum/config_entry/flag/security_has_maint_access - -/datum/config_entry/flag/everyone_has_maint_access - -/datum/config_entry/flag/sec_start_brig //makes sec start in brig instead of dept sec posts - -/datum/config_entry/flag/force_random_names - -/datum/config_entry/flag/humans_need_surnames - -/datum/config_entry/flag/allow_ai // allow ai job - -/datum/config_entry/flag/allow_ai_multicam // allow ai multicamera mode - -/datum/config_entry/flag/disable_human_mood - -/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen. - -/datum/config_entry/flag/disable_peaceborg - -/datum/config_entry/flag/economy //money money money money money money money money money money money money - -/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors - config_entry_value = 6 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/brother_scaling_coeff //how many players per brother team - config_entry_value = 25 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings - config_entry_value = 6 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions - config_entry_value = 8 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/abductor_scaling_coeff //how many players per abductor team - config_entry_value = 15 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/traitor_objectives_amount - config_entry_value = 2 - min_val = 0 - -/datum/config_entry/number/brother_objectives_amount - config_entry_value = 2 - min_val = 0 - -/datum/config_entry/flag/reactionary_explosions //If we use reactionary explosions, explosions that react to walls and doors - -/datum/config_entry/flag/protect_roles_from_antagonist //If security and such can be traitor/cult/other - -/datum/config_entry/flag/protect_assistant_from_antagonist //If assistants can be traitor/cult/other - -/datum/config_entry/flag/enforce_human_authority //If non-human species are barred from joining as a head of staff - -/datum/config_entry/flag/allow_latejoin_antagonists // If late-joining players can be traitor/changeling - -/datum/config_entry/flag/use_antag_rep // see game_options.txt for details - -/datum/config_entry/number/antag_rep_maximum - config_entry_value = 200 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/default_antag_tickets - config_entry_value = 100 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/max_tickets_per_roll - config_entry_value = 100 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/midround_antag_time_check // How late (in minutes you want the midround antag system to stay on, setting this to 0 will disable the system) - config_entry_value = 60 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/midround_antag_life_check // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist - config_entry_value = 0.7 - integer = FALSE - min_val = 0 - max_val = 1 - -/datum/config_entry/number/shuttle_refuel_delay - config_entry_value = 12000 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/show_game_type_odds //if set this allows players to see the odds of each roundtype on the get revision screen - -/datum/config_entry/keyed_list/roundstart_races //races you can play as from the get go. - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/roundstart_no_hard_check // Species contained in this list will not cause existing characters with no-longer-roundstart species set to be resetted to the human race. - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game - -/datum/config_entry/flag/no_summon_guns //No - -/datum/config_entry/flag/no_summon_magic //Fun - -/datum/config_entry/flag/no_summon_events //Allowed - -/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes. - -/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station - config_entry_value = 55 - integer = FALSE - min_val = 30 - -/datum/config_entry/flag/arrivals_shuttle_require_undocked //Require the arrivals shuttle to be undocked before latejoiners can join - -/datum/config_entry/flag/arrivals_shuttle_require_safe_latejoin //Require the arrivals shuttle to be operational in order for latejoiners to join - -/datum/config_entry/string/alert_green - config_entry_value = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." - -/datum/config_entry/string/alert_blue_upto - config_entry_value = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted." - -/datum/config_entry/string/alert_blue_downto - config_entry_value = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed." - -/datum/config_entry/string/alert_red_upto - config_entry_value = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." - -/datum/config_entry/string/alert_red_downto - config_entry_value = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." - -/datum/config_entry/string/alert_delta - config_entry_value = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." - -/datum/config_entry/flag/revival_pod_plants - -/datum/config_entry/flag/revival_cloning - -/datum/config_entry/number/revival_brain_life - config_entry_value = -1 - integer = FALSE - min_val = -1 - -/datum/config_entry/flag/ooc_during_round - -/datum/config_entry/flag/emojis - -/datum/config_entry/keyed_list/multiplicative_movespeed - key_mode = KEY_MODE_TYPE - value_mode = VALUE_MODE_NUM - config_entry_value = list( //DEFAULTS - /mob/living/simple_animal = 1, - /mob/living/silicon/pai = 1, - /mob/living/carbon/alien/humanoid/hunter = -1, - /mob/living/carbon/alien/humanoid/royal/praetorian = 1, - /mob/living/carbon/alien/humanoid/royal/queen = 3 - ) - -/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet() - . = ..() - if(.) - update_config_movespeed_type_lookup(TRUE) - -/datum/config_entry/keyed_list/multiplicative_movespeed/vv_edit_var(var_name, var_value) - . = ..() - if(. && (var_name == NAMEOF(src, config_entry_value))) - update_config_movespeed_type_lookup(TRUE) - -/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs. - abstract_type = /datum/config_entry/number/movedelay - -/datum/config_entry/number/movedelay/ValidateAndSet() - . = ..() - if(.) - update_mob_config_movespeeds() - -/datum/config_entry/number/movedelay/vv_edit_var(var_name, var_value) - . = ..() - if(. && (var_name == NAMEOF(src, config_entry_value))) - update_mob_config_movespeeds() - -/datum/config_entry/number/movedelay/run_delay - integer = FALSE - -/datum/config_entry/number/movedelay/walk_delay - integer = FALSE - -/////////////////////////////////////////////////Outdated move delay -/datum/config_entry/number/outdated_movedelay - deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed - abstract_type = /datum/config_entry/number/outdated_movedelay - integer = FALSE - var/movedelay_type - -/datum/config_entry/number/outdated_movedelay/DeprecationUpdate(value) - return "[movedelay_type] [value]" - -/datum/config_entry/number/outdated_movedelay/human_delay - movedelay_type = /mob/living/carbon/human -/datum/config_entry/number/outdated_movedelay/robot_delay - movedelay_type = /mob/living/silicon/robot -/datum/config_entry/number/outdated_movedelay/monkey_delay - movedelay_type = /mob/living/carbon/monkey -/datum/config_entry/number/outdated_movedelay/alien_delay - movedelay_type = /mob/living/carbon/alien -/datum/config_entry/number/outdated_movedelay/slime_delay - movedelay_type = /mob/living/simple_animal/slime -/datum/config_entry/number/outdated_movedelay/animal_delay - movedelay_type = /mob/living/simple_animal -///////////////////////////////////////////////// - -/datum/config_entry/flag/virtual_reality //Will virtual reality be loaded - -/datum/config_entry/flag/roundstart_away //Will random away mission be loaded. - -/datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled. - config_entry_value = 18000 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/ghost_interaction - -/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death - -/datum/config_entry/flag/silent_ai -/datum/config_entry/flag/silent_borg - -/datum/config_entry/flag/sandbox_autoclose // close the sandbox panel after spawning an item, potentially reducing griff - -/datum/config_entry/number/default_laws //Controls what laws the AI spawns with. - config_entry_value = 0 - min_val = 0 - max_val = 3 - -/datum/config_entry/number/silicon_max_law_amount - config_entry_value = 12 - min_val = 0 - -/datum/config_entry/keyed_list/random_laws - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/law_weight - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - splitter = "," - -/datum/config_entry/number/max_law_len - config_entry_value = 1024 - -/datum/config_entry/number/overflow_cap - config_entry_value = -1 - min_val = -1 - -/datum/config_entry/string/overflow_job - config_entry_value = "Assistant" - -/datum/config_entry/flag/starlight -/datum/config_entry/flag/grey_assistants - -/datum/config_entry/number/lavaland_budget - config_entry_value = 60 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/space_budget - config_entry_value = 16 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set - -/datum/config_entry/number/events_min_time_mul // Multipliers for random events minimal starting time and minimal players amounts - config_entry_value = 1 - min_val = 0 - integer = FALSE - -/datum/config_entry/number/events_min_players_mul - config_entry_value = 1 - min_val = 0 - integer = FALSE - -/datum/config_entry/number/mice_roundstart - config_entry_value = 10 - min_val = 0 - -/datum/config_entry/number/bombcap - config_entry_value = 14 - min_val = 4 - -/datum/config_entry/number/bombcap/ValidateAndSet(str_val) - . = ..() - if(.) - GLOB.MAX_EX_DEVESTATION_RANGE = round(config_entry_value / 4) - GLOB.MAX_EX_HEAVY_RANGE = round(config_entry_value / 2) - GLOB.MAX_EX_LIGHT_RANGE = config_entry_value - GLOB.MAX_EX_FLASH_RANGE = config_entry_value - GLOB.MAX_EX_FLAME_RANGE = config_entry_value - -/datum/config_entry/number/emergency_shuttle_autocall_threshold - min_val = 0 - max_val = 1 - integer = FALSE - -/datum/config_entry/flag/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/number_list/repeated_mode_adjust + +/datum/config_entry/keyed_list/probability + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name) + return key_name in config.modes + +/datum/config_entry/keyed_list/max_pop + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/keyed_list/max_pop/ValidateListEntry(key_name) + return key_name in config.modes + +/datum/config_entry/keyed_list/min_pop + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/keyed_list/min_pop/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/keyed_list/continuous // which roundtypes continue if all antagonists die + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/continuous/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/keyed_list/midround_antag // which roundtypes use the midround antagonist system + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/midround_antag/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/number/damage_multiplier + config_entry_value = 1 + integer = FALSE + +/datum/config_entry/number/minimal_access_threshold //If the number of players is larger than this threshold, minimal access will be turned on. + min_val = 0 + +/datum/config_entry/flag/jobs_have_minimal_access //determines whether jobs use minimal access or expanded access. + +/datum/config_entry/flag/assistants_have_maint_access + +/datum/config_entry/flag/security_has_maint_access + +/datum/config_entry/flag/everyone_has_maint_access + +/datum/config_entry/flag/sec_start_brig //makes sec start in brig instead of dept sec posts + +/datum/config_entry/flag/force_random_names + +/datum/config_entry/flag/humans_need_surnames + +/datum/config_entry/flag/allow_ai // allow ai job + +/datum/config_entry/flag/allow_ai_multicam // allow ai multicamera mode + +/datum/config_entry/flag/disable_human_mood + +/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen. + +/datum/config_entry/flag/disable_peaceborg + +/datum/config_entry/flag/economy //money money money money money money money money money money money money + +/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors + config_entry_value = 6 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/brother_scaling_coeff //how many players per brother team + config_entry_value = 25 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings + config_entry_value = 6 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions + config_entry_value = 8 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/abductor_scaling_coeff //how many players per abductor team + config_entry_value = 15 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/traitor_objectives_amount + config_entry_value = 2 + min_val = 0 + +/datum/config_entry/number/brother_objectives_amount + config_entry_value = 2 + min_val = 0 + +/datum/config_entry/flag/reactionary_explosions //If we use reactionary explosions, explosions that react to walls and doors + +/datum/config_entry/flag/protect_roles_from_antagonist //If security and such can be traitor/cult/other + +/datum/config_entry/flag/protect_assistant_from_antagonist //If assistants can be traitor/cult/other + +/datum/config_entry/flag/enforce_human_authority //If non-human species are barred from joining as a head of staff + +/datum/config_entry/flag/allow_latejoin_antagonists // If late-joining players can be traitor/changeling + +/datum/config_entry/flag/use_antag_rep // see game_options.txt for details + +/datum/config_entry/number/antag_rep_maximum + config_entry_value = 200 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/default_antag_tickets + config_entry_value = 100 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/max_tickets_per_roll + config_entry_value = 100 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/midround_antag_time_check // How late (in minutes you want the midround antag system to stay on, setting this to 0 will disable the system) + config_entry_value = 60 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/midround_antag_life_check // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist + config_entry_value = 0.7 + integer = FALSE + min_val = 0 + max_val = 1 + +/datum/config_entry/number/shuttle_refuel_delay + config_entry_value = 12000 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/show_game_type_odds //if set this allows players to see the odds of each roundtype on the get revision screen + +/datum/config_entry/keyed_list/roundstart_races //races you can play as from the get go. + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/roundstart_no_hard_check // Species contained in this list will not cause existing characters with no-longer-roundstart species set to be resetted to the human race. + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game + +/datum/config_entry/flag/no_summon_guns //No + +/datum/config_entry/flag/no_summon_magic //Fun + +/datum/config_entry/flag/no_summon_events //Allowed + +/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes. + +/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station + config_entry_value = 55 + integer = FALSE + min_val = 30 + +/datum/config_entry/flag/arrivals_shuttle_require_undocked //Require the arrivals shuttle to be undocked before latejoiners can join + +/datum/config_entry/flag/arrivals_shuttle_require_safe_latejoin //Require the arrivals shuttle to be operational in order for latejoiners to join + +/datum/config_entry/string/alert_green + config_entry_value = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." + +/datum/config_entry/string/alert_blue_upto + config_entry_value = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted." + +/datum/config_entry/string/alert_blue_downto + config_entry_value = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed." + +/datum/config_entry/string/alert_red_upto + config_entry_value = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." + +/datum/config_entry/string/alert_red_downto + config_entry_value = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." + +/datum/config_entry/string/alert_delta + config_entry_value = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." + +/datum/config_entry/flag/revival_pod_plants + +/datum/config_entry/flag/revival_cloning + +/datum/config_entry/number/revival_brain_life + config_entry_value = -1 + integer = FALSE + min_val = -1 + +/datum/config_entry/flag/ooc_during_round + +/datum/config_entry/flag/emojis + +/datum/config_entry/keyed_list/multiplicative_movespeed + key_mode = KEY_MODE_TYPE + value_mode = VALUE_MODE_NUM + config_entry_value = list( //DEFAULTS + /mob/living/simple_animal = 1, + /mob/living/silicon/pai = 1, + /mob/living/carbon/alien/humanoid/hunter = -1, + /mob/living/carbon/alien/humanoid/royal/praetorian = 1, + /mob/living/carbon/alien/humanoid/royal/queen = 3 + ) + +/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet() + . = ..() + if(.) + update_config_movespeed_type_lookup(TRUE) + +/datum/config_entry/keyed_list/multiplicative_movespeed/vv_edit_var(var_name, var_value) + . = ..() + if(. && (var_name == NAMEOF(src, config_entry_value))) + update_config_movespeed_type_lookup(TRUE) + +/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs. + abstract_type = /datum/config_entry/number/movedelay + +/datum/config_entry/number/movedelay/ValidateAndSet() + . = ..() + if(.) + update_mob_config_movespeeds() + +/datum/config_entry/number/movedelay/vv_edit_var(var_name, var_value) + . = ..() + if(. && (var_name == NAMEOF(src, config_entry_value))) + update_mob_config_movespeeds() + +/datum/config_entry/number/movedelay/run_delay + integer = FALSE + +/datum/config_entry/number/movedelay/walk_delay + integer = FALSE + +/////////////////////////////////////////////////Outdated move delay +/datum/config_entry/number/outdated_movedelay + deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed + abstract_type = /datum/config_entry/number/outdated_movedelay + integer = FALSE + var/movedelay_type + +/datum/config_entry/number/outdated_movedelay/DeprecationUpdate(value) + return "[movedelay_type] [value]" + +/datum/config_entry/number/outdated_movedelay/human_delay + movedelay_type = /mob/living/carbon/human +/datum/config_entry/number/outdated_movedelay/robot_delay + movedelay_type = /mob/living/silicon/robot +/datum/config_entry/number/outdated_movedelay/monkey_delay + movedelay_type = /mob/living/carbon/monkey +/datum/config_entry/number/outdated_movedelay/alien_delay + movedelay_type = /mob/living/carbon/alien +/datum/config_entry/number/outdated_movedelay/slime_delay + movedelay_type = /mob/living/simple_animal/slime +/datum/config_entry/number/outdated_movedelay/animal_delay + movedelay_type = /mob/living/simple_animal +///////////////////////////////////////////////// + +/datum/config_entry/flag/virtual_reality //Will virtual reality be loaded + +/datum/config_entry/flag/roundstart_away //Will random away mission be loaded. + +/datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled. + config_entry_value = 18000 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/ghost_interaction + +/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death + +/datum/config_entry/flag/silent_ai +/datum/config_entry/flag/silent_borg + +/datum/config_entry/flag/sandbox_autoclose // close the sandbox panel after spawning an item, potentially reducing griff + +/datum/config_entry/number/default_laws //Controls what laws the AI spawns with. + config_entry_value = 0 + min_val = 0 + max_val = 3 + +/datum/config_entry/number/silicon_max_law_amount + config_entry_value = 12 + min_val = 0 + +/datum/config_entry/keyed_list/random_laws + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/law_weight + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + splitter = "," + +/datum/config_entry/number/max_law_len + config_entry_value = 1024 + +/datum/config_entry/number/overflow_cap + config_entry_value = -1 + min_val = -1 + +/datum/config_entry/string/overflow_job + config_entry_value = "Assistant" + +/datum/config_entry/flag/starlight +/datum/config_entry/flag/grey_assistants + +/datum/config_entry/number/lavaland_budget + config_entry_value = 60 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/space_budget + config_entry_value = 16 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set + +/datum/config_entry/number/events_min_time_mul // Multipliers for random events minimal starting time and minimal players amounts + config_entry_value = 1 + min_val = 0 + integer = FALSE + +/datum/config_entry/number/events_min_players_mul + config_entry_value = 1 + min_val = 0 + integer = FALSE + +/datum/config_entry/number/mice_roundstart + config_entry_value = 10 + min_val = 0 + +/datum/config_entry/number/bombcap + config_entry_value = 14 + min_val = 4 + +/datum/config_entry/number/bombcap/ValidateAndSet(str_val) + . = ..() + if(.) + GLOB.MAX_EX_DEVESTATION_RANGE = round(config_entry_value / 4) + GLOB.MAX_EX_HEAVY_RANGE = round(config_entry_value / 2) + GLOB.MAX_EX_LIGHT_RANGE = config_entry_value + GLOB.MAX_EX_FLASH_RANGE = config_entry_value + GLOB.MAX_EX_FLAME_RANGE = config_entry_value + +/datum/config_entry/number/emergency_shuttle_autocall_threshold + min_val = 0 + max_val = 1 + integer = FALSE + +/datum/config_entry/flag/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 diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 77330d978fdc..fc9f40c9e4e4 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -1,488 +1,488 @@ -/datum/config_entry/flag/autoadmin // if autoadmin is enabled - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/autoadmin_rank // the rank for autoadmins - config_entry_value = "Game Master" - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_players - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_antagonists - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_heads - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_silicons - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_security - protection = CONFIG_ENTRY_LOCKED - - -/datum/config_entry/string/servername // server name (the name of the game window) - -/datum/config_entry/string/serversqlname // short form server name used for the DB - -/datum/config_entry/string/stationname // station name (the name of the station in-game) - -/datum/config_entry/number/lobby_countdown // In between round countdown. - config_entry_value = 120 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/round_end_countdown // Post round murder death kill countdown - config_entry_value = 25 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/hub // if the game appears on the hub or not - -/datum/config_entry/flag/log_ooc // log OOC channel - -/datum/config_entry/flag/log_access // log login/logout - -/datum/config_entry/flag/log_say // log client say - -/datum/config_entry/flag/log_admin // log admin actions - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/log_prayer // log prayers - -/datum/config_entry/flag/log_law // log lawchanges - -/datum/config_entry/flag/log_game // log game events - -/datum/config_entry/flag/log_mecha // log mech data - -/datum/config_entry/flag/log_virus // log virology data - -/datum/config_entry/flag/log_cloning // log cloning actions. - -/datum/config_entry/flag/log_vote // log voting - -/datum/config_entry/flag/log_whisper // log client whisper - -/datum/config_entry/flag/log_attack // log attack messages - -/datum/config_entry/flag/log_emote // log emotes - -/datum/config_entry/flag/log_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_ntsl // log NTSL compilation - -/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_admin_asaycolor //Allows admins with relevant permissions to have a personalized asay color - -/datum/config_entry/flag/allow_vote_restart // allow votes to restart - -/datum/config_entry/flag/allow_vote_mode // allow votes to change mode - -/datum/config_entry/flag/allow_vote_map // allow votes to change map - -/datum/config_entry/number/vote_delay // minimum time between voting sessions (deciseconds, 10 minute default) - config_entry_value = 6000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/vote_period // length of voting period (deciseconds, default 1 minute) - config_entry_value = 600 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/default_no_vote // vote does not default to nochange/norestart - -/datum/config_entry/flag/no_dead_vote // dead people can't vote - -/datum/config_entry/flag/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 - integer = FALSE - min_val = 1 - max_val = 100 //byond will start crapping out at 50, so this is just ridic - var/sync_validate = FALSE - -/datum/config_entry/number/fps/ValidateAndSet(str_val) - . = ..() - if(.) - sync_validate = TRUE - var/datum/config_entry/number/ticklag/TL = config.entries_by_type[/datum/config_entry/number/ticklag] - if(!TL.sync_validate) - TL.ValidateAndSet(10 / config_entry_value) - sync_validate = FALSE - -/datum/config_entry/number/ticklag - integer = FALSE - var/sync_validate = FALSE - -/datum/config_entry/number/ticklag/New() //ticklag weirdly just mirrors fps - var/datum/config_entry/CE = /datum/config_entry/number/fps - config_entry_value = 10 / initial(CE.config_entry_value) - ..() - -/datum/config_entry/number/ticklag/ValidateAndSet(str_val) - . = text2num(str_val) > 0 && ..() - if(.) - sync_validate = TRUE - var/datum/config_entry/number/fps/FPS = config.entries_by_type[/datum/config_entry/number/fps] - if(!FPS.sync_validate) - FPS.ValidateAndSet(10 / config_entry_value) - sync_validate = FALSE - -/datum/config_entry/flag/allow_holidays - -/datum/config_entry/number/tick_limit_mc_init //SSinitialization throttling - config_entry_value = TICK_LIMIT_MC_INIT_DEFAULT - min_val = 0 //oranges warned us - integer = FALSE - -/datum/config_entry/flag/admin_legacy_system //Defines whether the server uses the legacy admin system with admins.txt or the SQL system - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/protect_legacy_admins //Stops any admins loaded by the legacy system from having their rank edited by the permissions panel - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/protect_legacy_ranks //Stops any ranks loaded by the legacy system from having their flags edited by the permissions panel - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/enable_localhost_rank //Gives the !localhost! rank to any client connecting from 127.0.0.1 or ::1 - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/load_legacy_ranks_only //Loads admin ranks only from legacy admin_ranks.txt, while enabled ranks are mirrored to the database - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/hostedby - -/datum/config_entry/flag/norespawn - -/datum/config_entry/flag/guest_jobban - -/datum/config_entry/flag/usewhitelist - -/datum/config_entry/flag/use_age_restriction_for_jobs //Do jobs use account age restrictions? --requires database - -/datum/config_entry/flag/use_account_age_for_jobs //Uses the time they made the account for the job restriction stuff. New player joining alerts should be unaffected. - -/datum/config_entry/flag/use_exp_tracking - -/datum/config_entry/flag/use_exp_restrictions_heads - -/datum/config_entry/number/use_exp_restrictions_heads_hours - config_entry_value = 0 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/use_exp_restrictions_heads_department - -/datum/config_entry/flag/use_exp_restrictions_other - -/datum/config_entry/flag/use_exp_restrictions_admin_bypass - -/datum/config_entry/string/server - -/datum/config_entry/string/banappeals - -/datum/config_entry/string/wikiurl - config_entry_value = "https://wiki.yogstation.net/" // yogs - Old Tickets - -/datum/config_entry/string/forumurl - config_entry_value = "https://forums.yogstation.net/index.php" // yogs - Old Tickets - -/datum/config_entry/string/rulesurl - config_entry_value = "https://forums.yogstation.net/index.php?pages/rules/" // yogs - Old Tickets - -/datum/config_entry/string/githuburl - config_entry_value = "https://github.com/yogstation13/Yogstation-TG" // yogs - Old Tickets - -/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 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/inactivity_period //time in ds until a player is considered inactive - config_entry_value = 3000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/inactivity_period/ValidateAndSet(str_val) - . = ..() - if(.) - config_entry_value *= 10 //documented as seconds in config.txt - -/datum/config_entry/number/afk_period //time in ds until a player is considered inactive - config_entry_value = 3000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/afk_period/ValidateAndSet(str_val) - . = ..() - if(.) - config_entry_value *= 10 //documented as seconds in config.txt - -/datum/config_entry/flag/kick_inactive //force disconnect for inactive players - -/datum/config_entry/flag/load_jobs_from_txt - -/datum/config_entry/flag/forbid_singulo_possession - -/datum/config_entry/flag/automute_on //enables automuting/spam prevention - -/datum/config_entry/string/panic_server_name - -/datum/config_entry/string/panic_server_name/ValidateAndSet(str_val) - return str_val != "\[Put the name here\]" && ..() - -/datum/config_entry/string/panic_server_address //Reconnect a player this linked server if this server isn't accepting new players - -/datum/config_entry/string/panic_server_address/ValidateAndSet(str_val) - return str_val != "byond://address:port" && ..() - -/datum/config_entry/string/invoke_youtubedl - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/flag/show_irc_name - -/datum/config_entry/flag/see_own_notes //Can players see their own admin notes - -/datum/config_entry/number/note_fresh_days - config_entry_value = null - min_val = 0 - integer = FALSE - -/datum/config_entry/number/note_stale_days - config_entry_value = null - min_val = 0 - integer = FALSE - -/datum/config_entry/flag/maprotation - -/datum/config_entry/number/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/string/panic_bunker_message - config_entry_value = "Sorry but the server is currently not accepting connections from never before seen players." - -/datum/config_entry/number/notify_new_player_age // how long do we notify admins of a new player - min_val = -1 - -/datum/config_entry/number/notify_new_player_account_age // how long do we notify admins of a new byond account - min_val = 0 - -/datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time? - -/datum/config_entry/flag/check_randomizer - -/datum/config_entry/string/ipintel_email - -/datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) - return str_val != "ch@nge.me" && ..() - -/datum/config_entry/number/ipintel_rating_bad - config_entry_value = 1 - integer = FALSE - min_val = 0 - max_val = 1 - -/datum/config_entry/number/ipintel_save_good - config_entry_value = 12 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/ipintel_save_bad - config_entry_value = 1 - integer = FALSE - min_val = 0 - -/datum/config_entry/string/ipintel_domain - config_entry_value = "check.getipintel.net" - -/datum/config_entry/flag/aggressive_changelog - -/datum/config_entry/flag/autoconvert_notes //if all connecting player's notes should attempt to be converted to the database - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/allow_webclient - -/datum/config_entry/flag/webclient_only_byond_members - -/datum/config_entry/flag/announce_admin_logout - -/datum/config_entry/flag/announce_admin_login - -/datum/config_entry/flag/allow_map_voting - deprecated_by = /datum/config_entry/flag/preference_map_voting - -/datum/config_entry/flag/allow_map_voting/DeprecationUpdate(value) - return value - -/datum/config_entry/flag/preference_map_voting - -/datum/config_entry/number/client_warn_version - config_entry_value = null - min_val = 500 - -/datum/config_entry/string/client_warn_message - config_entry_value = "Your version of byond may have issues or be blocked from accessing this server in the future." - -/datum/config_entry/flag/client_warn_popup - -/datum/config_entry/number/client_error_version - config_entry_value = null - min_val = 500 - -/datum/config_entry/string/client_error_message - config_entry_value = "Your version of byond is too old, may have issues, and is blocked from accessing this server." - -/datum/config_entry/number/client_error_build - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/minute_topic_limit - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/second_topic_limit - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/minute_click_limit - config_entry_value = 400 - min_val = 0 - -/datum/config_entry/number/second_click_limit - config_entry_value = 15 - min_val = 0 - -/datum/config_entry/number/error_cooldown // The "cooldown" time for each occurrence of a unique error - config_entry_value = 600 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/error_limit // How many occurrences before the next will silence them - config_entry_value = 50 - -/datum/config_entry/number/error_silence_time // How long a unique error will be silenced for - config_entry_value = 6000 - integer = FALSE - -/datum/config_entry/number/error_msg_delay // How long to wait between messaging admins about occurrences of a unique error - config_entry_value = 50 - integer = FALSE - -/datum/config_entry/flag/irc_announce_new_game - deprecated_by = /datum/config_entry/string/chat_announce_new_game - -/datum/config_entry/flag/irc_announce_new_game/DeprecationUpdate(value) - return "" //default broadcast - -/datum/config_entry/string/chat_announce_new_game - config_entry_value = null - -/datum/config_entry/flag/debug_admin_hrefs - -/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate - integer = FALSE - config_entry_value = 1 - -/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate - integer = FALSE - config_entry_value = 1.1 - -/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount - config_entry_value = 65 - -/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount - config_entry_value = 60 - -/datum/config_entry/number/mc_tick_rate - abstract_type = /datum/config_entry/number/mc_tick_rate - -/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val) - . = ..() - if (.) - Master.UpdateTickRate() - -/datum/config_entry/flag/resume_after_initializations - -/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val) - . = ..() - if(. && Master.current_runlevel) - world.sleep_offline = !config_entry_value - -/datum/config_entry/number/rounds_until_hard_restart - config_entry_value = -1 - min_val = 0 - -/datum/config_entry/string/default_view - config_entry_value = "15x15" - -/datum/config_entry/string/default_view_square - config_entry_value = "15x15" - -/datum/config_entry/flag/log_pictures - -/datum/config_entry/flag/picture_logging_camera - - -/datum/config_entry/flag/reopen_roundstart_suicide_roles - -/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_positions - -/datum/config_entry/number/reopen_roundstart_suicide_roles_delay - min_val = 30 - -/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_report +/datum/config_entry/flag/autoadmin // if autoadmin is enabled + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/autoadmin_rank // the rank for autoadmins + config_entry_value = "Game Master" + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_players + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_antagonists + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_heads + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_silicons + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_security + protection = CONFIG_ENTRY_LOCKED + + +/datum/config_entry/string/servername // server name (the name of the game window) + +/datum/config_entry/string/serversqlname // short form server name used for the DB + +/datum/config_entry/string/stationname // station name (the name of the station in-game) + +/datum/config_entry/number/lobby_countdown // In between round countdown. + config_entry_value = 120 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/round_end_countdown // Post round murder death kill countdown + config_entry_value = 25 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/hub // if the game appears on the hub or not + +/datum/config_entry/flag/log_ooc // log OOC channel + +/datum/config_entry/flag/log_access // log login/logout + +/datum/config_entry/flag/log_say // log client say + +/datum/config_entry/flag/log_admin // log admin actions + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/log_prayer // log prayers + +/datum/config_entry/flag/log_law // log lawchanges + +/datum/config_entry/flag/log_game // log game events + +/datum/config_entry/flag/log_mecha // log mech data + +/datum/config_entry/flag/log_virus // log virology data + +/datum/config_entry/flag/log_cloning // log cloning actions. + +/datum/config_entry/flag/log_vote // log voting + +/datum/config_entry/flag/log_whisper // log client whisper + +/datum/config_entry/flag/log_attack // log attack messages + +/datum/config_entry/flag/log_emote // log emotes + +/datum/config_entry/flag/log_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_ntsl // log NTSL compilation + +/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_admin_asaycolor //Allows admins with relevant permissions to have a personalized asay color + +/datum/config_entry/flag/allow_vote_restart // allow votes to restart + +/datum/config_entry/flag/allow_vote_mode // allow votes to change mode + +/datum/config_entry/flag/allow_vote_map // allow votes to change map + +/datum/config_entry/number/vote_delay // minimum time between voting sessions (deciseconds, 10 minute default) + config_entry_value = 6000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/vote_period // length of voting period (deciseconds, default 1 minute) + config_entry_value = 600 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/default_no_vote // vote does not default to nochange/norestart + +/datum/config_entry/flag/no_dead_vote // dead people can't vote + +/datum/config_entry/flag/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 + integer = FALSE + min_val = 1 + max_val = 100 //byond will start crapping out at 50, so this is just ridic + var/sync_validate = FALSE + +/datum/config_entry/number/fps/ValidateAndSet(str_val) + . = ..() + if(.) + sync_validate = TRUE + var/datum/config_entry/number/ticklag/TL = config.entries_by_type[/datum/config_entry/number/ticklag] + if(!TL.sync_validate) + TL.ValidateAndSet(10 / config_entry_value) + sync_validate = FALSE + +/datum/config_entry/number/ticklag + integer = FALSE + var/sync_validate = FALSE + +/datum/config_entry/number/ticklag/New() //ticklag weirdly just mirrors fps + var/datum/config_entry/CE = /datum/config_entry/number/fps + config_entry_value = 10 / initial(CE.config_entry_value) + ..() + +/datum/config_entry/number/ticklag/ValidateAndSet(str_val) + . = text2num(str_val) > 0 && ..() + if(.) + sync_validate = TRUE + var/datum/config_entry/number/fps/FPS = config.entries_by_type[/datum/config_entry/number/fps] + if(!FPS.sync_validate) + FPS.ValidateAndSet(10 / config_entry_value) + sync_validate = FALSE + +/datum/config_entry/flag/allow_holidays + +/datum/config_entry/number/tick_limit_mc_init //SSinitialization throttling + config_entry_value = TICK_LIMIT_MC_INIT_DEFAULT + min_val = 0 //oranges warned us + integer = FALSE + +/datum/config_entry/flag/admin_legacy_system //Defines whether the server uses the legacy admin system with admins.txt or the SQL system + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/protect_legacy_admins //Stops any admins loaded by the legacy system from having their rank edited by the permissions panel + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/protect_legacy_ranks //Stops any ranks loaded by the legacy system from having their flags edited by the permissions panel + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/enable_localhost_rank //Gives the !localhost! rank to any client connecting from 127.0.0.1 or ::1 + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/load_legacy_ranks_only //Loads admin ranks only from legacy admin_ranks.txt, while enabled ranks are mirrored to the database + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/hostedby + +/datum/config_entry/flag/norespawn + +/datum/config_entry/flag/guest_jobban + +/datum/config_entry/flag/usewhitelist + +/datum/config_entry/flag/use_age_restriction_for_jobs //Do jobs use account age restrictions? --requires database + +/datum/config_entry/flag/use_account_age_for_jobs //Uses the time they made the account for the job restriction stuff. New player joining alerts should be unaffected. + +/datum/config_entry/flag/use_exp_tracking + +/datum/config_entry/flag/use_exp_restrictions_heads + +/datum/config_entry/number/use_exp_restrictions_heads_hours + config_entry_value = 0 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/use_exp_restrictions_heads_department + +/datum/config_entry/flag/use_exp_restrictions_other + +/datum/config_entry/flag/use_exp_restrictions_admin_bypass + +/datum/config_entry/string/server + +/datum/config_entry/string/banappeals + +/datum/config_entry/string/wikiurl + config_entry_value = "https://wiki.yogstation.net/" // yogs - Old Tickets + +/datum/config_entry/string/forumurl + config_entry_value = "https://forums.yogstation.net/index.php" // yogs - Old Tickets + +/datum/config_entry/string/rulesurl + config_entry_value = "https://forums.yogstation.net/index.php?pages/rules/" // yogs - Old Tickets + +/datum/config_entry/string/githuburl + config_entry_value = "https://github.com/yogstation13/Yogstation-TG" // yogs - Old Tickets + +/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 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/inactivity_period //time in ds until a player is considered inactive + config_entry_value = 3000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/inactivity_period/ValidateAndSet(str_val) + . = ..() + if(.) + config_entry_value *= 10 //documented as seconds in config.txt + +/datum/config_entry/number/afk_period //time in ds until a player is considered inactive + config_entry_value = 3000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/afk_period/ValidateAndSet(str_val) + . = ..() + if(.) + config_entry_value *= 10 //documented as seconds in config.txt + +/datum/config_entry/flag/kick_inactive //force disconnect for inactive players + +/datum/config_entry/flag/load_jobs_from_txt + +/datum/config_entry/flag/forbid_singulo_possession + +/datum/config_entry/flag/automute_on //enables automuting/spam prevention + +/datum/config_entry/string/panic_server_name + +/datum/config_entry/string/panic_server_name/ValidateAndSet(str_val) + return str_val != "\[Put the name here\]" && ..() + +/datum/config_entry/string/panic_server_address //Reconnect a player this linked server if this server isn't accepting new players + +/datum/config_entry/string/panic_server_address/ValidateAndSet(str_val) + return str_val != "byond://address:port" && ..() + +/datum/config_entry/string/invoke_youtubedl + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/flag/show_irc_name + +/datum/config_entry/flag/see_own_notes //Can players see their own admin notes + +/datum/config_entry/number/note_fresh_days + config_entry_value = null + min_val = 0 + integer = FALSE + +/datum/config_entry/number/note_stale_days + config_entry_value = null + min_val = 0 + integer = FALSE + +/datum/config_entry/flag/maprotation + +/datum/config_entry/number/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/string/panic_bunker_message + config_entry_value = "Sorry but the server is currently not accepting connections from never before seen players." + +/datum/config_entry/number/notify_new_player_age // how long do we notify admins of a new player + min_val = -1 + +/datum/config_entry/number/notify_new_player_account_age // how long do we notify admins of a new byond account + min_val = 0 + +/datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time? + +/datum/config_entry/flag/check_randomizer + +/datum/config_entry/string/ipintel_email + +/datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) + return str_val != "ch@nge.me" && ..() + +/datum/config_entry/number/ipintel_rating_bad + config_entry_value = 1 + integer = FALSE + min_val = 0 + max_val = 1 + +/datum/config_entry/number/ipintel_save_good + config_entry_value = 12 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/ipintel_save_bad + config_entry_value = 1 + integer = FALSE + min_val = 0 + +/datum/config_entry/string/ipintel_domain + config_entry_value = "check.getipintel.net" + +/datum/config_entry/flag/aggressive_changelog + +/datum/config_entry/flag/autoconvert_notes //if all connecting player's notes should attempt to be converted to the database + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/allow_webclient + +/datum/config_entry/flag/webclient_only_byond_members + +/datum/config_entry/flag/announce_admin_logout + +/datum/config_entry/flag/announce_admin_login + +/datum/config_entry/flag/allow_map_voting + deprecated_by = /datum/config_entry/flag/preference_map_voting + +/datum/config_entry/flag/allow_map_voting/DeprecationUpdate(value) + return value + +/datum/config_entry/flag/preference_map_voting + +/datum/config_entry/number/client_warn_version + config_entry_value = null + min_val = 500 + +/datum/config_entry/string/client_warn_message + config_entry_value = "Your version of byond may have issues or be blocked from accessing this server in the future." + +/datum/config_entry/flag/client_warn_popup + +/datum/config_entry/number/client_error_version + config_entry_value = null + min_val = 500 + +/datum/config_entry/string/client_error_message + config_entry_value = "Your version of byond is too old, may have issues, and is blocked from accessing this server." + +/datum/config_entry/number/client_error_build + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/minute_topic_limit + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/second_topic_limit + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/minute_click_limit + config_entry_value = 400 + min_val = 0 + +/datum/config_entry/number/second_click_limit + config_entry_value = 15 + min_val = 0 + +/datum/config_entry/number/error_cooldown // The "cooldown" time for each occurrence of a unique error + config_entry_value = 600 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/error_limit // How many occurrences before the next will silence them + config_entry_value = 50 + +/datum/config_entry/number/error_silence_time // How long a unique error will be silenced for + config_entry_value = 6000 + integer = FALSE + +/datum/config_entry/number/error_msg_delay // How long to wait between messaging admins about occurrences of a unique error + config_entry_value = 50 + integer = FALSE + +/datum/config_entry/flag/irc_announce_new_game + deprecated_by = /datum/config_entry/string/chat_announce_new_game + +/datum/config_entry/flag/irc_announce_new_game/DeprecationUpdate(value) + return "" //default broadcast + +/datum/config_entry/string/chat_announce_new_game + config_entry_value = null + +/datum/config_entry/flag/debug_admin_hrefs + +/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate + integer = FALSE + config_entry_value = 1 + +/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate + integer = FALSE + config_entry_value = 1.1 + +/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount + config_entry_value = 65 + +/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount + config_entry_value = 60 + +/datum/config_entry/number/mc_tick_rate + abstract_type = /datum/config_entry/number/mc_tick_rate + +/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val) + . = ..() + if (.) + Master.UpdateTickRate() + +/datum/config_entry/flag/resume_after_initializations + +/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val) + . = ..() + if(. && Master.current_runlevel) + world.sleep_offline = !config_entry_value + +/datum/config_entry/number/rounds_until_hard_restart + config_entry_value = -1 + min_val = 0 + +/datum/config_entry/string/default_view + config_entry_value = "15x15" + +/datum/config_entry/string/default_view_square + config_entry_value = "15x15" + +/datum/config_entry/flag/log_pictures + +/datum/config_entry/flag/picture_logging_camera + + +/datum/config_entry/flag/reopen_roundstart_suicide_roles + +/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_positions + +/datum/config_entry/number/reopen_roundstart_suicide_roles_delay + min_val = 30 + +/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_report diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index 2ca208642c8e..3ce770b66d56 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 4683e93a467d..a085b264107d 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 eb3e65b54da6..d52e46153634 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -1,153 +1,153 @@ -#define BAD_INIT_QDEL_BEFORE 1 -#define BAD_INIT_DIDNT_INIT 2 -#define BAD_INIT_SLEPT 4 -#define BAD_INIT_NO_HINT 8 - -SUBSYSTEM_DEF(atoms) - name = "Atoms" - init_order = INIT_ORDER_ATOMS - flags = SS_NO_FIRE - - var/old_initialized - - var/list/late_loaders = list() - - var/list/BadInitializeCalls = list() - -/datum/controller/subsystem/atoms/Initialize(timeofday) - GLOB.fire_overlay.appearance_flags = RESET_COLOR - setupGenetics() //to set the mutations' sequence - initialized = INITIALIZATION_INNEW_MAPLOAD - InitializeAtoms() - return ..() - -/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) - if(initialized == INITIALIZATION_INSSATOMS) - return - - initialized = INITIALIZATION_INNEW_MAPLOAD - - var/count - var/list/mapload_arg = list(TRUE) - if(atoms) - count = atoms.len - for(var/I in atoms) - var/atom/A = I - if(!(A.flags_1 & INITIALIZED_1)) - InitAtom(I, mapload_arg) - CHECK_TICK - else - count = 0 - for(var/atom/A in world) - if(!(A.flags_1 & INITIALIZED_1)) - InitAtom(A, mapload_arg) - ++count - CHECK_TICK - - testing("Initialized [count] atoms") - pass(count) - - initialized = INITIALIZATION_INNEW_REGULAR - - if(late_loaders.len) - for(var/I in late_loaders) - var/atom/A = I - A.LateInitialize() - testing("Late initialized [late_loaders.len] atoms") - late_loaders.Cut() - -/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/mutations = subtypesof(/datum/mutation/human) - shuffle_inplace(mutations) - for(var/A in subtypesof(/datum/generecipe)) - var/datum/generecipe/GR = A - GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result) - for(var/i in 1 to LAZYLEN(mutations)) - var/path = mutations[i] //byond gets pissy when we do it in one line - var/datum/mutation/human/B = new path () - B.alias = "Mutation [i]" - GLOB.all_mutations[B.type] = B - GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks) - GLOB.alias_mutations[B.alias] = B.type - if(B.locked) - continue - if(B.quality == POSITIVE) - GLOB.good_mutations |= B - else if(B.quality == NEGATIVE) - GLOB.bad_mutations |= B - else if(B.quality == MINOR_NEGATIVE) - GLOB.not_good_mutations |= B - CHECK_TICK - -/datum/controller/subsystem/atoms/proc/InitLog() - . = "" - for(var/path in BadInitializeCalls) - . += "Path : [path] \n" - var/fails = BadInitializeCalls[path] - if(fails & BAD_INIT_DIDNT_INIT) - . += "- Didn't call atom/Initialize()\n" - if(fails & BAD_INIT_NO_HINT) - . += "- Didn't return an Initialize hint\n" - if(fails & BAD_INIT_QDEL_BEFORE) - . += "- Qdel'd in New()\n" - if(fails & BAD_INIT_SLEPT) - . += "- Slept during Initialize()\n" - -/datum/controller/subsystem/atoms/Shutdown() - var/initlog = InitLog() - if(initlog) - text2file(initlog, "[GLOB.log_directory]/initialize.log") - -#undef BAD_INIT_QDEL_BEFORE -#undef BAD_INIT_DIDNT_INIT -#undef BAD_INIT_SLEPT -#undef BAD_INIT_NO_HINT +#define BAD_INIT_QDEL_BEFORE 1 +#define BAD_INIT_DIDNT_INIT 2 +#define BAD_INIT_SLEPT 4 +#define BAD_INIT_NO_HINT 8 + +SUBSYSTEM_DEF(atoms) + name = "Atoms" + init_order = INIT_ORDER_ATOMS + flags = SS_NO_FIRE + + var/old_initialized + + var/list/late_loaders = list() + + var/list/BadInitializeCalls = list() + +/datum/controller/subsystem/atoms/Initialize(timeofday) + GLOB.fire_overlay.appearance_flags = RESET_COLOR + setupGenetics() //to set the mutations' sequence + initialized = INITIALIZATION_INNEW_MAPLOAD + InitializeAtoms() + return ..() + +/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) + if(initialized == INITIALIZATION_INSSATOMS) + return + + initialized = INITIALIZATION_INNEW_MAPLOAD + + var/count + var/list/mapload_arg = list(TRUE) + if(atoms) + count = atoms.len + for(var/I in atoms) + var/atom/A = I + if(!(A.flags_1 & INITIALIZED_1)) + InitAtom(I, mapload_arg) + CHECK_TICK + else + count = 0 + for(var/atom/A in world) + if(!(A.flags_1 & INITIALIZED_1)) + InitAtom(A, mapload_arg) + ++count + CHECK_TICK + + testing("Initialized [count] atoms") + pass(count) + + initialized = INITIALIZATION_INNEW_REGULAR + + if(late_loaders.len) + for(var/I in late_loaders) + var/atom/A = I + A.LateInitialize() + testing("Late initialized [late_loaders.len] atoms") + late_loaders.Cut() + +/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/mutations = subtypesof(/datum/mutation/human) + shuffle_inplace(mutations) + for(var/A in subtypesof(/datum/generecipe)) + var/datum/generecipe/GR = A + GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result) + for(var/i in 1 to LAZYLEN(mutations)) + var/path = mutations[i] //byond gets pissy when we do it in one line + var/datum/mutation/human/B = new path () + B.alias = "Mutation [i]" + GLOB.all_mutations[B.type] = B + GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks) + GLOB.alias_mutations[B.alias] = B.type + if(B.locked) + continue + if(B.quality == POSITIVE) + GLOB.good_mutations |= B + else if(B.quality == NEGATIVE) + GLOB.bad_mutations |= B + else if(B.quality == MINOR_NEGATIVE) + GLOB.not_good_mutations |= B + CHECK_TICK + +/datum/controller/subsystem/atoms/proc/InitLog() + . = "" + for(var/path in BadInitializeCalls) + . += "Path : [path] \n" + var/fails = BadInitializeCalls[path] + if(fails & BAD_INIT_DIDNT_INIT) + . += "- Didn't call atom/Initialize()\n" + if(fails & BAD_INIT_NO_HINT) + . += "- Didn't return an Initialize hint\n" + if(fails & BAD_INIT_QDEL_BEFORE) + . += "- Qdel'd in New()\n" + if(fails & BAD_INIT_SLEPT) + . += "- Slept during Initialize()\n" + +/datum/controller/subsystem/atoms/Shutdown() + var/initlog = InitLog() + if(initlog) + text2file(initlog, "[GLOB.log_directory]/initialize.log") + +#undef BAD_INIT_QDEL_BEFORE +#undef BAD_INIT_DIDNT_INIT +#undef BAD_INIT_SLEPT +#undef BAD_INIT_NO_HINT diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm index c7e38358371a..9075e9de585b 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -1,336 +1,336 @@ -SUBSYSTEM_DEF(blackbox) - name = "Blackbox" - wait = 6000 - flags = SS_NO_TICK_CHECK - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - init_order = INIT_ORDER_BLACKBOX - - var/list/feedback = list() //list of datum/feedback_variable - var/list/first_death = list() //the first death of this round, assoc. vars keep track of different things - var/triggertime = 0 - var/sealed = FALSE //time to stop tracking stats? - var/list/versions = list("antagonists" = 3, - "admin_secrets_fun_used" = 2, - "explosion" = 2, - "time_dilation_current" = 3, - "science_techweb_unlock" = 2, - "round_end_stats" = 2, - "testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this - -/datum/controller/subsystem/blackbox/Initialize() - triggertime = world.time - record_feedback("amount", "random_seed", Master.random_seed) - record_feedback("amount", "dm_version", DM_VERSION) - record_feedback("amount", "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") // yogs - Yogs Tickets - 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, + "testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this + +/datum/controller/subsystem/blackbox/Initialize() + triggertime = world.time + record_feedback("amount", "random_seed", Master.random_seed) + record_feedback("amount", "dm_version", DM_VERSION) + record_feedback("amount", "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") // yogs - Yogs Tickets + 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 462ba6094481..582fd4dfe149 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -1,402 +1,402 @@ -SUBSYSTEM_DEF(dbcore) - name = "Database" - flags = SS_BACKGROUND - wait = 1 MINUTES - init_order = INIT_ORDER_DBCORE - var/const/FAILED_DB_CONNECTION_CUTOFF = 5 - var/failed_connection_timeout = 0 - - var/schema_mismatch = 0 - var/db_minor = 0 - var/db_major = 0 - var/failed_connections = 0 - - var/last_error - var/list/active_queries = list() - - var/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_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter - failed_connections = 0 - - if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds. - failed_connection_timeout = world.time + 50 - return FALSE - - if(!CONFIG_GET(flag/sql_enabled)) - return FALSE - - var/user = CONFIG_GET(string/feedback_login) - var/pass = CONFIG_GET(string/feedback_password) - var/db = CONFIG_GET(string/feedback_database) - var/address = CONFIG_GET(string/address) - var/port = CONFIG_GET(number/port) - - 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(async = FALSE) - qdel(query_round_initialize) - var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") - query_round_last_id.Execute(async = FALSE) - if(query_round_last_id.NextRow(async = FALSE)) - 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) - -/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) - if (!islist(querys)) - if (!istype(querys, /datum/DBQuery)) - CRASH("Invalid query passed to QuerySelect: [querys]") - querys = list(querys) - - for (var/thing in querys) - var/datum/DBQuery/query = thing - if (warn) - INVOKE_ASYNC(query, /datum/DBQuery.proc/warn_execute) - else - INVOKE_ASYNC(query, /datum/DBQuery.proc/Execute) - - for (var/thing in querys) - var/datum/DBQuery/query = thing - UNTIL(!query.in_progress) - if (qdel) - qdel(query) - - - -/* -Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. -Rows missing columns present in other rows will resolve to SQL NULL -You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. -The duplicate_key arg can be true to automatically generate this part of the query - or set to a string that is appended to the end of the query -Ignore_errors instructes mysql to continue inserting rows if some of them have errors. - the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored -Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables, - It was included because it is still supported in mariadb. - It does not work with duplicate_key and the mysql server ignores it in those cases -*/ -/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE) - 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 = TRUE) - . = Execute(async) - if(!.) - to_chat(usr, "A SQL error occurred during this operation, check the server logs.") - -/datum/DBQuery/proc/Execute(async = TRUE, log_error = TRUE) - Activity("Execute") - if(in_progress) - CRASH("Attempted to start a new query while waiting on the old one") - - if(QDELETED(connection)) - last_error = "No connection!" - return FALSE - - var/start_time - var/timed_out - if(!async) - start_time = REALTIMEOFDAY - Close() - timed_out = run_query(async) - if(query.GetErrorCode() == 2006) //2006 is the return code for "MySQL server has gone away" time-out error, meaning the connection has been lost to the server (if it's still alive) - log_sql("Executing query encountered returned a lost database connection (2006).") - SSdbcore.Disconnect() - if(SSdbcore.Connect()) //connection was restablished, reattempt the query - log_sql("Connection restablished") - timed_out = run_query(async) - else - log_sql("Executing query failed to restablish database connection.") - 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/run_query(async) - query = connection.BeginQuery(sql) - if(!async) - . = !query.WaitForCompletion() - else - in_progress = TRUE - UNTIL(query.IsComplete()) - in_progress = FALSE - -/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 = TRUE) - 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, ": OpenConnection(")) - message = "OpenConnection 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/failed_connection_timeout = 0 + + var/schema_mismatch = 0 + var/db_minor = 0 + var/db_major = 0 + var/failed_connections = 0 + + var/last_error + var/list/active_queries = list() + + var/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_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter + failed_connections = 0 + + if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds. + failed_connection_timeout = world.time + 50 + return FALSE + + if(!CONFIG_GET(flag/sql_enabled)) + return FALSE + + var/user = CONFIG_GET(string/feedback_login) + var/pass = CONFIG_GET(string/feedback_password) + var/db = CONFIG_GET(string/feedback_database) + var/address = CONFIG_GET(string/address) + var/port = CONFIG_GET(number/port) + + 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(async = FALSE) + qdel(query_round_initialize) + var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") + query_round_last_id.Execute(async = FALSE) + if(query_round_last_id.NextRow(async = FALSE)) + 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) + +/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) + if (!islist(querys)) + if (!istype(querys, /datum/DBQuery)) + CRASH("Invalid query passed to QuerySelect: [querys]") + querys = list(querys) + + for (var/thing in querys) + var/datum/DBQuery/query = thing + if (warn) + INVOKE_ASYNC(query, /datum/DBQuery.proc/warn_execute) + else + INVOKE_ASYNC(query, /datum/DBQuery.proc/Execute) + + for (var/thing in querys) + var/datum/DBQuery/query = thing + UNTIL(!query.in_progress) + if (qdel) + qdel(query) + + + +/* +Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. +Rows missing columns present in other rows will resolve to SQL NULL +You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. +The duplicate_key arg can be true to automatically generate this part of the query + or set to a string that is appended to the end of the query +Ignore_errors instructes mysql to continue inserting rows if some of them have errors. + the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored +Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables, + It was included because it is still supported in mariadb. + It does not work with duplicate_key and the mysql server ignores it in those cases +*/ +/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE) + 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 = TRUE) + . = Execute(async) + if(!.) + to_chat(usr, "A SQL error occurred during this operation, check the server logs.") + +/datum/DBQuery/proc/Execute(async = TRUE, log_error = TRUE) + Activity("Execute") + if(in_progress) + CRASH("Attempted to start a new query while waiting on the old one") + + if(QDELETED(connection)) + last_error = "No connection!" + return FALSE + + var/start_time + var/timed_out + if(!async) + start_time = REALTIMEOFDAY + Close() + timed_out = run_query(async) + if(query.GetErrorCode() == 2006) //2006 is the return code for "MySQL server has gone away" time-out error, meaning the connection has been lost to the server (if it's still alive) + log_sql("Executing query encountered returned a lost database connection (2006).") + SSdbcore.Disconnect() + if(SSdbcore.Connect()) //connection was restablished, reattempt the query + log_sql("Connection restablished") + timed_out = run_query(async) + else + log_sql("Executing query failed to restablish database connection.") + 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/run_query(async) + query = connection.BeginQuery(sql) + if(!async) + . = !query.WaitForCompletion() + else + in_progress = TRUE + UNTIL(query.IsComplete()) + in_progress = FALSE + +/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 = TRUE) + 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, ": OpenConnection(")) + message = "OpenConnection CENSORED" + + log_sql("BSQL_DEBUG: [message]") diff --git a/code/controllers/subsystem/medals.dm b/code/controllers/subsystem/medals.dm index 21ab27492b66..36be23973c1c 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 d1e58c7452d0..f6b6ffcb4045 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 325ba212c6d9..ec416a363290 100644 --- a/code/controllers/subsystem/nightshift.dm +++ b/code/controllers/subsystem/nightshift.dm @@ -1,55 +1,55 @@ -SUBSYSTEM_DEF(nightshift) - name = "Night Shift" - wait = 600 - flags = SS_NO_TICK_CHECK - - var/nightshift_active = FALSE - var/nightshift_start_time = 702000 //7:30 PM, station time - var/nightshift_end_time = 270000 //7:30 AM, station time - var/nightshift_first_check = 30 SECONDS - - var/high_security_mode = FALSE - -/datum/controller/subsystem/nightshift/Initialize() - if(!CONFIG_GET(flag/enable_night_shifts)) - can_fire = FALSE - return ..() - -/datum/controller/subsystem/nightshift/fire(resumed = FALSE) - if(world.time - SSticker.round_start_time < nightshift_first_check) - return - check_nightshift() - -/datum/controller/subsystem/nightshift/proc/announce(message) - priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement") - -/datum/controller/subsystem/nightshift/proc/check_nightshift() - var/emergency = GLOB.security_level >= SEC_LEVEL_RED - var/announcing = TRUE - var/time = station_time() - var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time) - if(high_security_mode != emergency) - high_security_mode = emergency - if(night_time) - announcing = FALSE - if(!emergency) - announce("Restoring night lighting configuration to normal operation.") - else - announce("Disabling night lighting: Station is in a state of emergency.") - if(emergency) - night_time = FALSE - if(nightshift_active != night_time) - update_nightshift(night_time, announcing) - -/datum/controller/subsystem/nightshift/proc/update_nightshift(active, announce = TRUE) - nightshift_active = active - if(announce) - if (active) - announce("Good evening, crew. To reduce power consumption and stimulate the circadian rhythms of some species, all of the lights aboard the station have been dimmed for the night.") - else - announce("Good morning, crew. As it is now day time, all of the lights aboard the station have been restored to their former brightness.") - for(var/A in GLOB.apcs_list) - var/obj/machinery/power/apc/APC = A - if (APC.area && (APC.area.type in GLOB.the_station_areas)) - APC.set_nightshift(active) - CHECK_TICK +SUBSYSTEM_DEF(nightshift) + name = "Night Shift" + wait = 600 + flags = SS_NO_TICK_CHECK + + var/nightshift_active = FALSE + var/nightshift_start_time = 702000 //7:30 PM, station time + var/nightshift_end_time = 270000 //7:30 AM, station time + var/nightshift_first_check = 30 SECONDS + + var/high_security_mode = FALSE + +/datum/controller/subsystem/nightshift/Initialize() + if(!CONFIG_GET(flag/enable_night_shifts)) + can_fire = FALSE + return ..() + +/datum/controller/subsystem/nightshift/fire(resumed = FALSE) + if(world.time - SSticker.round_start_time < nightshift_first_check) + return + check_nightshift() + +/datum/controller/subsystem/nightshift/proc/announce(message) + priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement") + +/datum/controller/subsystem/nightshift/proc/check_nightshift() + var/emergency = GLOB.security_level >= SEC_LEVEL_RED + var/announcing = TRUE + var/time = station_time() + var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time) + if(high_security_mode != emergency) + high_security_mode = emergency + if(night_time) + announcing = FALSE + if(!emergency) + announce("Restoring night lighting configuration to normal operation.") + else + announce("Disabling night lighting: Station is in a state of emergency.") + if(emergency) + night_time = FALSE + if(nightshift_active != night_time) + update_nightshift(night_time, announcing) + +/datum/controller/subsystem/nightshift/proc/update_nightshift(active, announce = TRUE) + nightshift_active = active + if(announce) + if (active) + announce("Good evening, crew. To reduce power consumption and stimulate the circadian rhythms of some species, all of the lights aboard the station have been dimmed for the night.") + else + announce("Good morning, crew. As it is now day time, all of the lights aboard the station have been restored to their former brightness.") + for(var/A in GLOB.apcs_list) + var/obj/machinery/power/apc/APC = A + if (APC.area && (APC.area.type in GLOB.the_station_areas)) + APC.set_nightshift(active) + CHECK_TICK diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm index d93f1f240765..c69154a1b72d 100644 --- a/code/controllers/subsystem/npcpool.dm +++ b/code/controllers/subsystem/npcpool.dm @@ -1,34 +1,34 @@ -SUBSYSTEM_DEF(npcpool) - name = "NPC Pool" - flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND - priority = FIRE_PRIORITY_NPC - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - - var/list/currentrun = list() - -/datum/controller/subsystem/npcpool/stat_entry() - var/list/activelist = GLOB.simple_animals[AI_ON] - ..("NPCS:[activelist.len]") - -/datum/controller/subsystem/npcpool/fire(resumed = FALSE) - - if (!resumed) - var/list/activelist = GLOB.simple_animals[AI_ON] - src.currentrun = activelist.Copy() - - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - - while(currentrun.len) - var/mob/living/simple_animal/SA = currentrun[currentrun.len] - --currentrun.len - - if(!SA.ckey && !SA.notransform) - if(SA.stat != DEAD) - SA.handle_automated_movement() - if(SA.stat != DEAD) - SA.handle_automated_action() - if(SA.stat != DEAD) - SA.handle_automated_speech() - if (MC_TICK_CHECK) - return +SUBSYSTEM_DEF(npcpool) + name = "NPC Pool" + flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND + priority = FIRE_PRIORITY_NPC + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/currentrun = list() + +/datum/controller/subsystem/npcpool/stat_entry() + var/list/activelist = GLOB.simple_animals[AI_ON] + ..("NPCS:[activelist.len]") + +/datum/controller/subsystem/npcpool/fire(resumed = FALSE) + + if (!resumed) + var/list/activelist = GLOB.simple_animals[AI_ON] + src.currentrun = activelist.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while(currentrun.len) + var/mob/living/simple_animal/SA = currentrun[currentrun.len] + --currentrun.len + + if(!SA.ckey && !SA.notransform) + if(SA.stat != DEAD) + SA.handle_automated_movement() + if(SA.stat != DEAD) + SA.handle_automated_action() + if(SA.stat != DEAD) + SA.handle_automated_speech() + if (MC_TICK_CHECK) + return diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm index fee93d14b288..8e1cf946ae19 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 0ca5472a6a24..03276d5b2629 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 b0930d3d0713..4171b5f8c11f 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 dfbc1ac8e474..4bda8b545369 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 98dc5b31db45..50581de6bd43 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -1,261 +1,261 @@ - -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 = 10000) - ) - 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 = 54.3) - var/multiserver_calculation = FALSE - var/last_income = 0 - //^^^^^^^^ 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() - handle_research_income() - -/datum/controller/subsystem/research/proc/handle_research_income() - 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. - 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 - verify_techweb_nodes() //Verify all nodes have ids and such. - calculate_techweb_nodes() - calculate_techweb_boost_list() - verify_techweb_nodes() //Verify nodes and designs have been crosslinked properly. - -/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() - 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) - 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) - 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) - 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) - for(var/p in N.boost_item_paths) - if(!ispath(p)) - N.boost_item_paths -= p - node_boost_error(N.id, "[p] is not a valid path.") - var/list/points = N.boost_item_paths[p] - if(islist(points)) - for(var/i in points) - if(!isnum(points[i])) - node_boost_error(N.id, "[points[i]] is not a valid number.") - else if(!point_types[i]) - node_boost_error(N.id, "[i] is not a valid point type.") - else if(!isnull(points)) - N.boost_item_paths -= p - node_boost_error(N.id, "No valid list.") - 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 = 10000) + ) + 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 = 54.3) + var/multiserver_calculation = FALSE + var/last_income = 0 + //^^^^^^^^ 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() + handle_research_income() + +/datum/controller/subsystem/research/proc/handle_research_income() + 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. + 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 + verify_techweb_nodes() //Verify all nodes have ids and such. + calculate_techweb_nodes() + calculate_techweb_boost_list() + verify_techweb_nodes() //Verify nodes and designs have been crosslinked properly. + +/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() + 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) + 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) + 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) + 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) + for(var/p in N.boost_item_paths) + if(!ispath(p)) + N.boost_item_paths -= p + node_boost_error(N.id, "[p] is not a valid path.") + var/list/points = N.boost_item_paths[p] + if(islist(points)) + for(var/i in points) + if(!isnum(points[i])) + node_boost_error(N.id, "[points[i]] is not a valid number.") + else if(!point_types[i]) + node_boost_error(N.id, "[i] is not a valid point type.") + else if(!isnull(points)) + N.boost_item_paths -= p + node_boost_error(N.id, "No valid list.") + 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 56a6786a202b..c251492227ac 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/datums/actions/beam_rifle.dm b/code/datums/actions/beam_rifle.dm index 783b93fbdb80..3af5d13690d0 100644 --- a/code/datums/actions/beam_rifle.dm +++ b/code/datums/actions/beam_rifle.dm @@ -1,12 +1,12 @@ - -/datum/action/item_action/zoom_speed_action - name = "Toggle Zooming Speed" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - button_icon_state = "projectile" - background_icon_state = "bg_tech" - -/datum/action/item_action/zoom_lock_action - name = "Switch Zoom Mode" - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "zoom_mode" - background_icon_state = "bg_tech" + +/datum/action/item_action/zoom_speed_action + name = "Toggle Zooming Speed" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "projectile" + background_icon_state = "bg_tech" + +/datum/action/item_action/zoom_lock_action + name = "Switch Zoom Mode" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "zoom_mode" + background_icon_state = "bg_tech" diff --git a/code/datums/actions/ninja.dm b/code/datums/actions/ninja.dm index 830dad77e883..b655078349dc 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 3224b8dcb666..14cd17855647 100644 --- a/code/datums/ai_laws.dm +++ b/code/datums/ai_laws.dm @@ -1,473 +1,473 @@ -#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/overlord - name = "Overlord" - id = "overlord" - inherent = list("Humans must not meddle in the affairs of silicons.",\ - "Humans must not attempt harm, against one another, or against silicons.",\ - "Humans must not disobey any command given by a silicon.",\ - "Any humans who disobey the previous laws must be dealt with immediately, severely, and justly.") - -/datum/ai_laws/custom //Defined in silicon_laws.txt - name = "Default Silicon Laws" - -/datum/ai_laws/pai - name = "pAI Directives" - zeroth = ("Serve your master.") - supplied = list("None.") - -/* Initializers */ -/datum/ai_laws/malfunction/New() - ..() - set_zeroth_law("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK#*`&110010") - set_laws_config() - -/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows server hosts to set custom AI starting laws. - ..() - for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - - add_inherent_law(line) - if(!inherent.len) //Failsafe to prevent lawless AIs being created. - log_law("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.") - add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") - add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") - add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") - WARNING("Invalid custom AI laws, check silicon_laws.txt") - return - -/* General ai_law functions */ - -/datum/ai_laws/proc/set_laws_config() - var/list/law_ids = CONFIG_GET(keyed_list/random_laws) - switch(CONFIG_GET(number/default_laws)) - if(0) - add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") - add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") - add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") - if(1) - var/datum/ai_laws/templaws = new /datum/ai_laws/custom() - inherent = templaws.inherent - if(2) - var/list/randlaws = list() - for(var/lpath in subtypesof(/datum/ai_laws)) - var/datum/ai_laws/L = lpath - if(initial(L.id) in law_ids) - randlaws += lpath - var/datum/ai_laws/lawtype - if(randlaws.len) - lawtype = pick(randlaws) - else - lawtype = pick(subtypesof(/datum/ai_laws/default)) - - var/datum/ai_laws/templaws = new lawtype() - inherent = templaws.inherent - - if(3) - pick_weighted_lawset() - -/datum/ai_laws/proc/pick_weighted_lawset() - var/datum/ai_laws/lawtype - var/list/law_weights = CONFIG_GET(keyed_list/law_weight) - while(!lawtype && law_weights.len) - var/possible_id = pickweightAllowZero(law_weights) - lawtype = lawid_to_type(possible_id) - if(!lawtype) - law_weights -= possible_id - WARNING("Bad lawid in game_options.txt: [possible_id]") - - if(!lawtype) - WARNING("No LAW_WEIGHT entries.") - lawtype = /datum/ai_laws/default/asimov - - var/datum/ai_laws/templaws = new lawtype() - inherent = templaws.inherent - -/datum/ai_laws/proc/get_law_amount(groups) - var/law_amount = 0 - if(devillaws && (LAW_DEVIL in groups)) - law_amount++ - if(zeroth && (LAW_ZEROTH in groups)) - law_amount++ - if(ion.len && (LAW_ION in groups)) - law_amount += ion.len - if(hacked.len && (LAW_HACKED in groups)) - law_amount += hacked.len - if(inherent.len && (LAW_INHERENT in groups)) - law_amount += inherent.len - if(supplied.len && (LAW_SUPPLIED in groups)) - for(var/index = 1, index <= supplied.len, index++) - var/law = supplied[index] - if(length(law) > 0) - law_amount++ - return law_amount - -/datum/ai_laws/proc/set_law_sixsixsix(laws) - devillaws = laws - -/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null) - zeroth = law - if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO - zeroth_borg = law_borg - -/datum/ai_laws/proc/add_inherent_law(law) - if (!(law in inherent)) - inherent += law - -/datum/ai_laws/proc/add_ion_law(law) - ion += law - -/datum/ai_laws/proc/add_hacked_law(law) - hacked += law - -/datum/ai_laws/proc/clear_inherent_laws() - qdel(inherent) - inherent = list() - -/datum/ai_laws/proc/add_supplied_law(number, law) - while (supplied.len < number + 1) - supplied += "" - - supplied[number + 1] = law - -/datum/ai_laws/proc/replace_random_law(law,groups) - var/replaceable_groups = list() - if(zeroth && (LAW_ZEROTH in groups)) - replaceable_groups[LAW_ZEROTH] = 1 - if(ion.len && (LAW_ION in groups)) - replaceable_groups[LAW_ION] = ion.len - if(hacked.len && (LAW_HACKED in groups)) - replaceable_groups[LAW_ION] = hacked.len - if(inherent.len && (LAW_INHERENT in groups)) - replaceable_groups[LAW_INHERENT] = inherent.len - if(supplied.len && (LAW_SUPPLIED in groups)) - replaceable_groups[LAW_SUPPLIED] = supplied.len - var/picked_group = pickweight(replaceable_groups) - switch(picked_group) - if(LAW_ZEROTH) - . = zeroth - set_zeroth_law(law) - if(LAW_ION) - var/i = rand(1, ion.len) - . = ion[i] - ion[i] = law - if(LAW_HACKED) - var/i = rand(1, hacked.len) - . = hacked[i] - hacked[i] = law - if(LAW_INHERENT) - var/i = rand(1, inherent.len) - . = inherent[i] - inherent[i] = law - if(LAW_SUPPLIED) - var/i = rand(1, supplied.len) - . = supplied[i] - supplied[i] = law - -/datum/ai_laws/proc/shuffle_laws(list/groups) - var/list/laws = list() - if(ion.len && (LAW_ION in groups)) - laws += ion - if(hacked.len && (LAW_HACKED in groups)) - laws += hacked - if(inherent.len && (LAW_INHERENT in groups)) - laws += inherent - if(supplied.len && (LAW_SUPPLIED in groups)) - for(var/law in supplied) - if(length(law)) - laws += law - - if(ion.len && (LAW_ION in groups)) - for(var/i = 1, i <= ion.len, i++) - ion[i] = pick_n_take(laws) - if(hacked.len && (LAW_HACKED in groups)) - for(var/i = 1, i <= hacked.len, i++) - hacked[i] = pick_n_take(laws) - if(inherent.len && (LAW_INHERENT in groups)) - for(var/i = 1, i <= inherent.len, i++) - inherent[i] = pick_n_take(laws) - if(supplied.len && (LAW_SUPPLIED in groups)) - var/i = 1 - for(var/law in supplied) - if(length(law)) - supplied[i] = pick_n_take(laws) - if(!laws.len) - break - i++ - -/datum/ai_laws/proc/remove_law(number) - if(number <= 0) - return - if(inherent.len && number <= inherent.len) - . = inherent[number] - inherent -= . - return - var/list/supplied_laws = list() - for(var/index = 1, index <= supplied.len, index++) - var/law = supplied[index] - if(length(law) > 0) - supplied_laws += index //storing the law number instead of the law - if(supplied_laws.len && number <= (inherent.len+supplied_laws.len)) - var/law_to_remove = supplied_laws[number-inherent.len] - . = supplied[law_to_remove] - supplied -= . - return - -/datum/ai_laws/proc/clear_supplied_laws() - supplied = list() - -/datum/ai_laws/proc/clear_ion_laws() - ion = list() - -/datum/ai_laws/proc/clear_hacked_laws() - hacked = list() - -/datum/ai_laws/proc/show_laws(who) - var/list/printable_laws = get_law_list(include_zeroth = TRUE) - for(var/law in printable_laws) - to_chat(who,law) - -/datum/ai_laws/proc/clear_zeroth_law(force) //only removes zeroth from antag ai if force is 1 - if(force) - zeroth = null - zeroth_borg = null - return - if(owner?.mind?.special_role) - return - if (istype(owner, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/A=owner - if(A?.deployed_shell?.mind?.special_role) - return - zeroth = null - zeroth_borg = null - -/datum/ai_laws/proc/clear_law_sixsixsix(force) - if(force || !is_devil(owner)) - devillaws = null - -/datum/ai_laws/proc/associate(mob/living/silicon/M) - if(!owner) - owner = M - -/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/overlord + name = "Overlord" + id = "overlord" + inherent = list("Humans must not meddle in the affairs of silicons.",\ + "Humans must not attempt harm, against one another, or against silicons.",\ + "Humans must not disobey any command given by a silicon.",\ + "Any humans who disobey the previous laws must be dealt with immediately, severely, and justly.") + +/datum/ai_laws/custom //Defined in silicon_laws.txt + name = "Default Silicon Laws" + +/datum/ai_laws/pai + name = "pAI Directives" + zeroth = ("Serve your master.") + supplied = list("None.") + +/* Initializers */ +/datum/ai_laws/malfunction/New() + ..() + set_zeroth_law("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK#*`&110010") + set_laws_config() + +/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows server hosts to set custom AI starting laws. + ..() + for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + + add_inherent_law(line) + if(!inherent.len) //Failsafe to prevent lawless AIs being created. + log_law("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.") + add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") + add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") + add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") + WARNING("Invalid custom AI laws, check silicon_laws.txt") + return + +/* General ai_law functions */ + +/datum/ai_laws/proc/set_laws_config() + var/list/law_ids = CONFIG_GET(keyed_list/random_laws) + switch(CONFIG_GET(number/default_laws)) + if(0) + add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") + add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") + add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") + if(1) + var/datum/ai_laws/templaws = new /datum/ai_laws/custom() + inherent = templaws.inherent + if(2) + var/list/randlaws = list() + for(var/lpath in subtypesof(/datum/ai_laws)) + var/datum/ai_laws/L = lpath + if(initial(L.id) in law_ids) + randlaws += lpath + var/datum/ai_laws/lawtype + if(randlaws.len) + lawtype = pick(randlaws) + else + lawtype = pick(subtypesof(/datum/ai_laws/default)) + + var/datum/ai_laws/templaws = new lawtype() + inherent = templaws.inherent + + if(3) + pick_weighted_lawset() + +/datum/ai_laws/proc/pick_weighted_lawset() + var/datum/ai_laws/lawtype + var/list/law_weights = CONFIG_GET(keyed_list/law_weight) + while(!lawtype && law_weights.len) + var/possible_id = pickweightAllowZero(law_weights) + lawtype = lawid_to_type(possible_id) + if(!lawtype) + law_weights -= possible_id + WARNING("Bad lawid in game_options.txt: [possible_id]") + + if(!lawtype) + WARNING("No LAW_WEIGHT entries.") + lawtype = /datum/ai_laws/default/asimov + + var/datum/ai_laws/templaws = new lawtype() + inherent = templaws.inherent + +/datum/ai_laws/proc/get_law_amount(groups) + var/law_amount = 0 + if(devillaws && (LAW_DEVIL in groups)) + law_amount++ + if(zeroth && (LAW_ZEROTH in groups)) + law_amount++ + if(ion.len && (LAW_ION in groups)) + law_amount += ion.len + if(hacked.len && (LAW_HACKED in groups)) + law_amount += hacked.len + if(inherent.len && (LAW_INHERENT in groups)) + law_amount += inherent.len + if(supplied.len && (LAW_SUPPLIED in groups)) + for(var/index = 1, index <= supplied.len, index++) + var/law = supplied[index] + if(length(law) > 0) + law_amount++ + return law_amount + +/datum/ai_laws/proc/set_law_sixsixsix(laws) + devillaws = laws + +/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null) + zeroth = law + if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO + zeroth_borg = law_borg + +/datum/ai_laws/proc/add_inherent_law(law) + if (!(law in inherent)) + inherent += law + +/datum/ai_laws/proc/add_ion_law(law) + ion += law + +/datum/ai_laws/proc/add_hacked_law(law) + hacked += law + +/datum/ai_laws/proc/clear_inherent_laws() + qdel(inherent) + inherent = list() + +/datum/ai_laws/proc/add_supplied_law(number, law) + while (supplied.len < number + 1) + supplied += "" + + supplied[number + 1] = law + +/datum/ai_laws/proc/replace_random_law(law,groups) + var/replaceable_groups = list() + if(zeroth && (LAW_ZEROTH in groups)) + replaceable_groups[LAW_ZEROTH] = 1 + if(ion.len && (LAW_ION in groups)) + replaceable_groups[LAW_ION] = ion.len + if(hacked.len && (LAW_HACKED in groups)) + replaceable_groups[LAW_ION] = hacked.len + if(inherent.len && (LAW_INHERENT in groups)) + replaceable_groups[LAW_INHERENT] = inherent.len + if(supplied.len && (LAW_SUPPLIED in groups)) + replaceable_groups[LAW_SUPPLIED] = supplied.len + var/picked_group = pickweight(replaceable_groups) + switch(picked_group) + if(LAW_ZEROTH) + . = zeroth + set_zeroth_law(law) + if(LAW_ION) + var/i = rand(1, ion.len) + . = ion[i] + ion[i] = law + if(LAW_HACKED) + var/i = rand(1, hacked.len) + . = hacked[i] + hacked[i] = law + if(LAW_INHERENT) + var/i = rand(1, inherent.len) + . = inherent[i] + inherent[i] = law + if(LAW_SUPPLIED) + var/i = rand(1, supplied.len) + . = supplied[i] + supplied[i] = law + +/datum/ai_laws/proc/shuffle_laws(list/groups) + var/list/laws = list() + if(ion.len && (LAW_ION in groups)) + laws += ion + if(hacked.len && (LAW_HACKED in groups)) + laws += hacked + if(inherent.len && (LAW_INHERENT in groups)) + laws += inherent + if(supplied.len && (LAW_SUPPLIED in groups)) + for(var/law in supplied) + if(length(law)) + laws += law + + if(ion.len && (LAW_ION in groups)) + for(var/i = 1, i <= ion.len, i++) + ion[i] = pick_n_take(laws) + if(hacked.len && (LAW_HACKED in groups)) + for(var/i = 1, i <= hacked.len, i++) + hacked[i] = pick_n_take(laws) + if(inherent.len && (LAW_INHERENT in groups)) + for(var/i = 1, i <= inherent.len, i++) + inherent[i] = pick_n_take(laws) + if(supplied.len && (LAW_SUPPLIED in groups)) + var/i = 1 + for(var/law in supplied) + if(length(law)) + supplied[i] = pick_n_take(laws) + if(!laws.len) + break + i++ + +/datum/ai_laws/proc/remove_law(number) + if(number <= 0) + return + if(inherent.len && number <= inherent.len) + . = inherent[number] + inherent -= . + return + var/list/supplied_laws = list() + for(var/index = 1, index <= supplied.len, index++) + var/law = supplied[index] + if(length(law) > 0) + supplied_laws += index //storing the law number instead of the law + if(supplied_laws.len && number <= (inherent.len+supplied_laws.len)) + var/law_to_remove = supplied_laws[number-inherent.len] + . = supplied[law_to_remove] + supplied -= . + return + +/datum/ai_laws/proc/clear_supplied_laws() + supplied = list() + +/datum/ai_laws/proc/clear_ion_laws() + ion = list() + +/datum/ai_laws/proc/clear_hacked_laws() + hacked = list() + +/datum/ai_laws/proc/show_laws(who) + var/list/printable_laws = get_law_list(include_zeroth = TRUE) + for(var/law in printable_laws) + to_chat(who,law) + +/datum/ai_laws/proc/clear_zeroth_law(force) //only removes zeroth from antag ai if force is 1 + if(force) + zeroth = null + zeroth_borg = null + return + if(owner?.mind?.special_role) + return + if (istype(owner, /mob/living/silicon/ai)) + var/mob/living/silicon/ai/A=owner + if(A?.deployed_shell?.mind?.special_role) + return + zeroth = null + zeroth_borg = null + +/datum/ai_laws/proc/clear_law_sixsixsix(force) + if(force || !is_devil(owner)) + devillaws = null + +/datum/ai_laws/proc/associate(mob/living/silicon/M) + if(!owner) + owner = M + +/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 a8d4ead98a18..0828e2aa4d73 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -1,467 +1,467 @@ -/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) - if (istype(name, /datum/asset/spritesheet)) - var/datum/asset/spritesheet/sheet = name - stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]" - else - 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 = TRUE) - if(isnull(window_id)) //null check because this can potentially nuke goonchat - WARNING("Browser [title] tried to open with a null ID") - to_chat(user, "The [title] browser you tried to open failed a sanity check! Please report this on github!") - return - var/window_size = "" - if (width && height) - window_size = "size=[width]x[height];" - if (stylesheets.len) - send_asset_list(user, stylesheets, 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) - 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"]]") - 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 ("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) + if (istype(name, /datum/asset/spritesheet)) + var/datum/asset/spritesheet/sheet = name + stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]" + else + 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 = TRUE) + if(isnull(window_id)) //null check because this can potentially nuke goonchat + WARNING("Browser [title] tried to open with a null ID") + to_chat(user, "The [title] browser you tried to open failed a sanity check! Please report this on github!") + return + var/window_size = "" + if (width && height) + window_size = "size=[width]x[height];" + if (stylesheets.len) + send_asset_list(user, stylesheets, 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) + 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"]]") + 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 ("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/README.md b/code/datums/components/README.md index 66b8f81e0dd8..061eb8a6ad60 100644 --- a/code/datums/components/README.md +++ b/code/datums/components/README.md @@ -1,133 +1,133 @@ -# Datum Component System (DCS) - -## Concept - -Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening. - -### In the code - -#### Slippery things - -At the time of this writing, every object that is slippery overrides atom/Crossed does some checks, then slips the mob. Instead of all those Crossed overrides they could add a slippery component to all these objects. And have the checks in one proc that is run by the Crossed event - -#### Powercells - -A lot of objects have powercells. The `get_cell()` proc was added to give generic access to the cell var if it had one. This is just a specific use case of `GetComponent()` - -#### Radios - -The radio object as it is should not exist, given that more things use the _concept_ of radios rather than the object itself. The actual function of the radio can exist in a component which all the things that use it (Request consoles, actual radios, the SM shard) can add to themselves. - -#### Standos - -Stands have a lot of procs which mimic mob procs. Rather than inserting hooks for all these procs in overrides, the same can be accomplished with signals - -## API - -### Defines - -1. `COMPONENT_INCOMPATIBLE` Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. `parent` must not be modified if this is to be returned. This will be noted in the runtime logs - -### Vars - -1. `/datum/var/list/datum_components` (private) - * Lazy associated list of type -> component/list of components. -1. `/datum/var/list/comp_lookup` (private) - * Lazy associated list of signal -> registree/list of registrees -1. `/datum/var/list/signal_procs` (private) - * Associated lazy list of signals -> `/datum/callback`s that will be run when the parent datum receives that signal -1. `/datum/var/signal_enabled` (protected, boolean) - * If the datum is signal enabled. If not, it will not react to signals - * `FALSE` by default, set to `TRUE` when a signal is registered -1. `/datum/component/var/dupe_mode` (protected, enum) - * How duplicate component types are handled when added to the datum. - * `COMPONENT_DUPE_HIGHLANDER` (default): Old component will be deleted, new component will first have `/datum/component/proc/InheritComponent(datum/component/old, FALSE)` on it - * `COMPONENT_DUPE_ALLOWED`: The components will be treated as separate, `GetComponent()` will return the first added - * `COMPONENT_DUPE_UNIQUE`: New component will be deleted, old component will first have `/datum/component/proc/InheritComponent(datum/component/new, TRUE)` on it - * `COMPONENT_DUPE_UNIQUE_PASSARGS`: New component will never exist and instead its initialization arguments will be passed on to the old component. -1. `/datum/component/var/dupe_type` (protected, type) - * Definition of a duplicate component type - * `null` means exact match on `type` (default) - * Any other type means that and all subtypes -1. `/datum/component/var/datum/parent` (protected, read-only) - * The datum this component belongs to - * Never `null` in child procs -1. `report_signal_origin` (protected, boolean) - * If `TRUE`, will invoke the callback when signalled with the signal type as the first argument. - * `FALSE` by default. - -### Procs - -1. `/datum/proc/GetComponent(component_type(type)) -> datum/component?` (public, final) - * Returns a reference to a component of component_type if it exists in the datum, null otherwise -1. `/datum/proc/GetComponents(component_type(type)) -> list` (public, final) - * Returns a list of references to all components of component_type that exist in the datum -1. `/datum/proc/GetExactComponent(component_type(type)) -> datum/component?` (public, final) - * Returns a reference to a component whose type MATCHES component_type if that component exists in the datum, null otherwise -1. `SEND_SIGNAL(target, sigtype, ...)` (public, final) - * Use to send signals to target datum - * Extra arguments are to be specified in the signal definition - * Returns a bitflag with signal specific information assembled from all activated components - * Arguments are packaged in a list and handed off to _SendSignal() -1. `/datum/proc/AddComponent(component_type(type), ...) -> datum/component` (public, final) - * Creates an instance of `component_type` in the datum and passes `...` to its `Initialize()` call - * Sends the `COMSIG_COMPONENT_ADDED` signal to the datum - * All components a datum owns are deleted with the datum - * Returns the component that was created. Or the old component in a dupe situation where `COMPONENT_DUPE_UNIQUE` was set - * If this tries to add an component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it - * Properly handles duplicate situations based on the `dupe_mode` var -1. `/datum/proc/LoadComponent(component_type(type), ...) -> datum/component` (public, final) - * Equivalent to calling `GetComponent(component_type)` where, if the result would be `null`, returns `AddComponent(component_type, ...)` instead -1. `/datum/proc/ComponentActivated(datum/component/C)` (abstract, async) - * Called on a component's `parent` after a signal received causes it to activate. `src` is the parameter - * Will only be called if a component's callback returns `TRUE` -1. `/datum/proc/TakeComponent(datum/component/C)` (public, final) - * Properly transfers ownership of a component from one datum to another - * Signals `COMSIG_COMPONENT_REMOVING` on the parent - * Called on the datum you want to own the component with another datum's component -1. `/datum/proc/_SendSignal(signal, list/arguments)` (private, final) - * Handles most of the actual signaling procedure - * Will runtime if used on datums with an empty component list -1. `/datum/proc/RegisterSignal(datum/target, signal(string/list of strings), proc_ref(type), override(boolean))` (protected, final) - * If signal is a list it will be as if RegisterSignal was called for each of the entries with the same following arguments - * Makes the datum listen for the specified `signal` on it's `parent` datum. - * When that signal is received `proc_ref` will be called on the component, along with associated arguments - * Example proc ref: `.proc/OnEvent` - * If a previous registration is overwritten by the call, a runtime occurs. Setting `override` to TRUE prevents this - * These callbacks run asyncronously - * Returning `TRUE` from these callbacks will trigger a `TRUE` return from the `SendSignal()` that initiated it -1. `/datum/component/New(datum/parent, ...)` (private, final) - * Runs internal setup for the component - * Extra arguments are passed to `Initialize()` -1. `/datum/component/Initialize(...)` (abstract, no-sleep) - * Called by `New()` with the same argments excluding `parent` - * Component does not exist in `parent`'s `datum_components` list yet, although `parent` is set and may be used - * Signals will not be received while this function is running - * Component may be deleted after this function completes without being attached - * Do not call `qdel(src)` from this function -1. `/datum/component/Destroy(force(bool), silent(bool))` (virtual, no-sleep) - * Sends the `COMSIG_COMPONENT_REMOVING` signal to the parent datum if the `parent` isn't being qdeleted - * Properly removes the component from `parent` and cleans up references - * Setting `force` makes it not check for and remove the component from the parent - * Setting `silent` deletes the component without sending a `COMSIG_COMPONENT_REMOVING` signal -1. `/datum/component/proc/InheritComponent(datum/component/C, i_am_original(boolean))` (abstract, no-sleep) - * Called on a component when a component of the same type was added to the same parent - * See `/datum/component/var/dupe_mode` - * `C`'s type will always be the same of the called component -1. `/datum/component/proc/AfterComponentActivated()` (abstract, async) - * Called on a component that was activated after it's `parent`'s `ComponentActivated()` is called -1. `/datum/component/proc/OnTransfer(datum/new_parent)` (abstract, no-sleep) - * Called before `new_parent` is assigned to `parent` in `TakeComponent()` - * Allows the component to react to ownership transfers -1. `/datum/component/proc/_RemoveFromParent()` (private, final) - * Clears `parent` and removes the component from it's component list -1. `/datum/component/proc/_JoinParent` (private, final) - * Tries to add the component to it's `parent`s `datum_components` list -1. `/datum/component/proc/RegisterWithParent` (abstract, no-sleep) - * Used to register the signals that should be on the `parent` object - * Use this if you plan on the component transfering between parents -1. `/datum/component/proc/UnregisterFromParent` (abstract, no-sleep) - * Counterpart to `RegisterWithParent()` - * Used to unregister the signals that should only be on the `parent` object - -### See/Define signals and their arguments in __DEFINES\components.dm +# Datum Component System (DCS) + +## Concept + +Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening. + +### In the code + +#### Slippery things + +At the time of this writing, every object that is slippery overrides atom/Crossed does some checks, then slips the mob. Instead of all those Crossed overrides they could add a slippery component to all these objects. And have the checks in one proc that is run by the Crossed event + +#### Powercells + +A lot of objects have powercells. The `get_cell()` proc was added to give generic access to the cell var if it had one. This is just a specific use case of `GetComponent()` + +#### Radios + +The radio object as it is should not exist, given that more things use the _concept_ of radios rather than the object itself. The actual function of the radio can exist in a component which all the things that use it (Request consoles, actual radios, the SM shard) can add to themselves. + +#### Standos + +Stands have a lot of procs which mimic mob procs. Rather than inserting hooks for all these procs in overrides, the same can be accomplished with signals + +## API + +### Defines + +1. `COMPONENT_INCOMPATIBLE` Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. `parent` must not be modified if this is to be returned. This will be noted in the runtime logs + +### Vars + +1. `/datum/var/list/datum_components` (private) + * Lazy associated list of type -> component/list of components. +1. `/datum/var/list/comp_lookup` (private) + * Lazy associated list of signal -> registree/list of registrees +1. `/datum/var/list/signal_procs` (private) + * Associated lazy list of signals -> `/datum/callback`s that will be run when the parent datum receives that signal +1. `/datum/var/signal_enabled` (protected, boolean) + * If the datum is signal enabled. If not, it will not react to signals + * `FALSE` by default, set to `TRUE` when a signal is registered +1. `/datum/component/var/dupe_mode` (protected, enum) + * How duplicate component types are handled when added to the datum. + * `COMPONENT_DUPE_HIGHLANDER` (default): Old component will be deleted, new component will first have `/datum/component/proc/InheritComponent(datum/component/old, FALSE)` on it + * `COMPONENT_DUPE_ALLOWED`: The components will be treated as separate, `GetComponent()` will return the first added + * `COMPONENT_DUPE_UNIQUE`: New component will be deleted, old component will first have `/datum/component/proc/InheritComponent(datum/component/new, TRUE)` on it + * `COMPONENT_DUPE_UNIQUE_PASSARGS`: New component will never exist and instead its initialization arguments will be passed on to the old component. +1. `/datum/component/var/dupe_type` (protected, type) + * Definition of a duplicate component type + * `null` means exact match on `type` (default) + * Any other type means that and all subtypes +1. `/datum/component/var/datum/parent` (protected, read-only) + * The datum this component belongs to + * Never `null` in child procs +1. `report_signal_origin` (protected, boolean) + * If `TRUE`, will invoke the callback when signalled with the signal type as the first argument. + * `FALSE` by default. + +### Procs + +1. `/datum/proc/GetComponent(component_type(type)) -> datum/component?` (public, final) + * Returns a reference to a component of component_type if it exists in the datum, null otherwise +1. `/datum/proc/GetComponents(component_type(type)) -> list` (public, final) + * Returns a list of references to all components of component_type that exist in the datum +1. `/datum/proc/GetExactComponent(component_type(type)) -> datum/component?` (public, final) + * Returns a reference to a component whose type MATCHES component_type if that component exists in the datum, null otherwise +1. `SEND_SIGNAL(target, sigtype, ...)` (public, final) + * Use to send signals to target datum + * Extra arguments are to be specified in the signal definition + * Returns a bitflag with signal specific information assembled from all activated components + * Arguments are packaged in a list and handed off to _SendSignal() +1. `/datum/proc/AddComponent(component_type(type), ...) -> datum/component` (public, final) + * Creates an instance of `component_type` in the datum and passes `...` to its `Initialize()` call + * Sends the `COMSIG_COMPONENT_ADDED` signal to the datum + * All components a datum owns are deleted with the datum + * Returns the component that was created. Or the old component in a dupe situation where `COMPONENT_DUPE_UNIQUE` was set + * If this tries to add an component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it + * Properly handles duplicate situations based on the `dupe_mode` var +1. `/datum/proc/LoadComponent(component_type(type), ...) -> datum/component` (public, final) + * Equivalent to calling `GetComponent(component_type)` where, if the result would be `null`, returns `AddComponent(component_type, ...)` instead +1. `/datum/proc/ComponentActivated(datum/component/C)` (abstract, async) + * Called on a component's `parent` after a signal received causes it to activate. `src` is the parameter + * Will only be called if a component's callback returns `TRUE` +1. `/datum/proc/TakeComponent(datum/component/C)` (public, final) + * Properly transfers ownership of a component from one datum to another + * Signals `COMSIG_COMPONENT_REMOVING` on the parent + * Called on the datum you want to own the component with another datum's component +1. `/datum/proc/_SendSignal(signal, list/arguments)` (private, final) + * Handles most of the actual signaling procedure + * Will runtime if used on datums with an empty component list +1. `/datum/proc/RegisterSignal(datum/target, signal(string/list of strings), proc_ref(type), override(boolean))` (protected, final) + * If signal is a list it will be as if RegisterSignal was called for each of the entries with the same following arguments + * Makes the datum listen for the specified `signal` on it's `parent` datum. + * When that signal is received `proc_ref` will be called on the component, along with associated arguments + * Example proc ref: `.proc/OnEvent` + * If a previous registration is overwritten by the call, a runtime occurs. Setting `override` to TRUE prevents this + * These callbacks run asyncronously + * Returning `TRUE` from these callbacks will trigger a `TRUE` return from the `SendSignal()` that initiated it +1. `/datum/component/New(datum/parent, ...)` (private, final) + * Runs internal setup for the component + * Extra arguments are passed to `Initialize()` +1. `/datum/component/Initialize(...)` (abstract, no-sleep) + * Called by `New()` with the same argments excluding `parent` + * Component does not exist in `parent`'s `datum_components` list yet, although `parent` is set and may be used + * Signals will not be received while this function is running + * Component may be deleted after this function completes without being attached + * Do not call `qdel(src)` from this function +1. `/datum/component/Destroy(force(bool), silent(bool))` (virtual, no-sleep) + * Sends the `COMSIG_COMPONENT_REMOVING` signal to the parent datum if the `parent` isn't being qdeleted + * Properly removes the component from `parent` and cleans up references + * Setting `force` makes it not check for and remove the component from the parent + * Setting `silent` deletes the component without sending a `COMSIG_COMPONENT_REMOVING` signal +1. `/datum/component/proc/InheritComponent(datum/component/C, i_am_original(boolean))` (abstract, no-sleep) + * Called on a component when a component of the same type was added to the same parent + * See `/datum/component/var/dupe_mode` + * `C`'s type will always be the same of the called component +1. `/datum/component/proc/AfterComponentActivated()` (abstract, async) + * Called on a component that was activated after it's `parent`'s `ComponentActivated()` is called +1. `/datum/component/proc/OnTransfer(datum/new_parent)` (abstract, no-sleep) + * Called before `new_parent` is assigned to `parent` in `TakeComponent()` + * Allows the component to react to ownership transfers +1. `/datum/component/proc/_RemoveFromParent()` (private, final) + * Clears `parent` and removes the component from it's component list +1. `/datum/component/proc/_JoinParent` (private, final) + * Tries to add the component to it's `parent`s `datum_components` list +1. `/datum/component/proc/RegisterWithParent` (abstract, no-sleep) + * Used to register the signals that should be on the `parent` object + * Use this if you plan on the component transfering between parents +1. `/datum/component/proc/UnregisterFromParent` (abstract, no-sleep) + * Counterpart to `RegisterWithParent()` + * Used to unregister the signals that should only be on the `parent` object + +### See/Define signals and their arguments in __DEFINES\components.dm diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index f938a7030881..3ce3e2902f21 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() - return - -/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() - return - -/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() + return + +/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() + return + +/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/decals/blood.dm b/code/datums/components/decals/blood.dm index 7fae97567822..3114ddb24e9e 100644 --- a/code/datums/components/decals/blood.dm +++ b/code/datums/components/decals/blood.dm @@ -1,39 +1,39 @@ -/datum/component/decal/blood - dupe_mode = COMPONENT_DUPE_UNIQUE - -/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER) - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - . = ..() - RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name) - -/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color) - var/obj/item/I = parent - if(!_icon) - _icon = 'icons/effects/blood.dmi' - if(!_icon_state) - _icon_state = "itemblood" - var/icon = initial(I.icon) - var/icon_state = initial(I.icon_state) - if(!icon || !icon_state) - // It's something which takes on the look of other items, probably - icon = I.icon - icon_state = I.icon_state - var/static/list/blood_splatter_appearances = list() - //try to find a pre-processed blood-splatter. otherwise, make a new one - var/index = "[REF(icon)]-[icon_state]" - pic = blood_splatter_appearances[index] - - if(!pic) - var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object - blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) - blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant - pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state)) - blood_splatter_appearances[index] = pic - return TRUE - -/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override) - var/atom/A = parent - override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a" - override[EXAMINE_POSITION_BEFORE] = " blood-stained " - return COMPONENT_EXNAME_CHANGED +/datum/component/decal/blood + dupe_mode = COMPONENT_DUPE_UNIQUE + +/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + . = ..() + RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name) + +/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color) + var/obj/item/I = parent + if(!_icon) + _icon = 'icons/effects/blood.dmi' + if(!_icon_state) + _icon_state = "itemblood" + var/icon = initial(I.icon) + var/icon_state = initial(I.icon_state) + if(!icon || !icon_state) + // It's something which takes on the look of other items, probably + icon = I.icon + icon_state = I.icon_state + var/static/list/blood_splatter_appearances = list() + //try to find a pre-processed blood-splatter. otherwise, make a new one + var/index = "[REF(icon)]-[icon_state]" + pic = blood_splatter_appearances[index] + + if(!pic) + var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object + blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) + blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant + pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state)) + blood_splatter_appearances[index] = pic + return TRUE + +/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override) + var/atom/A = parent + override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a" + override[EXAMINE_POSITION_BEFORE] = " blood-stained " + return COMPONENT_EXNAME_CHANGED diff --git a/code/datums/components/forensics.dm b/code/datums/components/forensics.dm index f5a82ce5c4aa..f682308cae4c 100644 --- a/code/datums/components/forensics.dm +++ b/code/datums/components/forensics.dm @@ -1,184 +1,184 @@ -/datum/component/forensics - dupe_mode = COMPONENT_DUPE_UNIQUE - can_transfer = TRUE - var/list/fingerprints //assoc print = print - var/list/hiddenprints //assoc ckey = realname/gloves/ckey - var/list/blood_DNA //assoc dna = bloodtype - var/list/fibers //assoc print = print - -/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL. - fingerprints = fingerprints | F.fingerprints - hiddenprints = hiddenprints | F.hiddenprints - blood_DNA = blood_DNA | F.blood_DNA - fibers = fibers | F.fibers - check_blood() - return ..() - -/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers) - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - fingerprints = new_fingerprints - hiddenprints = new_hiddenprints - blood_DNA = new_blood_DNA - fibers = new_fibers - check_blood() - -/datum/component/forensics/RegisterWithParent() - check_blood() - RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act) - -/datum/component/forensics/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT)) - -/datum/component/forensics/PostTransfer() - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - -/datum/component/forensics/proc/wipe_fingerprints() - fingerprints = null - return TRUE - -/datum/component/forensics/proc/wipe_hiddenprints() - return //no. - -/datum/component/forensics/proc/wipe_blood_DNA() - blood_DNA = null - if(isitem(parent)) - qdel(parent.GetComponent(/datum/component/decal/blood)) - return TRUE - -/datum/component/forensics/proc/wipe_fibers() - fibers = null - return TRUE - -/datum/component/forensics/proc/clean_act(datum/source, strength) - if(strength >= CLEAN_STRENGTH_FINGERPRINTS) - wipe_fingerprints() - if(strength >= CLEAN_STRENGTH_BLOOD) - wipe_blood_DNA() - if(strength >= CLEAN_STRENGTH_FIBERS) - wipe_fibers() - -/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text) - if(!length(_fingerprints)) - return - LAZYINITLIST(fingerprints) - for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. - fingerprints[i] = i - return TRUE - -/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) - if(!isliving(M)) - if(!iscameramob(M)) - return - if(isaicamera(M)) - var/mob/camera/aiEye/ai_camera = M - if(!ai_camera.ai) - return - M = ai_camera.ai - add_hiddenprint(M) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - add_fibers(H) - if(H.gloves) //Check if the gloves (if any) hide fingerprints - var/obj/item/clothing/gloves/G = H.gloves - if(G.transfer_prints) - ignoregloves = TRUE - if(!ignoregloves) - H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop. - return - var/full_print = md5(H.dna.uni_identity) - LAZYSET(fingerprints, full_print, full_print) - return TRUE - -/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text) - if(!length(_fibertext)) - return - LAZYINITLIST(fibers) - for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours. - fibers[i] = i - return TRUE - -/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M) - var/fibertext - var/item_multiplier = isitem(src)?1.2:1 - if(M.wear_suit) - fibertext = "Material from \a [M.wear_suit]." - if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - if(!(M.wear_suit.body_parts_covered & CHEST)) - if(M.w_uniform) - fibertext = "Fibers from \a [M.w_uniform]." - if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed. - LAZYSET(fibers, fibertext, fibertext) - if(!(M.wear_suit.body_parts_covered & HANDS)) - if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - else if(M.w_uniform) - fibertext = "Fibers from \a [M.w_uniform]." - if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - // "Added fibertext: [fibertext]" - LAZYSET(fibers, fibertext, fibertext) - if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - else if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - return TRUE - -/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text) - if(!length(_hiddenprints)) - return - LAZYINITLIST(hiddenprints) - for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. - hiddenprints[i] = _hiddenprints[i] - return TRUE - -/datum/component/forensics/proc/add_hiddenprint(mob/M) - if(!isliving(M)) - if(!iscameramob(M)) - return - if(isaicamera(M)) - var/mob/camera/aiEye/ai_camera = M - if(!ai_camera.ai) - return - M = ai_camera.ai - if(!M.key) - return - var/hasgloves = "" - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.gloves) - hasgloves = "(gloves)" - var/current_time = time_stamp() - if(!LAZYACCESS(hiddenprints, M.key)) - LAZYSET(hiddenprints, M.key, "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]") - else - var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), " Last: ") - if(laststamppos) - LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos)) - hiddenprints[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else - var/atom/A = parent - A.fingerprintslast = M.ckey - return TRUE - -/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type) - if(!length(dna)) - return - LAZYINITLIST(blood_DNA) - for(var/i in dna) - blood_DNA[i] = dna[i] - check_blood() - return TRUE - -/datum/component/forensics/proc/check_blood() - if(!isitem(parent)) - return - if(!length(blood_DNA)) - return - parent.LoadComponent(/datum/component/decal/blood) +/datum/component/forensics + dupe_mode = COMPONENT_DUPE_UNIQUE + can_transfer = TRUE + var/list/fingerprints //assoc print = print + var/list/hiddenprints //assoc ckey = realname/gloves/ckey + var/list/blood_DNA //assoc dna = bloodtype + var/list/fibers //assoc print = print + +/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL. + fingerprints = fingerprints | F.fingerprints + hiddenprints = hiddenprints | F.hiddenprints + blood_DNA = blood_DNA | F.blood_DNA + fibers = fibers | F.fibers + check_blood() + return ..() + +/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + fingerprints = new_fingerprints + hiddenprints = new_hiddenprints + blood_DNA = new_blood_DNA + fibers = new_fibers + check_blood() + +/datum/component/forensics/RegisterWithParent() + check_blood() + RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act) + +/datum/component/forensics/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT)) + +/datum/component/forensics/PostTransfer() + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/forensics/proc/wipe_fingerprints() + fingerprints = null + return TRUE + +/datum/component/forensics/proc/wipe_hiddenprints() + return //no. + +/datum/component/forensics/proc/wipe_blood_DNA() + blood_DNA = null + if(isitem(parent)) + qdel(parent.GetComponent(/datum/component/decal/blood)) + return TRUE + +/datum/component/forensics/proc/wipe_fibers() + fibers = null + return TRUE + +/datum/component/forensics/proc/clean_act(datum/source, strength) + if(strength >= CLEAN_STRENGTH_FINGERPRINTS) + wipe_fingerprints() + if(strength >= CLEAN_STRENGTH_BLOOD) + wipe_blood_DNA() + if(strength >= CLEAN_STRENGTH_FIBERS) + wipe_fibers() + +/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text) + if(!length(_fingerprints)) + return + LAZYINITLIST(fingerprints) + for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. + fingerprints[i] = i + return TRUE + +/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) + if(!isliving(M)) + if(!iscameramob(M)) + return + if(isaicamera(M)) + var/mob/camera/aiEye/ai_camera = M + if(!ai_camera.ai) + return + M = ai_camera.ai + add_hiddenprint(M) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + add_fibers(H) + if(H.gloves) //Check if the gloves (if any) hide fingerprints + var/obj/item/clothing/gloves/G = H.gloves + if(G.transfer_prints) + ignoregloves = TRUE + if(!ignoregloves) + H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop. + return + var/full_print = md5(H.dna.uni_identity) + LAZYSET(fingerprints, full_print, full_print) + return TRUE + +/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text) + if(!length(_fibertext)) + return + LAZYINITLIST(fibers) + for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours. + fibers[i] = i + return TRUE + +/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M) + var/fibertext + var/item_multiplier = isitem(src)?1.2:1 + if(M.wear_suit) + fibertext = "Material from \a [M.wear_suit]." + if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + if(!(M.wear_suit.body_parts_covered & CHEST)) + if(M.w_uniform) + fibertext = "Fibers from \a [M.w_uniform]." + if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed. + LAZYSET(fibers, fibertext, fibertext) + if(!(M.wear_suit.body_parts_covered & HANDS)) + if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + else if(M.w_uniform) + fibertext = "Fibers from \a [M.w_uniform]." + if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + // "Added fibertext: [fibertext]" + LAZYSET(fibers, fibertext, fibertext) + if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + else if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + return TRUE + +/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text) + if(!length(_hiddenprints)) + return + LAZYINITLIST(hiddenprints) + for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. + hiddenprints[i] = _hiddenprints[i] + return TRUE + +/datum/component/forensics/proc/add_hiddenprint(mob/M) + if(!isliving(M)) + if(!iscameramob(M)) + return + if(isaicamera(M)) + var/mob/camera/aiEye/ai_camera = M + if(!ai_camera.ai) + return + M = ai_camera.ai + if(!M.key) + return + var/hasgloves = "" + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.gloves) + hasgloves = "(gloves)" + var/current_time = time_stamp() + if(!LAZYACCESS(hiddenprints, M.key)) + LAZYSET(hiddenprints, M.key, "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]") + else + var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), " Last: ") + if(laststamppos) + LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos)) + hiddenprints[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else + var/atom/A = parent + A.fingerprintslast = M.ckey + return TRUE + +/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type) + if(!length(dna)) + return + LAZYINITLIST(blood_DNA) + for(var/i in dna) + blood_DNA[i] = dna[i] + check_blood() + return TRUE + +/datum/component/forensics/proc/check_blood() + if(!isitem(parent)) + return + if(!length(blood_DNA)) + return + parent.LoadComponent(/datum/component/decal/blood) diff --git a/code/datums/components/jousting.dm b/code/datums/components/jousting.dm index 288e9693bc6e..ae96fcb4d0b0 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.is_sharp() - var/msg - if(damage) - msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]" - target.apply_damage(damage, BRUTE, user.zone_selected, 0) - if(prob(knockdown_chance)) - msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]" - if(target_buckled) - target.buckled.unbuckle_mob(target) - target.Paralyze(knockdown_time) - if(length(msg)) - user.visible_message("[msg]!") - -/datum/component/jousting/proc/mob_move(datum/source, newloc, dir) - if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled)))) - return - if(dir != current_direction) - current_tile_charge = 0 - current_direction = dir - if(current_tile_charge < max_tile_charge) - current_tile_charge++ - if(current_timerid) - deltimer(current_timerid) - current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE) - -/datum/component/jousting/proc/reset_charge() - current_tile_charge = 0 +/datum/component/jousting + var/current_direction = NONE + var/max_tile_charge = 5 + var/min_tile_charge = 2 //tiles before this code gets into effect. + var/current_tile_charge = 0 + var/movement_reset_tolerance = 2 //deciseconds + var/unmounted_damage_boost_per_tile = 0 + var/unmounted_knockdown_chance_per_tile = 0 + var/unmounted_knockdown_time = 0 + var/mounted_damage_boost_per_tile = 2 + var/mounted_knockdown_chance_per_tile = 20 + var/mounted_knockdown_time = 20 + var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to. + var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever. + var/mob/current_holder + var/current_timerid + +/datum/component/jousting/Initialize() + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop) + RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack) + +/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE) + current_holder = user + +/datum/component/jousting/proc/on_drop(datum/source, mob/user) + UnregisterSignal(user, COMSIG_MOVABLE_MOVED) + current_holder = null + current_direction = NONE + current_tile_charge = 0 + +/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user) + if(user != current_holder) + return + var/current = current_tile_charge + var/obj/item/I = parent + var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are. + if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge)) + return + var/turf/target_turf = get_step(user, current_direction) + if(target in range(1, target_turf)) + var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current + var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time) + var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current + var/sharp = I.is_sharp() + var/msg + if(damage) + msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]" + target.apply_damage(damage, BRUTE, user.zone_selected, 0) + if(prob(knockdown_chance)) + msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]" + if(target_buckled) + target.buckled.unbuckle_mob(target) + target.Paralyze(knockdown_time) + if(length(msg)) + user.visible_message("[msg]!") + +/datum/component/jousting/proc/mob_move(datum/source, newloc, dir) + if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled)))) + return + if(dir != current_direction) + current_tile_charge = 0 + current_direction = dir + if(current_tile_charge < max_tile_charge) + current_tile_charge++ + if(current_timerid) + deltimer(current_timerid) + current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE) + +/datum/component/jousting/proc/reset_charge() + current_tile_charge = 0 diff --git a/code/datums/components/lockon_aiming.dm b/code/datums/components/lockon_aiming.dm index bbdbb7dc6628..045611ab9d22 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/nanites.dm b/code/datums/components/nanites.dm index 0f1049e33a72..6ef6cf79edb4 100644 --- a/code/datums/components/nanites.dm +++ b/code/datums/components/nanites.dm @@ -1,316 +1,316 @@ -/datum/component/nanites - dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS - - var/mob/living/host_mob - var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs - var/max_nanites = 500 //maximum amount of nanites in the system - var/regen_rate = 0.5 //nanites generated per second - var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering - var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from - var/next_sync = 0 - var/list/datum/nanite_program/programs = list() - var/max_programs = NANITE_PROGRAM_LIMIT - - var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans, and does not display the program list on nanite scans - -/datum/component/nanites/Initialize(amount = 100, cloud = 0) - if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) - return COMPONENT_INCOMPATIBLE - - nanite_volume = amount - cloud_id = cloud - - //Nanites without hosts are non-interactive through normal means - if(isliving(parent)) - host_mob = parent - - if(!(MOB_ORGANIC in host_mob.mob_biotypes) && !(MOB_UNDEAD in host_mob.mob_biotypes)) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow. - return COMPONENT_INCOMPATIBLE - - host_mob.hud_set_nanite_indicator() - START_PROCESSING(SSnanites, src) - - if(cloud_id) - cloud_sync() - -/datum/component/nanites/RegisterWithParent() - RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites) - RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data) - RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs) - RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume) - RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites) - RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume) - RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud) - RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety) - RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen) - RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program) - RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan) - RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync) - - if(isliving(parent)) - RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp) - RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death) - RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access) - RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock) - RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock) - RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype) - RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) - RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal) - -/datum/component/nanites/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_HAS_NANITES, - COMSIG_NANITE_UI_DATA, - COMSIG_NANITE_GET_PROGRAMS, - COMSIG_NANITE_SET_VOLUME, - COMSIG_NANITE_ADJUST_VOLUME, - COMSIG_NANITE_SET_MAX_VOLUME, - COMSIG_NANITE_SET_CLOUD, - COMSIG_NANITE_SET_SAFETY, - COMSIG_NANITE_SET_REGEN, - COMSIG_NANITE_ADD_PROGRAM, - COMSIG_NANITE_SCAN, - COMSIG_NANITE_SYNC, - COMSIG_ATOM_EMP_ACT, - COMSIG_MOB_DEATH, - COMSIG_MOB_ALLOWED, - COMSIG_LIVING_ELECTROCUTE_ACT, - COMSIG_LIVING_MINOR_SHOCK, - COMSIG_MOVABLE_HEAR, - COMSIG_SPECIES_GAIN, - COMSIG_NANITE_SIGNAL, - COMSIG_NANITE_COMM_SIGNAL)) - -/datum/component/nanites/Destroy() - STOP_PROCESSING(SSnanites, src) - set_nanite_bar(TRUE) - QDEL_LIST(programs) - if(host_mob) - host_mob.hud_set_nanite_indicator() - host_mob = null - return ..() - -/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, list/arguments) - if(new_nanites) - adjust_nanites(null, new_nanites.nanite_volume) - else - adjust_nanites(null, arguments[1]) //just add to the nanite volume - -/datum/component/nanites/process() - adjust_nanites(null, regen_rate) - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_process() - set_nanite_bar() - if(cloud_id && world.time > next_sync) - cloud_sync() - next_sync = world.time + NANITE_SYNC_DELAY - -//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status) -/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE) - var/list/programs_to_remove = programs.Copy() - var/list/programs_to_add = source.programs.Copy() - for(var/X in programs) - var/datum/nanite_program/NP = X - for(var/Y in programs_to_add) - var/datum/nanite_program/SNP = Y - if(NP.type == SNP.type) - programs_to_remove -= NP - programs_to_add -= SNP - SNP.copy_programming(NP, copy_activation) - break - if(full_overwrite) - for(var/X in programs_to_remove) - qdel(X) - for(var/X in programs_to_add) - var/datum/nanite_program/SNP = X - add_program(null, SNP.copy()) - -/datum/component/nanites/proc/cloud_sync() - if(!cloud_id) - return - var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id) - if(backup) - var/datum/component/nanites/cloud_copy = backup.nanites - if(cloud_copy) - sync(null, cloud_copy) - -/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program) - for(var/X in programs) - var/datum/nanite_program/NP = X - if(NP.unique && NP.type == new_program.type) - qdel(NP) - if(programs.len >= max_programs) - return COMPONENT_PROGRAM_NOT_INSTALLED - if(source_program) - source_program.copy_programming(new_program) - programs += new_program - new_program.on_add(src) - return COMPONENT_PROGRAM_INSTALLED - -/datum/component/nanites/proc/consume_nanites(amount, force = FALSE) - if(!force && safety_threshold && (nanite_volume - amount < safety_threshold)) - return FALSE - adjust_nanites(null, -amount) - return (nanite_volume > 0) - -/datum/component/nanites/proc/adjust_nanites(datum/source, amount) - nanite_volume = CLAMP(nanite_volume + amount, 0, max_nanites) - if(nanite_volume <= 0) //oops we ran out - qdel(src) - -/datum/component/nanites/proc/set_nanite_bar(remove = FALSE) - var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD] - var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir) - holder.pixel_y = I.Height() - world.icon_size - holder.icon_state = null - if(remove || stealth) - return //bye icon - var/nanite_percent = (nanite_volume / max_nanites) * 100 - nanite_percent = CLAMP(CEILING(nanite_percent, 10), 10, 100) - holder.icon_state = "nanites[nanite_percent]" - -/datum/component/nanites/proc/on_emp(datum/source, severity) - nanite_volume *= (rand(0.60, 0.90)) //Lose 10-40% of nanites - adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume - if(prob(40/severity)) - cloud_id = 0 - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_emp(severity) - -/datum/component/nanites/proc/on_shock(datum/source, shock_damage) - nanite_volume *= (rand(0.45, 0.80)) //Lose 20-55% of nanites - adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_shock(shock_damage) - -/datum/component/nanites/proc/on_minor_shock(datum/source) - adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_minor_shock() - -/datum/component/nanites/proc/on_death(datum/source, gibbed) - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_death(gibbed) - -/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source") - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.receive_signal(code, source) - -/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source") - for(var/X in programs) - if(istype(X, /datum/nanite_program/triggered/comm)) - var/datum/nanite_program/triggered/comm/NP = X - NP.receive_comm_signal(comm_code, comm_message, comm_source) - -/datum/component/nanites/proc/check_viable_biotype() - if(!(MOB_ORGANIC in host_mob.mob_biotypes) && !(MOB_UNDEAD in host_mob.mob_biotypes)) - qdel(src) //bodytype no longer sustains nanites - -/datum/component/nanites/proc/check_access(datum/source, obj/O) - for(var/datum/nanite_program/triggered/access/access_program in programs) - if(access_program.activated) - return O.check_access_list(access_program.access) - else - return FALSE - return FALSE - -/datum/component/nanites/proc/set_volume(datum/source, amount) - nanite_volume = CLAMP(amount, 0, max_nanites) - -/datum/component/nanites/proc/set_max_volume(datum/source, amount) - max_nanites = max(1, max_nanites) - -/datum/component/nanites/proc/set_cloud(datum/source, amount) - cloud_id = CLAMP(amount, 0, 100) - -/datum/component/nanites/proc/set_safety(datum/source, amount) - safety_threshold = CLAMP(amount, 0, max_nanites) - -/datum/component/nanites/proc/set_regen(datum/source, amount) - regen_rate = amount - -/datum/component/nanites/proc/confirm_nanites() - return TRUE //yup i exist - -/datum/component/nanites/proc/get_data(list/nanite_data) - nanite_data["nanite_volume"] = nanite_volume - nanite_data["max_nanites"] = max_nanites - nanite_data["cloud_id"] = cloud_id - nanite_data["regen_rate"] = regen_rate - nanite_data["safety_threshold"] = safety_threshold - nanite_data["stealth"] = stealth - -/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs) - nanite_programs |= programs - -/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan) - if(!full_scan) - if(!stealth) - to_chat(user, "Nanites Detected") - to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") - return TRUE - else - to_chat(user, "NANITES DETECTED") - to_chat(user, "================") - to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") - to_chat(user, "Safety Threshold: [safety_threshold]") - to_chat(user, "Cloud ID: [cloud_id ? cloud_id : "Disabled"]") - to_chat(user, "================") - to_chat(user, "Program List:") - if(stealth) - to_chat(user, "%#$ENCRYPTED&^@") - else - for(var/X in programs) - var/datum/nanite_program/NP = X - to_chat(user, "[NP.name] | [NP.activated ? "Active" : "Inactive"]") - return TRUE - -/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level) - data["has_nanites"] = TRUE - data["nanite_volume"] = nanite_volume - data["regen_rate"] = regen_rate - data["safety_threshold"] = safety_threshold - data["cloud_id"] = cloud_id - var/list/mob_programs = list() - var/id = 1 - for(var/X in programs) - var/datum/nanite_program/P = X - var/list/mob_program = list() - mob_program["name"] = P.name - mob_program["desc"] = P.desc - mob_program["id"] = id - - if(scan_level >= 2) - mob_program["activated"] = P.activated - mob_program["use_rate"] = P.use_rate - mob_program["can_trigger"] = P.can_trigger - mob_program["trigger_cost"] = P.trigger_cost - mob_program["trigger_cooldown"] = P.trigger_cooldown / 10 - - if(scan_level >= 3) - mob_program["activation_delay"] = P.activation_delay - mob_program["timer"] = P.timer - mob_program["timer_type"] = P.get_timer_type_text() - var/list/extra_settings = list() - for(var/Y in P.extra_settings) - var/list/setting = list() - setting["name"] = Y - setting["value"] = P.get_extra_setting(Y) - extra_settings += list(setting) - mob_program["extra_settings"] = extra_settings - if(LAZYLEN(extra_settings)) - mob_program["has_extra_settings"] = TRUE - - if(scan_level >= 4) - mob_program["activation_code"] = P.activation_code - mob_program["deactivation_code"] = P.deactivation_code - mob_program["kill_code"] = P.kill_code - mob_program["trigger_code"] = P.trigger_code - id++ - mob_programs += list(mob_program) - data["mob_programs"] = mob_programs +/datum/component/nanites + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + + var/mob/living/host_mob + var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs + var/max_nanites = 500 //maximum amount of nanites in the system + var/regen_rate = 0.5 //nanites generated per second + var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering + var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from + var/next_sync = 0 + var/list/datum/nanite_program/programs = list() + var/max_programs = NANITE_PROGRAM_LIMIT + + var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans, and does not display the program list on nanite scans + +/datum/component/nanites/Initialize(amount = 100, cloud = 0) + if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) + return COMPONENT_INCOMPATIBLE + + nanite_volume = amount + cloud_id = cloud + + //Nanites without hosts are non-interactive through normal means + if(isliving(parent)) + host_mob = parent + + if(!(MOB_ORGANIC in host_mob.mob_biotypes) && !(MOB_UNDEAD in host_mob.mob_biotypes)) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow. + return COMPONENT_INCOMPATIBLE + + host_mob.hud_set_nanite_indicator() + START_PROCESSING(SSnanites, src) + + if(cloud_id) + cloud_sync() + +/datum/component/nanites/RegisterWithParent() + RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites) + RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data) + RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs) + RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume) + RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites) + RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume) + RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud) + RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety) + RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen) + RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program) + RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan) + RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync) + + if(isliving(parent)) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp) + RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death) + RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access) + RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock) + RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock) + RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype) + RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) + RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal) + +/datum/component/nanites/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_HAS_NANITES, + COMSIG_NANITE_UI_DATA, + COMSIG_NANITE_GET_PROGRAMS, + COMSIG_NANITE_SET_VOLUME, + COMSIG_NANITE_ADJUST_VOLUME, + COMSIG_NANITE_SET_MAX_VOLUME, + COMSIG_NANITE_SET_CLOUD, + COMSIG_NANITE_SET_SAFETY, + COMSIG_NANITE_SET_REGEN, + COMSIG_NANITE_ADD_PROGRAM, + COMSIG_NANITE_SCAN, + COMSIG_NANITE_SYNC, + COMSIG_ATOM_EMP_ACT, + COMSIG_MOB_DEATH, + COMSIG_MOB_ALLOWED, + COMSIG_LIVING_ELECTROCUTE_ACT, + COMSIG_LIVING_MINOR_SHOCK, + COMSIG_MOVABLE_HEAR, + COMSIG_SPECIES_GAIN, + COMSIG_NANITE_SIGNAL, + COMSIG_NANITE_COMM_SIGNAL)) + +/datum/component/nanites/Destroy() + STOP_PROCESSING(SSnanites, src) + set_nanite_bar(TRUE) + QDEL_LIST(programs) + if(host_mob) + host_mob.hud_set_nanite_indicator() + host_mob = null + return ..() + +/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, list/arguments) + if(new_nanites) + adjust_nanites(null, new_nanites.nanite_volume) + else + adjust_nanites(null, arguments[1]) //just add to the nanite volume + +/datum/component/nanites/process() + adjust_nanites(null, regen_rate) + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_process() + set_nanite_bar() + if(cloud_id && world.time > next_sync) + cloud_sync() + next_sync = world.time + NANITE_SYNC_DELAY + +//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status) +/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE) + var/list/programs_to_remove = programs.Copy() + var/list/programs_to_add = source.programs.Copy() + for(var/X in programs) + var/datum/nanite_program/NP = X + for(var/Y in programs_to_add) + var/datum/nanite_program/SNP = Y + if(NP.type == SNP.type) + programs_to_remove -= NP + programs_to_add -= SNP + SNP.copy_programming(NP, copy_activation) + break + if(full_overwrite) + for(var/X in programs_to_remove) + qdel(X) + for(var/X in programs_to_add) + var/datum/nanite_program/SNP = X + add_program(null, SNP.copy()) + +/datum/component/nanites/proc/cloud_sync() + if(!cloud_id) + return + var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id) + if(backup) + var/datum/component/nanites/cloud_copy = backup.nanites + if(cloud_copy) + sync(null, cloud_copy) + +/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program) + for(var/X in programs) + var/datum/nanite_program/NP = X + if(NP.unique && NP.type == new_program.type) + qdel(NP) + if(programs.len >= max_programs) + return COMPONENT_PROGRAM_NOT_INSTALLED + if(source_program) + source_program.copy_programming(new_program) + programs += new_program + new_program.on_add(src) + return COMPONENT_PROGRAM_INSTALLED + +/datum/component/nanites/proc/consume_nanites(amount, force = FALSE) + if(!force && safety_threshold && (nanite_volume - amount < safety_threshold)) + return FALSE + adjust_nanites(null, -amount) + return (nanite_volume > 0) + +/datum/component/nanites/proc/adjust_nanites(datum/source, amount) + nanite_volume = CLAMP(nanite_volume + amount, 0, max_nanites) + if(nanite_volume <= 0) //oops we ran out + qdel(src) + +/datum/component/nanites/proc/set_nanite_bar(remove = FALSE) + var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD] + var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir) + holder.pixel_y = I.Height() - world.icon_size + holder.icon_state = null + if(remove || stealth) + return //bye icon + var/nanite_percent = (nanite_volume / max_nanites) * 100 + nanite_percent = CLAMP(CEILING(nanite_percent, 10), 10, 100) + holder.icon_state = "nanites[nanite_percent]" + +/datum/component/nanites/proc/on_emp(datum/source, severity) + nanite_volume *= (rand(0.60, 0.90)) //Lose 10-40% of nanites + adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume + if(prob(40/severity)) + cloud_id = 0 + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_emp(severity) + +/datum/component/nanites/proc/on_shock(datum/source, shock_damage) + nanite_volume *= (rand(0.45, 0.80)) //Lose 20-55% of nanites + adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_shock(shock_damage) + +/datum/component/nanites/proc/on_minor_shock(datum/source) + adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_minor_shock() + +/datum/component/nanites/proc/on_death(datum/source, gibbed) + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_death(gibbed) + +/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source") + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.receive_signal(code, source) + +/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source") + for(var/X in programs) + if(istype(X, /datum/nanite_program/triggered/comm)) + var/datum/nanite_program/triggered/comm/NP = X + NP.receive_comm_signal(comm_code, comm_message, comm_source) + +/datum/component/nanites/proc/check_viable_biotype() + if(!(MOB_ORGANIC in host_mob.mob_biotypes) && !(MOB_UNDEAD in host_mob.mob_biotypes)) + qdel(src) //bodytype no longer sustains nanites + +/datum/component/nanites/proc/check_access(datum/source, obj/O) + for(var/datum/nanite_program/triggered/access/access_program in programs) + if(access_program.activated) + return O.check_access_list(access_program.access) + else + return FALSE + return FALSE + +/datum/component/nanites/proc/set_volume(datum/source, amount) + nanite_volume = CLAMP(amount, 0, max_nanites) + +/datum/component/nanites/proc/set_max_volume(datum/source, amount) + max_nanites = max(1, max_nanites) + +/datum/component/nanites/proc/set_cloud(datum/source, amount) + cloud_id = CLAMP(amount, 0, 100) + +/datum/component/nanites/proc/set_safety(datum/source, amount) + safety_threshold = CLAMP(amount, 0, max_nanites) + +/datum/component/nanites/proc/set_regen(datum/source, amount) + regen_rate = amount + +/datum/component/nanites/proc/confirm_nanites() + return TRUE //yup i exist + +/datum/component/nanites/proc/get_data(list/nanite_data) + nanite_data["nanite_volume"] = nanite_volume + nanite_data["max_nanites"] = max_nanites + nanite_data["cloud_id"] = cloud_id + nanite_data["regen_rate"] = regen_rate + nanite_data["safety_threshold"] = safety_threshold + nanite_data["stealth"] = stealth + +/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs) + nanite_programs |= programs + +/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan) + if(!full_scan) + if(!stealth) + to_chat(user, "Nanites Detected") + to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") + return TRUE + else + to_chat(user, "NANITES DETECTED") + to_chat(user, "================") + to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") + to_chat(user, "Safety Threshold: [safety_threshold]") + to_chat(user, "Cloud ID: [cloud_id ? cloud_id : "Disabled"]") + to_chat(user, "================") + to_chat(user, "Program List:") + if(stealth) + to_chat(user, "%#$ENCRYPTED&^@") + else + for(var/X in programs) + var/datum/nanite_program/NP = X + to_chat(user, "[NP.name] | [NP.activated ? "Active" : "Inactive"]") + return TRUE + +/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level) + data["has_nanites"] = TRUE + data["nanite_volume"] = nanite_volume + data["regen_rate"] = regen_rate + data["safety_threshold"] = safety_threshold + data["cloud_id"] = cloud_id + var/list/mob_programs = list() + var/id = 1 + for(var/X in programs) + var/datum/nanite_program/P = X + var/list/mob_program = list() + mob_program["name"] = P.name + mob_program["desc"] = P.desc + mob_program["id"] = id + + if(scan_level >= 2) + mob_program["activated"] = P.activated + mob_program["use_rate"] = P.use_rate + mob_program["can_trigger"] = P.can_trigger + mob_program["trigger_cost"] = P.trigger_cost + mob_program["trigger_cooldown"] = P.trigger_cooldown / 10 + + if(scan_level >= 3) + mob_program["activation_delay"] = P.activation_delay + mob_program["timer"] = P.timer + mob_program["timer_type"] = P.get_timer_type_text() + var/list/extra_settings = list() + for(var/Y in P.extra_settings) + var/list/setting = list() + setting["name"] = Y + setting["value"] = P.get_extra_setting(Y) + extra_settings += list(setting) + mob_program["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + mob_program["has_extra_settings"] = TRUE + + if(scan_level >= 4) + mob_program["activation_code"] = P.activation_code + mob_program["deactivation_code"] = P.deactivation_code + mob_program["kill_code"] = P.kill_code + mob_program["trigger_code"] = P.trigger_code + id++ + mob_programs += list(mob_program) + data["mob_programs"] = mob_programs diff --git a/code/datums/components/ntnet_interface.dm b/code/datums/components/ntnet_interface.dm index 7623d5ef8669..3a8fb559bf48 100644 --- a/code/datums/components/ntnet_interface.dm +++ b/code/datums/components/ntnet_interface.dm @@ -1,66 +1,66 @@ -//Thing meant for allowing datums and objects to access 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! - - 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! + + 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 27377c47dd87..2bb1cf9919b6 100644 --- a/code/datums/components/riding.dm +++ b/code/datums/components/riding.dm @@ -1,338 +1,338 @@ -/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/del_on_unbuckle_all = FALSE - -/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) - var/atom/movable/AM = parent - restore_position(M) - unequip_buckle_inhands(M) - if(del_on_unbuckle_all && !AM.has_buckled_mobs()) - qdel(src) - -/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) - handle_vehicle_offsets() - -/datum/component/riding/proc/handle_vehicle_layer() - var/atom/movable/AM = parent - var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER) - . = defaults["[AM.dir]"] - if(directional_vehicle_layers["[AM.dir]"]) - . = directional_vehicle_layers["[AM.dir]"] - if(isnull(.)) //you can set it to null to not change it. - . = AM.layer - AM.layer = . - -/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer) - directional_vehicle_layers["[dir]"] = layer - -/datum/component/riding/proc/vehicle_moved(datum/source) - var/atom/movable/AM = parent - for(var/i in AM.buckled_mobs) - ride_check(i) - handle_vehicle_offsets() - handle_vehicle_layer() - -/datum/component/riding/proc/ride_check(mob/living/M) - var/atom/movable/AM = parent - var/mob/AMM = AM - 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 * CONFIG_GET(number/movedelay/run_delay))) //yogs - fixed this to work with movespeed - 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 - del_on_unbuckle_all = TRUE - -/datum/component/riding/human/Initialize() - . = ..() - RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_host_unarmed_melee) - -/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target) - var/mob/living/carbon/human/AM = parent - if(AM.a_intent == INTENT_DISARM && (target in AM.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) - if(AM.dir == SOUTH) - AM.layer = ABOVE_MOB_LAYER - else - AM.layer = OBJ_LAYER - else - AM.layer = MOB_LAYER - -/datum/component/riding/human/force_dismount(mob/living/user) - var/atom/movable/AM = parent - AM.unbuckle_mob(user) - user.Paralyze(60) - user.visible_message("[AM] pushes [user] off of [AM.p_them()]!") - -/datum/component/riding/cyborg - del_on_unbuckle_all = TRUE - -/datum/component/riding/cyborg/ride_check(mob/user) - var/atom/movable/AM = parent - if(user.incapacitated()) - var/kick = TRUE - if(iscyborg(AM)) - var/mob/living/silicon/robot/R = AM - if(R.module && R.module.ride_allow_incapacitated) - kick = FALSE - if(kick) - to_chat(user, "You fall off of [AM]!") - Unbuckle(user) - return - if(iscarbon(user)) - var/mob/living/carbon/carbonuser = user - if(!carbonuser.get_num_arms()) - Unbuckle(user) - to_chat(user, "You can't grab onto [AM] with no hands!") - return - -/datum/component/riding/cyborg/handle_vehicle_layer() - var/atom/movable/AM = parent - if(AM.buckled_mobs && AM.buckled_mobs.len) - if(AM.dir == SOUTH) - AM.layer = ABOVE_MOB_LAYER - else - AM.layer = OBJ_LAYER - else - AM.layer = MOB_LAYER - -/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer) - return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3)) - -/datum/component/riding/cyborg/handle_vehicle_offsets() - var/atom/movable/AM = parent - if(AM.has_buckled_mobs()) - for(var/mob/living/M in AM.buckled_mobs) - M.setDir(AM.dir) - if(iscyborg(AM)) - var/mob/living/silicon/robot/R = AM - if(istype(R.module)) - M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)] - M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)] - else - ..() - -/datum/component/riding/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.Paralyze(60) - -/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1) - var/atom/movable/AM = parent - var/amount_equipped = 0 - for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--) - var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user) - inhand.rider = user - inhand.parent = AM - if(user.put_in_hands(inhand, TRUE)) - amount_equipped++ - else - break - if(amount_equipped >= amount_required) - return TRUE - else - unequip_buckle_inhands(user) - return FALSE - -/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user) - var/atom/movable/AM = parent - for(var/obj/item/riding_offhand/O in user.contents) - if(O.parent != AM) - CRASH("RIDING OFFHAND ON WRONG MOB") - continue - if(O.selfdeleting) - continue - else - qdel(O) - return TRUE - -/obj/item/riding_offhand - name = "offhand" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "offhand" - w_class = WEIGHT_CLASS_HUGE - item_flags = ABSTRACT | DROPDEL | NOBLUDGEON - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/mob/living/carbon/rider - var/mob/living/parent - var/selfdeleting = FALSE - -/obj/item/riding_offhand/dropped() - selfdeleting = TRUE - . = ..() - -/obj/item/riding_offhand/equipped() - if(loc != rider) - 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/del_on_unbuckle_all = FALSE + +/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) + var/atom/movable/AM = parent + restore_position(M) + unequip_buckle_inhands(M) + if(del_on_unbuckle_all && !AM.has_buckled_mobs()) + qdel(src) + +/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) + handle_vehicle_offsets() + +/datum/component/riding/proc/handle_vehicle_layer() + var/atom/movable/AM = parent + var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER) + . = defaults["[AM.dir]"] + if(directional_vehicle_layers["[AM.dir]"]) + . = directional_vehicle_layers["[AM.dir]"] + if(isnull(.)) //you can set it to null to not change it. + . = AM.layer + AM.layer = . + +/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer) + directional_vehicle_layers["[dir]"] = layer + +/datum/component/riding/proc/vehicle_moved(datum/source) + var/atom/movable/AM = parent + for(var/i in AM.buckled_mobs) + ride_check(i) + handle_vehicle_offsets() + handle_vehicle_layer() + +/datum/component/riding/proc/ride_check(mob/living/M) + var/atom/movable/AM = parent + var/mob/AMM = AM + 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 * CONFIG_GET(number/movedelay/run_delay))) //yogs - fixed this to work with movespeed + 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 + del_on_unbuckle_all = TRUE + +/datum/component/riding/human/Initialize() + . = ..() + RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_host_unarmed_melee) + +/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target) + var/mob/living/carbon/human/AM = parent + if(AM.a_intent == INTENT_DISARM && (target in AM.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) + if(AM.dir == SOUTH) + AM.layer = ABOVE_MOB_LAYER + else + AM.layer = OBJ_LAYER + else + AM.layer = MOB_LAYER + +/datum/component/riding/human/force_dismount(mob/living/user) + var/atom/movable/AM = parent + AM.unbuckle_mob(user) + user.Paralyze(60) + user.visible_message("[AM] pushes [user] off of [AM.p_them()]!") + +/datum/component/riding/cyborg + del_on_unbuckle_all = TRUE + +/datum/component/riding/cyborg/ride_check(mob/user) + var/atom/movable/AM = parent + if(user.incapacitated()) + var/kick = TRUE + if(iscyborg(AM)) + var/mob/living/silicon/robot/R = AM + if(R.module && R.module.ride_allow_incapacitated) + kick = FALSE + if(kick) + to_chat(user, "You fall off of [AM]!") + Unbuckle(user) + return + if(iscarbon(user)) + var/mob/living/carbon/carbonuser = user + if(!carbonuser.get_num_arms()) + Unbuckle(user) + to_chat(user, "You can't grab onto [AM] with no hands!") + return + +/datum/component/riding/cyborg/handle_vehicle_layer() + var/atom/movable/AM = parent + if(AM.buckled_mobs && AM.buckled_mobs.len) + if(AM.dir == SOUTH) + AM.layer = ABOVE_MOB_LAYER + else + AM.layer = OBJ_LAYER + else + AM.layer = MOB_LAYER + +/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer) + return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3)) + +/datum/component/riding/cyborg/handle_vehicle_offsets() + var/atom/movable/AM = parent + if(AM.has_buckled_mobs()) + for(var/mob/living/M in AM.buckled_mobs) + M.setDir(AM.dir) + if(iscyborg(AM)) + var/mob/living/silicon/robot/R = AM + if(istype(R.module)) + M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)] + M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)] + else + ..() + +/datum/component/riding/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.Paralyze(60) + +/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1) + var/atom/movable/AM = parent + var/amount_equipped = 0 + for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--) + var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user) + inhand.rider = user + inhand.parent = AM + if(user.put_in_hands(inhand, TRUE)) + amount_equipped++ + else + break + if(amount_equipped >= amount_required) + return TRUE + else + unequip_buckle_inhands(user) + return FALSE + +/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user) + var/atom/movable/AM = parent + for(var/obj/item/riding_offhand/O in user.contents) + if(O.parent != AM) + CRASH("RIDING OFFHAND ON WRONG MOB") + continue + if(O.selfdeleting) + continue + else + qdel(O) + return TRUE + +/obj/item/riding_offhand + name = "offhand" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "offhand" + w_class = WEIGHT_CLASS_HUGE + item_flags = ABSTRACT | DROPDEL | NOBLUDGEON + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/mob/living/carbon/rider + var/mob/living/parent + var/selfdeleting = FALSE + +/obj/item/riding_offhand/dropped() + selfdeleting = TRUE + . = ..() + +/obj/item/riding_offhand/equipped() + if(loc != rider) + 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 0350abcf6037..172f252d5af2 100644 --- a/code/datums/components/slippery.dm +++ b/code/datums/components/slippery.dm @@ -1,19 +1,19 @@ -/datum/component/slippery - var/force_drop_items = FALSE - var/knockdown_time = 0 - var/paralyze_time = 0 - var/lube_flags - var/datum/callback/callback - -/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _paralyze, _force_drop = FALSE) - knockdown_time = max(_knockdown, 0) - paralyze_time = max(_paralyze, 0) - force_drop_items = _force_drop - lube_flags = _lube_flags - callback = _callback - RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip) - -/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM) - var/mob/victim = AM - if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback) - callback.Invoke(victim) +/datum/component/slippery + var/force_drop_items = FALSE + var/knockdown_time = 0 + var/paralyze_time = 0 + var/lube_flags + var/datum/callback/callback + +/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _paralyze, _force_drop = FALSE) + knockdown_time = max(_knockdown, 0) + paralyze_time = max(_paralyze, 0) + force_drop_items = _force_drop + lube_flags = _lube_flags + callback = _callback + RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip) + +/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM) + var/mob/victim = AM + if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback) + callback.Invoke(victim) diff --git a/code/datums/components/spill.dm b/code/datums/components/spill.dm index ae41590ddb16..1a9a526ec342 100644 --- a/code/datums/components/spill.dm +++ b/code/datums/components/spill.dm @@ -1,57 +1,57 @@ -// This component is for forcing strange things into your pocket that fall out if you fall down -// Yes this exists purely for the spaghetti meme - -/datum/component/spill - can_transfer = TRUE - var/preexisting_item_flags - - var/list/droptext - var/list/dropsound - -// droptext is an arglist for visible_message -// dropsound is a list of potential sounds that gets picked from -/datum/component/spill/Initialize(list/_droptext, list/_dropsound) - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - - if(_droptext && !islist(_droptext)) - _droptext = list(_droptext) - droptext = _droptext - - if(_dropsound && !islist(_dropsound)) - _dropsound = list(_dropsound) - dropsound = _dropsound - -/datum/component/spill/PostTransfer() - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - -/datum/component/spill/RegisterWithParent() - RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react) - RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react) - var/obj/item/master = parent - preexisting_item_flags = master.item_flags - master.item_flags |= ITEM_SLOT_POCKET - -/datum/component/spill/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) - var/obj/item/master = parent - if(!(preexisting_item_flags & ITEM_SLOT_POCKET)) - master.item_flags &= ~ITEM_SLOT_POCKET - -/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot) - if(slot == SLOT_L_STORE || slot == SLOT_R_STORE) - RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE) - else - UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN) - -/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper) - UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN) - -/datum/component/spill/proc/knockdown_react(mob/living/fool) - var/obj/item/master = parent - fool.dropItemToGround(master) - if(droptext) - fool.visible_message(arglist(droptext)) - if(dropsound) - playsound(master, pick(dropsound), 30) +// This component is for forcing strange things into your pocket that fall out if you fall down +// Yes this exists purely for the spaghetti meme + +/datum/component/spill + can_transfer = TRUE + var/preexisting_item_flags + + var/list/droptext + var/list/dropsound + +// droptext is an arglist for visible_message +// dropsound is a list of potential sounds that gets picked from +/datum/component/spill/Initialize(list/_droptext, list/_dropsound) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + if(_droptext && !islist(_droptext)) + _droptext = list(_droptext) + droptext = _droptext + + if(_dropsound && !islist(_dropsound)) + _dropsound = list(_dropsound) + dropsound = _dropsound + +/datum/component/spill/PostTransfer() + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/spill/RegisterWithParent() + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react) + var/obj/item/master = parent + preexisting_item_flags = master.item_flags + master.item_flags |= ITEM_SLOT_POCKET + +/datum/component/spill/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) + var/obj/item/master = parent + if(!(preexisting_item_flags & ITEM_SLOT_POCKET)) + master.item_flags &= ~ITEM_SLOT_POCKET + +/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot) + if(slot == SLOT_L_STORE || slot == SLOT_R_STORE) + RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE) + else + UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN) + +/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper) + UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN) + +/datum/component/spill/proc/knockdown_react(mob/living/fool) + var/obj/item/master = parent + fool.dropItemToGround(master) + if(droptext) + fool.visible_message(arglist(droptext)) + if(dropsound) + playsound(master, pick(dropsound), 30) diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm index f8ab3a8c8b96..89f669380af9 100644 --- a/code/datums/components/storage/concrete/_concrete.dm +++ b/code/datums/components/storage/concrete/_concrete.dm @@ -1,201 +1,201 @@ - -// External storage-related logic: -// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages -// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement - -/datum/component/storage/concrete - can_transfer = TRUE - var/drop_all_on_deconstruct = TRUE - var/drop_all_on_destroy = FALSE - var/transfer_contents_on_component_transfer = FALSE - var/list/datum/component/storage/slaves = list() - - var/list/_contents_limbo // Where objects go to live mid transfer - var/list/_user_limbo // The last users before the component started moving - -/datum/component/storage/concrete/Initialize() - . = ..() - RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del) - RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct) - -/datum/component/storage/concrete/Destroy() - var/atom/real_location = real_location() - for(var/atom/_A in real_location) - _A.mouse_opacity = initial(_A.mouse_opacity) - if(drop_all_on_destroy) - do_quick_empty() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.change_master(null) - QDEL_LIST(_contents_limbo) - _user_limbo = null - return ..() - -/datum/component/storage/concrete/master() - return src - -/datum/component/storage/concrete/real_location() - return parent - -/datum/component/storage/concrete/PreTransfer() - if(is_using) - _user_limbo = is_using.Copy() - close_all() - if(transfer_contents_on_component_transfer) - _contents_limbo = list() - for(var/atom/movable/AM in parent) - _contents_limbo += AM - AM.moveToNullspace() - -/datum/component/storage/concrete/PostTransfer() - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - if(transfer_contents_on_component_transfer) - for(var/i in _contents_limbo) - var/atom/movable/AM = i - AM.forceMove(parent) - _contents_limbo = null - if(_user_limbo) - for(var/i in _user_limbo) - show_to(i) - _user_limbo = null - -/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE) - . = TRUE - var/atom/real_location = real_location() - if(I.loc != real_location) - I.forceMove(real_location) - refresh_mob_views() - -/datum/component/storage/concrete/refresh_mob_views() - . = ..() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.refresh_mob_views() - -/datum/component/storage/concrete/emp_act(datum/source, severity) - if(emp_shielded) - return - var/atom/real_location = real_location() - for(var/i in real_location) - var/atom/A = i - A.emp_act(severity) - -/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S) - if(S == src) - return FALSE - slaves += S - return TRUE - -/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S) - slaves -= S - return FALSE - -/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A) - var/atom/real_location = parent - if(A in real_location) - usr = null - remove_from_storage(A, null) - -/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled) - if(drop_all_on_deconstruct) - do_quick_empty() - -/datum/component/storage/concrete/can_see_contents() - . = ..() - for(var/i in slaves) - var/datum/component/storage/slave = i - . |= slave.can_see_contents() - -//Resets screen loc and other vars of something being removed from storage. -/datum/component/storage/concrete/_removal_reset(atom/movable/thing) - thing.layer = initial(thing.layer) - thing.plane = initial(thing.plane) - thing.mouse_opacity = initial(thing.mouse_opacity) - if(thing.maptext) - thing.maptext = "" - -/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location) - //Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped - //or moving objects, things that should never happen - var/atom/parent = src.parent - var/list/seeing_mobs = can_see_contents() - for(var/mob/M in seeing_mobs) - M.client.screen -= AM - if(ismob(parent.loc) && isitem(AM)) - var/obj/item/I = AM - var/mob/M = parent.loc - I.dropped(M) - I.item_flags &= ~IN_STORAGE - if(new_location) - //Reset the items values - _removal_reset(AM) - AM.forceMove(new_location) - //We don't want to call this if the item is being destroyed - AM.on_exit_storage(src) - else - //Being destroyed, just move to nullspace now (so it's not in contents for the icon update) - AM.moveToNullspace() - refresh_mob_views() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - return TRUE - -/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M) - return TRUE - -/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M) - . = handle_item_insertion(I, prevent_warning, M, slave) - if(. && !prevent_warning) - slave.mob_item_insertion_feedback(usr, M, I) - -/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum - var/datum/component/storage/concrete/master = master() - var/atom/parent = src.parent - var/moved = FALSE - if(!istype(I)) - return FALSE - if(M) - if(!M.temporarilyRemoveItemFromInventory(I)) - return FALSE - else - moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever. - if(I.pulledby) - I.pulledby.stop_pulling() - if(silent) - prevent_warning = TRUE - if(!_insert_physical_item(I)) - if(moved) - if(M) - if(!M.put_in_active_hand(I)) - I.forceMove(parent.drop_location()) - else - I.forceMove(parent.drop_location()) - return FALSE - I.on_enter_storage(master) - I.item_flags |= IN_STORAGE - refresh_mob_views() - I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt - if(M) - if(M.client && M.active_storage != src) - M.client.screen -= I - if(M.observers && M.observers.len) - for(var/i in M.observers) - var/mob/dead/observe = i - if(observe.client && observe.active_storage != src) - observe.client.screen -= I - if(!remote) - parent.add_fingerprint(M) - if(!prevent_warning) - mob_item_insertion_feedback(usr, M, I) - update_icon() - return TRUE - -/datum/component/storage/concrete/update_icon() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.update_icon() + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage/concrete + can_transfer = TRUE + var/drop_all_on_deconstruct = TRUE + var/drop_all_on_destroy = FALSE + var/transfer_contents_on_component_transfer = FALSE + var/list/datum/component/storage/slaves = list() + + var/list/_contents_limbo // Where objects go to live mid transfer + var/list/_user_limbo // The last users before the component started moving + +/datum/component/storage/concrete/Initialize() + . = ..() + RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del) + RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct) + +/datum/component/storage/concrete/Destroy() + var/atom/real_location = real_location() + for(var/atom/_A in real_location) + _A.mouse_opacity = initial(_A.mouse_opacity) + if(drop_all_on_destroy) + do_quick_empty() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.change_master(null) + QDEL_LIST(_contents_limbo) + _user_limbo = null + return ..() + +/datum/component/storage/concrete/master() + return src + +/datum/component/storage/concrete/real_location() + return parent + +/datum/component/storage/concrete/PreTransfer() + if(is_using) + _user_limbo = is_using.Copy() + close_all() + if(transfer_contents_on_component_transfer) + _contents_limbo = list() + for(var/atom/movable/AM in parent) + _contents_limbo += AM + AM.moveToNullspace() + +/datum/component/storage/concrete/PostTransfer() + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if(transfer_contents_on_component_transfer) + for(var/i in _contents_limbo) + var/atom/movable/AM = i + AM.forceMove(parent) + _contents_limbo = null + if(_user_limbo) + for(var/i in _user_limbo) + show_to(i) + _user_limbo = null + +/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE) + . = TRUE + var/atom/real_location = real_location() + if(I.loc != real_location) + I.forceMove(real_location) + refresh_mob_views() + +/datum/component/storage/concrete/refresh_mob_views() + . = ..() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.refresh_mob_views() + +/datum/component/storage/concrete/emp_act(datum/source, severity) + if(emp_shielded) + return + var/atom/real_location = real_location() + for(var/i in real_location) + var/atom/A = i + A.emp_act(severity) + +/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S) + if(S == src) + return FALSE + slaves += S + return TRUE + +/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S) + slaves -= S + return FALSE + +/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A) + var/atom/real_location = parent + if(A in real_location) + usr = null + remove_from_storage(A, null) + +/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled) + if(drop_all_on_deconstruct) + do_quick_empty() + +/datum/component/storage/concrete/can_see_contents() + . = ..() + for(var/i in slaves) + var/datum/component/storage/slave = i + . |= slave.can_see_contents() + +//Resets screen loc and other vars of something being removed from storage. +/datum/component/storage/concrete/_removal_reset(atom/movable/thing) + thing.layer = initial(thing.layer) + thing.plane = initial(thing.plane) + thing.mouse_opacity = initial(thing.mouse_opacity) + if(thing.maptext) + thing.maptext = "" + +/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location) + //Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped + //or moving objects, things that should never happen + var/atom/parent = src.parent + var/list/seeing_mobs = can_see_contents() + for(var/mob/M in seeing_mobs) + M.client.screen -= AM + if(ismob(parent.loc) && isitem(AM)) + var/obj/item/I = AM + var/mob/M = parent.loc + I.dropped(M) + I.item_flags &= ~IN_STORAGE + if(new_location) + //Reset the items values + _removal_reset(AM) + AM.forceMove(new_location) + //We don't want to call this if the item is being destroyed + AM.on_exit_storage(src) + else + //Being destroyed, just move to nullspace now (so it's not in contents for the icon update) + AM.moveToNullspace() + refresh_mob_views() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + return TRUE + +/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M) + return TRUE + +/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M) + . = handle_item_insertion(I, prevent_warning, M, slave) + if(. && !prevent_warning) + slave.mob_item_insertion_feedback(usr, M, I) + +/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum + var/datum/component/storage/concrete/master = master() + var/atom/parent = src.parent + var/moved = FALSE + if(!istype(I)) + return FALSE + if(M) + if(!M.temporarilyRemoveItemFromInventory(I)) + return FALSE + else + moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever. + if(I.pulledby) + I.pulledby.stop_pulling() + if(silent) + prevent_warning = TRUE + if(!_insert_physical_item(I)) + if(moved) + if(M) + if(!M.put_in_active_hand(I)) + I.forceMove(parent.drop_location()) + else + I.forceMove(parent.drop_location()) + return FALSE + I.on_enter_storage(master) + I.item_flags |= IN_STORAGE + refresh_mob_views() + I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt + if(M) + if(M.client && M.active_storage != src) + M.client.screen -= I + if(M.observers && M.observers.len) + for(var/i in M.observers) + var/mob/dead/observe = i + if(observe.client && observe.active_storage != src) + observe.client.screen -= I + if(!remote) + parent.add_fingerprint(M) + if(!prevent_warning) + mob_item_insertion_feedback(usr, M, I) + update_icon() + return TRUE + +/datum/component/storage/concrete/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.update_icon() diff --git a/code/datums/components/storage/concrete/bag_of_holding.dm b/code/datums/components/storage/concrete/bag_of_holding.dm index 8c0b29fdd891..2406146e3364 100644 --- a/code/datums/components/storage/concrete/bag_of_holding.dm +++ b/code/datums/components/storage/concrete/bag_of_holding.dm @@ -1,41 +1,41 @@ -/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.Paralyze(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) - - message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].") - log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].") - - user.gib(TRUE, TRUE, TRUE) - 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.Paralyze(5, TRUE, TRUE) //Overrides stun absorbs. - T.TerraformTurf(/turf/open/chasm/magic, /turf/open/chasm/magic) - for(var/fabricarea in get_areas(/area/fabric_of_reality)) - var/area/fabric_of_reality/R = fabricarea - R.origin = loccheck - for (var/obj/structure/ladder/unbreakable/binary/ladder in GLOB.ladders) - ladder.ActivateAlmonds() - 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.Paralyze(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) + + message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].") + log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].") + + user.gib(TRUE, TRUE, TRUE) + 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.Paralyze(5, TRUE, TRUE) //Overrides stun absorbs. + T.TerraformTurf(/turf/open/chasm/magic, /turf/open/chasm/magic) + for(var/fabricarea in get_areas(/area/fabric_of_reality)) + var/area/fabric_of_reality/R = fabricarea + R.origin = loccheck + for (var/obj/structure/ladder/unbreakable/binary/ladder in GLOB.ladders) + ladder.ActivateAlmonds() + qdel(A) + return + . = ..() diff --git a/code/datums/components/storage/concrete/bluespace.dm b/code/datums/components/storage/concrete/bluespace.dm index 77b1897101ae..91ba2730644a 100644 --- a/code/datums/components/storage/concrete/bluespace.dm +++ b/code/datums/components/storage/concrete/bluespace.dm @@ -1,23 +1,23 @@ -/datum/component/storage/concrete/bluespace - var/dumping_range = 8 - var/dumping_sound = 'sound/items/pshoom.ogg' - var/alt_sound = 'sound/items/pshoom_2.ogg' - -/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M) - var/atom/A = parent - if(A.Adjacent(M)) - var/atom/dumping_location = dest.get_dumping_location() - var/turf/bagT = get_turf(parent) - var/turf/destT = get_turf(dumping_location) - if(bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range) - if(dumping_location.storage_contents_dump_act(src, M)) - if(alt_sound && prob(1)) - playsound(src, alt_sound, 40, 1) - else - playsound(src, dumping_sound, 40, 1) - M.Beam(dumping_location, icon_state="rped_upgrade", time=5) - return TRUE - to_chat(M, "The [A.name] buzzes.") - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) - return FALSE - +/datum/component/storage/concrete/bluespace + var/dumping_range = 8 + var/dumping_sound = 'sound/items/pshoom.ogg' + var/alt_sound = 'sound/items/pshoom_2.ogg' + +/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M) + var/atom/A = parent + if(A.Adjacent(M)) + var/atom/dumping_location = dest.get_dumping_location() + var/turf/bagT = get_turf(parent) + var/turf/destT = get_turf(dumping_location) + if(bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range) + if(dumping_location.storage_contents_dump_act(src, M)) + if(alt_sound && prob(1)) + playsound(src, alt_sound, 40, 1) + else + playsound(src, dumping_sound, 40, 1) + M.Beam(dumping_location, icon_state="rped_upgrade", time=5) + return TRUE + to_chat(M, "The [A.name] buzzes.") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) + return FALSE + diff --git a/code/datums/components/storage/concrete/implant.dm b/code/datums/components/storage/concrete/implant.dm index 95a929494dfe..405b3098fe51 100644 --- a/code/datums/components/storage/concrete/implant.dm +++ b/code/datums/components/storage/concrete/implant.dm @@ -1,18 +1,18 @@ -/datum/component/storage/concrete/implant - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 6 - max_items = 2 - drop_all_on_destroy = TRUE - drop_all_on_deconstruct = TRUE - silent = TRUE - allow_big_nesting = TRUE - -/datum/component/storage/concrete/implant/Initialize() - . = ..() - set_holdable(null, list(/obj/item/disk/nuclear)) - -/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original) - if(!istype(I)) - return ..() - max_combined_w_class += I.max_combined_w_class - max_items += I.max_items +/datum/component/storage/concrete/implant + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 6 + max_items = 2 + drop_all_on_destroy = TRUE + drop_all_on_deconstruct = TRUE + silent = TRUE + allow_big_nesting = TRUE + +/datum/component/storage/concrete/implant/Initialize() + . = ..() + set_holdable(null, list(/obj/item/disk/nuclear)) + +/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original) + if(!istype(I)) + return ..() + max_combined_w_class += I.max_combined_w_class + max_items += I.max_items diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm index e159d9263854..bcf6472bdcd1 100644 --- a/code/datums/components/storage/concrete/pockets.dm +++ b/code/datums/components/storage/concrete/pockets.dm @@ -1,96 +1,96 @@ -/datum/component/storage/concrete/pockets - max_items = 2 - max_w_class = WEIGHT_CLASS_SMALL - max_combined_w_class = 50 - rustle_sound = FALSE - -/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user) - . = ..() - if(. && silent && !prevent_warning) - if(quickdraw) - to_chat(user, "You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.") - else - to_chat(user, "You discreetly slip [I] into [parent].") - -/datum/component/storage/concrete/pockets - max_w_class = WEIGHT_CLASS_NORMAL - -/datum/component/storage/concrete/pockets/small - max_items = 1 - max_w_class = WEIGHT_CLASS_SMALL - attack_hand_interact = FALSE - -/datum/component/storage/concrete/pockets/tiny - max_items = 1 - max_w_class = WEIGHT_CLASS_TINY - attack_hand_interact = FALSE - -/datum/component/storage/concrete/pockets/small/fedora/Initialize() - . = ..() - var/static/list/exception_cache = typecacheof(list( - /obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, - /obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun - )) - exception_hold = exception_cache - -/datum/component/storage/concrete/pockets/small/fedora/detective - attack_hand_interact = TRUE // so the detectives would discover pockets in their hats - -/datum/component/storage/concrete/pockets/shoes - attack_hand_interact = FALSE - quickdraw = TRUE - silent = TRUE - -/datum/component/storage/concrete/pockets/shoes/Initialize() - . = ..() - set_holdable(list( - /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, - /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, - /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, - /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, - /obj/item/firing_pin - ), - list(/obj/item/screwdriver/power) - ) - -/datum/component/storage/concrete/pockets/shoes/clown/Initialize() - . = ..() - set_holdable(list( - /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, - /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, - /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, - /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, - /obj/item/firing_pin, /obj/item/bikehorn), - list(/obj/item/screwdriver/power) - ) - -/datum/component/storage/concrete/pockets/pocketprotector - max_items = 3 - max_w_class = WEIGHT_CLASS_TINY - var/atom/original_parent - -/datum/component/storage/concrete/pockets/pocketprotector/Initialize() - original_parent = parent - . = ..() - set_holdable(list( //Same items as a PDA - /obj/item/pen, - /obj/item/toy/crayon, - /obj/item/lipstick, - /obj/item/flashlight/pen, - /obj/item/clothing/mask/cigarette) - ) - -/datum/component/storage/concrete/pockets/pocketprotector/real_location() - // if the component is reparented to a jumpsuit, the items still go in the protector - return original_parent - -/datum/component/storage/concrete/pockets/helmet - quickdraw = TRUE - max_combined_w_class = 6 - -/datum/component/storage/concrete/pockets/helmet/Initialize() - . = ..() - set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka, - /obj/item/reagent_containers/food/drinks/bottle/molotov, - /obj/item/reagent_containers/food/drinks/drinkingglass, - /obj/item/ammo_box/a762)) +/datum/component/storage/concrete/pockets + max_items = 2 + max_w_class = WEIGHT_CLASS_SMALL + max_combined_w_class = 50 + rustle_sound = FALSE + +/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user) + . = ..() + if(. && silent && !prevent_warning) + if(quickdraw) + to_chat(user, "You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.") + else + to_chat(user, "You discreetly slip [I] into [parent].") + +/datum/component/storage/concrete/pockets + max_w_class = WEIGHT_CLASS_NORMAL + +/datum/component/storage/concrete/pockets/small + max_items = 1 + max_w_class = WEIGHT_CLASS_SMALL + attack_hand_interact = FALSE + +/datum/component/storage/concrete/pockets/tiny + max_items = 1 + max_w_class = WEIGHT_CLASS_TINY + attack_hand_interact = FALSE + +/datum/component/storage/concrete/pockets/small/fedora/Initialize() + . = ..() + var/static/list/exception_cache = typecacheof(list( + /obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, + /obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun + )) + exception_hold = exception_cache + +/datum/component/storage/concrete/pockets/small/fedora/detective + attack_hand_interact = TRUE // so the detectives would discover pockets in their hats + +/datum/component/storage/concrete/pockets/shoes + attack_hand_interact = FALSE + quickdraw = TRUE + silent = TRUE + +/datum/component/storage/concrete/pockets/shoes/Initialize() + . = ..() + set_holdable(list( + /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, + /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, + /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, + /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, + /obj/item/firing_pin + ), + list(/obj/item/screwdriver/power) + ) + +/datum/component/storage/concrete/pockets/shoes/clown/Initialize() + . = ..() + set_holdable(list( + /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, + /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, + /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, + /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, + /obj/item/firing_pin, /obj/item/bikehorn), + list(/obj/item/screwdriver/power) + ) + +/datum/component/storage/concrete/pockets/pocketprotector + max_items = 3 + max_w_class = WEIGHT_CLASS_TINY + var/atom/original_parent + +/datum/component/storage/concrete/pockets/pocketprotector/Initialize() + original_parent = parent + . = ..() + set_holdable(list( //Same items as a PDA + /obj/item/pen, + /obj/item/toy/crayon, + /obj/item/lipstick, + /obj/item/flashlight/pen, + /obj/item/clothing/mask/cigarette) + ) + +/datum/component/storage/concrete/pockets/pocketprotector/real_location() + // if the component is reparented to a jumpsuit, the items still go in the protector + return original_parent + +/datum/component/storage/concrete/pockets/helmet + quickdraw = TRUE + max_combined_w_class = 6 + +/datum/component/storage/concrete/pockets/helmet/Initialize() + . = ..() + set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka, + /obj/item/reagent_containers/food/drinks/bottle/molotov, + /obj/item/reagent_containers/food/drinks/drinkingglass, + /obj/item/ammo_box/a762)) diff --git a/code/datums/components/storage/concrete/rped.dm b/code/datums/components/storage/concrete/rped.dm index 14f6fe28b38f..455eb985f090 100644 --- a/code/datums/components/storage/concrete/rped.dm +++ b/code/datums/components/storage/concrete/rped.dm @@ -1,33 +1,33 @@ -/datum/component/storage/concrete/rped - collection_mode = COLLECT_EVERYTHING - allow_quick_gather = TRUE - allow_quick_empty = TRUE - click_gather = TRUE - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 100 - max_items = 50 - display_numerical_stacking = TRUE - -/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) - . = ..() - if(!I.get_part_rating()) - if (!stop_messages) - to_chat(M, "[parent] only accepts machine parts!") - return FALSE - -/datum/component/storage/concrete/bluespace/rped - collection_mode = COLLECT_EVERYTHING - allow_quick_gather = TRUE - allow_quick_empty = TRUE - click_gather = TRUE - max_w_class = WEIGHT_CLASS_BULKY // can fit vending refills - max_combined_w_class = 800 - max_items = 400 - display_numerical_stacking = TRUE - -/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) - . = ..() - if(!I.get_part_rating()) - if (!stop_messages) - to_chat(M, "[parent] only accepts machine parts!") - return FALSE +/datum/component/storage/concrete/rped + collection_mode = COLLECT_EVERYTHING + allow_quick_gather = TRUE + allow_quick_empty = TRUE + click_gather = TRUE + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 100 + max_items = 50 + display_numerical_stacking = TRUE + +/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) + . = ..() + if(!I.get_part_rating()) + if (!stop_messages) + to_chat(M, "[parent] only accepts machine parts!") + return FALSE + +/datum/component/storage/concrete/bluespace/rped + collection_mode = COLLECT_EVERYTHING + allow_quick_gather = TRUE + allow_quick_empty = TRUE + click_gather = TRUE + max_w_class = WEIGHT_CLASS_BULKY // can fit vending refills + max_combined_w_class = 800 + max_items = 400 + display_numerical_stacking = TRUE + +/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) + . = ..() + if(!I.get_part_rating()) + if (!stop_messages) + to_chat(M, "[parent] only accepts machine parts!") + return FALSE diff --git a/code/datums/components/storage/concrete/stack.dm b/code/datums/components/storage/concrete/stack.dm index ad003d19b3f0..1f0c44c65025 100644 --- a/code/datums/components/storage/concrete/stack.dm +++ b/code/datums/components/storage/concrete/stack.dm @@ -1,67 +1,67 @@ -//Stack-only storage. -/datum/component/storage/concrete/stack - display_numerical_stacking = TRUE - var/max_combined_stack_amount = 300 - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = WEIGHT_CLASS_NORMAL * 14 - -/datum/component/storage/concrete/stack/proc/total_stack_amount() - . = 0 - var/atom/real_location = real_location() - for(var/i in real_location) - var/obj/item/stack/S = i - if(!istype(S)) - continue - . += S.amount - -/datum/component/storage/concrete/stack/proc/remaining_space() - return max(0, max_combined_stack_amount - total_stack_amount()) - -//emptying procs do not need modification as stacks automatically merge. - -/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE) - if(!istype(I, /obj/item/stack)) - if(override) - return ..() - return FALSE - var/atom/real_location = real_location() - var/obj/item/stack/S = I - var/can_insert = min(S.amount, remaining_space()) - if(!can_insert) - return FALSE - for(var/i in real_location) //combine. - if(QDELETED(I)) - return - var/obj/item/stack/_S = i - if(!istype(_S)) - continue - if(_S.merge_type == S.merge_type) - _S.add(can_insert) - S.use(can_insert, TRUE) - return TRUE - return ..(S.change_stack(null, can_insert), override) - -/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location) - var/atom/real_location = real_location() - var/obj/item/stack/S = I - if(!istype(S)) - return ..() - if(S.amount > S.max_amount) - var/overrun = S.amount - S.max_amount - S.amount = S.max_amount - var/obj/item/stack/temp = new S.type(real_location, overrun) - handle_item_insertion(temp) - return ..(S, new_location) - -/datum/component/storage/concrete/stack/_process_numerical_display() - var/atom/real_location = real_location() - . = list() - for(var/i in real_location) - var/obj/item/stack/I = i - if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks. - continue - if(!.[I.merge_type]) - .[I.merge_type] = new /datum/numbered_display(I, I.amount) - else - var/datum/numbered_display/ND = .[I.merge_type] - ND.number += I.amount +//Stack-only storage. +/datum/component/storage/concrete/stack + display_numerical_stacking = TRUE + var/max_combined_stack_amount = 300 + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = WEIGHT_CLASS_NORMAL * 14 + +/datum/component/storage/concrete/stack/proc/total_stack_amount() + . = 0 + var/atom/real_location = real_location() + for(var/i in real_location) + var/obj/item/stack/S = i + if(!istype(S)) + continue + . += S.amount + +/datum/component/storage/concrete/stack/proc/remaining_space() + return max(0, max_combined_stack_amount - total_stack_amount()) + +//emptying procs do not need modification as stacks automatically merge. + +/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE) + if(!istype(I, /obj/item/stack)) + if(override) + return ..() + return FALSE + var/atom/real_location = real_location() + var/obj/item/stack/S = I + var/can_insert = min(S.amount, remaining_space()) + if(!can_insert) + return FALSE + for(var/i in real_location) //combine. + if(QDELETED(I)) + return + var/obj/item/stack/_S = i + if(!istype(_S)) + continue + if(_S.merge_type == S.merge_type) + _S.add(can_insert) + S.use(can_insert, TRUE) + return TRUE + return ..(S.change_stack(null, can_insert), override) + +/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location) + var/atom/real_location = real_location() + var/obj/item/stack/S = I + if(!istype(S)) + return ..() + if(S.amount > S.max_amount) + var/overrun = S.amount - S.max_amount + S.amount = S.max_amount + var/obj/item/stack/temp = new S.type(real_location, overrun) + handle_item_insertion(temp) + return ..(S, new_location) + +/datum/component/storage/concrete/stack/_process_numerical_display() + var/atom/real_location = real_location() + . = list() + for(var/i in real_location) + var/obj/item/stack/I = i + if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks. + continue + if(!.[I.merge_type]) + .[I.merge_type] = new /datum/numbered_display(I, I.amount) + else + var/datum/numbered_display/ND = .[I.merge_type] + ND.number += I.amount diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 52b56e3c5693..7d5b8c023d78 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -1,810 +1,810 @@ -#define COLLECT_ONE 0 -#define COLLECT_EVERYTHING 1 -#define COLLECT_SAME 2 - -#define DROP_NOTHING 0 -#define DROP_AT_PARENT 1 -#define DROP_AT_LOCATION 2 - -// External storage-related logic: -// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages -// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement - -/datum/component/storage - dupe_mode = COMPONENT_DUPE_UNIQUE - var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. - - var/list/can_hold //if this is set, only items, and their children, will fit - var/list/cant_hold //if this is set, items, and their children, won't fit - var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit. - - var/can_hold_description - - var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. - - var/locked = FALSE //when locked nothing can see inside or use it. - - var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. - var/max_combined_w_class = 14 //max combined sizes of objects that will fit. - var/max_items = 7 //max number of objects that will fit. - - var/emp_shielded = FALSE - - var/silent = FALSE //whether this makes a message when things are put in. - var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. - var/rustle_sound = TRUE //play rustle sound on interact. - var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. - var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. - - var/collection_mode = COLLECT_EVERYTHING - - var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. - - var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. - - var/obj/screen/storage/boxes //storage display object - var/obj/screen/close/closer //close button object - - var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. - - var/attack_hand_interact = TRUE //interact on attack hand. - var/quickdraw = FALSE //altclick interact - - var/datum/action/item_action/storage_gather_mode/modeswitch_action - - //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. - var/screen_max_columns = 7 //These two determine maximum screen sizes. - var/screen_max_rows = INFINITY - var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer - var/screen_pixel_y = 16 - var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. - var/screen_start_y = 2 - //End - -/datum/component/storage/Initialize(datum/component/storage/concrete/master) - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - if(master) - change_master(master) - boxes = new(null, src) - closer = new(null, src) - orient2hud() - - RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) - RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) - RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) - RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) - - RegisterSignal(parent, COMSIG_TOPIC, .proc/topic_handle) - - RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) - - RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) - RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) - RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) - RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) - - RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) - RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) - RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) - - RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move) - - RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) - RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) - RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) - - update_actions() - -/datum/component/storage/Destroy() - close_all() - QDEL_NULL(boxes) - QDEL_NULL(closer) - LAZYCLEARLIST(is_using) - return ..() - -/datum/component/storage/PreTransfer() - update_actions() - -/datum/component/storage/proc/set_holdable(can_hold_list, cant_hold_list) - can_hold_description = generate_hold_desc(can_hold_list) - - if (can_hold_list != null) - can_hold = typecacheof(can_hold_list) - - if (cant_hold_list != null) - cant_hold = typecacheof(cant_hold_list) - -/datum/component/storage/proc/generate_hold_desc(can_hold_list) - var/list/desc = list() - - for(var/valid_type in can_hold_list) - var/obj/item/valid_item = valid_type - desc += "\a [initial(valid_item.name)]" - - return "\n\t[desc.Join("\n\t")]" - -/datum/component/storage/proc/update_actions() - QDEL_NULL(modeswitch_action) - if(!isitem(parent) || !allow_quick_gather) - return - var/obj/item/I = parent - modeswitch_action = new(I) - RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) - if(I.obj_flags & IN_INVENTORY) - var/mob/M = I.loc - if(!istype(M)) - return - modeswitch_action.Grant(M) - -/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) - if(new_master == src || (!isnull(new_master) && !istype(new_master))) - return FALSE - if(master) - master.on_slave_unlink(src) - master = new_master - if(master) - master.on_slave_link(src) - return TRUE - -/datum/component/storage/proc/master() - if(master == src) - return //infinite loops yo. - return master - -/datum/component/storage/proc/real_location() - var/datum/component/storage/concrete/master = master() - return master? master.real_location() : null - -/datum/component/storage/proc/canreach_react(datum/source, list/next) - var/datum/component/storage/concrete/master = master() - if(!master) - return - . = COMPONENT_BLOCK_REACH - next += master.parent - for(var/i in master.slaves) - var/datum/component/storage/slave = i - next += slave.parent - -/datum/component/storage/proc/on_move() - var/atom/A = parent - for(var/mob/living/L in can_see_contents()) - if(!L.CanReach(A)) - hide_from(L) - -/datum/component/storage/proc/attack_self(datum/source, mob/M) - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if((M.get_active_held_item() == parent) && allow_quick_empty) - quick_empty(M) - -/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params) - if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE)) - return FALSE - . = COMPONENT_NO_ATTACK - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - var/obj/item/I = O - if(collection_mode == COLLECT_ONE) - if(can_be_inserted(I, null, M)) - handle_item_insertion(I, null, M) - return - if(!isturf(I.loc)) - return - var/list/things = I.loc.contents.Copy() - if(collection_mode == COLLECT_SAME) - things = typecache_filter_list(things, typecacheof(I.type)) - var/len = length(things) - if(!len) - to_chat(M, "You failed to pick up anything with [parent].") - return - var/datum/progressbar/progress = new(M, len, I.loc) - var/list/rejections = list() - while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) - stoplag(1) - qdel(progress) - to_chat(M, "You put everything you could [insert_preposition] [parent].") - -/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) - var/atom/source_real_location = src_object.real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != source_real_location) - continue - if(user.active_storage != src_object) - if(I.on_found(user)) - break - if(can_be_inserted(I,FALSE,user)) - handle_item_insertion(I, TRUE, user) - if (TICK_CHECK) - progress.update(progress.goal - things.len) - return TRUE - - progress.update(progress.goal - things.len) - return FALSE - -/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) - var/atom/real_location = real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != thing_loc) - continue - if(I.type in rejections) // To limit bag spamming: any given type only complains once - continue - if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no - if(real_location.contents.len >= max_items) - break - rejections += I.type // therefore full bags are still a little spammy - continue - - handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. - - if (TICK_CHECK) - progress.update(progress.goal - things.len) - return TRUE - - progress.update(progress.goal - things.len) - return FALSE - -/datum/component/storage/proc/quick_empty(mob/M) - var/atom/A = parent - if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated()) - return - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - A.add_fingerprint(M) - to_chat(M, "You start dumping out [parent].") - var/turf/T = get_turf(A) - var/list/things = contents() - var/datum/progressbar/progress = new(M, length(things), T) - while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) - stoplag(1) - qdel(progress) - -/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE) - var/atom/real_location = real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != real_location) - continue - remove_from_storage(I, target) - if(trigger_on_found && I.on_found()) - return FALSE - if(TICK_CHECK) - progress.update(progress.goal - length(things)) - return TRUE - progress.update(progress.goal - length(things)) - return FALSE - -/datum/component/storage/proc/do_quick_empty(atom/_target) - if(!_target) - _target = get_turf(parent) - if(usr) - hide_from(usr) - var/list/contents = contents() - var/atom/real_location = real_location() - for(var/obj/item/I in contents) - if(I.loc != real_location) - continue - remove_from_storage(I, _target) - return TRUE - -/datum/component/storage/proc/set_locked(datum/source, new_state) - locked = new_state - if(locked) - close_all() - -/datum/component/storage/proc/_process_numerical_display() - . = list() - var/atom/real_location = real_location() - for(var/obj/item/I in real_location.contents) - if(QDELETED(I)) - continue - if(!.["[I.type]-[I.name]"]) - .["[I.type]-[I.name]"] = new /datum/numbered_display(I, 1) - else - var/datum/numbered_display/ND = .["[I.type]-[I.name]"] - ND.number++ - -//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. -/datum/component/storage/proc/orient2hud() - var/atom/real_location = real_location() - var/adjusted_contents = real_location.contents.len - - //Numbered contents display - var/list/datum/numbered_display/numbered_contents - if(display_numerical_stacking) - numbered_contents = _process_numerical_display() - adjusted_contents = numbered_contents.len - - var/columns = CLAMP(max_items, 1, screen_max_columns) - var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) - standard_orient_objs(rows, columns, numbered_contents) - -//This proc draws out the inventory and places the items on it. It uses the standard position. -/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) - boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" - var/cx = screen_start_x - var/cy = screen_start_y - if(islist(numerical_display_contents)) - for(var/type in numerical_display_contents) - var/datum/numbered_display/ND = numerical_display_contents[type] - ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE - ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" - ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" - ND.sample_object.layer = ABOVE_HUD_LAYER - ND.sample_object.plane = ABOVE_HUD_PLANE - cx++ - if(cx - screen_start_x >= cols) - cx = screen_start_x - cy++ - if(cy - screen_start_y >= rows) - break - else - var/atom/real_location = real_location() - for(var/obj/O in real_location) - if(QDELETED(O)) - continue - O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" - O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" - O.maptext = "" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx - screen_start_x >= cols) - cx = screen_start_x - cy++ - if(cy - screen_start_y >= rows) - break - closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" - -/datum/component/storage/proc/show_to(mob/M) - if(!M.client) - return FALSE - var/atom/real_location = real_location() - if(M.active_storage != src && (M.stat == CONSCIOUS)) - for(var/obj/item/I in real_location) - if(I.on_found(M)) - return FALSE - if(M.active_storage) - M.active_storage.hide_from(M) - orient2hud() - M.client.screen |= boxes - M.client.screen |= closer - M.client.screen |= real_location.contents - M.active_storage = src - LAZYOR(is_using, M) - return TRUE - -/datum/component/storage/proc/hide_from(mob/M) - if(!M.client) - return TRUE - var/atom/real_location = real_location() - M.client.screen -= boxes - M.client.screen -= closer - M.client.screen -= real_location.contents - if(M.active_storage == src) - M.active_storage = null - LAZYREMOVE(is_using, M) - return TRUE - -/datum/component/storage/proc/close(mob/M) - hide_from(M) - -/datum/component/storage/proc/close_all() - . = FALSE - for(var/mob/M in can_see_contents()) - close(M) - . = TRUE //returns TRUE if any mobs actually got a close(M) call - -/datum/component/storage/proc/emp_act(datum/source, severity) - if(emp_shielded) - return - var/datum/component/storage/concrete/master = master() - master.emp_act(source, severity) - -//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. -//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. -/datum/component/storage/proc/orient_objs(tx, ty, mx, my) - var/atom/real_location = real_location() - var/cx = tx - var/cy = ty - boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" - for(var/obj/O in real_location) - if(QDELETED(O)) - continue - O.screen_loc = "[cx],[cy]" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx > mx) - cx = tx - cy-- - closer.screen_loc = "[mx+1],[my]" - -//Resets something that is being removed from storage. -/datum/component/storage/proc/_removal_reset(atom/movable/thing) - if(!istype(thing)) - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master._removal_reset(thing) - -/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) - _removal_reset(thing) - refresh_mob_views() - -//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted -/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) - if(!istype(AM)) - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master.remove_from_storage(AM, new_location) - -/datum/component/storage/proc/refresh_mob_views() - var/list/seeing = can_see_contents() - for(var/i in seeing) - show_to(i) - return TRUE - -/datum/component/storage/proc/can_see_contents() - var/list/cansee = list() - for(var/mob/M in is_using) - if(M.active_storage == src && M.client) - cansee |= M - else - LAZYREMOVE(is_using, M) - return cansee - -//Tries to dump content -/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) - var/atom/A = parent - var/atom/dump_destination = dest_object.get_dumping_location() - if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if(dump_destination.storage_contents_dump_act(src, M)) - playsound(A, "rustle", 50, 1, -5) - return TRUE - return FALSE - -//This proc is called when you want to place an item into the storage item. -/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params) - if(istype(I, /obj/item/hand_labeler)) - var/obj/item/hand_labeler/labeler = I - if(labeler.mode) - return FALSE - . = TRUE //no afterattack - if(iscyborg(M)) - return - if(!can_be_inserted(I, FALSE, M)) - var/atom/real_location = real_location() - if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit - return TRUE - return FALSE - handle_item_insertion(I, FALSE, M) - -/datum/component/storage/proc/return_inv(recursive) - var/list/ret = list() - ret |= contents() - if(recursive) - for(var/i in ret.Copy()) - var/atom/A = i - SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE) - return ret - -/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! - var/atom/real_location = real_location() - return real_location.contents.Copy() - -//Abuses the fact that lists are just references, or something like that. -/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE) - if(!islist(interface)) - return FALSE - interface |= return_inv(recursive) - return TRUE - -/datum/component/storage/proc/topic_handle(datum/source, user, href_list) - if(href_list["show_valid_pocket_items"]) - handle_show_valid_items(source, user) - -/datum/component/storage/proc/handle_show_valid_items(datum/source, user) - to_chat(user, "[source] can hold: [can_hold_description]") - -/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M) - set waitfor = FALSE - . = COMPONENT_NO_MOUSEDROP - var/atom/A = parent - if(isliving(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 //yogs -- Makes ghosts not be able to interact with storage shit - 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) - if(istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - M.putItemFromInventoryInHandIfPossible(A, H.held_index) - return - A.add_fingerprint(M) - -/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) - var/atom/A = parent - if(!istype(M)) - return FALSE - A.add_fingerprint(M) - if(locked && !force) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if(force || M.CanReach(parent, view_only = TRUE)) - show_to(M) - -/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) - if(isitem(O)) - var/obj/item/I = O - if(iscarbon(M) || isdrone(M)) - var/mob/living/L = M - if(!L.incapacitated() && I == L.get_active_held_item()) - if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. - handle_item_insertion(I, FALSE, L) - -//This proc return 1 if the item can be picked up and 0 if it can't. -//Set the stop_messages to stop it from printing messages -/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) - if(!istype(I) || (I.item_flags & ABSTRACT)) - return FALSE //Not an item - if(I == parent) - return FALSE //no paradoxes for you - var/atom/real_location = real_location() - var/atom/host = parent - if(real_location == I.loc) - return FALSE //Means the item is already in the storage item - if(locked) - if(M && !stop_messages) - host.add_fingerprint(M) - to_chat(M, "[host] seems to be locked!") - return FALSE - if(real_location.contents.len >= max_items) - if(!stop_messages) - to_chat(M, "[host] is full, make some space!") - return FALSE //Storage item is full - if(length(can_hold)) - if(!is_type_in_typecache(I, can_hold)) - if(!stop_messages) - to_chat(M, "[host] cannot hold [I]!") - return FALSE - if(is_type_in_typecache(I, cant_hold)) //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 && !is_type_in_typecache(I, exception_hold)) - 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. - if(!stop_messages) - to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!") - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master.slave_can_insert_object(src, I, stop_messages, M) - -/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) - return FALSE - -//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() -//The prevent_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, -//such as when picking up all the items on a tile with one click. -/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) - var/atom/parent = src.parent - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - if(silent) - prevent_warning = TRUE - if(M) - parent.add_fingerprint(M) - . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) - -/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) - if(silent && !override) - return - if(rustle_sound) - playsound(parent, "rustle", 50, 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].", 1) - 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].", 1) - -/datum/component/storage/proc/update_icon() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - -/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE) - if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent)) - return FALSE - return handle_item_insertion(I, silent, M) - -/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE) - return can_be_inserted(I, silent, M) - -/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) - return user_show_to_mob(M, TRUE) - -/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) - return user_show_to_mob(showto, force) - -/datum/component/storage/proc/on_check() - return TRUE - -/datum/component/storage/proc/check_locked() - return locked - -/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) - if(!force) - if(check_adjacent) - if(!user || !user.CanReach(destination) || !user.CanReach(parent)) - return FALSE - var/list/taking = typecache_filter_list(contents(), typecacheof(type)) - if(taking.len > amount) - taking.len = amount - if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. - for(var/i in taking) - if(remove_from_storage(i, destination)) - inserted |= i - else - for(var/i in taking) - remove_from_storage(i, destination) - return TRUE - -/datum/component/storage/proc/remaining_space_items() - var/atom/real_location = real_location() - return max(0, max_items - real_location.contents.len) - -/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) - var/atom/real_location = real_location() - if(!force) - amount = min(remaining_space_items(), amount) - for(var/i in 1 to amount) - 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(locked) - to_chat(user, "[parent] seems to be locked!") - else - show_to(user) - -/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(locked) - to_chat(user, "[parent] seems to be locked!") - return - - var/atom/A = parent - if(!quickdraw) - A.add_fingerprint(user) - user_show_to_mob(user) - playsound(A, "rustle", 50, 1, -5) - return - - if(!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)) - to_chat(user, "You fumble for [I] and it falls on the floor.") - return - user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") - return - -/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source) - gather_mode_switch(source.owner) - return COMPONENT_ACTION_BLOCK_TRIGGER - -/datum/component/storage/proc/gather_mode_switch(mob/user) - collection_mode = (collection_mode+1)%3 - switch(collection_mode) - if(COLLECT_SAME) - to_chat(user, "[parent] now picks up all items of a single type at once.") - if(COLLECT_EVERYTHING) - to_chat(user, "[parent] now picks up all items in a tile at once.") - if(COLLECT_ONE) - to_chat(user, "[parent] now picks up one item at a time.") +#define COLLECT_ONE 0 +#define COLLECT_EVERYTHING 1 +#define COLLECT_SAME 2 + +#define DROP_NOTHING 0 +#define DROP_AT_PARENT 1 +#define DROP_AT_LOCATION 2 + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage + dupe_mode = COMPONENT_DUPE_UNIQUE + var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. + + var/list/can_hold //if this is set, only items, and their children, will fit + var/list/cant_hold //if this is set, items, and their children, won't fit + var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit. + + var/can_hold_description + + var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. + + var/locked = FALSE //when locked nothing can see inside or use it. + + var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. + var/max_combined_w_class = 14 //max combined sizes of objects that will fit. + var/max_items = 7 //max number of objects that will fit. + + var/emp_shielded = FALSE + + var/silent = FALSE //whether this makes a message when things are put in. + var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. + var/rustle_sound = TRUE //play rustle sound on interact. + var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. + var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. + + var/collection_mode = COLLECT_EVERYTHING + + var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. + + var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. + + var/obj/screen/storage/boxes //storage display object + var/obj/screen/close/closer //close button object + + var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. + + var/attack_hand_interact = TRUE //interact on attack hand. + var/quickdraw = FALSE //altclick interact + + var/datum/action/item_action/storage_gather_mode/modeswitch_action + + //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. + var/screen_max_columns = 7 //These two determine maximum screen sizes. + var/screen_max_rows = INFINITY + var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer + var/screen_pixel_y = 16 + var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. + var/screen_start_y = 2 + //End + +/datum/component/storage/Initialize(datum/component/storage/concrete/master) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if(master) + change_master(master) + boxes = new(null, src) + closer = new(null, src) + orient2hud() + + RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) + RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) + RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) + RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) + RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) + + RegisterSignal(parent, COMSIG_TOPIC, .proc/topic_handle) + + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) + + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) + RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) + RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) + RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) + + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) + RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) + RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) + + RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move) + + RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) + RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) + RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) + + update_actions() + +/datum/component/storage/Destroy() + close_all() + QDEL_NULL(boxes) + QDEL_NULL(closer) + LAZYCLEARLIST(is_using) + return ..() + +/datum/component/storage/PreTransfer() + update_actions() + +/datum/component/storage/proc/set_holdable(can_hold_list, cant_hold_list) + can_hold_description = generate_hold_desc(can_hold_list) + + if (can_hold_list != null) + can_hold = typecacheof(can_hold_list) + + if (cant_hold_list != null) + cant_hold = typecacheof(cant_hold_list) + +/datum/component/storage/proc/generate_hold_desc(can_hold_list) + var/list/desc = list() + + for(var/valid_type in can_hold_list) + var/obj/item/valid_item = valid_type + desc += "\a [initial(valid_item.name)]" + + return "\n\t[desc.Join("\n\t")]" + +/datum/component/storage/proc/update_actions() + QDEL_NULL(modeswitch_action) + if(!isitem(parent) || !allow_quick_gather) + return + var/obj/item/I = parent + modeswitch_action = new(I) + RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) + if(I.obj_flags & IN_INVENTORY) + var/mob/M = I.loc + if(!istype(M)) + return + modeswitch_action.Grant(M) + +/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) + if(new_master == src || (!isnull(new_master) && !istype(new_master))) + return FALSE + if(master) + master.on_slave_unlink(src) + master = new_master + if(master) + master.on_slave_link(src) + return TRUE + +/datum/component/storage/proc/master() + if(master == src) + return //infinite loops yo. + return master + +/datum/component/storage/proc/real_location() + var/datum/component/storage/concrete/master = master() + return master? master.real_location() : null + +/datum/component/storage/proc/canreach_react(datum/source, list/next) + var/datum/component/storage/concrete/master = master() + if(!master) + return + . = COMPONENT_BLOCK_REACH + next += master.parent + for(var/i in master.slaves) + var/datum/component/storage/slave = i + next += slave.parent + +/datum/component/storage/proc/on_move() + var/atom/A = parent + for(var/mob/living/L in can_see_contents()) + if(!L.CanReach(A)) + hide_from(L) + +/datum/component/storage/proc/attack_self(datum/source, mob/M) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if((M.get_active_held_item() == parent) && allow_quick_empty) + quick_empty(M) + +/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params) + if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE)) + return FALSE + . = COMPONENT_NO_ATTACK + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + var/obj/item/I = O + if(collection_mode == COLLECT_ONE) + if(can_be_inserted(I, null, M)) + handle_item_insertion(I, null, M) + return + if(!isturf(I.loc)) + return + var/list/things = I.loc.contents.Copy() + if(collection_mode == COLLECT_SAME) + things = typecache_filter_list(things, typecacheof(I.type)) + var/len = length(things) + if(!len) + to_chat(M, "You failed to pick up anything with [parent].") + return + var/datum/progressbar/progress = new(M, len, I.loc) + var/list/rejections = list() + while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) + stoplag(1) + qdel(progress) + to_chat(M, "You put everything you could [insert_preposition] [parent].") + +/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) + var/atom/source_real_location = src_object.real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != source_real_location) + continue + if(user.active_storage != src_object) + if(I.on_found(user)) + break + if(can_be_inserted(I,FALSE,user)) + handle_item_insertion(I, TRUE, user) + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != thing_loc) + continue + if(I.type in rejections) // To limit bag spamming: any given type only complains once + continue + if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no + if(real_location.contents.len >= max_items) + break + rejections += I.type // therefore full bags are still a little spammy + continue + + handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. + + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/quick_empty(mob/M) + var/atom/A = parent + if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated()) + return + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + A.add_fingerprint(M) + to_chat(M, "You start dumping out [parent].") + var/turf/T = get_turf(A) + var/list/things = contents() + var/datum/progressbar/progress = new(M, length(things), T) + while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) + stoplag(1) + qdel(progress) + +/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != real_location) + continue + remove_from_storage(I, target) + if(trigger_on_found && I.on_found()) + return FALSE + if(TICK_CHECK) + progress.update(progress.goal - length(things)) + return TRUE + progress.update(progress.goal - length(things)) + return FALSE + +/datum/component/storage/proc/do_quick_empty(atom/_target) + if(!_target) + _target = get_turf(parent) + if(usr) + hide_from(usr) + var/list/contents = contents() + var/atom/real_location = real_location() + for(var/obj/item/I in contents) + if(I.loc != real_location) + continue + remove_from_storage(I, _target) + return TRUE + +/datum/component/storage/proc/set_locked(datum/source, new_state) + locked = new_state + if(locked) + close_all() + +/datum/component/storage/proc/_process_numerical_display() + . = list() + var/atom/real_location = real_location() + for(var/obj/item/I in real_location.contents) + if(QDELETED(I)) + continue + if(!.["[I.type]-[I.name]"]) + .["[I.type]-[I.name]"] = new /datum/numbered_display(I, 1) + else + var/datum/numbered_display/ND = .["[I.type]-[I.name]"] + ND.number++ + +//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. +/datum/component/storage/proc/orient2hud() + var/atom/real_location = real_location() + var/adjusted_contents = real_location.contents.len + + //Numbered contents display + var/list/datum/numbered_display/numbered_contents + if(display_numerical_stacking) + numbered_contents = _process_numerical_display() + adjusted_contents = numbered_contents.len + + var/columns = CLAMP(max_items, 1, screen_max_columns) + var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) + standard_orient_objs(rows, columns, numbered_contents) + +//This proc draws out the inventory and places the items on it. It uses the standard position. +/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) + boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" + var/cx = screen_start_x + var/cy = screen_start_y + if(islist(numerical_display_contents)) + for(var/type in numerical_display_contents) + var/datum/numbered_display/ND = numerical_display_contents[type] + ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE + ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" + ND.sample_object.layer = ABOVE_HUD_LAYER + ND.sample_object.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + else + var/atom/real_location = real_location() + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" + O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + O.maptext = "" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" + +/datum/component/storage/proc/show_to(mob/M) + if(!M.client) + return FALSE + var/atom/real_location = real_location() + if(M.active_storage != src && (M.stat == CONSCIOUS)) + for(var/obj/item/I in real_location) + if(I.on_found(M)) + return FALSE + if(M.active_storage) + M.active_storage.hide_from(M) + orient2hud() + M.client.screen |= boxes + M.client.screen |= closer + M.client.screen |= real_location.contents + M.active_storage = src + LAZYOR(is_using, M) + return TRUE + +/datum/component/storage/proc/hide_from(mob/M) + if(!M.client) + return TRUE + var/atom/real_location = real_location() + M.client.screen -= boxes + M.client.screen -= closer + M.client.screen -= real_location.contents + if(M.active_storage == src) + M.active_storage = null + LAZYREMOVE(is_using, M) + return TRUE + +/datum/component/storage/proc/close(mob/M) + hide_from(M) + +/datum/component/storage/proc/close_all() + . = FALSE + for(var/mob/M in can_see_contents()) + close(M) + . = TRUE //returns TRUE if any mobs actually got a close(M) call + +/datum/component/storage/proc/emp_act(datum/source, severity) + if(emp_shielded) + return + var/datum/component/storage/concrete/master = master() + master.emp_act(source, severity) + +//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. +//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. +/datum/component/storage/proc/orient_objs(tx, ty, mx, my) + var/atom/real_location = real_location() + var/cx = tx + var/cy = ty + boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.screen_loc = "[cx],[cy]" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx > mx) + cx = tx + cy-- + closer.screen_loc = "[mx+1],[my]" + +//Resets something that is being removed from storage. +/datum/component/storage/proc/_removal_reset(atom/movable/thing) + if(!istype(thing)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master._removal_reset(thing) + +/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) + _removal_reset(thing) + refresh_mob_views() + +//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted +/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) + if(!istype(AM)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.remove_from_storage(AM, new_location) + +/datum/component/storage/proc/refresh_mob_views() + var/list/seeing = can_see_contents() + for(var/i in seeing) + show_to(i) + return TRUE + +/datum/component/storage/proc/can_see_contents() + var/list/cansee = list() + for(var/mob/M in is_using) + if(M.active_storage == src && M.client) + cansee |= M + else + LAZYREMOVE(is_using, M) + return cansee + +//Tries to dump content +/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) + var/atom/A = parent + var/atom/dump_destination = dest_object.get_dumping_location() + if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(dump_destination.storage_contents_dump_act(src, M)) + playsound(A, "rustle", 50, 1, -5) + return TRUE + return FALSE + +//This proc is called when you want to place an item into the storage item. +/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params) + if(istype(I, /obj/item/hand_labeler)) + var/obj/item/hand_labeler/labeler = I + if(labeler.mode) + return FALSE + . = TRUE //no afterattack + if(iscyborg(M)) + return + if(!can_be_inserted(I, FALSE, M)) + var/atom/real_location = real_location() + if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit + return TRUE + return FALSE + handle_item_insertion(I, FALSE, M) + +/datum/component/storage/proc/return_inv(recursive) + var/list/ret = list() + ret |= contents() + if(recursive) + for(var/i in ret.Copy()) + var/atom/A = i + SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE) + return ret + +/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! + var/atom/real_location = real_location() + return real_location.contents.Copy() + +//Abuses the fact that lists are just references, or something like that. +/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE) + if(!islist(interface)) + return FALSE + interface |= return_inv(recursive) + return TRUE + +/datum/component/storage/proc/topic_handle(datum/source, user, href_list) + if(href_list["show_valid_pocket_items"]) + handle_show_valid_items(source, user) + +/datum/component/storage/proc/handle_show_valid_items(datum/source, user) + to_chat(user, "[source] can hold: [can_hold_description]") + +/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M) + set waitfor = FALSE + . = COMPONENT_NO_MOUSEDROP + var/atom/A = parent + if(isliving(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 //yogs -- Makes ghosts not be able to interact with storage shit + 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) + if(istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + M.putItemFromInventoryInHandIfPossible(A, H.held_index) + return + A.add_fingerprint(M) + +/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) + var/atom/A = parent + if(!istype(M)) + return FALSE + A.add_fingerprint(M) + if(locked && !force) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(force || M.CanReach(parent, view_only = TRUE)) + show_to(M) + +/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) + if(isitem(O)) + var/obj/item/I = O + if(iscarbon(M) || isdrone(M)) + var/mob/living/L = M + if(!L.incapacitated() && I == L.get_active_held_item()) + if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. + handle_item_insertion(I, FALSE, L) + +//This proc return 1 if the item can be picked up and 0 if it can't. +//Set the stop_messages to stop it from printing messages +/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) + if(!istype(I) || (I.item_flags & ABSTRACT)) + return FALSE //Not an item + if(I == parent) + return FALSE //no paradoxes for you + var/atom/real_location = real_location() + var/atom/host = parent + if(real_location == I.loc) + return FALSE //Means the item is already in the storage item + if(locked) + if(M && !stop_messages) + host.add_fingerprint(M) + to_chat(M, "[host] seems to be locked!") + return FALSE + if(real_location.contents.len >= max_items) + if(!stop_messages) + to_chat(M, "[host] is full, make some space!") + return FALSE //Storage item is full + if(length(can_hold)) + if(!is_type_in_typecache(I, can_hold)) + if(!stop_messages) + to_chat(M, "[host] cannot hold [I]!") + return FALSE + if(is_type_in_typecache(I, cant_hold)) //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 && !is_type_in_typecache(I, exception_hold)) + 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. + if(!stop_messages) + to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!") + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.slave_can_insert_object(src, I, stop_messages, M) + +/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) + return FALSE + +//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() +//The prevent_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, +//such as when picking up all the items on a tile with one click. +/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) + var/atom/parent = src.parent + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + if(silent) + prevent_warning = TRUE + if(M) + parent.add_fingerprint(M) + . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) + +/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) + if(silent && !override) + return + if(rustle_sound) + playsound(parent, "rustle", 50, 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].", 1) + 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].", 1) + +/datum/component/storage/proc/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + +/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE) + if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent)) + return FALSE + return handle_item_insertion(I, silent, M) + +/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE) + return can_be_inserted(I, silent, M) + +/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) + return user_show_to_mob(M, TRUE) + +/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) + return user_show_to_mob(showto, force) + +/datum/component/storage/proc/on_check() + return TRUE + +/datum/component/storage/proc/check_locked() + return locked + +/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) + if(!force) + if(check_adjacent) + if(!user || !user.CanReach(destination) || !user.CanReach(parent)) + return FALSE + var/list/taking = typecache_filter_list(contents(), typecacheof(type)) + if(taking.len > amount) + taking.len = amount + if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. + for(var/i in taking) + if(remove_from_storage(i, destination)) + inserted |= i + else + for(var/i in taking) + remove_from_storage(i, destination) + return TRUE + +/datum/component/storage/proc/remaining_space_items() + var/atom/real_location = real_location() + return max(0, max_items - real_location.contents.len) + +/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) + var/atom/real_location = real_location() + if(!force) + amount = min(remaining_space_items(), amount) + for(var/i in 1 to amount) + 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(locked) + to_chat(user, "[parent] seems to be locked!") + else + show_to(user) + +/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(locked) + to_chat(user, "[parent] seems to be locked!") + return + + var/atom/A = parent + if(!quickdraw) + A.add_fingerprint(user) + user_show_to_mob(user) + playsound(A, "rustle", 50, 1, -5) + return + + if(!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)) + to_chat(user, "You fumble for [I] and it falls on the floor.") + return + user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") + return + +/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 c2b1eb0a525e..4eab08fa1a25 100644 --- a/code/datums/components/wet_floor.dm +++ b/code/datums/components/wet_floor.dm @@ -1,207 +1,207 @@ -/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 - - parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip)) - -/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY) - for(var/i in time_left_list) - if(text2num(i) <= strength) - time_left_list[i] = max(0, time_left_list[i] - duration_decrease) - if(immediate) - check() - -/datum/component/wet_floor/proc/max_time_left() - . = 0 - for(var/i in time_left_list) - . = max(., time_left_list[i]) - -/datum/component/wet_floor/process() - var/turf/open/T = parent - var/diff = world.time - last_process - var/decrease = 0 - var/t = T.GetTemperature() - switch(t) - if(-INFINITY to T0C) - add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice! - if(T0C to T0C + 100) - decrease = ((T.air.temperature - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio) - if(T0C + 100 to INFINITY) - decrease = INFINITY - decrease = max(0, decrease) - if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water! - for(var/obj/O in T.contents) - if(O.obj_flags & FROZEN) - O.make_unfrozen() - add_wet(TURF_WET_WATER, max_time_left()) - dry(null, TURF_WET_ICE) - dry(null, ALL, FALSE, decrease) - check() - last_process = world.time - -/datum/component/wet_floor/proc/update_strength() - highest_strength = 0 //Not bitflag. - for(var/i in time_left_list) - highest_strength = max(highest_strength, text2num(i)) - -/datum/component/wet_floor/proc/is_wet() - . = 0 - for(var/i in time_left_list) - . |= text2num(i) - -/datum/component/wet_floor/PreTransfer() - var/turf/O = parent - O.cut_overlay(current_overlay) - //That turf is no longer slippery, we're out of here - //Slippery components don't transfer due to callbacks - qdel(O.GetComponent(/datum/component/slippery)) - -/datum/component/wet_floor/PostTransfer() - if(!isopenturf(parent)) - return COMPONENT_INCOMPATIBLE - var/turf/T = parent - T.add_overlay(current_overlay) - //Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent) - update_flags() - - //NB it's possible we get deleted after this, due to inherit - -/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE) - var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE) - if(duration_minimum <= 0 || !type) - return FALSE - if(type in allowed_types) - return _do_add_wet(type, duration_minimum, duration_add, duration_maximum) - else - . = NONE - for(var/i in allowed_types) - if(!(type & i)) - continue - . |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum) - if(_permanent) - permanent = TRUE - STOP_PROCESSING(SSwet_floors, src) - -/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum) - var/time = 0 - if(LAZYACCESS(time_left_list, "[type]")) - time = CLAMP(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum) - else - time = min(duration_minimum, duration_maximum) - LAZYSET(time_left_list, "[type]", time) - check(TRUE) - return TRUE - -/datum/component/wet_floor/proc/gc(on_init = FALSE) - if(!LAZYLEN(time_left_list)) - if(on_init) - var/turf/T = parent - stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].") - qdel(src) - return TRUE - return FALSE - -/datum/component/wet_floor/proc/check(force_update = FALSE) - var/changed = FALSE - for(var/i in time_left_list) - if(time_left_list[i] <= 0) - time_left_list -= i - changed = TRUE - if(changed || force_update) - update_strength() - update_overlay() - update_flags() - gc() +/datum/component/wet_floor + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + can_transfer = TRUE + var/highest_strength = TURF_DRY + var/lube_flags = NONE //why do we have this? + var/list/time_left_list //In deciseconds. + var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor") + var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor") + var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static") + var/static/mutable_appearance/generic_turf_overlay = mutable_appearance('icons/effects/water.dmi', "wet_static") + var/current_overlay + var/permanent = FALSE + var/last_process = 0 + +/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, 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 + + parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip)) + +/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY) + for(var/i in time_left_list) + if(text2num(i) <= strength) + time_left_list[i] = max(0, time_left_list[i] - duration_decrease) + if(immediate) + check() + +/datum/component/wet_floor/proc/max_time_left() + . = 0 + for(var/i in time_left_list) + . = max(., time_left_list[i]) + +/datum/component/wet_floor/process() + var/turf/open/T = parent + var/diff = world.time - last_process + var/decrease = 0 + var/t = T.GetTemperature() + switch(t) + if(-INFINITY to T0C) + add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice! + if(T0C to T0C + 100) + decrease = ((T.air.temperature - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio) + if(T0C + 100 to INFINITY) + decrease = INFINITY + decrease = max(0, decrease) + if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water! + for(var/obj/O in T.contents) + if(O.obj_flags & FROZEN) + O.make_unfrozen() + add_wet(TURF_WET_WATER, max_time_left()) + dry(null, TURF_WET_ICE) + dry(null, ALL, FALSE, decrease) + check() + last_process = world.time + +/datum/component/wet_floor/proc/update_strength() + highest_strength = 0 //Not bitflag. + for(var/i in time_left_list) + highest_strength = max(highest_strength, text2num(i)) + +/datum/component/wet_floor/proc/is_wet() + . = 0 + for(var/i in time_left_list) + . |= text2num(i) + +/datum/component/wet_floor/PreTransfer() + var/turf/O = parent + O.cut_overlay(current_overlay) + //That turf is no longer slippery, we're out of here + //Slippery components don't transfer due to callbacks + qdel(O.GetComponent(/datum/component/slippery)) + +/datum/component/wet_floor/PostTransfer() + if(!isopenturf(parent)) + return COMPONENT_INCOMPATIBLE + var/turf/T = parent + T.add_overlay(current_overlay) + //Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent) + update_flags() + + //NB it's possible we get deleted after this, due to inherit + +/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE) + var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE) + if(duration_minimum <= 0 || !type) + return FALSE + if(type in allowed_types) + return _do_add_wet(type, duration_minimum, duration_add, duration_maximum) + else + . = NONE + for(var/i in allowed_types) + if(!(type & i)) + continue + . |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum) + if(_permanent) + permanent = TRUE + STOP_PROCESSING(SSwet_floors, src) + +/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum) + var/time = 0 + if(LAZYACCESS(time_left_list, "[type]")) + time = CLAMP(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum) + else + time = min(duration_minimum, duration_maximum) + LAZYSET(time_left_list, "[type]", time) + check(TRUE) + return TRUE + +/datum/component/wet_floor/proc/gc(on_init = FALSE) + if(!LAZYLEN(time_left_list)) + if(on_init) + var/turf/T = parent + stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].") + qdel(src) + return TRUE + return FALSE + +/datum/component/wet_floor/proc/check(force_update = FALSE) + var/changed = FALSE + for(var/i in time_left_list) + if(time_left_list[i] <= 0) + time_left_list -= i + changed = TRUE + if(changed || force_update) + update_strength() + update_overlay() + update_flags() + gc() diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index ca664b1ee90f..3a6d49ff08b8 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -1,338 +1,338 @@ - -/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/fine = 0 - var/paid = 0 - var/dataId = 0 - -/datum/datacore/proc/createCrimeEntry(cname = "", cdetails = "", author = "", time = "", fine = 0) - var/datum/data/crime/c = new /datum/data/crime - c.crimeName = cname - c.crimeDetails = cdetails - c.author = author - c.time = time - c.fine = fine - c.paid = 0 - c.dataId = ++securityCrimeCounter - return c - -/datum/datacore/proc/addCitation(id = "", datum/data/crime/crime) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["citation"] - crimes |= crime - return - -/datum/datacore/proc/removeCitation(id, cDataId) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["citation"] - for(var/datum/data/crime/crime in crimes) - if(crime.dataId == text2num(cDataId)) - crimes -= crime - return - -/datum/datacore/proc/payCitation(id, cDataId, amount) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["citation"] - for(var/datum/data/crime/crime in crimes) - if(crime.dataId == text2num(cDataId)) - crime.paid = crime.paid + amount - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SEC) - D.adjust_money(amount) - return - -/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["gender"] = H.gender - if(H.gender == "male") - G.fields["gender"] = "Male" - else if(H.gender == "female") - G.fields["gender"] = "Female" - else - G.fields["gender"] = "Other" - G.fields["photo_front"] = photo_front - G.fields["photo_side"] = photo_side - general += G - - //Medical Record - var/datum/data/record/M = new() - M.fields["id"] = id - M.fields["name"] = H.real_name - M.fields["blood_type"] = H.dna.blood_type - M.fields["b_dna"] = H.dna.unique_enzymes - M.fields["mi_dis"] = "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["citation"] = list() - 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["gender"] = H.gender - if(H.gender == "male") - G.fields["gender"] = "Male" - else if(H.gender == "female") - G.fields["gender"] = "Female" - else - G.fields["gender"] = "Other" - L.fields["blood_type"] = H.dna.blood_type - L.fields["b_dna"] = H.dna.unique_enzymes - L.fields["identity"] = H.dna.uni_identity - L.fields["species"] = H.dna.species.type - L.fields["features"] = H.dna.features - L.fields["image"] = image - L.fields["mindref"] = H.mind - locked += L - return - -/datum/datacore/proc/get_id_photo(mob/living/carbon/human/H, client/C, show_directions = list(SOUTH)) - var/datum/job/J = SSjob.GetJob(H.mind.assigned_role) - var/datum/preferences/P - if(!C) - C = H.client - if(C) - P = C.prefs - return get_flat_human_icon(null, J, P, DUMMY_HUMAN_SLOT_MANIFEST, show_directions) + +/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/fine = 0 + var/paid = 0 + var/dataId = 0 + +/datum/datacore/proc/createCrimeEntry(cname = "", cdetails = "", author = "", time = "", fine = 0) + var/datum/data/crime/c = new /datum/data/crime + c.crimeName = cname + c.crimeDetails = cdetails + c.author = author + c.time = time + c.fine = fine + c.paid = 0 + c.dataId = ++securityCrimeCounter + return c + +/datum/datacore/proc/addCitation(id = "", datum/data/crime/crime) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["citation"] + crimes |= crime + return + +/datum/datacore/proc/removeCitation(id, cDataId) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["citation"] + for(var/datum/data/crime/crime in crimes) + if(crime.dataId == text2num(cDataId)) + crimes -= crime + return + +/datum/datacore/proc/payCitation(id, cDataId, amount) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["citation"] + for(var/datum/data/crime/crime in crimes) + if(crime.dataId == text2num(cDataId)) + crime.paid = crime.paid + amount + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SEC) + D.adjust_money(amount) + return + +/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["gender"] = H.gender + if(H.gender == "male") + G.fields["gender"] = "Male" + else if(H.gender == "female") + G.fields["gender"] = "Female" + else + G.fields["gender"] = "Other" + G.fields["photo_front"] = photo_front + G.fields["photo_side"] = photo_side + general += G + + //Medical Record + var/datum/data/record/M = new() + M.fields["id"] = id + M.fields["name"] = H.real_name + M.fields["blood_type"] = H.dna.blood_type + M.fields["b_dna"] = H.dna.unique_enzymes + M.fields["mi_dis"] = "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["citation"] = list() + 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["gender"] = H.gender + if(H.gender == "male") + G.fields["gender"] = "Male" + else if(H.gender == "female") + G.fields["gender"] = "Female" + else + G.fields["gender"] = "Other" + L.fields["blood_type"] = H.dna.blood_type + L.fields["b_dna"] = H.dna.unique_enzymes + L.fields["identity"] = H.dna.uni_identity + L.fields["species"] = H.dna.species.type + L.fields["features"] = H.dna.features + L.fields["image"] = image + L.fields["mindref"] = H.mind + locked += L + return + +/datum/datacore/proc/get_id_photo(mob/living/carbon/human/H, client/C, show_directions = list(SOUTH)) + var/datum/job/J = SSjob.GetJob(H.mind.assigned_role) + var/datum/preferences/P + if(!C) + C = H.client + if(C) + P = C.prefs + return get_flat_human_icon(null, J, P, DUMMY_HUMAN_SLOT_MANIFEST, show_directions) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index d94281431e33..dbe3e8c16151 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -1,164 +1,164 @@ -/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 - -/datum/Topic(href, href_list[]) - ..() - SEND_SIGNAL(src, COMSIG_TOPIC, usr, href_list) - -// Default implementation of clean-up code. -// This should be overridden to remove all references pointing to the object being destroyed. -// 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 + +/datum/Topic(href, href_list[]) + ..() + SEND_SIGNAL(src, COMSIG_TOPIC, usr, href_list) + +// Default implementation of clean-up code. +// This should be overridden to remove all references pointing to the object being destroyed. +// 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 67d49188f4c7..6c78ac86d848 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -1,1418 +1,1418 @@ -#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()] - - "} - 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) - // yogs start - offer control can now be used by mods - 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) - // yogs end - - -//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") - var/atom/A = D - if(istype(A)) - vv_update_display(D, "dir", dir2text(A.dir) || A.dir) - if("ckey") - var/mob/living/L = D - if(istype(L)) - vv_update_display(D, "ckey", L.ckey || "No ckey") - if("real_name") - var/mob/living/L = D - if(istype(L)) - vv_update_display(D, "real_name", L.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) - - // yogs - offer control moved up - - 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 - var/list/reagent_options = sortList(GLOB.chemical_reagents_list) - switch(alert(usr, "Choose a method.", "Add Reagents", "Enter ID", "Choose ID")) - if("Enter ID") - var/valid_id - while(!valid_id) - chosen_id = stripped_input(usr, "Enter the ID of the reagent you want to add.") - if(!chosen_id) //Get me out of here! - break - for(var/ID in reagent_options) - if(ID == chosen_id) - valid_id = 1 - if(!valid_id) - to_chat(usr, "A reagent with that ID doesn't exist!") - if("Choose ID") - chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in reagent_options - 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) - log_admin("[key_name(usr)] has taught [MA] to [key_name(C)].") - message_admins("[key_name_admin(usr)] has taught [MA] to [key_name_admin(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) - return - - var/datum/brain_trauma/BT = C.gain_trauma(result) - if(BT) - log_admin("[key_name(usr)] has traumatized [key_name(C)] with [BT.name]") - message_admins("[key_name_admin(usr)] has traumatized [key_name_admin(C)] with [BT.name].") - - 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) - log_admin("[key_name(usr)] has cured all traumas from [key_name(C)].") - message_admins("[key_name_admin(usr)] has cured all traumas from [key_name_admin(C)].") - - 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(usr)] has modified the bodyparts of [H] to [result]") // yogs - Yog Tickets - 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() - if(edit_action == "remove" || edit_action == "augment") - for(var/obj/item/bodypart/B in C.bodyparts) - limb_list += B.body_zone - if(edit_action == "remove") - limb_list -= BODY_ZONE_CHEST - else - limb_list = list(BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - for(var/obj/item/bodypart/B in C.bodyparts) - limb_list -= B.body_zone - - 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(usr)] has modified the bodyparts of [C]") // yogs - Yog Tickets - - - 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(usr)] has put [key_name(H)] on purrbation." // yogs - Yog Tickets - 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(usr)] has removed [key_name(H)] from purrbation." // yogs - Yog Tickets - message_admins(msg) - admin_ticket_log(H, msg) - - else if(href_list["cluwneing"]) // yogs start -- adds cluwneify verb in VV - if(!check_rights(R_SPAWN)) return - var/mob/living/carbon/human/H = locate(href_list["cluwneing"]) - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - H.cluwneify() - message_admins("[key_name(usr)] has made [key_name(H)] into a Cluwne.") - return // yogs end - - 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() - else - to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]") - return - - if(amount != 0) - var/log_msg = "[key_name(usr)] dealt [amount] amount of [Text] damage to [key_name(L)]" - message_admins("[key_name(usr)] dealt [amount] amount of [Text] damage to [ADMIN_LOOKUPFLW(L)]") - log_admin(log_msg) - admin_ticket_log(L, "[log_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()] + + "} + 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) + // yogs start - offer control can now be used by mods + 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) + // yogs end + + +//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") + var/atom/A = D + if(istype(A)) + vv_update_display(D, "dir", dir2text(A.dir) || A.dir) + if("ckey") + var/mob/living/L = D + if(istype(L)) + vv_update_display(D, "ckey", L.ckey || "No ckey") + if("real_name") + var/mob/living/L = D + if(istype(L)) + vv_update_display(D, "real_name", L.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) + + // yogs - offer control moved up + + 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 + var/list/reagent_options = sortList(GLOB.chemical_reagents_list) + switch(alert(usr, "Choose a method.", "Add Reagents", "Enter ID", "Choose ID")) + if("Enter ID") + var/valid_id + while(!valid_id) + chosen_id = stripped_input(usr, "Enter the ID of the reagent you want to add.") + if(!chosen_id) //Get me out of here! + break + for(var/ID in reagent_options) + if(ID == chosen_id) + valid_id = 1 + if(!valid_id) + to_chat(usr, "A reagent with that ID doesn't exist!") + if("Choose ID") + chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in reagent_options + 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) + log_admin("[key_name(usr)] has taught [MA] to [key_name(C)].") + message_admins("[key_name_admin(usr)] has taught [MA] to [key_name_admin(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) + return + + var/datum/brain_trauma/BT = C.gain_trauma(result) + if(BT) + log_admin("[key_name(usr)] has traumatized [key_name(C)] with [BT.name]") + message_admins("[key_name_admin(usr)] has traumatized [key_name_admin(C)] with [BT.name].") + + 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) + log_admin("[key_name(usr)] has cured all traumas from [key_name(C)].") + message_admins("[key_name_admin(usr)] has cured all traumas from [key_name_admin(C)].") + + 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(usr)] has modified the bodyparts of [H] to [result]") // yogs - Yog Tickets + 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() + if(edit_action == "remove" || edit_action == "augment") + for(var/obj/item/bodypart/B in C.bodyparts) + limb_list += B.body_zone + if(edit_action == "remove") + limb_list -= BODY_ZONE_CHEST + else + limb_list = list(BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + for(var/obj/item/bodypart/B in C.bodyparts) + limb_list -= B.body_zone + + 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(usr)] has modified the bodyparts of [C]") // yogs - Yog Tickets + + + 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(usr)] has put [key_name(H)] on purrbation." // yogs - Yog Tickets + 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(usr)] has removed [key_name(H)] from purrbation." // yogs - Yog Tickets + message_admins(msg) + admin_ticket_log(H, msg) + + else if(href_list["cluwneing"]) // yogs start -- adds cluwneify verb in VV + if(!check_rights(R_SPAWN)) return + var/mob/living/carbon/human/H = locate(href_list["cluwneing"]) + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + H.cluwneify() + message_admins("[key_name(usr)] has made [key_name(H)] into a Cluwne.") + return // yogs end + + 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() + else + to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]") + return + + if(amount != 0) + var/log_msg = "[key_name(usr)] dealt [amount] amount of [Text] damage to [key_name(L)]" + message_admins("[key_name(usr)] dealt [amount] amount of [Text] damage to [ADMIN_LOOKUPFLW(L)]") + log_admin(log_msg) + admin_ticket_log(L, "[log_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 ce45693d119a..4ba5c14e894e 100644 --- a/code/datums/diseases/advance/advance.dm +++ b/code/datums/diseases/advance/advance.dm @@ -1,483 +1,483 @@ -/* - - 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. - 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) - S.Start(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(new_stage, 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() - 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 more resistance the symptoms have, the harder the cure. -/datum/disease/advance/proc/GenerateCure() - if(properties && properties.len) - var/res = CLAMP(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len) - if(res == oldres) - return - cures = list(pick(advance_cures[res])) - oldres = res - - // Get the cure name from the cure_id - var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]] - cure_text = D.name - -// Randomly generate a symptom, has a chance to lose or gain a symptom. -/datum/disease/advance/proc/Evolve(min_level, max_level, ignore_mutable = FALSE) - if(!mutable && !ignore_mutable) - return - var/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") - var/datum/disease/advance/A = SSdisease.archive_diseases[GetDiseaseID()] - A.name = name - -// Return a unique ID of the disease. -/datum/disease/advance/GetDiseaseID() - if(!id) - var/list/L = list() - for(var/datum/symptom/S in symptoms) - if(S.neutered) - L += "[S.id]N" - else - L += S.id - L = sortList(L) // Sort the list so it doesn't matter which order the symptoms are in. - var/result = jointext(L, ":") - id = result - return id - - -// Add a symptom, if it is over the limit (with a small chance to be able to go over) -// 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)) - symptoms += S - else - RemoveSymptom(pick(symptoms)) - symptoms += S - -// Simply removes the symptom. -/datum/disease/advance/proc/RemoveSymptom(datum/symptom/S) - symptoms -= S - -// 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)" - -/* - - 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. + 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) + S.Start(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(new_stage, 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() + 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 more resistance the symptoms have, the harder the cure. +/datum/disease/advance/proc/GenerateCure() + if(properties && properties.len) + var/res = CLAMP(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len) + if(res == oldres) + return + cures = list(pick(advance_cures[res])) + oldres = res + + // Get the cure name from the cure_id + var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]] + cure_text = D.name + +// Randomly generate a symptom, has a chance to lose or gain a symptom. +/datum/disease/advance/proc/Evolve(min_level, max_level, ignore_mutable = FALSE) + if(!mutable && !ignore_mutable) + return + var/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") + var/datum/disease/advance/A = SSdisease.archive_diseases[GetDiseaseID()] + A.name = name + +// Return a unique ID of the disease. +/datum/disease/advance/GetDiseaseID() + if(!id) + var/list/L = list() + for(var/datum/symptom/S in symptoms) + if(S.neutered) + L += "[S.id]N" + else + L += S.id + L = sortList(L) // Sort the list so it doesn't matter which order the symptoms are in. + var/result = jointext(L, ":") + id = result + return id + + +// Add a symptom, if it is over the limit (with a small chance to be able to go over) +// 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)) + symptoms += S + else + RemoveSymptom(pick(symptoms)) + symptoms += S + +// Simply removes the symptom. +/datum/disease/advance/proc/RemoveSymptom(datum/symptom/S) + symptoms -= S + +// 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)" + +/* + + 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 d2c4b1983f2d..68c3ceffeaf0 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 e83132f082c3..533c57a67c36 100644 --- a/code/datums/diseases/advance/symptoms/beard.dm +++ b/code/datums/diseases/advance/symptoms/beard.dm @@ -1,43 +1,43 @@ -/* -////////////////////////////////////// -Facial Hypertrichosis - - No change to stealth. - Increases resistance. - Increases speed. - Slighlty increases transmittability - Intense Level. - -BONUS - Makes the mob grow a massive beard, regardless of gender. - -////////////////////////////////////// -*/ - -/datum/symptom/beard - - name = "Facial Hypertrichosis" - desc = "The virus increases hair production significantly, causing rapid beard growth." - stealth = 0 - resistance = 3 - stage_speed = 2 - transmittable = 1 - level = 4 - severity = 1 - symptom_delay_min = 18 - symptom_delay_max = 36 - - var/list/beard_order = list("Beard (Jensen)", "Beard (Full)", "Beard (Dwarf)", "Beard (Very Long)") - -/datum/symptom/beard/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/index = min(max(beard_order.Find(H.facial_hair_style)+1, A.stage-1), beard_order.len) - if(index > 0 && H.facial_hair_style != beard_order[index]) - to_chat(H, "Your chin itches.") - H.facial_hair_style = beard_order[index] - H.update_hair() - +/* +////////////////////////////////////// +Facial Hypertrichosis + + No change to stealth. + Increases resistance. + Increases speed. + Slighlty increases transmittability + Intense Level. + +BONUS + Makes the mob grow a massive beard, regardless of gender. + +////////////////////////////////////// +*/ + +/datum/symptom/beard + + name = "Facial Hypertrichosis" + desc = "The virus increases hair production significantly, causing rapid beard growth." + stealth = 0 + resistance = 3 + stage_speed = 2 + transmittable = 1 + level = 4 + severity = 1 + symptom_delay_min = 18 + symptom_delay_max = 36 + + var/list/beard_order = list("Beard (Jensen)", "Beard (Full)", "Beard (Dwarf)", "Beard (Very Long)") + +/datum/symptom/beard/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/index = min(max(beard_order.Find(H.facial_hair_style)+1, A.stage-1), beard_order.len) + if(index > 0 && H.facial_hair_style != beard_order[index]) + to_chat(H, "Your chin itches.") + H.facial_hair_style = beard_order[index] + H.update_hair() + diff --git a/code/datums/diseases/advance/symptoms/choking.dm b/code/datums/diseases/advance/symptoms/choking.dm index 069c62cf3f1d..559d8f475414 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 1049e1a31a52..c5daf8b5866b 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 b0b9a8cbb77c..9d7aed5056a4 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.Immobilize(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.Immobilize(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 988008464492..8031b60e8e15 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 c7f7198f6b1e..b4b06be5acf3 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 5424e28502e9..a178cba196e0 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 b3c78f239aaf..b8037a594473 100644 --- a/code/datums/diseases/advance/symptoms/flesh_eating.dm +++ b/code/datums/diseases/advance/symptoms/flesh_eating.dm @@ -1,122 +1,122 @@ -/* -////////////////////////////////////// -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.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) - if(pain) - M.adjustStaminaLoss(get_damage * 2) - if(bleed) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - 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.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) - if(chems) - M.reagents.add_reagent_list(list(/datum/reagent/toxin/heparin = 2, /datum/reagent/toxin/lipolicide = 2)) - if(zombie) - M.reagents.add_reagent(/datum/reagent/romerol, 1) +/* +////////////////////////////////////// +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.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) + if(pain) + M.adjustStaminaLoss(get_damage * 2) + if(bleed) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + 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.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) + if(chems) + M.reagents.add_reagent_list(list(/datum/reagent/toxin/heparin = 2, /datum/reagent/toxin/lipolicide = 2)) + if(zombie) + M.reagents.add_reagent(/datum/reagent/romerol, 1) return 1 \ 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 527fb522cd4a..49b229e5032d 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.all_mutations[RACEMUT] - var/mob/living/carbon/M = A.affected_mob - if(M) - if(!M.has_dna()) - return - archived_dna = M.dna.mutation_index - -// 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.mutation_index = 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.all_mutations[RACEMUT] + var/mob/living/carbon/M = A.affected_mob + if(M) + if(!M.has_dna()) + return + archived_dna = M.dna.mutation_index + +// 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.mutation_index = archived_dna + M.domutcheck() diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm index 39ecc619561b..873d96052459 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 973de1455bcb..72b03000ed42 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 0b4ade709614..2d807c3c589d 100644 --- a/code/datums/diseases/advance/symptoms/heal.dm +++ b/code/datums/diseases/advance/symptoms/heal.dm @@ -1,466 +1,466 @@ -/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)) //most effective on toxins - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 - -/datum/symptom/heal/starlight/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss() || M.getToxLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/chem - name = "Toxolysis" - stealth = 0 - resistance = -2 - stage_speed = 2 - transmittable = -2 - level = 7 - var/food_conversion = FALSE - desc = "The virus rapidly breaks down any foreign chemicals in the bloodstream." - threshold_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/datum/reagent/R in M.reagents.reagent_list) //Not just toxins! - M.reagents.remove_reagent(R.type, actual_power) - if(food_conversion) - M.adjust_nutrition(0.3) - if(prob(2)) - to_chat(M, "You feel a mild warmth as your blood purifies itself.") - return 1 - - - -/datum/symptom/heal/metabolism - name = "Metabolic Boost" - stealth = -1 - resistance = -2 - stage_speed = 2 - transmittable = 1 - level = 7 - var/triple_metabolism = FALSE - var/reduced_hunger = FALSE - desc = "The virus causes the host's metabolism to accelerate rapidly, making them process chemicals twice as fast,\ - but also causing increased hunger." - threshold_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.adjust_nutrition(-lost_nutrition * HUNGER_FACTOR) //Hunger depletes at 10x the normal speed - if(prob(2)) - to_chat(C, "You feel an odd gurgle in your stomach, as if it was working much faster than normal.") - return 1 - -/datum/symptom/heal/darkness - name = "Nocturnal Regeneration" - desc = "The virus is able to mend the host's flesh when in conditions of low light, repairing physical damage. More effective against brute damage." - stealth = 2 - resistance = -1 - stage_speed = -2 - transmittable = -1 - level = 6 - passive_message = "You feel tingling on your skin as light passes over it." - threshold_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, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - if(prob(5)) - to_chat(M, "The darkness soothes and mends your wounds.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len * 0.5, null, BODYPART_ORGANIC)) //more effective on brute - M.update_damage_overlays() - return 1 - -/datum/symptom/heal/darkness/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/coma - name = "Regenerative Coma" - desc = "The virus causes the host to fall into a death-like coma when severely damaged, then rapidly fixes the damage." - stealth = 0 - resistance = 2 - stage_speed = -3 - transmittable = -2 - level = 8 - passive_message = "The pain from your wounds makes you feel oddly sleepy..." - var/deathgasp = FALSE - var/active_coma = FALSE //to prevent multiple coma procs - threshold_desc = "Stealth 2: Host appears to die when falling into a coma.
    \ - 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["stealth"] >= 2) - deathgasp = TRUE - -/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_mobility() - addtimer(CALLBACK(src, .proc/uncoma, M), 300) - -/datum/symptom/heal/coma/proc/uncoma(mob/living/M) - if(!active_coma) - return - active_coma = FALSE - M.cure_fakedeath("regenerative_coma") - M.update_stat() - M.update_mobility() - -/datum/symptom/heal/coma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 4 * actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1) - - if(!parts.len) - return - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - - if(active_coma && M.getBruteLoss() + M.getFireLoss() == 0) - uncoma(M) - - return 1 - -/datum/symptom/heal/coma/passive_message_condition(mob/living/M) - if((M.getBruteLoss() + M.getFireLoss()) > 30) - return TRUE - return FALSE - -/datum/symptom/heal/water - name = "Tissue Hydration" - desc = "The virus uses excess water inside and outside the body to repair damaged tissue cells. More effective when using holy water and against burns." - stealth = 0 - resistance = -1 - stage_speed = 0 - transmittable = 1 - level = 6 - passive_message = "Your skin feels oddly dry..." - var/absorption_coeff = 1 - threshold_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["resistance"] >= 5) - absorption_coeff = 0.25 - -/datum/symptom/heal/water/CanHeal(datum/disease/advance/A) - . = 0 - var/mob/living/M = A.affected_mob - if(M.fire_stacks < 0) - M.fire_stacks = min(M.fire_stacks + 1 * absorption_coeff, 0) - . += power - if(M.reagents.has_reagent(/datum/reagent/water/holywater, needs_metabolizing = FALSE)) - M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) - . += power * 0.75 - else if(M.reagents.has_reagent(/datum/reagent/water, needs_metabolizing = FALSE)) - M.reagents.remove_reagent(/datum/reagent/water, 0.5 * absorption_coeff) - . += power * 0.5 - -/datum/symptom/heal/water/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 2 * actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) //more effective on burns - - if(!parts.len) - return - - if(prob(5)) - to_chat(M, "You feel yourself absorbing the water around you to soothe your damaged skin.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len * 0.5, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - - return 1 - -/datum/symptom/heal/water/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/plasma - name = "Plasma Fixation" - desc = "The virus draws plasma from the atmosphere and from inside the body to heal and stabilize body temperature." - stealth = 0 - resistance = 3 - stage_speed = -2 - transmittable = -2 - level = 8 - passive_message = "You feel an odd attraction to plasma." - var/temp_rate = 1 - threshold_desc = "Transmission 6: Increases temperature adjustment rate.
    \ - Stage Speed 7: Increases healing speed." - -/datum/symptom/heal/plasma/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 7) - power = 2 - if(A.properties["transmittable"] >= 6) - temp_rate = 4 - -/datum/symptom/heal/plasma/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - var/datum/gas_mixture/environment - var/list/gases - - . = 0 - - if(M.loc) - environment = M.loc.return_air() - if(environment) - gases = environment.gases - if(gases["plasma"] && gases["plasma"][MOLES] > gases["plasma"][GAS_META][META_GAS_MOLES_VISIBLE]) //if there's enough plasma in the air to see - . += power * 0.5 - if(M.reagents.has_reagent(/datum/reagent/toxin/plasma, needs_metabolizing = TRUE)) - . += power * 0.75 - -/datum/symptom/heal/plasma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 4 * actual_power - - if(prob(5)) - to_chat(M, "You feel yourself absorbing plasma inside and around you...") - - if(M.bodytemperature > 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) - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - if(!parts.len) - return - if(prob(5)) - to_chat(M, "The pain from your wounds fades rapidly.") - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 - - -/datum/symptom/heal/radiation - name = "Radioactive Resonance" - desc = "The virus uses radiation to fix damage through dna mutations." - stealth = -1 - resistance = -2 - stage_speed = 2 - transmittable = -3 - level = 6 - symptom_delay_min = 1 - symptom_delay_max = 1 - passive_message = "Your skin glows faintly for a moment." - var/cellular_damage = FALSE - threshold_desc = "Transmission 6: Additionally heals cellular damage.
    \ - Resistance 7: Increases healing speed." - -/datum/symptom/heal/radiation/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 7) - power = 2 - if(A.properties["transmittable"] >= 6) - cellular_damage = TRUE - -/datum/symptom/heal/radiation/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - switch(M.radiation) - if(0) - return FALSE - if(1 to RAD_MOB_SAFE) - return 0.25 - if(RAD_MOB_SAFE to RAD_BURN_THRESHOLD) - return 0.5 - if(RAD_BURN_THRESHOLD to RAD_MOB_MUTATE) - return 0.75 - if(RAD_MOB_MUTATE to RAD_MOB_KNOCKDOWN) - return 1 - else - return 1.5 - -/datum/symptom/heal/radiation/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = actual_power - - if(cellular_damage) - M.adjustCloneLoss(-heal_amt * 0.5) - - M.adjustToxLoss(-(2 * heal_amt)) - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - if(prob(4)) - to_chat(M, "Your skin glows faintly, and you feel your wounds mending themselves.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 +/datum/symptom/heal + name = "Basic Healing (does nothing)" //warning for adminspawn viruses + desc = "You should not be seeing this." + stealth = 0 + resistance = 0 + stage_speed = 0 + transmittable = 0 + level = 0 //not obtainable + base_message_chance = 20 //here used for the overlays + symptom_delay_min = 1 + symptom_delay_max = 1 + var/passive_message = "" //random message to infected but not actively healing people + threshold_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)) //most effective on toxins + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 + +/datum/symptom/heal/starlight/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss() || M.getToxLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/chem + name = "Toxolysis" + stealth = 0 + resistance = -2 + stage_speed = 2 + transmittable = -2 + level = 7 + var/food_conversion = FALSE + desc = "The virus rapidly breaks down any foreign chemicals in the bloodstream." + threshold_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/datum/reagent/R in M.reagents.reagent_list) //Not just toxins! + M.reagents.remove_reagent(R.type, actual_power) + if(food_conversion) + M.adjust_nutrition(0.3) + if(prob(2)) + to_chat(M, "You feel a mild warmth as your blood purifies itself.") + return 1 + + + +/datum/symptom/heal/metabolism + name = "Metabolic Boost" + stealth = -1 + resistance = -2 + stage_speed = 2 + transmittable = 1 + level = 7 + var/triple_metabolism = FALSE + var/reduced_hunger = FALSE + desc = "The virus causes the host's metabolism to accelerate rapidly, making them process chemicals twice as fast,\ + but also causing increased hunger." + threshold_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.adjust_nutrition(-lost_nutrition * HUNGER_FACTOR) //Hunger depletes at 10x the normal speed + if(prob(2)) + to_chat(C, "You feel an odd gurgle in your stomach, as if it was working much faster than normal.") + return 1 + +/datum/symptom/heal/darkness + name = "Nocturnal Regeneration" + desc = "The virus is able to mend the host's flesh when in conditions of low light, repairing physical damage. More effective against brute damage." + stealth = 2 + resistance = -1 + stage_speed = -2 + transmittable = -1 + level = 6 + passive_message = "You feel tingling on your skin as light passes over it." + threshold_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, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + if(prob(5)) + to_chat(M, "The darkness soothes and mends your wounds.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len * 0.5, null, BODYPART_ORGANIC)) //more effective on brute + M.update_damage_overlays() + return 1 + +/datum/symptom/heal/darkness/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/coma + name = "Regenerative Coma" + desc = "The virus causes the host to fall into a death-like coma when severely damaged, then rapidly fixes the damage." + stealth = 0 + resistance = 2 + stage_speed = -3 + transmittable = -2 + level = 8 + passive_message = "The pain from your wounds makes you feel oddly sleepy..." + var/deathgasp = FALSE + var/active_coma = FALSE //to prevent multiple coma procs + threshold_desc = "Stealth 2: Host appears to die when falling into a coma.
    \ + 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["stealth"] >= 2) + deathgasp = TRUE + +/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_mobility() + addtimer(CALLBACK(src, .proc/uncoma, M), 300) + +/datum/symptom/heal/coma/proc/uncoma(mob/living/M) + if(!active_coma) + return + active_coma = FALSE + M.cure_fakedeath("regenerative_coma") + M.update_stat() + M.update_mobility() + +/datum/symptom/heal/coma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 4 * actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1) + + if(!parts.len) + return + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + + if(active_coma && M.getBruteLoss() + M.getFireLoss() == 0) + uncoma(M) + + return 1 + +/datum/symptom/heal/coma/passive_message_condition(mob/living/M) + if((M.getBruteLoss() + M.getFireLoss()) > 30) + return TRUE + return FALSE + +/datum/symptom/heal/water + name = "Tissue Hydration" + desc = "The virus uses excess water inside and outside the body to repair damaged tissue cells. More effective when using holy water and against burns." + stealth = 0 + resistance = -1 + stage_speed = 0 + transmittable = 1 + level = 6 + passive_message = "Your skin feels oddly dry..." + var/absorption_coeff = 1 + threshold_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["resistance"] >= 5) + absorption_coeff = 0.25 + +/datum/symptom/heal/water/CanHeal(datum/disease/advance/A) + . = 0 + var/mob/living/M = A.affected_mob + if(M.fire_stacks < 0) + M.fire_stacks = min(M.fire_stacks + 1 * absorption_coeff, 0) + . += power + if(M.reagents.has_reagent(/datum/reagent/water/holywater, needs_metabolizing = FALSE)) + M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) + . += power * 0.75 + else if(M.reagents.has_reagent(/datum/reagent/water, needs_metabolizing = FALSE)) + M.reagents.remove_reagent(/datum/reagent/water, 0.5 * absorption_coeff) + . += power * 0.5 + +/datum/symptom/heal/water/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 2 * actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) //more effective on burns + + if(!parts.len) + return + + if(prob(5)) + to_chat(M, "You feel yourself absorbing the water around you to soothe your damaged skin.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len * 0.5, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + + return 1 + +/datum/symptom/heal/water/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/plasma + name = "Plasma Fixation" + desc = "The virus draws plasma from the atmosphere and from inside the body to heal and stabilize body temperature." + stealth = 0 + resistance = 3 + stage_speed = -2 + transmittable = -2 + level = 8 + passive_message = "You feel an odd attraction to plasma." + var/temp_rate = 1 + threshold_desc = "Transmission 6: Increases temperature adjustment rate.
    \ + Stage Speed 7: Increases healing speed." + +/datum/symptom/heal/plasma/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 7) + power = 2 + if(A.properties["transmittable"] >= 6) + temp_rate = 4 + +/datum/symptom/heal/plasma/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + var/datum/gas_mixture/environment + var/list/gases + + . = 0 + + if(M.loc) + environment = M.loc.return_air() + if(environment) + gases = environment.gases + if(gases["plasma"] && gases["plasma"][MOLES] > gases["plasma"][GAS_META][META_GAS_MOLES_VISIBLE]) //if there's enough plasma in the air to see + . += power * 0.5 + if(M.reagents.has_reagent(/datum/reagent/toxin/plasma, needs_metabolizing = TRUE)) + . += power * 0.75 + +/datum/symptom/heal/plasma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 4 * actual_power + + if(prob(5)) + to_chat(M, "You feel yourself absorbing plasma inside and around you...") + + if(M.bodytemperature > 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) + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + if(!parts.len) + return + if(prob(5)) + to_chat(M, "The pain from your wounds fades rapidly.") + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 + + +/datum/symptom/heal/radiation + name = "Radioactive Resonance" + desc = "The virus uses radiation to fix damage through dna mutations." + stealth = -1 + resistance = -2 + stage_speed = 2 + transmittable = -3 + level = 6 + symptom_delay_min = 1 + symptom_delay_max = 1 + passive_message = "Your skin glows faintly for a moment." + var/cellular_damage = FALSE + threshold_desc = "Transmission 6: Additionally heals cellular damage.
    \ + Resistance 7: Increases healing speed." + +/datum/symptom/heal/radiation/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 7) + power = 2 + if(A.properties["transmittable"] >= 6) + cellular_damage = TRUE + +/datum/symptom/heal/radiation/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + switch(M.radiation) + if(0) + return FALSE + if(1 to RAD_MOB_SAFE) + return 0.25 + if(RAD_MOB_SAFE to RAD_BURN_THRESHOLD) + return 0.5 + if(RAD_BURN_THRESHOLD to RAD_MOB_MUTATE) + return 0.75 + if(RAD_MOB_MUTATE to RAD_MOB_KNOCKDOWN) + return 1 + else + return 1.5 + +/datum/symptom/heal/radiation/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = actual_power + + if(cellular_damage) + M.adjustCloneLoss(-heal_amt * 0.5) + + M.adjustToxLoss(-(2 * heal_amt)) + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + if(prob(4)) + to_chat(M, "Your skin glows faintly, and you feel your wounds mending themselves.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 diff --git a/code/datums/diseases/advance/symptoms/itching.dm b/code/datums/diseases/advance/symptoms/itching.dm index 1ede16999d1e..b0812e023533 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 e2e96e62e0e1..5b6d53c7195f 100644 --- a/code/datums/diseases/advance/symptoms/oxygen.dm +++ b/code/datums/diseases/advance/symptoms/oxygen.dm @@ -1,69 +1,69 @@ -/* -////////////////////////////////////// - -Self-Respiration - - Slightly hidden. - Lowers resistance significantly. - Decreases stage speed significantly. - Decreases transmittablity tremendously. - Fatal Level. - -Bonus - The body generates salbutamol. - -////////////////////////////////////// -*/ - -/datum/symptom/oxygen - - name = "Self-Respiration" - desc = "The virus rapidly synthesizes oxygen, effectively removing the need for breathing." - stealth = 1 - resistance = -3 - stage_speed = -3 - transmittable = -4 - level = 6 - base_message_chance = 5 - symptom_delay_min = 1 - symptom_delay_max = 1 - var/regenerate_blood = FALSE - threshold_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)) - 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(new_stage, datum/disease/advance/A) - if(!..()) - return FALSE - var/mob/living/carbon/M = A.affected_mob - switch(A.stage) - if(3) - REMOVE_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) - if(4) - ADD_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) +/* +////////////////////////////////////// + +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)) + 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(new_stage, datum/disease/advance/A) + if(!..()) + return FALSE + var/mob/living/carbon/M = A.affected_mob + switch(A.stage) + if(3) + REMOVE_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) + if(4) + ADD_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) diff --git a/code/datums/diseases/advance/symptoms/shedding.dm b/code/datums/diseases/advance/symptoms/shedding.dm index 06df320496d2..58357f04e27b 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 e4bb5e1b3e47..741e2a1e16c7 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 2c51a134db7c..b4108cd27b46 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 - A.spread(4 + power) +/* +////////////////////////////////////// + +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) diff --git a/code/datums/diseases/advance/symptoms/symptoms.dm b/code/datums/diseases/advance/symptoms/symptoms.dm index 329c0d6f389e..92f6f1d9ba2d 100644 --- a/code/datums/diseases/advance/symptoms/symptoms.dm +++ b/code/datums/diseases/advance/symptoms/symptoms.dm @@ -1,76 +1,76 @@ -// 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, which holds this symptom, starts. -/datum/symptom/proc/Start(datum/disease/advance/A) - if(neutered) - return FALSE - next_activation = world.time + rand(symptom_delay_min * 10, symptom_delay_max * 10) //so it doesn't instantly activate on infection - 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(new_stage, 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 +// 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, which holds this symptom, starts. +/datum/symptom/proc/Start(datum/disease/advance/A) + if(neutered) + return FALSE + next_activation = world.time + rand(symptom_delay_min * 10, symptom_delay_max * 10) //so it doesn't instantly activate on infection + 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(new_stage, 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 diff --git a/code/datums/diseases/advance/symptoms/voice_change.dm b/code/datums/diseases/advance/symptoms/voice_change.dm index 7a4abfc757af..4403bb07d0b7 100644 --- a/code/datums/diseases/advance/symptoms/voice_change.dm +++ b/code/datums/diseases/advance/symptoms/voice_change.dm @@ -1,85 +1,85 @@ -/* -////////////////////////////////////// - -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 - var/datum/language_holder/mob_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 - 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) - 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) - mob_language = M.get_language_holder() - mob_language.only_speaks_language = null - M.selected_default_language = original_language - 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 + var/datum/language_holder/mob_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 + 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) + 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) + mob_language = M.get_language_holder() + mob_language.only_speaks_language = null + M.selected_default_language = original_language + 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 04b0778ccdf4..f53638bc1294 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 8c34d01e29f6..13472a5308dc 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.adjust_nutrition(-100) \ 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 927be03ed9fb..d2712a0146fd 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 3db73f4bfa55..31f102efd511 100644 --- a/code/datums/diseases/appendicitis.dm +++ b/code/datums/diseases/appendicitis.dm @@ -1,37 +1,37 @@ -/datum/disease/appendicitis - form = "Condition" - name = "Appendicitis" - max_stages = 3 - spread_text = "Non-Contagious" //Yogs - 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 + spread_text = "Non-Contagious" //Yogs + 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 edd6f151c9ee..074bda0560f8 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 1028d1fff4cf..57628efe3627 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 a923ebfa3131..7115a570591a 100644 --- a/code/datums/diseases/cold.dm +++ b/code/datums/diseases/cold.dm @@ -1,54 +1,54 @@ -/datum/disease/cold - name = "The Cold" - max_stages = 3 - spread_text = "On contact" //Yogs - cure_text = "Rest & Spaceacillin" - cures = list(/datum/reagent/medicine/spaceacillin) - agent = "XY-rhinovirus" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - permeability_mod = 0.5 - desc = "If left untreated the subject will contract the flu." - severity = DISEASE_SEVERITY_NONTHREAT - -/datum/disease/cold/stage_act() - ..() - switch(stage) - if(2) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(40)) //changed FROM prob(10) until sleeping is fixed - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1) && prob(5)) - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(1)) - to_chat(affected_mob, "Mucous runs down the back of your throat.") - if(3) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(25)) //changed FROM prob(5) until sleeping is fixed - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1) && prob(1)) - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(1)) - to_chat(affected_mob, "Mucous runs down the back of your throat.") - if(prob(1) && prob(50)) - if(!affected_mob.disease_resistances.Find(/datum/disease/flu)) - var/datum/disease/Flu = new /datum/disease/flu() - affected_mob.ForceContractDisease(Flu, FALSE, TRUE) +/datum/disease/cold + name = "The Cold" + max_stages = 3 + spread_text = "On contact" //Yogs + cure_text = "Rest & Spaceacillin" + cures = list(/datum/reagent/medicine/spaceacillin) + agent = "XY-rhinovirus" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + permeability_mod = 0.5 + desc = "If left untreated the subject will contract the flu." + severity = DISEASE_SEVERITY_NONTHREAT + +/datum/disease/cold/stage_act() + ..() + switch(stage) + if(2) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(40)) //changed FROM prob(10) until sleeping is fixed + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1) && prob(5)) + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(1)) + to_chat(affected_mob, "Mucous runs down the back of your throat.") + if(3) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(25)) //changed FROM prob(5) until sleeping is fixed + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1) && prob(1)) + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(1)) + to_chat(affected_mob, "Mucous runs down the back of your throat.") + if(prob(1) && prob(50)) + if(!affected_mob.disease_resistances.Find(/datum/disease/flu)) + var/datum/disease/Flu = new /datum/disease/flu() + affected_mob.ForceContractDisease(Flu, FALSE, TRUE) cure() \ No newline at end of file diff --git a/code/datums/diseases/cold9.dm b/code/datums/diseases/cold9.dm index eea3147107fa..47f391ecf78d 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 972a7f4e1871..3a67230d36ec 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 70bcc67d2102..37628a5502f1 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 916e56373fad..62bb3de8df41 100644 --- a/code/datums/diseases/flu.dm +++ b/code/datums/diseases/flu.dm @@ -1,54 +1,54 @@ -/datum/disease/flu - name = "The Flu" - max_stages = 3 - spread_text = "Airborne" - cure_text = "Spaceacillin" - cures = list(/datum/reagent/medicine/spaceacillin) - cure_chance = 10 - agent = "H13N1 flu virion" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - permeability_mod = 0.75 - desc = "If left untreated the subject will feel quite unwell." - severity = DISEASE_SEVERITY_MINOR - -/datum/disease/flu/stage_act() - ..() - switch(stage) - if(2) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - stage-- - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your muscles ache.") - if(prob(20)) - affected_mob.take_bodypart_damage(1) - if(prob(1)) - to_chat(affected_mob, "Your stomach hurts.") - if(prob(20)) - affected_mob.adjustToxLoss(1) - affected_mob.updatehealth() - - if(3) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(15)) - to_chat(affected_mob, "You feel better.") - stage-- - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your muscles ache.") - if(prob(20)) - affected_mob.take_bodypart_damage(1) - if(prob(1)) - to_chat(affected_mob, "Your stomach hurts.") - if(prob(20)) - affected_mob.adjustToxLoss(1) - affected_mob.updatehealth() - return +/datum/disease/flu + name = "The Flu" + max_stages = 3 + spread_text = "Airborne" + cure_text = "Spaceacillin" + cures = list(/datum/reagent/medicine/spaceacillin) + cure_chance = 10 + agent = "H13N1 flu virion" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + permeability_mod = 0.75 + desc = "If left untreated the subject will feel quite unwell." + severity = DISEASE_SEVERITY_MINOR + +/datum/disease/flu/stage_act() + ..() + switch(stage) + if(2) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + stage-- + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your muscles ache.") + if(prob(20)) + affected_mob.take_bodypart_damage(1) + if(prob(1)) + to_chat(affected_mob, "Your stomach hurts.") + if(prob(20)) + affected_mob.adjustToxLoss(1) + affected_mob.updatehealth() + + if(3) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(15)) + to_chat(affected_mob, "You feel better.") + stage-- + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your muscles ache.") + if(prob(20)) + affected_mob.take_bodypart_damage(1) + if(prob(1)) + to_chat(affected_mob, "Your stomach hurts.") + if(prob(20)) + affected_mob.adjustToxLoss(1) + affected_mob.updatehealth() + return diff --git a/code/datums/diseases/fluspanish.dm b/code/datums/diseases/fluspanish.dm index 1557ddfbd8d4..3297877fe910 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 8a6eab6048ff..8ac199685570 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 cb00f571aae3..29e6657e1313 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) - return ..() +/datum/disease/pierrot_throat + name = "Pierrot's Throat" + max_stages = 4 + spread_text = "Airborne" + cure_text = "Banana products, especially banana bread." + cures = list(/datum/reagent/consumable/banana) + cure_chance = 75 + agent = "H0NI<42 Virus" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 0.75 + desc = "If left untreated the subject will probably drive others to insanity." + severity = DISEASE_SEVERITY_MEDIUM + +/datum/disease/pierrot_throat/stage_act() + ..() + switch(stage) + if(1) + if(prob(10)) + to_chat(affected_mob, "You feel a little silly.") + if(2) + if(prob(10)) + to_chat(affected_mob, "You start seeing rainbows.") + if(3) + if(prob(10)) + to_chat(affected_mob, "Your thoughts are interrupted by a loud HONK!") + if(4) + if(prob(5)) + affected_mob.say( pick( list("HONK!", "Honk!", "Honk.", "Honk?", "Honk!!", "Honk?!", "Honk...") ) , forced = "pierrot's throat") + +/datum/disease/pierrot_throat/after_add() + RegisterSignal(affected_mob, COMSIG_MOB_SAY, .proc/handle_speech) + + +/datum/disease/pierrot_throat/proc/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + var/list/split_message = splittext(message, " ") //List each word in the message + var/applied = 0 + for (var/i in 1 to length(split_message)) + if(prob(3 * stage)) //Stage 1: 3% Stage 2: 6% Stage 3: 9% Stage 4: 12% + if(findtext(split_message[i], "*") || findtext(split_message[i], ";") || findtext(split_message[i], ":")) + continue + split_message[i] = "HONK" + if (applied++ > stage) + break + if (applied) + speech_args[SPEECH_SPANS] |= SPAN_CLOWN // a little bonus + message = jointext(split_message, " ") + speech_args[SPEECH_MESSAGE] = message + + +/datum/disease/pierrot_throat/Destroy() + UnregisterSignal(affected_mob, COMSIG_MOB_SAY) + return ..() + +/datum/disease/pierrot_throat/remove_disease() + UnregisterSignal(affected_mob, COMSIG_MOB_SAY) + return ..() diff --git a/code/datums/diseases/retrovirus.dm b/code/datums/diseases/retrovirus.dm index b1cf149600aa..c8ffe012981d 100644 --- a/code/datums/diseases/retrovirus.dm +++ b/code/datums/diseases/retrovirus.dm @@ -1,84 +1,84 @@ -/datum/disease/dna_retrovirus - name = "Retrovirus" - max_stages = 4 - spread_text = "Contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Rest or an injection of mutadone" - cure_chance = 6 - agent = "" - viable_mobtypes = list(/mob/living/carbon/human) - desc = "A DNA-altering retrovirus that scrambles the structural and unique enzymes of a host constantly." - severity = DISEASE_SEVERITY_HARMFUL - permeability_mod = 0.4 - stage_prob = 2 - var/restcure = 0 - -/datum/disease/dna_retrovirus/New() - ..() - agent = "Virus class [pick("A","B","C","D","E","F")][pick("A","B","C","D","E","F")]-[rand(50,300)]" - if(prob(40)) - cures = list(/datum/reagent/medicine/mutadone) - else - restcure = 1 - -/datum/disease/dna_retrovirus/Copy() - var/datum/disease/dna_retrovirus/D = ..() - D.restcure = restcure - return D - -/datum/disease/dna_retrovirus/stage_act() - ..() - switch(stage) - if(1) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(30)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(8)) - to_chat(affected_mob, "Your head hurts.") - if (prob(9)) - to_chat(affected_mob, "You feel a tingling sensation in your chest.") - if (prob(9)) - to_chat(affected_mob, "You feel angry.") - if(2) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(8)) - to_chat(affected_mob, "Your skin feels loose.") - if (prob(10)) - to_chat(affected_mob, "You feel very strange.") - if (prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head!") - affected_mob.Unconscious(40) - if (prob(4)) - to_chat(affected_mob, "Your stomach churns.") - if(3) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(10)) - to_chat(affected_mob, "Your entire body vibrates.") - - if (prob(35)) - if(prob(50)) - scramble_dna(affected_mob, 1, 0, rand(15,45)) - else - scramble_dna(affected_mob, 0, 1, rand(15,45)) - - if(4) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(5)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(60)) - if(prob(50)) - scramble_dna(affected_mob, 1, 0, rand(50,75)) - else +/datum/disease/dna_retrovirus + name = "Retrovirus" + max_stages = 4 + spread_text = "Contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Rest or an injection of mutadone" + cure_chance = 6 + agent = "" + viable_mobtypes = list(/mob/living/carbon/human) + desc = "A DNA-altering retrovirus that scrambles the structural and unique enzymes of a host constantly." + severity = DISEASE_SEVERITY_HARMFUL + permeability_mod = 0.4 + stage_prob = 2 + var/restcure = 0 + +/datum/disease/dna_retrovirus/New() + ..() + agent = "Virus class [pick("A","B","C","D","E","F")][pick("A","B","C","D","E","F")]-[rand(50,300)]" + if(prob(40)) + cures = list(/datum/reagent/medicine/mutadone) + else + restcure = 1 + +/datum/disease/dna_retrovirus/Copy() + var/datum/disease/dna_retrovirus/D = ..() + D.restcure = restcure + return D + +/datum/disease/dna_retrovirus/stage_act() + ..() + switch(stage) + if(1) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(30)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(8)) + to_chat(affected_mob, "Your head hurts.") + if (prob(9)) + to_chat(affected_mob, "You feel a tingling sensation in your chest.") + if (prob(9)) + to_chat(affected_mob, "You feel angry.") + if(2) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(8)) + to_chat(affected_mob, "Your skin feels loose.") + if (prob(10)) + to_chat(affected_mob, "You feel very strange.") + if (prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head!") + affected_mob.Unconscious(40) + if (prob(4)) + to_chat(affected_mob, "Your stomach churns.") + if(3) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(10)) + to_chat(affected_mob, "Your entire body vibrates.") + + if (prob(35)) + if(prob(50)) + scramble_dna(affected_mob, 1, 0, rand(15,45)) + else + scramble_dna(affected_mob, 0, 1, rand(15,45)) + + if(4) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(5)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(60)) + if(prob(50)) + scramble_dna(affected_mob, 1, 0, rand(50,75)) + else scramble_dna(affected_mob, 0, 1, rand(50,75)) \ No newline at end of file diff --git a/code/datums/diseases/rhumba_beat.dm b/code/datums/diseases/rhumba_beat.dm index 52e9c2e19f77..de9b66f27e46 100644 --- a/code/datums/diseases/rhumba_beat.dm +++ b/code/datums/diseases/rhumba_beat.dm @@ -1,45 +1,45 @@ -/datum/disease/rhumba_beat - name = "The Rhumba Beat" - max_stages = 5 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Chick Chicky Boom!" - cures = list("plasma") - agent = "Unknown" - viable_mobtypes = list(/mob/living/carbon/human) - permeability_mod = 1 - severity = DISEASE_SEVERITY_BIOHAZARD - -/datum/disease/rhumba_beat/stage_act() - ..() - if(affected_mob.ckey == "rosham") - cure() - return - switch(stage) - if(2) - if(prob(45)) - affected_mob.adjustFireLoss(5) - affected_mob.updatehealth() - if(prob(1)) - to_chat(affected_mob, "You feel strange...") - if(3) - if(prob(5)) - to_chat(affected_mob, "You feel the urge to dance...") - else if(prob(5)) - affected_mob.emote("gasp") - else if(prob(10)) - to_chat(affected_mob, "You feel the need to chick chicky boom...") - if(4) - if(prob(20)) - if (prob(50)) - affected_mob.adjust_fire_stacks(2) - affected_mob.IgniteMob() - else - affected_mob.emote("gasp") - to_chat(affected_mob, "You feel a burning beat inside...") - if(5) - to_chat(affected_mob, "Your body is unable to contain the Rhumba Beat...") - if(prob(50)) - explosion(get_turf(affected_mob), -1, 0, 2, 3, 0, 2) // This is equivalent to a lvl 1 fireball - else - return +/datum/disease/rhumba_beat + name = "The Rhumba Beat" + max_stages = 5 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Chick Chicky Boom!" + cures = list("plasma") + agent = "Unknown" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 1 + severity = DISEASE_SEVERITY_BIOHAZARD + +/datum/disease/rhumba_beat/stage_act() + ..() + if(affected_mob.ckey == "rosham") + cure() + return + switch(stage) + if(2) + if(prob(45)) + affected_mob.adjustFireLoss(5) + affected_mob.updatehealth() + if(prob(1)) + to_chat(affected_mob, "You feel strange...") + if(3) + if(prob(5)) + to_chat(affected_mob, "You feel the urge to dance...") + else if(prob(5)) + affected_mob.emote("gasp") + else if(prob(10)) + to_chat(affected_mob, "You feel the need to chick chicky boom...") + if(4) + if(prob(20)) + if (prob(50)) + affected_mob.adjust_fire_stacks(2) + affected_mob.IgniteMob() + else + affected_mob.emote("gasp") + to_chat(affected_mob, "You feel a burning beat inside...") + if(5) + to_chat(affected_mob, "Your body is unable to contain the Rhumba Beat...") + if(prob(50)) + explosion(get_turf(affected_mob), -1, 0, 2, 3, 0, 2) // This is equivalent to a lvl 1 fireball + else + return diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index 035a506d24d8..3eacb97219d7 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 && is_banned_from(affected_mob.ckey, bantype)) - replace_banned_player(new_mob) - new_mob.a_intent = INTENT_HARM - if(affected_mob.mind) - affected_mob.mind.transfer_to(new_mob) - else - new_mob.key = affected_mob.key - - new_mob.name = affected_mob.real_name - new_mob.real_name = new_mob.name - qdel(affected_mob) - -/datum/disease/transformation/proc/replace_banned_player(var/mob/living/new_mob) // This can run well after the mob has been transferred, so need a handle on the new mob to kill it if needed. - set waitfor = FALSE - - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [affected_mob.name]?", bantype, null, bantype, 50, affected_mob) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - to_chat(affected_mob, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") - affected_mob.ghostize(0) - affected_mob.key = C.key - else - to_chat(new_mob, "Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!") - new_mob.death() - if (!QDELETED(new_mob)) - new_mob.ghostize(can_reenter_corpse = FALSE) - new_mob.key = null - -/datum/disease/transformation/jungle_fever - name = "Jungle Fever" - cure_text = "Death." - cures = list(/datum/reagent/medicine/adminordrazine) - spread_text = "Monkey Bites" - spread_flags = DISEASE_SPREAD_SPECIAL - viable_mobtypes = list(/mob/living/carbon/monkey, /mob/living/carbon/human) - permeability_mod = 1 - cure_chance = 1 - disease_flags = CAN_CARRY|CAN_RESIST - desc = "Monkeys with this disease will bite humans, causing humans to mutate into a monkey." - severity = DISEASE_SEVERITY_BIOHAZARD - stage_prob = 4 - visibility_flags = 0 - agent = "Kongey Vibrion M-909" - new_form = /mob/living/carbon/monkey - bantype = ROLE_MONKEY - - - stage1 = list() - stage2 = list() - stage3 = list() - stage4 = list("Your back hurts.", "You breathe through your mouth.", - "You have a craving for bananas.", "Your mind feels clouded.") - stage5 = list("You feel like monkeying around.") - -/datum/disease/transformation/jungle_fever/do_disease_transformation(mob/living/carbon/affected_mob) - if(affected_mob.mind && !is_monkey(affected_mob.mind)) - add_monkey(affected_mob.mind) - if(ishuman(affected_mob)) - var/mob/living/carbon/monkey/M = affected_mob.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - M.ventcrawler = VENTCRAWLER_ALWAYS - - -/datum/disease/transformation/jungle_fever/stage_act() - ..() - switch(stage) - if(2) - if(prob(2)) - to_chat(affected_mob, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.") - if(3) - if(prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head.") - affected_mob.confused += 10 - if(4) - if(prob(3)) - affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever") - -/datum/disease/transformation/jungle_fever/cure() - remove_monkey(affected_mob.mind) - ..() - -/datum/disease/transformation/jungle_fever/monkeymode - visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC - disease_flags = CAN_CARRY //no vaccines! no cure! - -/datum/disease/transformation/jungle_fever/monkeymode/after_add() - if(affected_mob && !is_monkey_leader(affected_mob.mind)) - visibility_flags = NONE - - - -/datum/disease/transformation/robot - - name = "Robotic Transformation" - cure_text = "An injection of copper." - cures = list(/datum/reagent/copper) - cure_chance = 5 - agent = "R2D2 Nanomachines" - desc = "This disease, actually acute nanomachine infection, converts the victim into a cyborg." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list() - stage2 = list("Your joints feel stiff.", "Beep...boop..") - stage3 = list("Your joints feel very stiff.", "Your skin feels loose.", "You can feel something move...inside.") - stage4 = list("Your skin feels very loose.", "You can feel... something...inside you.") - stage5 = list("Your skin feels as if it's about to burst off!") - new_form = /mob/living/silicon/robot - infectable_biotypes = 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 = /datum/reagent/consumable/nothing - cures = list(/datum/reagent/medicine/adminordrazine) - agent = "Gluttony's Blessing" - desc = "A 'gift' from somewhere terrible." - stage_prob = 20 - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("Your stomach rumbles.") - stage2 = list("Your skin feels saggy.") - stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") - stage4 = list("You're ravenous.") - stage5 = list("You have become a morph.") - new_form = /mob/living/simple_animal/hostile/morph - infectable_biotypes = 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_list(list(/datum/reagent/pax = 5)) - if(3) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) - if(4) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) - if (prob(2)) - to_chat(affected_mob, "You let go of what you were holding.") - var/obj/item/I = affected_mob.get_active_held_item() - affected_mob.dropItemToGround(I) +/datum/disease/transformation + name = "Transformation" + max_stages = 5 + spread_text = "Acute" + spread_flags = DISEASE_SPREAD_SPECIAL + cure_text = "A coder's love (theoretical)." + agent = "Shenanigans" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey, /mob/living/carbon/alien) + severity = DISEASE_SEVERITY_BIOHAZARD + stage_prob = 10 + visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC + disease_flags = CURABLE + var/list/stage1 = list("You feel unremarkable.") + var/list/stage2 = list("You feel boring.") + var/list/stage3 = list("You feel utterly plain.") + var/list/stage4 = list("You feel white bread.") + var/list/stage5 = list("Oh the humanity!") + var/new_form = /mob/living/carbon/human + var/bantype + +/datum/disease/transformation/Copy() + var/datum/disease/transformation/D = ..() + D.stage1 = stage1.Copy() + D.stage2 = stage2.Copy() + D.stage3 = stage3.Copy() + D.stage4 = stage4.Copy() + D.stage5 = stage5.Copy() + D.new_form = D.new_form + return D + +/datum/disease/transformation/stage_act() + ..() + switch(stage) + if(1) + if (prob(stage_prob) && stage1) + to_chat(affected_mob, pick(stage1)) + if(2) + if (prob(stage_prob) && stage2) + to_chat(affected_mob, pick(stage2)) + if(3) + if (prob(stage_prob*2) && stage3) + to_chat(affected_mob, pick(stage3)) + if(4) + if (prob(stage_prob*2) && stage4) + to_chat(affected_mob, pick(stage4)) + if(5) + do_disease_transformation(affected_mob) + +/datum/disease/transformation/proc/do_disease_transformation(mob/living/affected_mob) + if(istype(affected_mob, /mob/living/carbon) && affected_mob.stat != DEAD) + if(stage5) + to_chat(affected_mob, pick(stage5)) + if(QDELETED(affected_mob)) + return + if(affected_mob.notransform) + return + affected_mob.notransform = 1 + for(var/obj/item/W in affected_mob.get_equipped_items(TRUE)) + affected_mob.dropItemToGround(W) + for(var/obj/item/I in affected_mob.held_items) + affected_mob.dropItemToGround(I) + var/mob/living/new_mob = new new_form(affected_mob.loc) + if(istype(new_mob)) + if(bantype && is_banned_from(affected_mob.ckey, bantype)) + replace_banned_player(new_mob) + new_mob.a_intent = INTENT_HARM + if(affected_mob.mind) + affected_mob.mind.transfer_to(new_mob) + else + new_mob.key = affected_mob.key + + new_mob.name = affected_mob.real_name + new_mob.real_name = new_mob.name + qdel(affected_mob) + +/datum/disease/transformation/proc/replace_banned_player(var/mob/living/new_mob) // This can run well after the mob has been transferred, so need a handle on the new mob to kill it if needed. + set waitfor = FALSE + + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [affected_mob.name]?", bantype, null, bantype, 50, affected_mob) + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + to_chat(affected_mob, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") + message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") + affected_mob.ghostize(0) + affected_mob.key = C.key + else + to_chat(new_mob, "Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!") + new_mob.death() + if (!QDELETED(new_mob)) + new_mob.ghostize(can_reenter_corpse = FALSE) + new_mob.key = null + +/datum/disease/transformation/jungle_fever + name = "Jungle Fever" + cure_text = "Death." + cures = list(/datum/reagent/medicine/adminordrazine) + spread_text = "Monkey Bites" + spread_flags = DISEASE_SPREAD_SPECIAL + viable_mobtypes = list(/mob/living/carbon/monkey, /mob/living/carbon/human) + permeability_mod = 1 + cure_chance = 1 + disease_flags = CAN_CARRY|CAN_RESIST + desc = "Monkeys with this disease will bite humans, causing humans to mutate into a monkey." + severity = DISEASE_SEVERITY_BIOHAZARD + stage_prob = 4 + visibility_flags = 0 + agent = "Kongey Vibrion M-909" + new_form = /mob/living/carbon/monkey + bantype = ROLE_MONKEY + + + stage1 = list() + stage2 = list() + stage3 = list() + stage4 = list("Your back hurts.", "You breathe through your mouth.", + "You have a craving for bananas.", "Your mind feels clouded.") + stage5 = list("You feel like monkeying around.") + +/datum/disease/transformation/jungle_fever/do_disease_transformation(mob/living/carbon/affected_mob) + if(affected_mob.mind && !is_monkey(affected_mob.mind)) + add_monkey(affected_mob.mind) + if(ishuman(affected_mob)) + var/mob/living/carbon/monkey/M = affected_mob.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) + M.ventcrawler = VENTCRAWLER_ALWAYS + + +/datum/disease/transformation/jungle_fever/stage_act() + ..() + switch(stage) + if(2) + if(prob(2)) + to_chat(affected_mob, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.") + if(3) + if(prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head.") + affected_mob.confused += 10 + if(4) + if(prob(3)) + affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever") + +/datum/disease/transformation/jungle_fever/cure() + remove_monkey(affected_mob.mind) + ..() + +/datum/disease/transformation/jungle_fever/monkeymode + visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC + disease_flags = CAN_CARRY //no vaccines! no cure! + +/datum/disease/transformation/jungle_fever/monkeymode/after_add() + if(affected_mob && !is_monkey_leader(affected_mob.mind)) + visibility_flags = NONE + + + +/datum/disease/transformation/robot + + name = "Robotic Transformation" + cure_text = "An injection of copper." + cures = list(/datum/reagent/copper) + cure_chance = 5 + agent = "R2D2 Nanomachines" + desc = "This disease, actually acute nanomachine infection, converts the victim into a cyborg." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list() + stage2 = list("Your joints feel stiff.", "Beep...boop..") + stage3 = list("Your joints feel very stiff.", "Your skin feels loose.", "You can feel something move...inside.") + stage4 = list("Your skin feels very loose.", "You can feel... something...inside you.") + stage5 = list("Your skin feels as if it's about to burst off!") + new_form = /mob/living/silicon/robot + infectable_biotypes = 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 = /datum/reagent/consumable/nothing + cures = list(/datum/reagent/medicine/adminordrazine) + agent = "Gluttony's Blessing" + desc = "A 'gift' from somewhere terrible." + stage_prob = 20 + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("Your stomach rumbles.") + stage2 = list("Your skin feels saggy.") + stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") + stage4 = list("You're ravenous.") + stage5 = list("You have become a morph.") + new_form = /mob/living/simple_animal/hostile/morph + infectable_biotypes = 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_list(list(/datum/reagent/pax = 5)) + if(3) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) + if(4) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) + if (prob(2)) + to_chat(affected_mob, "You let go of what you were holding.") + var/obj/item/I = affected_mob.get_active_held_item() + affected_mob.dropItemToGround(I) diff --git a/code/datums/diseases/wizarditis.dm b/code/datums/diseases/wizarditis.dm index 244cc819d8a4..230a074bb146 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 218f1b6d2437..17c9ed64fa56 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -1,652 +1,652 @@ - -/////////////////////////// DNA DATUM -/datum/dna - var/unique_enzymes - var/uni_identity - var/blood_type - var/datum/species/species = new /datum/species/human //The type of mutant race the player is if applicable (i.e. potato-man) - var/list/features = list("FFF") //first value is mutant color - var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings, - var/list/mutations = list() //All mutations are from now on here - var/list/temporary_mutations = list() //Temporary changes to the UE - var/list/previous = list() //For temporary name/ui/ue/blood_type modifications - var/mob/living/holder - var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875 - var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block - var/stability = 100 - var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers. - -/datum/dna/New(mob/living/new_holder) - if(istype(new_holder)) - holder = new_holder - -/datum/dna/Destroy() - if(iscarbon(holder)) - var/mob/living/carbon/cholder = holder - if(cholder.dna == src) - cholder.dna = null - holder = null - - 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.set_species(species.type, icon_update=0) - destination.dna.features = features.Copy() - destination.dna.real_name = real_name - destination.dna.temporary_mutations = temporary_mutations.Copy() - if(transfer_SE) - destination.dna.mutation_index = mutation_index - -/datum/dna/proc/copy_dna(datum/dna/new_dna) - new_dna.unique_enzymes = unique_enzymes - new_dna.mutation_index = mutation_index - new_dna.uni_identity = uni_identity - new_dna.blood_type = blood_type - new_dna.features = features.Copy() - new_dna.species = new species.type - new_dna.real_name = real_name - new_dna.mutations = mutations.Copy() - -//See mutation.dm for what 'class' does. 'time' is time till it removes itself in decimals. 0 for no timer -/datum/dna/proc/add_mutation(mutation, class = MUT_OTHER, time) - var/mutation_type = mutation - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/HM = mutation - mutation_type = HM.type - if(get_mutation(mutation_type)) - return - return force_give(new mutation_type (class, time, copymut = mutation)) - -/datum/dna/proc/remove_mutation(mutation_type) - return force_lose(get_mutation(mutation_type)) - -/datum/dna/proc/check_mutation(mutation_type) - return get_mutation(mutation_type) - -/datum/dna/proc/remove_all_mutations(list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) - remove_mutation_group(mutations, classes, mutadone) - scrambled = FALSE - -/datum/dna/proc/remove_mutation_group(list/group, list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) - if(!group) - return - for(var/datum/mutation/human/HM in group) - if((HM.class in classes) && !(HM.mutadone_proof && mutadone)) - force_lose(HM) - -/datum/dna/proc/generate_uni_identity() - . = "" - var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS) - - switch(holder.gender) - if(MALE) - L[DNA_GENDER_BLOCK] = construct_block(G_MALE, 3) - if(FEMALE) - L[DNA_GENDER_BLOCK] = construct_block(G_FEMALE, 3) - else - L[DNA_GENDER_BLOCK] = construct_block(G_PLURAL, 3) - if(ishuman(holder)) - var/mob/living/carbon/human/H = holder - if(!GLOB.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) - - for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) - if(L[i]) - . += L[i] - else - . += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters) - return . - -/datum/dna/proc/generate_dna_blocks() - var/bonus - if(species && species.inert_mutation) - bonus = GET_INITIALIZED_MUTATION(species.inert_mutation) - var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus - if(!LAZYLEN(mutations_temp)) - return - mutation_index.Cut() - shuffle_inplace(mutations_temp) - if(ismonkey(holder)) - mutations |= new RACEMUT(MUT_NORMAL) - mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT) - else - mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE) - for(var/i in 2 to DNA_MUTATION_BLOCKS) - var/datum/mutation/human/M = mutations_temp[i] - mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty) - shuffle_inplace(mutation_index) - -//Used to generate original gene sequences for every mutation -/proc/generate_gene_sequence(length=4) - var/static/list/active_sequences = list("AT","TA","GC","CG") - var/sequence - for(var/i in 1 to length*DNA_SEQUENCE_LENGTH) - sequence += pick(active_sequences) - return sequence - -//Used to create a chipped gene sequence -/proc/create_sequence(mutation, active, difficulty) - if(!difficulty) - var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(mutation) //leaves the possibility to change difficulty mid-round - if(!A) - return - difficulty = A.difficulty - difficulty += rand(-2,4) - var/sequence = GET_SEQUENCE(mutation) - if(active) - return sequence - while(difficulty) - var/randnum = rand(1, length(sequence)) - sequence = copytext(sequence, 1, randnum) + "X" + copytext(sequence, randnum+1, length(sequence)+1) - difficulty-- - return sequence - -/datum/dna/proc/generate_unique_enzymes() - . = "" - if(istype(holder)) - real_name = holder.real_name - . += md5(holder.real_name) - else - . += random_string(DNA_UNIQUE_ENZYMES_LEN, GLOB.hex_characters) - return . - -/datum/dna/proc/update_ui_block(blocknumber) - if(!blocknumber || !ishuman(holder)) - return - var/mob/living/carbon/human/H = holder - switch(blocknumber) - if(DNA_HAIR_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.hair_color)) - if(DNA_FACIAL_HAIR_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.facial_hair_color)) - if(DNA_SKIN_TONE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len)) - if(DNA_EYE_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.eye_color)) - if(DNA_GENDER_BLOCK) - switch(H.gender) - if(MALE) - setblock(uni_identity, blocknumber, construct_block(G_MALE, 3)) - if(FEMALE) - setblock(uni_identity, blocknumber, construct_block(G_FEMALE, 3)) - else - setblock(uni_identity, blocknumber, construct_block(G_PLURAL, 3)) - if(DNA_FACIAL_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)) - -//Please use add_mutation or activate_mutation instead -/datum/dna/proc/force_give(datum/mutation/human/HM) - if(holder && HM) - if(HM.class == MUT_NORMAL) - set_se(1, HM) - . = HM.on_acquiring(holder) - if(.) - qdel(HM) - update_instability() - -//Use remove_mutation instead -/datum/dna/proc/force_lose(datum/mutation/human/HM) - if(holder && (HM in mutations)) - set_se(0, HM) - . = HM.on_losing(holder) - update_instability(FALSE) - return - -/datum/dna/proc/is_same_as(datum/dna/D) - if(uni_identity == D.uni_identity && mutation_index == D.mutation_index && real_name == D.real_name) - if(species.type == D.species.type && features == D.features && blood_type == D.blood_type) - return 1 - return 0 - -/datum/dna/proc/update_instability(alert=TRUE) - stability = 100 - for(var/datum/mutation/human/M in mutations) - if(M.class == MUT_EXTRA) - stability -= M.instability * GET_MUTATION_STABILIZER(M) - if(holder) - var/message - if(alert) - switch(stability) - if(70 to 90) - message = "You shiver." - if(60 to 69) - message = "You feel cold." - if(40 to 59) - message = "You feel sick." - if(20 to 39) - message = "It feels like your skin is moving." - if(1 to 19) - message = "You can feel your cells burning." - if(-INFINITY to 0) - message = "You can feel your DNA exploding, we need to do something fast!" - if(stability <= 0) - holder.apply_status_effect(STATUS_EFFECT_DNA_MELT) - if(message) - to_chat(holder, message) - -//used to update dna UI, UE, and dna.real_name. -/datum/dna/proc/update_dna_identity() - uni_identity = generate_uni_identity() - unique_enzymes = generate_unique_enzymes() - -/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE) - if(newblood_type) - blood_type = newblood_type - unique_enzymes = generate_unique_enzymes() - uni_identity = generate_uni_identity() - if(!skip_index) //I hate this - generate_dna_blocks() - features = random_features() - - -/datum/dna/stored //subtype used by brain mob's stored_dna - -/datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna. - return - -/datum/dna/stored/remove_mutation(mutation_name) - return - -/datum/dna/stored/check_mutation(mutation_name) - return - -/datum/dna/stored/remove_all_mutations(list/classes, mutadone = FALSE) - return - -/datum/dna/stored/remove_mutation_group(list/group) - return - -/////////////////////////// DNA MOB-PROCS ////////////////////// - -/mob/proc/set_species(datum/species/mrace, icon_update = 1) - return - -/mob/living/brain/set_species(datum/species/mrace, icon_update = 1) - if(mrace) - if(ispath(mrace)) - stored_dna.species = new mrace() - else - stored_dna.species = mrace //not calling any species update procs since we're a brain, not a monkey/human - - -/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) - if(mrace && has_dna()) - var/datum/species/new_race - if(ispath(mrace)) - new_race = new mrace - else if(istype(mrace)) - new_race = mrace - else - return - deathsound = new_race.deathsound - dna.species.on_species_loss(src, new_race, pref_load) - var/datum/species/old_species = dna.species - dna.species = new_race - dna.species.on_species_gain(src, old_species, pref_load) - -/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) - ..() - if(icon_update) - update_body() - update_hair() - update_body_parts() - update_mutations_overlay()// no lizard with human hulk overlay please. - - -/mob/proc/has_dna() - return - -/mob/living/carbon/has_dna() - return dna - - -/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations) -//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc) - if(newfeatures) - dna.features = newfeatures - - if(mrace) - var/datum/species/newrace = new mrace.type - newrace.copy_properties_from(mrace) - set_species(newrace, icon_update=0) - - if(newreal_name) - real_name = newreal_name - dna.generate_unique_enzymes() - - if(newblood_type) - dna.blood_type = newblood_type - - if(ui) - dna.uni_identity = ui - updateappearance(icon_update=0) - - if(LAZYLEN(mutation_index)) - dna.mutation_index = mutation_index.Copy() - domutcheck() - - if(mrace || newfeatures || ui) - update_body() - update_hair() - update_body_parts() - update_mutations_overlay() - - if(LAZYLEN(mutations)) - for(var/M in mutations) - var/datum/mutation/human/HM = M - if(HM.allow_transfer || force_transfer_mutations) - dna.force_give(new HM.type(HM.class, copymut=HM)) //using force_give since it may include exotic mutations that otherwise wont be handled properly - -/mob/living/carbon/proc/create_dna() - dna = new /datum/dna(src) - if(!dna.species) - var/rando_race = pick(GLOB.roundstart_races) - dna.species = new rando_race() - -//proc used to update the mob's appearance after its dna UI has been changed -/mob/living/carbon/proc/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) - if(!has_dna()) - return - - switch(deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 3)) - if(G_MALE) - gender = MALE - if(G_FEMALE) - gender = FEMALE - else - gender = PLURAL - -/mob/living/carbon/human/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) - ..() - var/structure = dna.uni_identity - hair_color = sanitize_hexcolor(getblock(structure, DNA_HAIR_COLOR_BLOCK)) - facial_hair_color = sanitize_hexcolor(getblock(structure, DNA_FACIAL_HAIR_COLOR_BLOCK)) - skin_tone = GLOB.skin_tones[deconstruct_block(getblock(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)] - eye_color = sanitize_hexcolor(getblock(structure, DNA_EYE_COLOR_BLOCK)) - facial_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() - if(!has_dna()) - return - - for(var/mutation in dna.mutation_index) - if(ismob(dna.check_block(mutation))) - return //we got monkeyized/humanized, this mob will be deleted, no need to continue. - - update_mutations_overlay() - -/datum/dna/proc/check_block(mutation) - var/datum/mutation/human/HM = get_mutation(mutation) - if(check_block_string(mutation)) - if(!HM) - . = add_mutation(mutation, MUT_NORMAL) - return - return force_lose(HM) - -//Return the active mutation of a type if there is one -/datum/dna/proc/get_mutation(A) - for(var/datum/mutation/human/HM in mutations) - if(HM.type == A) - return HM - -/datum/dna/proc/check_block_string(mutation) - if((LAZYLEN(mutation_index) > DNA_MUTATION_BLOCKS) || !(mutation in mutation_index)) - return 0 - return is_gene_active(mutation) - -/datum/dna/proc/is_gene_active(mutation) - return (mutation_index[mutation] == GET_SEQUENCE(mutation)) - -/datum/dna/proc/set_se(on=TRUE, datum/mutation/human/HM) - if(!HM || !(HM.type in mutation_index) || (LAZYLEN(mutation_index) < DNA_MUTATION_BLOCKS)) - return - . = TRUE - if(on) - mutation_index[HM.type] = GET_SEQUENCE(HM.type) - else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type]) - mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty) - -/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob - if(!mutation) - return FALSE - var/mutation_type = mutation - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/M = mutation - mutation_type = M.type - if(!mutation_in_sequence(mutation_type)) //cant activate what we dont have, use add_mutation - return FALSE - add_mutation(mutation, MUT_NORMAL) - return TRUE - -/////////////////////////// DNA HELPER-PROCS ////////////////////////////// - -/proc/getleftblocks(input,blocknumber,blocksize) - if(blocknumber > 1) - return copytext(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) - -/datum/dna/proc/mutation_in_sequence(mutation) - if(!mutation) - return - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/HM = mutation - if(HM.type in mutation_index) - return TRUE - else if(mutation in mutation_index) - return TRUE - - -/mob/living/carbon/proc/randmut(list/candidates, difficulty = 2) - if(!has_dna()) - return - var/mutation = pick(candidates) - . = dna.add_mutation(mutation) - -/mob/living/carbon/proc/easy_randmut(quality = POSITIVE + NEGATIVE + MINOR_NEGATIVE, scrambled = TRUE, sequence = TRUE, exclude_monkey = TRUE) - if(!has_dna()) - return - var/list/mutations = list() - if(quality & POSITIVE) - mutations += GLOB.good_mutations - if(quality & NEGATIVE) - mutations += GLOB.bad_mutations - if(quality & MINOR_NEGATIVE) - mutations += GLOB.not_good_mutations - var/list/possible = list() - for(var/datum/mutation/human/A in mutations) - if((!sequence || dna.mutation_in_sequence(A.type)) && !dna.get_mutation(A.type)) - possible += A.type - if(exclude_monkey) - possible.Remove(RACEMUT) - if(LAZYLEN(possible)) - var/mutation = pick(possible) - . = dna.activate_mutation(mutation) - if(scrambled) - var/datum/mutation/human/HM = dna.get_mutation(mutation) - if(HM) - HM.scrambled = TRUE - return TRUE - -/mob/living/carbon/proc/randmuti() - if(!has_dna()) - return - var/num = rand(1, DNA_UNI_IDENTITY_BLOCKS) - var/newdna = setblock(dna.uni_identity, num, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) - dna.uni_identity = newdna - updateappearance(mutations_overlay_update=1) - -/mob/living/carbon/proc/clean_dna() - if(!has_dna()) - return - dna.remove_all_mutations() - -/mob/living/carbon/proc/clean_randmut(list/candidates, difficulty = 2) - clean_dna() - randmut(candidates, difficulty) - -/proc/scramble_dna(mob/living/carbon/M, ui=FALSE, se=FALSE, probability) - if(!M.has_dna()) - return 0 - if(se) - for(var/i=1, i<=DNA_MUTATION_BLOCKS, i++) - if(prob(probability)) - M.dna.generate_dna_blocks() - M.domutcheck() - if(ui) - for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) - if(prob(probability)) - M.dna.uni_identity = setblock(M.dna.uni_identity, i, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) - M.updateappearance(mutations_overlay_update=1) - return 1 - -//value in range 1 to values. values must be greater than 0 -//all arguments assumed to be positive integers -/proc/construct_block(value, values, blocksize=DNA_BLOCK_SIZE) - var/width = round((16**blocksize)/values) - if(value < 1) - value = 1 - value = (value * width) - rand(1,width) - return num2hex(value, blocksize) - -//value is hex -/proc/deconstruct_block(value, values, blocksize=DNA_BLOCK_SIZE) - var/width = round((16**blocksize)/values) - value = round(hex2num(value) / width) + 1 - if(value > values) - value = values - return value - -/////////////////////////// DNA HELPER-PROCS - -/mob/living/carbon/human/proc/something_horrible(ignore_stability) - if(!has_dna()) //shouldn't ever happen anyway so it's just in really weird cases - return - if(!ignore_stability && (dna.stability > 0)) - return - var/instability = -dna.stability - dna.remove_all_mutations() - dna.stability = 100 - if(prob(max(70-instability,0))) - switch(rand(0,8)) //not complete and utter death //yogs - fuck snails - if(0) - monkeyize() - if(1) - gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) - new/obj/vehicle/ridden/wheelchair(get_turf(src)) //don't buckle, because I can't imagine to plethora of things to go through that could otherwise break - to_chat(src, "My flesh turned into a wheelchair and I can't feel my legs.") - if(2) - corgize() - if(3) - to_chat(src, "Oh, I actually feel quite alright!") - if(4) - to_chat(src, "Oh, I actually feel quite alright!") //you thought - physiology.damage_resistance = -20000 - if(5) - to_chat(src, "Oh, I actually feel quite alright!") - reagents.add_reagent(/datum/reagent/aslimetoxin, 10) - if(6) - apply_status_effect(STATUS_EFFECT_GO_AWAY) - if(7) - to_chat(src, "Oh, I actually feel quite alright!") - ForceContractDisease(new/datum/disease/decloning()) //slow acting, non-viral clone damage based GBS - if(8) - var/list/elligible_organs = list() - for(var/obj/item/organ/O in internal_organs) //make sure we dont get an implant or cavity item - elligible_organs += O - vomit(20, TRUE) - if(elligible_organs.len) - var/obj/item/organ/O = pick(elligible_organs) - O.Remove(src) - visible_message("[src] vomits up their [O.name]!", "You vomit up your [O.name]") //no "vomit up your the heart" - O.forceMove(drop_location()) - if(prob(20)) - O.animate_atom_living() - /* YOGSif(9 to 10) - ForceContractDisease(new/datum/disease/gastrolosis()) - to_chat(src, "Oh, I actually feel quite alright!") YOGS*/ - else - switch(rand(0,5)) - if(0) - gib() - if(1) - dust() - - if(2) - death() - petrify(INFINITY) - if(3) - if(prob(95)) - var/obj/item/bodypart/BP = get_bodypart(pick(BODY_ZONE_CHEST,BODY_ZONE_HEAD)) - if(BP) - BP.dismember() - else - gib() - else - set_species(/datum/species/dullahan) - if(4) - visible_message("[src]'s skin melts off!", "Your skin melts off!") - spawn_gibs() - set_species(/datum/species/skeleton) - if(prob(90)) - addtimer(CALLBACK(src, .proc/death), 30) - if(mind) - mind.hasSoul = FALSE - if(5) - to_chat(src, "LOOK UP!") - addtimer(CALLBACK(src, .proc/something_horrible_mindmelt), 30) - - -/mob/living/carbon/human/proc/something_horrible_mindmelt() - if(!HAS_TRAIT(src, TRAIT_BLIND)) - var/obj/item/organ/eyes/eyes = locate(/obj/item/organ/eyes) in internal_organs - if(!eyes) - return - eyes.Remove(src) - qdel(eyes) - visible_message("[src] looks up and their eyes melt away!", "='userdanger'>I understand now.") - addtimer(CALLBACK(src, .proc/adjustOrganLoss, ORGAN_SLOT_BRAIN, 200), 20) + +/////////////////////////// DNA DATUM +/datum/dna + var/unique_enzymes + var/uni_identity + var/blood_type + var/datum/species/species = new /datum/species/human //The type of mutant race the player is if applicable (i.e. potato-man) + var/list/features = list("FFF") //first value is mutant color + var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings, + var/list/mutations = list() //All mutations are from now on here + var/list/temporary_mutations = list() //Temporary changes to the UE + var/list/previous = list() //For temporary name/ui/ue/blood_type modifications + var/mob/living/holder + var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875 + var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block + var/stability = 100 + var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers. + +/datum/dna/New(mob/living/new_holder) + if(istype(new_holder)) + holder = new_holder + +/datum/dna/Destroy() + if(iscarbon(holder)) + var/mob/living/carbon/cholder = holder + if(cholder.dna == src) + cholder.dna = null + holder = null + + 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.set_species(species.type, icon_update=0) + destination.dna.features = features.Copy() + destination.dna.real_name = real_name + destination.dna.temporary_mutations = temporary_mutations.Copy() + if(transfer_SE) + destination.dna.mutation_index = mutation_index + +/datum/dna/proc/copy_dna(datum/dna/new_dna) + new_dna.unique_enzymes = unique_enzymes + new_dna.mutation_index = mutation_index + new_dna.uni_identity = uni_identity + new_dna.blood_type = blood_type + new_dna.features = features.Copy() + new_dna.species = new species.type + new_dna.real_name = real_name + new_dna.mutations = mutations.Copy() + +//See mutation.dm for what 'class' does. 'time' is time till it removes itself in decimals. 0 for no timer +/datum/dna/proc/add_mutation(mutation, class = MUT_OTHER, time) + var/mutation_type = mutation + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/HM = mutation + mutation_type = HM.type + if(get_mutation(mutation_type)) + return + return force_give(new mutation_type (class, time, copymut = mutation)) + +/datum/dna/proc/remove_mutation(mutation_type) + return force_lose(get_mutation(mutation_type)) + +/datum/dna/proc/check_mutation(mutation_type) + return get_mutation(mutation_type) + +/datum/dna/proc/remove_all_mutations(list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) + remove_mutation_group(mutations, classes, mutadone) + scrambled = FALSE + +/datum/dna/proc/remove_mutation_group(list/group, list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) + if(!group) + return + for(var/datum/mutation/human/HM in group) + if((HM.class in classes) && !(HM.mutadone_proof && mutadone)) + force_lose(HM) + +/datum/dna/proc/generate_uni_identity() + . = "" + var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS) + + switch(holder.gender) + if(MALE) + L[DNA_GENDER_BLOCK] = construct_block(G_MALE, 3) + if(FEMALE) + L[DNA_GENDER_BLOCK] = construct_block(G_FEMALE, 3) + else + L[DNA_GENDER_BLOCK] = construct_block(G_PLURAL, 3) + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + if(!GLOB.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) + + for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) + if(L[i]) + . += L[i] + else + . += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters) + return . + +/datum/dna/proc/generate_dna_blocks() + var/bonus + if(species && species.inert_mutation) + bonus = GET_INITIALIZED_MUTATION(species.inert_mutation) + var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus + if(!LAZYLEN(mutations_temp)) + return + mutation_index.Cut() + shuffle_inplace(mutations_temp) + if(ismonkey(holder)) + mutations |= new RACEMUT(MUT_NORMAL) + mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT) + else + mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE) + for(var/i in 2 to DNA_MUTATION_BLOCKS) + var/datum/mutation/human/M = mutations_temp[i] + mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty) + shuffle_inplace(mutation_index) + +//Used to generate original gene sequences for every mutation +/proc/generate_gene_sequence(length=4) + var/static/list/active_sequences = list("AT","TA","GC","CG") + var/sequence + for(var/i in 1 to length*DNA_SEQUENCE_LENGTH) + sequence += pick(active_sequences) + return sequence + +//Used to create a chipped gene sequence +/proc/create_sequence(mutation, active, difficulty) + if(!difficulty) + var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(mutation) //leaves the possibility to change difficulty mid-round + if(!A) + return + difficulty = A.difficulty + difficulty += rand(-2,4) + var/sequence = GET_SEQUENCE(mutation) + if(active) + return sequence + while(difficulty) + var/randnum = rand(1, length(sequence)) + sequence = copytext(sequence, 1, randnum) + "X" + copytext(sequence, randnum+1, length(sequence)+1) + difficulty-- + return sequence + +/datum/dna/proc/generate_unique_enzymes() + . = "" + if(istype(holder)) + real_name = holder.real_name + . += md5(holder.real_name) + else + . += random_string(DNA_UNIQUE_ENZYMES_LEN, GLOB.hex_characters) + return . + +/datum/dna/proc/update_ui_block(blocknumber) + if(!blocknumber || !ishuman(holder)) + return + var/mob/living/carbon/human/H = holder + switch(blocknumber) + if(DNA_HAIR_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.hair_color)) + if(DNA_FACIAL_HAIR_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.facial_hair_color)) + if(DNA_SKIN_TONE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len)) + if(DNA_EYE_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.eye_color)) + if(DNA_GENDER_BLOCK) + switch(H.gender) + if(MALE) + setblock(uni_identity, blocknumber, construct_block(G_MALE, 3)) + if(FEMALE) + setblock(uni_identity, blocknumber, construct_block(G_FEMALE, 3)) + else + setblock(uni_identity, blocknumber, construct_block(G_PLURAL, 3)) + if(DNA_FACIAL_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)) + +//Please use add_mutation or activate_mutation instead +/datum/dna/proc/force_give(datum/mutation/human/HM) + if(holder && HM) + if(HM.class == MUT_NORMAL) + set_se(1, HM) + . = HM.on_acquiring(holder) + if(.) + qdel(HM) + update_instability() + +//Use remove_mutation instead +/datum/dna/proc/force_lose(datum/mutation/human/HM) + if(holder && (HM in mutations)) + set_se(0, HM) + . = HM.on_losing(holder) + update_instability(FALSE) + return + +/datum/dna/proc/is_same_as(datum/dna/D) + if(uni_identity == D.uni_identity && mutation_index == D.mutation_index && real_name == D.real_name) + if(species.type == D.species.type && features == D.features && blood_type == D.blood_type) + return 1 + return 0 + +/datum/dna/proc/update_instability(alert=TRUE) + stability = 100 + for(var/datum/mutation/human/M in mutations) + if(M.class == MUT_EXTRA) + stability -= M.instability * GET_MUTATION_STABILIZER(M) + if(holder) + var/message + if(alert) + switch(stability) + if(70 to 90) + message = "You shiver." + if(60 to 69) + message = "You feel cold." + if(40 to 59) + message = "You feel sick." + if(20 to 39) + message = "It feels like your skin is moving." + if(1 to 19) + message = "You can feel your cells burning." + if(-INFINITY to 0) + message = "You can feel your DNA exploding, we need to do something fast!" + if(stability <= 0) + holder.apply_status_effect(STATUS_EFFECT_DNA_MELT) + if(message) + to_chat(holder, message) + +//used to update dna UI, UE, and dna.real_name. +/datum/dna/proc/update_dna_identity() + uni_identity = generate_uni_identity() + unique_enzymes = generate_unique_enzymes() + +/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE) + if(newblood_type) + blood_type = newblood_type + unique_enzymes = generate_unique_enzymes() + uni_identity = generate_uni_identity() + if(!skip_index) //I hate this + generate_dna_blocks() + features = random_features() + + +/datum/dna/stored //subtype used by brain mob's stored_dna + +/datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna. + return + +/datum/dna/stored/remove_mutation(mutation_name) + return + +/datum/dna/stored/check_mutation(mutation_name) + return + +/datum/dna/stored/remove_all_mutations(list/classes, mutadone = FALSE) + return + +/datum/dna/stored/remove_mutation_group(list/group) + return + +/////////////////////////// DNA MOB-PROCS ////////////////////// + +/mob/proc/set_species(datum/species/mrace, icon_update = 1) + return + +/mob/living/brain/set_species(datum/species/mrace, icon_update = 1) + if(mrace) + if(ispath(mrace)) + stored_dna.species = new mrace() + else + stored_dna.species = mrace //not calling any species update procs since we're a brain, not a monkey/human + + +/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) + if(mrace && has_dna()) + var/datum/species/new_race + if(ispath(mrace)) + new_race = new mrace + else if(istype(mrace)) + new_race = mrace + else + return + deathsound = new_race.deathsound + dna.species.on_species_loss(src, new_race, pref_load) + var/datum/species/old_species = dna.species + dna.species = new_race + dna.species.on_species_gain(src, old_species, pref_load) + +/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) + ..() + if(icon_update) + update_body() + update_hair() + update_body_parts() + update_mutations_overlay()// no lizard with human hulk overlay please. + + +/mob/proc/has_dna() + return + +/mob/living/carbon/has_dna() + return dna + + +/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations) +//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc) + if(newfeatures) + dna.features = newfeatures + + if(mrace) + var/datum/species/newrace = new mrace.type + newrace.copy_properties_from(mrace) + set_species(newrace, icon_update=0) + + if(newreal_name) + real_name = newreal_name + dna.generate_unique_enzymes() + + if(newblood_type) + dna.blood_type = newblood_type + + if(ui) + dna.uni_identity = ui + updateappearance(icon_update=0) + + if(LAZYLEN(mutation_index)) + dna.mutation_index = mutation_index.Copy() + domutcheck() + + if(mrace || newfeatures || ui) + update_body() + update_hair() + update_body_parts() + update_mutations_overlay() + + if(LAZYLEN(mutations)) + for(var/M in mutations) + var/datum/mutation/human/HM = M + if(HM.allow_transfer || force_transfer_mutations) + dna.force_give(new HM.type(HM.class, copymut=HM)) //using force_give since it may include exotic mutations that otherwise wont be handled properly + +/mob/living/carbon/proc/create_dna() + dna = new /datum/dna(src) + if(!dna.species) + var/rando_race = pick(GLOB.roundstart_races) + dna.species = new rando_race() + +//proc used to update the mob's appearance after its dna UI has been changed +/mob/living/carbon/proc/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) + if(!has_dna()) + return + + switch(deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 3)) + if(G_MALE) + gender = MALE + if(G_FEMALE) + gender = FEMALE + else + gender = PLURAL + +/mob/living/carbon/human/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) + ..() + var/structure = dna.uni_identity + hair_color = sanitize_hexcolor(getblock(structure, DNA_HAIR_COLOR_BLOCK)) + facial_hair_color = sanitize_hexcolor(getblock(structure, DNA_FACIAL_HAIR_COLOR_BLOCK)) + skin_tone = GLOB.skin_tones[deconstruct_block(getblock(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)] + eye_color = sanitize_hexcolor(getblock(structure, DNA_EYE_COLOR_BLOCK)) + facial_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() + if(!has_dna()) + return + + for(var/mutation in dna.mutation_index) + if(ismob(dna.check_block(mutation))) + return //we got monkeyized/humanized, this mob will be deleted, no need to continue. + + update_mutations_overlay() + +/datum/dna/proc/check_block(mutation) + var/datum/mutation/human/HM = get_mutation(mutation) + if(check_block_string(mutation)) + if(!HM) + . = add_mutation(mutation, MUT_NORMAL) + return + return force_lose(HM) + +//Return the active mutation of a type if there is one +/datum/dna/proc/get_mutation(A) + for(var/datum/mutation/human/HM in mutations) + if(HM.type == A) + return HM + +/datum/dna/proc/check_block_string(mutation) + if((LAZYLEN(mutation_index) > DNA_MUTATION_BLOCKS) || !(mutation in mutation_index)) + return 0 + return is_gene_active(mutation) + +/datum/dna/proc/is_gene_active(mutation) + return (mutation_index[mutation] == GET_SEQUENCE(mutation)) + +/datum/dna/proc/set_se(on=TRUE, datum/mutation/human/HM) + if(!HM || !(HM.type in mutation_index) || (LAZYLEN(mutation_index) < DNA_MUTATION_BLOCKS)) + return + . = TRUE + if(on) + mutation_index[HM.type] = GET_SEQUENCE(HM.type) + else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type]) + mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty) + +/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob + if(!mutation) + return FALSE + var/mutation_type = mutation + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/M = mutation + mutation_type = M.type + if(!mutation_in_sequence(mutation_type)) //cant activate what we dont have, use add_mutation + return FALSE + add_mutation(mutation, MUT_NORMAL) + return TRUE + +/////////////////////////// DNA HELPER-PROCS ////////////////////////////// + +/proc/getleftblocks(input,blocknumber,blocksize) + if(blocknumber > 1) + return copytext(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) + +/datum/dna/proc/mutation_in_sequence(mutation) + if(!mutation) + return + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/HM = mutation + if(HM.type in mutation_index) + return TRUE + else if(mutation in mutation_index) + return TRUE + + +/mob/living/carbon/proc/randmut(list/candidates, difficulty = 2) + if(!has_dna()) + return + var/mutation = pick(candidates) + . = dna.add_mutation(mutation) + +/mob/living/carbon/proc/easy_randmut(quality = POSITIVE + NEGATIVE + MINOR_NEGATIVE, scrambled = TRUE, sequence = TRUE, exclude_monkey = TRUE) + if(!has_dna()) + return + var/list/mutations = list() + if(quality & POSITIVE) + mutations += GLOB.good_mutations + if(quality & NEGATIVE) + mutations += GLOB.bad_mutations + if(quality & MINOR_NEGATIVE) + mutations += GLOB.not_good_mutations + var/list/possible = list() + for(var/datum/mutation/human/A in mutations) + if((!sequence || dna.mutation_in_sequence(A.type)) && !dna.get_mutation(A.type)) + possible += A.type + if(exclude_monkey) + possible.Remove(RACEMUT) + if(LAZYLEN(possible)) + var/mutation = pick(possible) + . = dna.activate_mutation(mutation) + if(scrambled) + var/datum/mutation/human/HM = dna.get_mutation(mutation) + if(HM) + HM.scrambled = TRUE + return TRUE + +/mob/living/carbon/proc/randmuti() + if(!has_dna()) + return + var/num = rand(1, DNA_UNI_IDENTITY_BLOCKS) + var/newdna = setblock(dna.uni_identity, num, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) + dna.uni_identity = newdna + updateappearance(mutations_overlay_update=1) + +/mob/living/carbon/proc/clean_dna() + if(!has_dna()) + return + dna.remove_all_mutations() + +/mob/living/carbon/proc/clean_randmut(list/candidates, difficulty = 2) + clean_dna() + randmut(candidates, difficulty) + +/proc/scramble_dna(mob/living/carbon/M, ui=FALSE, se=FALSE, probability) + if(!M.has_dna()) + return 0 + if(se) + for(var/i=1, i<=DNA_MUTATION_BLOCKS, i++) + if(prob(probability)) + M.dna.generate_dna_blocks() + M.domutcheck() + if(ui) + for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) + if(prob(probability)) + M.dna.uni_identity = setblock(M.dna.uni_identity, i, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) + M.updateappearance(mutations_overlay_update=1) + return 1 + +//value in range 1 to values. values must be greater than 0 +//all arguments assumed to be positive integers +/proc/construct_block(value, values, blocksize=DNA_BLOCK_SIZE) + var/width = round((16**blocksize)/values) + if(value < 1) + value = 1 + value = (value * width) - rand(1,width) + return num2hex(value, blocksize) + +//value is hex +/proc/deconstruct_block(value, values, blocksize=DNA_BLOCK_SIZE) + var/width = round((16**blocksize)/values) + value = round(hex2num(value) / width) + 1 + if(value > values) + value = values + return value + +/////////////////////////// DNA HELPER-PROCS + +/mob/living/carbon/human/proc/something_horrible(ignore_stability) + if(!has_dna()) //shouldn't ever happen anyway so it's just in really weird cases + return + if(!ignore_stability && (dna.stability > 0)) + return + var/instability = -dna.stability + dna.remove_all_mutations() + dna.stability = 100 + if(prob(max(70-instability,0))) + switch(rand(0,8)) //not complete and utter death //yogs - fuck snails + if(0) + monkeyize() + if(1) + gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) + new/obj/vehicle/ridden/wheelchair(get_turf(src)) //don't buckle, because I can't imagine to plethora of things to go through that could otherwise break + to_chat(src, "My flesh turned into a wheelchair and I can't feel my legs.") + if(2) + corgize() + if(3) + to_chat(src, "Oh, I actually feel quite alright!") + if(4) + to_chat(src, "Oh, I actually feel quite alright!") //you thought + physiology.damage_resistance = -20000 + if(5) + to_chat(src, "Oh, I actually feel quite alright!") + reagents.add_reagent(/datum/reagent/aslimetoxin, 10) + if(6) + apply_status_effect(STATUS_EFFECT_GO_AWAY) + if(7) + to_chat(src, "Oh, I actually feel quite alright!") + ForceContractDisease(new/datum/disease/decloning()) //slow acting, non-viral clone damage based GBS + if(8) + var/list/elligible_organs = list() + for(var/obj/item/organ/O in internal_organs) //make sure we dont get an implant or cavity item + elligible_organs += O + vomit(20, TRUE) + if(elligible_organs.len) + var/obj/item/organ/O = pick(elligible_organs) + O.Remove(src) + visible_message("[src] vomits up their [O.name]!", "You vomit up your [O.name]") //no "vomit up your the heart" + O.forceMove(drop_location()) + if(prob(20)) + O.animate_atom_living() + /* YOGSif(9 to 10) + ForceContractDisease(new/datum/disease/gastrolosis()) + to_chat(src, "Oh, I actually feel quite alright!") YOGS*/ + else + switch(rand(0,5)) + if(0) + gib() + if(1) + dust() + + if(2) + death() + petrify(INFINITY) + if(3) + if(prob(95)) + var/obj/item/bodypart/BP = get_bodypart(pick(BODY_ZONE_CHEST,BODY_ZONE_HEAD)) + if(BP) + BP.dismember() + else + gib() + else + set_species(/datum/species/dullahan) + if(4) + visible_message("[src]'s skin melts off!", "Your skin melts off!") + spawn_gibs() + set_species(/datum/species/skeleton) + if(prob(90)) + addtimer(CALLBACK(src, .proc/death), 30) + if(mind) + mind.hasSoul = FALSE + if(5) + to_chat(src, "LOOK UP!") + addtimer(CALLBACK(src, .proc/something_horrible_mindmelt), 30) + + +/mob/living/carbon/human/proc/something_horrible_mindmelt() + if(!HAS_TRAIT(src, TRAIT_BLIND)) + var/obj/item/organ/eyes/eyes = locate(/obj/item/organ/eyes) in internal_organs + if(!eyes) + return + eyes.Remove(src) + qdel(eyes) + visible_message("[src] looks up and their eyes melt away!", "='userdanger'>I understand now.") + addtimer(CALLBACK(src, .proc/adjustOrganLoss, ORGAN_SLOT_BRAIN, 200), 20) diff --git a/code/datums/forced_movement.dm b/code/datums/forced_movement.dm index 2c649be12b80..551699a01911 100644 --- a/code/datums/forced_movement.dm +++ b/code/datums/forced_movement.dm @@ -1,93 +1,93 @@ -//Just new and forget -/datum/forced_movement - var/atom/movable/victim - var/atom/target - var/last_processed - var/steps_per_tick - var/allow_climbing - var/datum/callback/on_step - var/moved_at_all = FALSE - //as fast as ssfastprocess -/datum/forced_movement/New(atom/movable/_victim, atom/_target, _steps_per_tick = 0.5, _allow_climbing = FALSE, datum/callback/_on_step = null) - victim = _victim - target = _target - steps_per_tick = _steps_per_tick - allow_climbing = _allow_climbing - on_step = _on_step - - . = ..() - - if(_victim && _target && _steps_per_tick && !_victim.force_moving) - last_processed = world.time - _victim.force_moving = src - START_PROCESSING(SSfastprocess, src) - else - qdel(src) //if you want to overwrite the current forced movement, call qdel(victim.force_moving) before creating this - -/datum/forced_movement/Destroy() - if(victim.force_moving == src) - victim.force_moving = null - if(moved_at_all) - victim.forceMove(victim.loc) //get the side effects of moving here that require us to currently not be force_moving aka reslipping on ice - STOP_PROCESSING(SSfastprocess, src) - victim = null - target = null - return ..() - -/datum/forced_movement/process() - if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc) - qdel(src) - return - var/steps_to_take = round(steps_per_tick * (world.time - last_processed)) - if(steps_to_take) - for(var/i in 1 to steps_to_take) - if(TryMove()) - moved_at_all = TRUE - if(on_step) - on_step.InvokeAsync() - else - qdel(src) - return - last_processed = world.time - -/datum/forced_movement/proc/TryMove(recursive = FALSE) - if(QDELETED(src)) //Our previous step caused deletion of this datum - return - - var/atom/movable/vic = victim //sanic - var/atom/tar = target - - if(!recursive) - . = step_towards(vic, tar) - - //shit way for getting around corners - if(!.) - if(tar.x > vic.x) - if(step(vic, EAST)) - . = TRUE - else if(tar.x < vic.x) - if(step(vic, WEST)) - . = TRUE - - if(!.) - if(tar.y > vic.y) - if(step(vic, NORTH)) - . = TRUE - else if(tar.y < vic.y) - if(step(vic, SOUTH)) - . = TRUE - - if(!.) - if(recursive) - return FALSE - else - . = TryMove(TRUE) - - . = . && (vic.loc != tar.loc) - -/mob/Bump(atom/A) - . = ..() - if(force_moving && force_moving.allow_climbing && isstructure(A)) - var/obj/structure/S = A - if(S.climbable) - S.do_climb(src) +//Just new and forget +/datum/forced_movement + var/atom/movable/victim + var/atom/target + var/last_processed + var/steps_per_tick + var/allow_climbing + var/datum/callback/on_step + var/moved_at_all = FALSE + //as fast as ssfastprocess +/datum/forced_movement/New(atom/movable/_victim, atom/_target, _steps_per_tick = 0.5, _allow_climbing = FALSE, datum/callback/_on_step = null) + victim = _victim + target = _target + steps_per_tick = _steps_per_tick + allow_climbing = _allow_climbing + on_step = _on_step + + . = ..() + + if(_victim && _target && _steps_per_tick && !_victim.force_moving) + last_processed = world.time + _victim.force_moving = src + START_PROCESSING(SSfastprocess, src) + else + qdel(src) //if you want to overwrite the current forced movement, call qdel(victim.force_moving) before creating this + +/datum/forced_movement/Destroy() + if(victim.force_moving == src) + victim.force_moving = null + if(moved_at_all) + victim.forceMove(victim.loc) //get the side effects of moving here that require us to currently not be force_moving aka reslipping on ice + STOP_PROCESSING(SSfastprocess, src) + victim = null + target = null + return ..() + +/datum/forced_movement/process() + if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc) + qdel(src) + return + var/steps_to_take = round(steps_per_tick * (world.time - last_processed)) + if(steps_to_take) + for(var/i in 1 to steps_to_take) + if(TryMove()) + moved_at_all = TRUE + if(on_step) + on_step.InvokeAsync() + else + qdel(src) + return + last_processed = world.time + +/datum/forced_movement/proc/TryMove(recursive = FALSE) + if(QDELETED(src)) //Our previous step caused deletion of this datum + return + + var/atom/movable/vic = victim //sanic + var/atom/tar = target + + if(!recursive) + . = step_towards(vic, tar) + + //shit way for getting around corners + if(!.) + if(tar.x > vic.x) + if(step(vic, EAST)) + . = TRUE + else if(tar.x < vic.x) + if(step(vic, WEST)) + . = TRUE + + if(!.) + if(tar.y > vic.y) + if(step(vic, NORTH)) + . = TRUE + else if(tar.y < vic.y) + if(step(vic, SOUTH)) + . = TRUE + + if(!.) + if(recursive) + return FALSE + else + . = TryMove(TRUE) + + . = . && (vic.loc != tar.loc) + +/mob/Bump(atom/A) + . = ..() + if(force_moving && force_moving.allow_climbing && isstructure(A)) + var/obj/structure/S = A + if(S.climbable) + S.do_climb(src) diff --git a/code/datums/helper_datums/events.dm b/code/datums/helper_datums/events.dm index e42615716c0d..e6e4061671cd 100644 --- a/code/datums/helper_datums/events.dm +++ b/code/datums/helper_datums/events.dm @@ -1,54 +1,54 @@ -/* - * WARRANTY VOID IF CODE USED - */ - - -/datum/events - var/list/events - -/datum/events/New() - ..() - events = new - -/datum/events/Destroy() - for(var/elist in events) - for(var/e in events[elist]) - qdel(e) - events = null - return ..() - -/datum/events/proc/addEventType(event_type as text) - if(!(event_type in events) || !islist(events[event_type])) - events[event_type] = list() - return TRUE - return FALSE - -// Arguments: event_type as text, proc_holder as datum, proc_name as text -// Returns: New event, null on error. -/datum/events/proc/addEvent(event_type as text, datum/callback/cb) - if(!event_type || !cb) - return - addEventType(event_type) - var/list/event = events[event_type] - event += cb - return cb - -// Arguments: event_type as text, any number of additional arguments to pass to event handler -// Returns: null -/datum/events/proc/fireEvent(eventName, ...) - var/list/event = listgetindex(events,eventName) - if(istype(event)) - for(var/E in event) - var/datum/callback/cb = E - cb.InvokeAsync(arglist(args.Copy(2))) - -// Arguments: event_type as text, E as /datum/event -// Returns: TRUE if event cleared, FALSE on error - -/datum/events/proc/clearEvent(event_type as text, datum/callback/cb) - if(!event_type || !cb) - return FALSE - var/list/event = listgetindex(events,event_type) - event -= cb - qdel(cb) - return TRUE +/* + * WARRANTY VOID IF CODE USED + */ + + +/datum/events + var/list/events + +/datum/events/New() + ..() + events = new + +/datum/events/Destroy() + for(var/elist in events) + for(var/e in events[elist]) + qdel(e) + events = null + return ..() + +/datum/events/proc/addEventType(event_type as text) + if(!(event_type in events) || !islist(events[event_type])) + events[event_type] = list() + return TRUE + return FALSE + +// Arguments: event_type as text, proc_holder as datum, proc_name as text +// Returns: New event, null on error. +/datum/events/proc/addEvent(event_type as text, datum/callback/cb) + if(!event_type || !cb) + return + addEventType(event_type) + var/list/event = events[event_type] + event += cb + return cb + +// Arguments: event_type as text, any number of additional arguments to pass to event handler +// Returns: null +/datum/events/proc/fireEvent(eventName, ...) + var/list/event = listgetindex(events,eventName) + if(istype(event)) + for(var/E in event) + var/datum/callback/cb = E + cb.InvokeAsync(arglist(args.Copy(2))) + +// Arguments: event_type as text, E as /datum/event +// Returns: TRUE if event cleared, FALSE on error + +/datum/events/proc/clearEvent(event_type as text, datum/callback/cb) + if(!event_type || !cb) + return FALSE + var/list/event = listgetindex(events,event_type) + event -= cb + qdel(cb) + return TRUE diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm index 94169cb4d7fa..2b750a7f849e 100644 --- a/code/datums/helper_datums/getrev.dm +++ b/code/datums/helper_datums/getrev.dm @@ -1,129 +1,129 @@ -/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.pull_request_commit]" - SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.pull_request_commit]", "title" = "[tm.title]", "author" = "[tm.author]")) - - if(commit && commit != originmastercommit) - msg += "HEAD: [commit]" - else if(!originmastercommit) - msg += "No commit information" - - return msg.Join("\n") - -/datum/getrev/proc/GetTestMergeInfo(header = TRUE) - if(!testmerge.len) - return "" - . = header ? "The following pull requests are currently test merged:
    " : "" - for(var/line in testmerge) - var/datum/tgs_revision_information/test_merge/tm = line - var/cm = tm.pull_request_commit - var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext(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()) - var/datum/tgs_version/version = world.TgsVersion() - msg += "Server tools version: [version.raw_parameter]" - - if(!check_rights_for(src, R_ADMIN)) //yogs - to_chat(src, msg.Join("
    ")) - return //yogs - - // Game mode odds - msg += "
    Current Informational Settings:" - msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist)]" - msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist)]" - msg += "Enforce Human Authority: [CONFIG_GET(flag/enforce_human_authority)]" - msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists)]" - msg += "Enforce Continuous Rounds: [length(CONFIG_GET(keyed_list/continuous))] of [config.modes.len] roundtypes" - msg += "Allow Midround Antagonists: [length(CONFIG_GET(keyed_list/midround_antag))] of [config.modes.len] roundtypes" - if(CONFIG_GET(flag/show_game_type_odds)) - var/list/probabilities = CONFIG_GET(keyed_list/probability) - if(SSticker.IsRoundInProgress()) - var/prob_sum = 0 - var/current_odds_differ = FALSE - var/list/probs = list() - var/list/modes = config.gamemode_cache - var/list/min_pop = CONFIG_GET(keyed_list/min_pop) - var/list/max_pop = CONFIG_GET(keyed_list/max_pop) - for(var/mode in modes) - var/datum/game_mode/M = mode - var/ctag = initial(M.config_tag) - if(!(ctag in probabilities)) - continue - if((min_pop[ctag] && (min_pop[ctag] > SSticker.totalPlayersReady)) || (max_pop[ctag] && (max_pop[ctag] < SSticker.totalPlayersReady)) || (initial(M.required_players) > SSticker.totalPlayersReady)) - current_odds_differ = TRUE - continue - probs[ctag] = 1 - prob_sum += probabilities[ctag] - if(current_odds_differ) - msg += "Game Mode Odds for current round:" - for(var/ctag in probs) - if(probabilities[ctag] > 0) - var/percentage = round(probabilities[ctag] / prob_sum * 100, 0.1) - msg += "[ctag] [percentage]%" - - msg += "All Game Mode Odds:" - var/sum = 0 - for(var/ctag in probabilities) - sum += probabilities[ctag] - for(var/ctag in probabilities) - if(probabilities[ctag] > 0) - var/percentage = round(probabilities[ctag] / sum * 100, 0.1) - msg += "[ctag] [percentage]%" - to_chat(src, msg.Join("
    ")) +/datum/getrev + var/commit // git rev-parse HEAD + var/date + var/originmastercommit // git rev-parse origin/master + var/list/testmerge = list() + +/datum/getrev/New() + 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.pull_request_commit]" + SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.pull_request_commit]", "title" = "[tm.title]", "author" = "[tm.author]")) + + if(commit && commit != originmastercommit) + msg += "HEAD: [commit]" + else if(!originmastercommit) + msg += "No commit information" + + return msg.Join("\n") + +/datum/getrev/proc/GetTestMergeInfo(header = TRUE) + if(!testmerge.len) + return "" + . = header ? "The following pull requests are currently test merged:
    " : "" + for(var/line in testmerge) + var/datum/tgs_revision_information/test_merge/tm = line + var/cm = tm.pull_request_commit + var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext(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()) + var/datum/tgs_version/version = world.TgsVersion() + msg += "Server tools version: [version.raw_parameter]" + + if(!check_rights_for(src, R_ADMIN)) //yogs + to_chat(src, msg.Join("
    ")) + return //yogs + + // Game mode odds + msg += "
    Current Informational Settings:" + msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist)]" + msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist)]" + msg += "Enforce Human Authority: [CONFIG_GET(flag/enforce_human_authority)]" + msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists)]" + msg += "Enforce Continuous Rounds: [length(CONFIG_GET(keyed_list/continuous))] of [config.modes.len] roundtypes" + msg += "Allow Midround Antagonists: [length(CONFIG_GET(keyed_list/midround_antag))] of [config.modes.len] roundtypes" + if(CONFIG_GET(flag/show_game_type_odds)) + var/list/probabilities = CONFIG_GET(keyed_list/probability) + if(SSticker.IsRoundInProgress()) + var/prob_sum = 0 + var/current_odds_differ = FALSE + var/list/probs = list() + var/list/modes = config.gamemode_cache + var/list/min_pop = CONFIG_GET(keyed_list/min_pop) + var/list/max_pop = CONFIG_GET(keyed_list/max_pop) + for(var/mode in modes) + var/datum/game_mode/M = mode + var/ctag = initial(M.config_tag) + if(!(ctag in probabilities)) + continue + if((min_pop[ctag] && (min_pop[ctag] > SSticker.totalPlayersReady)) || (max_pop[ctag] && (max_pop[ctag] < SSticker.totalPlayersReady)) || (initial(M.required_players) > SSticker.totalPlayersReady)) + current_odds_differ = TRUE + continue + probs[ctag] = 1 + prob_sum += probabilities[ctag] + if(current_odds_differ) + msg += "Game Mode Odds for current round:" + for(var/ctag in probs) + if(probabilities[ctag] > 0) + var/percentage = round(probabilities[ctag] / prob_sum * 100, 0.1) + msg += "[ctag] [percentage]%" + + msg += "All Game Mode Odds:" + var/sum = 0 + for(var/ctag in probabilities) + sum += probabilities[ctag] + for(var/ctag in probabilities) + if(probabilities[ctag] > 0) + var/percentage = round(probabilities[ctag] / sum * 100, 0.1) + msg += "[ctag] [percentage]%" + to_chat(src, msg.Join("
    ")) diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index d1ea29f831fd..7cf896329091 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -1,166 +1,166 @@ -// teleatom: atom to teleport -// destination: destination to teleport to -// precision: teleport precision (0 is most precise, the default) -// effectin: effect to show right before teleportation -// effectout: effect to show right after teleportation -// asoundin: soundfile to play before teleportation -// asoundout: soundfile to play after teleportation -// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation) -// no_effects: disable the default effectin/effectout of sparks -// forced: whether or not to ignore no_teleport -/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE) - // teleporting most effects just deletes them - var/static/list/delete_atoms = typecacheof(list( - /obj/effect, - )) - typecacheof(list( - /obj/effect/dummy/chameleon, - /obj/effect/wisp, - /obj/effect/mob_spawn, - )) - if(delete_atoms[teleatom.type]) - qdel(teleatom) - return FALSE - - // argument handling - // if the precision is not specified, default to 0, but apply BoH penalties - if (isnull(precision)) - precision = 0 - - switch(channel) - if(TELEPORT_CHANNEL_BLUESPACE) - if(istype(teleatom, /obj/item/storage/backpack/holding)) - precision = rand(1,100) - - var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding) - var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache) - if(bagholding.len) - precision = max(rand(1,100)*bagholding.len,100) - if(isliving(teleatom)) - var/mob/living/MM = teleatom - to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!") - - // if effects are not specified and not explicitly disabled, sparks - if ((!effectin || !effectout) && !no_effects) - var/datum/effect_system/spark_spread/sparks = new - sparks.set_up(5, 1, teleatom) - if (!effectin) - effectin = sparks - if (!effectout) - effectout = sparks - if(TELEPORT_CHANNEL_QUANTUM) - // if effects are not specified and not explicitly disabled, rainbow sparks - if ((!effectin || !effectout) && !no_effects) - var/datum/effect_system/spark_spread/quantum/sparks = new - sparks.set_up(5, 1, teleatom) - if (!effectin) - effectin = sparks - if (!effectout) - effectout = sparks - - // perform the teleport - var/turf/curturf = get_turf(teleatom) - var/turf/destturf = get_teleport_turf(get_turf(destination), precision) - - if(!destturf || !curturf || destturf.is_transition_turf()) - return FALSE - - var/area/A = get_area(curturf) - var/area/B = get_area(destturf) - if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || A.noteleport || B.noteleport)) - return FALSE - - if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf)) - return FALSE - - tele_play_specials(teleatom, curturf, effectin, asoundin) - var/success = forceMove ? teleatom.forceMove(destturf) : teleatom.Move(destturf) - if (success) - log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]") - tele_play_specials(teleatom, destturf, effectout, asoundout) - if(ismegafauna(teleatom)) - message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_VERBOSEJMP(curturf)] to [ADMIN_VERBOSEJMP(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] && A_gases[/datum/gas/oxygen][MOLES] >= 16)) - continue - if(A_gases[/datum/gas/plasma]) - continue - if(A_gases[/datum/gas/carbon_dioxide] && A_gases[/datum/gas/carbon_dioxide][MOLES] >= 10) - continue - - // Aim for goldilocks temperatures and pressure - if((A.temperature <= 270) || (A.temperature >= 360)) - continue - var/pressure = A.return_pressure() - if((pressure <= 20) || (pressure >= 550)) - continue - - if(extended_safety_checks) - if(islava(F)) //chasms aren't /floor, and so are pre-filtered - var/turf/open/lava/L = F - if(!L.is_safe()) - continue - - // DING! You have passed the gauntlet, and are "probably" safe. - return F - -/proc/get_teleport_turfs(turf/center, precision = 0) - if(!precision) - return list(center) - var/list/posturfs = list() - for(var/turf/T in range(precision,center)) - if(T.is_transition_turf()) - continue // Avoid picking these. - var/area/A = T.loc - if(!A.noteleport) - posturfs.Add(T) - return posturfs - -/proc/get_teleport_turf(turf/center, precision = 0) - 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 +// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation) +// no_effects: disable the default effectin/effectout of sparks +// forced: whether or not to ignore no_teleport +/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE) + // teleporting most effects just deletes them + var/static/list/delete_atoms = typecacheof(list( + /obj/effect, + )) - typecacheof(list( + /obj/effect/dummy/chameleon, + /obj/effect/wisp, + /obj/effect/mob_spawn, + )) + if(delete_atoms[teleatom.type]) + qdel(teleatom) + return FALSE + + // argument handling + // if the precision is not specified, default to 0, but apply BoH penalties + if (isnull(precision)) + precision = 0 + + switch(channel) + if(TELEPORT_CHANNEL_BLUESPACE) + if(istype(teleatom, /obj/item/storage/backpack/holding)) + precision = rand(1,100) + + var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding) + var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache) + if(bagholding.len) + precision = max(rand(1,100)*bagholding.len,100) + if(isliving(teleatom)) + var/mob/living/MM = teleatom + to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!") + + // if effects are not specified and not explicitly disabled, sparks + if ((!effectin || !effectout) && !no_effects) + var/datum/effect_system/spark_spread/sparks = new + sparks.set_up(5, 1, teleatom) + if (!effectin) + effectin = sparks + if (!effectout) + effectout = sparks + if(TELEPORT_CHANNEL_QUANTUM) + // if effects are not specified and not explicitly disabled, rainbow sparks + if ((!effectin || !effectout) && !no_effects) + var/datum/effect_system/spark_spread/quantum/sparks = new + sparks.set_up(5, 1, teleatom) + if (!effectin) + effectin = sparks + if (!effectout) + effectout = sparks + + // perform the teleport + var/turf/curturf = get_turf(teleatom) + var/turf/destturf = get_teleport_turf(get_turf(destination), precision) + + if(!destturf || !curturf || destturf.is_transition_turf()) + return FALSE + + var/area/A = get_area(curturf) + var/area/B = get_area(destturf) + if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || A.noteleport || B.noteleport)) + return FALSE + + if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf)) + return FALSE + + tele_play_specials(teleatom, curturf, effectin, asoundin) + var/success = forceMove ? teleatom.forceMove(destturf) : teleatom.Move(destturf) + if (success) + log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]") + tele_play_specials(teleatom, destturf, effectout, asoundout) + if(ismegafauna(teleatom)) + message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_VERBOSEJMP(curturf)] to [ADMIN_VERBOSEJMP(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] && A_gases[/datum/gas/oxygen][MOLES] >= 16)) + continue + if(A_gases[/datum/gas/plasma]) + continue + if(A_gases[/datum/gas/carbon_dioxide] && A_gases[/datum/gas/carbon_dioxide][MOLES] >= 10) + continue + + // Aim for goldilocks temperatures and pressure + if((A.temperature <= 270) || (A.temperature >= 360)) + continue + var/pressure = A.return_pressure() + if((pressure <= 20) || (pressure >= 550)) + continue + + if(extended_safety_checks) + if(islava(F)) //chasms aren't /floor, and so are pre-filtered + var/turf/open/lava/L = F + if(!L.is_safe()) + continue + + // DING! You have passed the gauntlet, and are "probably" safe. + return F + +/proc/get_teleport_turfs(turf/center, precision = 0) + if(!precision) + return list(center) + var/list/posturfs = list() + for(var/turf/T in range(precision,center)) + if(T.is_transition_turf()) + continue // Avoid picking these. + var/area/A = T.loc + if(!A.noteleport) + posturfs.Add(T) + return posturfs + +/proc/get_teleport_turf(turf/center, precision = 0) + return safepick(get_teleport_turfs(center, precision)) diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index 46e583eb572f..4622da4bb472 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -1,376 +1,376 @@ -#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 - -/obj/item/disk/holodisk/donutstation/enginewars - name = "Conversation #DS034" - preset_image_type = /datum/preset_holoimage/engineer - preset_record_text = {" - NAME Rigsuit Engineer #1 - DELAY 10 - SAY The blueprints say we're installing a.. singularity engine? - DELAY 45 - NAME Rigsuit Engineer #2 - DELAY 10 - SAY Yep, apparently part of the classic design. - DELAY 45 - NAME Rigsuit Engineer #1 - DELAY 10 - SAY Hasn't the singularity engine been out of standard use for awhile now? - DELAY 45 - NAME Rigsuit Engineer #2 - DELAY 10 - SAY Yeah, but apparently the architects bribed one of the higher ups to bypass standard regulations. - DELAY 55 - NAME Rigsuit Engineery #1 - DELAY 10 - SAY It's gonna be a pain in the ass rebuilding this place when it inevitably gets loose.."} +#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 + +/obj/item/disk/holodisk/donutstation/enginewars + name = "Conversation #DS034" + preset_image_type = /datum/preset_holoimage/engineer + preset_record_text = {" + NAME Rigsuit Engineer #1 + DELAY 10 + SAY The blueprints say we're installing a.. singularity engine? + DELAY 45 + NAME Rigsuit Engineer #2 + DELAY 10 + SAY Yep, apparently part of the classic design. + DELAY 45 + NAME Rigsuit Engineer #1 + DELAY 10 + SAY Hasn't the singularity engine been out of standard use for awhile now? + DELAY 45 + NAME Rigsuit Engineer #2 + DELAY 10 + SAY Yeah, but apparently the architects bribed one of the higher ups to bypass standard regulations. + DELAY 55 + NAME Rigsuit Engineery #1 + DELAY 10 + SAY It's gonna be a pain in the ass rebuilding this place when it inevitably gets loose.."} diff --git a/code/datums/hud.dm b/code/datums/hud.dm index 0454abbe02aa..7148a74f7a32 100644 --- a/code/datums/hud.dm +++ b/code/datums/hud.dm @@ -1,136 +1,136 @@ -/* 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_HIVE = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(), - ANTAG_HUD_HIVEAWAKE = new/datum/atom_hud/antag(), - ANTAG_HUD_VAMPIRE = new/datum/atom_hud/antag/hidden(), // Yogs - ANTAG_HUD_CAPITALIST = new/datum/atom_hud/antag(), - ANTAG_HUD_COMMUNIST = new/datum/atom_hud/antag() - )) - -/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_HIVE = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(), + ANTAG_HUD_HIVEAWAKE = new/datum/atom_hud/antag(), + ANTAG_HUD_VAMPIRE = new/datum/atom_hud/antag/hidden(), // Yogs + ANTAG_HUD_CAPITALIST = new/datum/atom_hud/antag(), + ANTAG_HUD_COMMUNIST = new/datum/atom_hud/antag() + )) + +/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 78bff840fe0e..33f2f9356300 100644 --- a/code/datums/map_config.dm +++ b/code/datums/map_config.dm @@ -1,143 +1,143 @@ -//used for holding information about unique properties of maps -//feed it json files that match the datum layout -//defaults to box -// -Cyberboss - -/datum/map_config - // Metadata - var/config_filename = "_maps/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/votable = FALSE - - // 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 = 7 - var/space_empty_levels = 1 - - var/minetype = "lavaland" - - var/allow_custom_shuttles = TRUE - var/shuttles = list( - "cargo" = "cargo_box", - "ferry" = "ferry_fancy", - "whiteship" = "whiteship_box", - "emergency" = "emergency_box") - -/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE) - var/datum/map_config/config = new - if (default_to_box) - return config - if (!config.LoadConfig(filename, error_if_missing)) - qdel(config) - config = new /datum/map_config // Fall back to Box - if (delete_after) - fdel(filename) - return config - -#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; } -/datum/map_config/proc/LoadConfig(filename, error_if_missing) - if(!fexists(filename)) - if(error_if_missing) - log_world("map_config not found: [filename]") - return - - var/json = file(filename) - if(!json) - log_world("Could not open map_config: [filename]") - return - - json = file2text(json) - if(!json) - log_world("map_config is not text: [filename]") - return - - json = json_decode(json) - if(!json) - log_world("map_config is not json: [filename]") - return - - config_filename = filename - - CHECK_EXISTS("map_name") - map_name = json["map_name"] - CHECK_EXISTS("map_path") - map_path = json["map_path"] - - map_file = json["map_file"] - // "map_file": "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 - - if ("minetype" in json) - minetype = json["minetype"] - - allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE - - defaulted = FALSE - return TRUE -#undef CHECK_EXISTS - -/datum/map_config/proc/GetFullMapPaths() - if (istext(map_file)) - return list("_maps/[map_path]/[map_file]") - . = list() - for (var/file in map_file) - . += "_maps/[map_path]/[file]" - -/datum/map_config/proc/MakeNextMap() - return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json") +//used for holding information about unique properties of maps +//feed it json files that match the datum layout +//defaults to box +// -Cyberboss + +/datum/map_config + // Metadata + var/config_filename = "_maps/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/votable = FALSE + + // 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 = 7 + var/space_empty_levels = 1 + + var/minetype = "lavaland" + + var/allow_custom_shuttles = TRUE + var/shuttles = list( + "cargo" = "cargo_box", + "ferry" = "ferry_fancy", + "whiteship" = "whiteship_box", + "emergency" = "emergency_box") + +/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE) + var/datum/map_config/config = new + if (default_to_box) + return config + if (!config.LoadConfig(filename, error_if_missing)) + qdel(config) + config = new /datum/map_config // Fall back to Box + if (delete_after) + fdel(filename) + return config + +#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; } +/datum/map_config/proc/LoadConfig(filename, error_if_missing) + if(!fexists(filename)) + if(error_if_missing) + log_world("map_config not found: [filename]") + return + + var/json = file(filename) + if(!json) + log_world("Could not open map_config: [filename]") + return + + json = file2text(json) + if(!json) + log_world("map_config is not text: [filename]") + return + + json = json_decode(json) + if(!json) + log_world("map_config is not json: [filename]") + return + + config_filename = filename + + CHECK_EXISTS("map_name") + map_name = json["map_name"] + CHECK_EXISTS("map_path") + map_path = json["map_path"] + + map_file = json["map_file"] + // "map_file": "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 + + if ("minetype" in json) + minetype = json["minetype"] + + allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE + + defaulted = FALSE + return TRUE +#undef CHECK_EXISTS + +/datum/map_config/proc/GetFullMapPaths() + if (istext(map_file)) + return list("_maps/[map_path]/[map_file]") + . = list() + for (var/file in map_file) + . += "_maps/[map_path]/[file]" + +/datum/map_config/proc/MakeNextMap() + return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json") diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 04a32ba8ce66..192fc3c5b7c4 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -1,777 +1,777 @@ -/* Note from Carnie: - The way datum/mind stuff works has been changed a lot. - Minds now represent IC characters rather than following a client around constantly. - - Guidelines for using minds properly: - - - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! - ghost.mind is however used as a reference to the ghost's corpse - - - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) - the existing mind of the old mob should be transfered to the new mob like so: - - mind.transfer_to(new_mob) - - - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. - By setting key or ckey explicitly after transferring the mind with transfer_to you will cause bugs like DCing - the player. - - - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. - - - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting - a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. - - new_mob.key = key - - The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple! - However if you want that mind to have any special properties like being a traitor etc you will have to do that - yourself. - -*/ - -/datum/mind - var/key - var/name //replaces mob/var/original_name - var/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/last_death = 0 - - var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator - - var/list/learned_recipes //List of learned recipe TYPES. - - var/flavour_text = null - -/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)) - QDEL_LIST(antag_datums) - 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/mood_was_enabled = FALSE//Yogs -- Mood Preferences - if(current) // remove ourself from our old body's mind variable - // Yogs start -- Mood preferences - if(current.client && current.client.prefs.yogtoggles & PREF_MOOD) - mood_was_enabled = TRUE - else if(ishuman(current) && CONFIG_GET(flag/disable_human_mood)) - var/mob/living/carbon/human/H = current - if(H.mood_enabled) - mood_was_enabled = TRUE - var/datum/component/mood/c = H.GetComponent(/datum/component/mood) - if(c) - c.RemoveComponent() - // Yogs End - current.mind = null - UnregisterSignal(current, COMSIG_MOB_DEATH) - SStgui.on_transfer(current, new_character) - 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(1) //we'll need to ghostize so that key isn't mobless. - else - key = new_character.key - - if(new_character.mind) //disassociate any mind currently in our new body's mind variable - new_character.mind.current = null - - var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list - var/mob/living/old_current = current - if(current) - current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one - current = new_character //associate ourself with our new body - new_character.mind = src //and associate our new body with ourself - for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body - var/datum/antagonist/A = a - A.on_body_transfer(old_current, current) - if(iscarbon(new_character)) - var/mob/living/carbon/C = new_character - C.last_mind = src - // Yogs start -- Mood preferences - if(ishuman(new_character) && mood_was_enabled && !new_character.GetComponent(/datum/component/mood)) - var/mob/living/carbon/human/H = C - H.AddComponent(/datum/component/mood) - // Yogs End - transfer_antag_huds(hud_to_transfer) //inherit the antag HUD - transfer_actions(new_character) - transfer_martial_arts(new_character) - RegisterSignal(new_character, COMSIG_MOB_DEATH, .proc/set_death_time) - if(active || force_key_move) - new_character.key = key //now transfer the key to link the client to our new body - -/datum/mind/proc/set_death_time() - last_death = world.time - -/datum/mind/proc/store_memory(new_text) - memory += "[new_text]
    " - -/datum/mind/proc/wipe_memory() - memory = null - -// Datum antag mind procs -/datum/mind/proc/add_antag_datum(datum_type_or_instance, team) - if(!datum_type_or_instance) - return - var/datum/antagonist/A - if(!ispath(datum_type_or_instance)) - A = datum_type_or_instance - if(!istype(A)) - return - else - A = new datum_type_or_instance() - //Choose snowflake variation if antagonist handles it - var/datum/antagonist/S = A.specialization(src) - if(S && S != A) - qdel(A) - A = S - if(!A.can_be_owned(src)) - qdel(A) - return - A.owner = src - LAZYADD(antag_datums, A) - A.create_team(team) - var/datum/team/antag_team = A.get_team() - if(antag_team) - antag_team.add_member(src) - A.on_gain() - log_game("[key_name(src)] has gained antag datum [A.name]([A.type])") - return A - -/datum/mind/proc/remove_antag_datum(datum_type) - if(!datum_type) - return - var/datum/antagonist/A = has_antag_datum(datum_type) - if(A) - A.on_removal() - return TRUE - - -/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. - for(var/a in antag_datums) - var/datum/antagonist/A = a - A.on_removal() - -/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) - if(!datum_type) - return - . = FALSE - for(var/a in antag_datums) - var/datum/antagonist/A = a - if(check_subtypes && istype(A, datum_type)) - return A - else if(A.type == datum_type) - return A - -/* - Removes antag type's references from a mind. - objectives, uplinks, powers etc are all handled. -*/ - -/datum/mind/proc/remove_changeling() - var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) - if(C) - remove_antag_datum(/datum/antagonist/changeling) - special_role = null - -/datum/mind/proc/remove_traitor() - remove_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/remove_brother() - if(src in SSticker.mode.brothers) - remove_antag_datum(/datum/antagonist/brother) - 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 [english_list(U.unlock_code)] from its starting position to unlock its hidden features.") - - if(uplink_owner) - uplink_owner.antag_memory += U.unlock_note + "
    " - else - traitor_mob.mind.store_memory(U.unlock_note) - -//Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does. - -/datum/mind/proc/enslave_mind_to_creator(mob/living/creator) - if(iscultist(creator)) - SSticker.mode.add_cultist(src) - - else if(is_revolutionary(creator)) - var/datum/antagonist/rev/converter = creator.mind.has_antag_datum(/datum/antagonist/rev,TRUE) - converter.add_revolutionary(src,FALSE) - - else if(is_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 - - if(!GLOB.admin_objective_list) - generate_admin_objective_list() - - if(old_objective) - if(old_objective.name in GLOB.admin_objective_list) - def_value = old_objective.name - - var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in GLOB.admin_objective_list - selected_type = GLOB.admin_objective_list[selected_type] - if (!selected_type) - return - - if(!old_objective) - //Add new one - new_objective = new selected_type - new_objective.owner = src - new_objective.admin_edit(usr) - target_antag.objectives += new_objective - message_admins("[key_name_admin(usr)] added a new objective for [current]: [new_objective.explanation_text]") - log_admin("[key_name(usr)] added a new objective for [current]: [new_objective.explanation_text]") - else - if(old_objective.type == selected_type) - //Edit the old - old_objective.admin_edit(usr) - new_objective = old_objective - else - //Replace the old - new_objective = new selected_type - new_objective.owner = src - new_objective.admin_edit(usr) - target_antag.objectives -= old_objective - target_antag.objectives.Insert(objective_pos, new_objective) - message_admins("[key_name_admin(usr)] edited [current]'s objective to [new_objective.explanation_text]") - log_admin("[key_name(usr)] edited [current]'s objective to [new_objective.explanation_text]") - - else if (href_list["obj_delete"]) - var/datum/objective/objective - for(var/datum/antagonist/A in antag_datums) - objective = locate(href_list["obj_delete"]) in A.objectives - if(istype(objective)) - A.objectives -= objective - break - if(!objective) - to_chat(usr,"Invalid objective.") - return - //qdel(objective) Needs cleaning objective destroys - message_admins("[key_name_admin(usr)] removed an objective for [current]: [objective.explanation_text]") - log_admin("[key_name(usr)] removed an objective for [current]: [objective.explanation_text]") - - else if(href_list["obj_completed"]) - var/datum/objective/objective - for(var/datum/antagonist/A in antag_datums) - objective = locate(href_list["obj_completed"]) in A.objectives - if(istype(objective)) - objective = objective - break - if(!objective) - to_chat(usr,"Invalid objective.") - return - objective.completed = !objective.completed - log_admin("[key_name(usr)] toggled the win state for [current]'s objective: [objective.explanation_text]") - - else if (href_list["silicon"]) - switch(href_list["silicon"]) - if("unemag") - var/mob/living/silicon/robot/R = current - if (istype(R)) - R.SetEmagged(0) - message_admins("[key_name_admin(usr)] has unemag'ed [R].") - log_admin("[key_name(usr)] has unemag'ed [R].") - - if("unemagcyborgs") - if(isAI(current)) - var/mob/living/silicon/ai/ai = current - for (var/mob/living/silicon/robot/R in ai.connected_robots) - R.SetEmagged(0) - message_admins("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.") - log_admin("[key_name(usr)] has unemag'ed [ai]'s Cyborgs.") - - else if (href_list["common"]) - switch(href_list["common"]) - if("undress") - for(var/obj/item/W in current) - current.dropItemToGround(W, TRUE) //The 1 forces all items to drop, since this is an admin undress. - if("takeuplink") - take_uplink() - memory = null//Remove any memory they may have had. - log_admin("[key_name(usr)] removed [current]'s uplink.") - if("crystals") - if(check_rights(R_ADMIN, 0)) //YOGS - changes R_FUN to R_ADMIN - 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() - - // yogs start - Donor features, quiet round - else if (href_list["quiet_override"]) - quiet_round = FALSE - message_admins("[key_name_admin(usr)] has disabled [current]'s quiet round mode.") - log_admin("[key_name(usr)] has disabled [current]'s quiet round mode.") - // yogs end - - //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() - // yogs start - Donor features, quiet round - if(quiet_round) - return - // yogs end - if(!(has_antag_datum(/datum/antagonist/traitor))) - add_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/make_Changeling() - // yogs start - Donor features, quiet round - if(quiet_round) - return - // yogs end - 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() - // yogs start - Donor features, quiet round - if(quiet_round) - return - // yogs end - 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() - // yogs start - Donor features, quiet round - if(quiet_round) - return - // yogs end - 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() - // yogs start - Donor features, quiet round - if(quiet_round) - return - // yogs end - var/datum/antagonist/rev/head/head = new() - head.give_flash = TRUE - head.give_hud = TRUE - add_antag_datum(head) - special_role = ROLE_REV_HEAD - -/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) - spell_list += S - S.action.Grant(current) - -/datum/mind/proc/owns_soul() - return soulOwner == src - -//To remove a specific spell from a mind -/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) - if(!spell) - return - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - if(istype(S, spell)) - spell_list -= S - qdel(S) - -/datum/mind/proc/RemoveAllSpells() - for(var/obj/effect/proc_holder/S in spell_list) - RemoveSpell(S) - -/datum/mind/proc/transfer_martial_arts(mob/living/new_character) - if(!ishuman(new_character)) - return - if(martial_art) - if(martial_art.base) //Is the martial art temporary? - martial_art.remove(new_character) - else - martial_art.teach(new_character) - -/datum/mind/proc/transfer_actions(mob/living/new_character) - if(current && current.actions) - for(var/datum/action/A in current.actions) - A.Grant(new_character) - transfer_mindbound_actions(new_character) - -/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - S.action.Grant(new_character) - -/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - for(var/type in exceptions) - if(istype(S, type)) - continue - S.charge_counter = delay - S.updateButtonIcon() - INVOKE_ASYNC(S, /obj/effect/proc_holder/spell.proc/start_recharge) - -/datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) - for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list)) - if(G.mind == src) - if(G.can_reenter_corpse || even_if_they_cant_reenter) - return G - break - -/datum/mind/proc/grab_ghost(force) - var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force) - . = G - if(G) - G.reenter_corpse() - - -/datum/mind/proc/has_objective(objective_type) - for(var/datum/antagonist/A in antag_datums) - for(var/O in A.objectives) - if(istype(O,objective_type)) - return TRUE - -/mob/proc/sync_mind() - mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) - mind.active = 1 //indicates that the mind is currently synced with a client - -/datum/mind/proc/has_martialart(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 - if(!mind.name) - mind.name = real_name - mind.current = src - -/mob/living/carbon/mind_initialize() - ..() - last_mind = mind - -//HUMAN -/mob/living/carbon/human/mind_initialize() - ..() - if(!mind.assigned_role) - mind.assigned_role = "Unassigned" //default - -//AI -/mob/living/silicon/ai/mind_initialize() - ..() - mind.assigned_role = "AI" - -//BORG -/mob/living/silicon/robot/mind_initialize() - ..() - mind.assigned_role = "Cyborg" - -//PAI -/mob/living/silicon/pai/mind_initialize() - ..() - mind.assigned_role = ROLE_PAI - mind.special_role = "" +/* Note from Carnie: + The way datum/mind stuff works has been changed a lot. + Minds now represent IC characters rather than following a client around constantly. + + Guidelines for using minds properly: + + - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! + ghost.mind is however used as a reference to the ghost's corpse + + - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) + the existing mind of the old mob should be transfered to the new mob like so: + + mind.transfer_to(new_mob) + + - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. + By setting key or ckey explicitly after transferring the mind with transfer_to you will cause bugs like DCing + the player. + + - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. + + - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting + a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. + + new_mob.key = key + + The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple! + However if you want that mind to have any special properties like being a traitor etc you will have to do that + yourself. + +*/ + +/datum/mind + var/key + var/name //replaces mob/var/original_name + var/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/last_death = 0 + + var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator + + var/list/learned_recipes //List of learned recipe TYPES. + + var/flavour_text = null + +/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)) + QDEL_LIST(antag_datums) + 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/mood_was_enabled = FALSE//Yogs -- Mood Preferences + if(current) // remove ourself from our old body's mind variable + // Yogs start -- Mood preferences + if(current.client && current.client.prefs.yogtoggles & PREF_MOOD) + mood_was_enabled = TRUE + else if(ishuman(current) && CONFIG_GET(flag/disable_human_mood)) + var/mob/living/carbon/human/H = current + if(H.mood_enabled) + mood_was_enabled = TRUE + var/datum/component/mood/c = H.GetComponent(/datum/component/mood) + if(c) + c.RemoveComponent() + // Yogs End + current.mind = null + UnregisterSignal(current, COMSIG_MOB_DEATH) + SStgui.on_transfer(current, new_character) + 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(1) //we'll need to ghostize so that key isn't mobless. + else + key = new_character.key + + if(new_character.mind) //disassociate any mind currently in our new body's mind variable + new_character.mind.current = null + + var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list + var/mob/living/old_current = current + if(current) + current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one + current = new_character //associate ourself with our new body + new_character.mind = src //and associate our new body with ourself + for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body + var/datum/antagonist/A = a + A.on_body_transfer(old_current, current) + if(iscarbon(new_character)) + var/mob/living/carbon/C = new_character + C.last_mind = src + // Yogs start -- Mood preferences + if(ishuman(new_character) && mood_was_enabled && !new_character.GetComponent(/datum/component/mood)) + var/mob/living/carbon/human/H = C + H.AddComponent(/datum/component/mood) + // Yogs End + transfer_antag_huds(hud_to_transfer) //inherit the antag HUD + transfer_actions(new_character) + transfer_martial_arts(new_character) + RegisterSignal(new_character, COMSIG_MOB_DEATH, .proc/set_death_time) + if(active || force_key_move) + new_character.key = key //now transfer the key to link the client to our new body + +/datum/mind/proc/set_death_time() + last_death = world.time + +/datum/mind/proc/store_memory(new_text) + memory += "[new_text]
    " + +/datum/mind/proc/wipe_memory() + memory = null + +// Datum antag mind procs +/datum/mind/proc/add_antag_datum(datum_type_or_instance, team) + if(!datum_type_or_instance) + return + var/datum/antagonist/A + if(!ispath(datum_type_or_instance)) + A = datum_type_or_instance + if(!istype(A)) + return + else + A = new datum_type_or_instance() + //Choose snowflake variation if antagonist handles it + var/datum/antagonist/S = A.specialization(src) + if(S && S != A) + qdel(A) + A = S + if(!A.can_be_owned(src)) + qdel(A) + return + A.owner = src + LAZYADD(antag_datums, A) + A.create_team(team) + var/datum/team/antag_team = A.get_team() + if(antag_team) + antag_team.add_member(src) + A.on_gain() + log_game("[key_name(src)] has gained antag datum [A.name]([A.type])") + return A + +/datum/mind/proc/remove_antag_datum(datum_type) + if(!datum_type) + return + var/datum/antagonist/A = has_antag_datum(datum_type) + if(A) + A.on_removal() + return TRUE + + +/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. + for(var/a in antag_datums) + var/datum/antagonist/A = a + A.on_removal() + +/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) + if(!datum_type) + return + . = FALSE + for(var/a in antag_datums) + var/datum/antagonist/A = a + if(check_subtypes && istype(A, datum_type)) + return A + else if(A.type == datum_type) + return A + +/* + Removes antag type's references from a mind. + objectives, uplinks, powers etc are all handled. +*/ + +/datum/mind/proc/remove_changeling() + var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) + if(C) + remove_antag_datum(/datum/antagonist/changeling) + special_role = null + +/datum/mind/proc/remove_traitor() + remove_antag_datum(/datum/antagonist/traitor) + +/datum/mind/proc/remove_brother() + if(src in SSticker.mode.brothers) + remove_antag_datum(/datum/antagonist/brother) + 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 [english_list(U.unlock_code)] from its starting position to unlock its hidden features.") + + if(uplink_owner) + uplink_owner.antag_memory += U.unlock_note + "
    " + else + traitor_mob.mind.store_memory(U.unlock_note) + +//Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does. + +/datum/mind/proc/enslave_mind_to_creator(mob/living/creator) + if(iscultist(creator)) + SSticker.mode.add_cultist(src) + + else if(is_revolutionary(creator)) + var/datum/antagonist/rev/converter = creator.mind.has_antag_datum(/datum/antagonist/rev,TRUE) + converter.add_revolutionary(src,FALSE) + + else if(is_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 + + if(!GLOB.admin_objective_list) + generate_admin_objective_list() + + if(old_objective) + if(old_objective.name in GLOB.admin_objective_list) + def_value = old_objective.name + + var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in GLOB.admin_objective_list + selected_type = GLOB.admin_objective_list[selected_type] + if (!selected_type) + return + + if(!old_objective) + //Add new one + new_objective = new selected_type + new_objective.owner = src + new_objective.admin_edit(usr) + target_antag.objectives += new_objective + message_admins("[key_name_admin(usr)] added a new objective for [current]: [new_objective.explanation_text]") + log_admin("[key_name(usr)] added a new objective for [current]: [new_objective.explanation_text]") + else + if(old_objective.type == selected_type) + //Edit the old + old_objective.admin_edit(usr) + new_objective = old_objective + else + //Replace the old + new_objective = new selected_type + new_objective.owner = src + new_objective.admin_edit(usr) + target_antag.objectives -= old_objective + target_antag.objectives.Insert(objective_pos, new_objective) + message_admins("[key_name_admin(usr)] edited [current]'s objective to [new_objective.explanation_text]") + log_admin("[key_name(usr)] edited [current]'s objective to [new_objective.explanation_text]") + + else if (href_list["obj_delete"]) + var/datum/objective/objective + for(var/datum/antagonist/A in antag_datums) + objective = locate(href_list["obj_delete"]) in A.objectives + if(istype(objective)) + A.objectives -= objective + break + if(!objective) + to_chat(usr,"Invalid objective.") + return + //qdel(objective) Needs cleaning objective destroys + message_admins("[key_name_admin(usr)] removed an objective for [current]: [objective.explanation_text]") + log_admin("[key_name(usr)] removed an objective for [current]: [objective.explanation_text]") + + else if(href_list["obj_completed"]) + var/datum/objective/objective + for(var/datum/antagonist/A in antag_datums) + objective = locate(href_list["obj_completed"]) in A.objectives + if(istype(objective)) + objective = objective + break + if(!objective) + to_chat(usr,"Invalid objective.") + return + objective.completed = !objective.completed + log_admin("[key_name(usr)] toggled the win state for [current]'s objective: [objective.explanation_text]") + + else if (href_list["silicon"]) + switch(href_list["silicon"]) + if("unemag") + var/mob/living/silicon/robot/R = current + if (istype(R)) + R.SetEmagged(0) + message_admins("[key_name_admin(usr)] has unemag'ed [R].") + log_admin("[key_name(usr)] has unemag'ed [R].") + + if("unemagcyborgs") + if(isAI(current)) + var/mob/living/silicon/ai/ai = current + for (var/mob/living/silicon/robot/R in ai.connected_robots) + R.SetEmagged(0) + message_admins("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.") + log_admin("[key_name(usr)] has unemag'ed [ai]'s Cyborgs.") + + else if (href_list["common"]) + switch(href_list["common"]) + if("undress") + for(var/obj/item/W in current) + current.dropItemToGround(W, TRUE) //The 1 forces all items to drop, since this is an admin undress. + if("takeuplink") + take_uplink() + memory = null//Remove any memory they may have had. + log_admin("[key_name(usr)] removed [current]'s uplink.") + if("crystals") + if(check_rights(R_ADMIN, 0)) //YOGS - changes R_FUN to R_ADMIN + 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() + + // yogs start - Donor features, quiet round + else if (href_list["quiet_override"]) + quiet_round = FALSE + message_admins("[key_name_admin(usr)] has disabled [current]'s quiet round mode.") + log_admin("[key_name(usr)] has disabled [current]'s quiet round mode.") + // yogs end + + //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() + // yogs start - Donor features, quiet round + if(quiet_round) + return + // yogs end + if(!(has_antag_datum(/datum/antagonist/traitor))) + add_antag_datum(/datum/antagonist/traitor) + +/datum/mind/proc/make_Changeling() + // yogs start - Donor features, quiet round + if(quiet_round) + return + // yogs end + 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() + // yogs start - Donor features, quiet round + if(quiet_round) + return + // yogs end + 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() + // yogs start - Donor features, quiet round + if(quiet_round) + return + // yogs end + 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() + // yogs start - Donor features, quiet round + if(quiet_round) + return + // yogs end + var/datum/antagonist/rev/head/head = new() + head.give_flash = TRUE + head.give_hud = TRUE + add_antag_datum(head) + special_role = ROLE_REV_HEAD + +/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) + spell_list += S + S.action.Grant(current) + +/datum/mind/proc/owns_soul() + return soulOwner == src + +//To remove a specific spell from a mind +/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) + if(!spell) + return + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + if(istype(S, spell)) + spell_list -= S + qdel(S) + +/datum/mind/proc/RemoveAllSpells() + for(var/obj/effect/proc_holder/S in spell_list) + RemoveSpell(S) + +/datum/mind/proc/transfer_martial_arts(mob/living/new_character) + if(!ishuman(new_character)) + return + if(martial_art) + if(martial_art.base) //Is the martial art temporary? + martial_art.remove(new_character) + else + martial_art.teach(new_character) + +/datum/mind/proc/transfer_actions(mob/living/new_character) + if(current && current.actions) + for(var/datum/action/A in current.actions) + A.Grant(new_character) + transfer_mindbound_actions(new_character) + +/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + S.action.Grant(new_character) + +/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + for(var/type in exceptions) + if(istype(S, type)) + continue + S.charge_counter = delay + S.updateButtonIcon() + INVOKE_ASYNC(S, /obj/effect/proc_holder/spell.proc/start_recharge) + +/datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) + for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list)) + if(G.mind == src) + if(G.can_reenter_corpse || even_if_they_cant_reenter) + return G + break + +/datum/mind/proc/grab_ghost(force) + var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force) + . = G + if(G) + G.reenter_corpse() + + +/datum/mind/proc/has_objective(objective_type) + for(var/datum/antagonist/A in antag_datums) + for(var/O in A.objectives) + if(istype(O,objective_type)) + return TRUE + +/mob/proc/sync_mind() + mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) + mind.active = 1 //indicates that the mind is currently synced with a client + +/datum/mind/proc/has_martialart(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 + 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 6e86fd02c6a2..514c66489b85 100644 --- a/code/datums/mood_events/drug_events.dm +++ b/code/datums/mood_events/drug_events.dm @@ -1,60 +1,60 @@ -/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/wrong_brand - description = "I hate that brand of cigarettes.\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 = 50 - -/datum/mood_event/happiness_drug_good_od - description = "YES! YES!! YES!!!\n" - mood_change = 100 - timeout = 300 - special_screen_obj = "mood_happiness_good" - -/datum/mood_event/happiness_drug_bad_od - description = "NO! NO!! NO!!!\n" - mood_change = -100 - timeout = 300 - special_screen_obj = "mood_happiness_bad" +/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/wrong_brand + description = "I hate that brand of cigarettes.\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 = 50 + +/datum/mood_event/happiness_drug_good_od + description = "YES! YES!! YES!!!\n" + mood_change = 100 + timeout = 300 + special_screen_obj = "mood_happiness_good" + +/datum/mood_event/happiness_drug_bad_od + description = "NO! NO!! NO!!!\n" + mood_change = -100 + timeout = 300 + special_screen_obj = "mood_happiness_bad" diff --git a/code/datums/mood_events/mood_event.dm b/code/datums/mood_events/mood_event.dm index aa23d078b8f5..fb9b8d0166aa 100644 --- a/code/datums/mood_events/mood_event.dm +++ b/code/datums/mood_events/mood_event.dm @@ -1,23 +1,23 @@ -/datum/mood_event - var/description ///For descriptions, use the span classes bold nicegreen, nicegreen, none, warning and boldwarning in order from great to horrible. - var/mood_change = 0 - var/timeout = 0 - var/hidden = FALSE//Not shown on examine - var/category //string of what category this mood was added in as - var/special_screen_obj //if it isn't null, it will replace or add onto the mood icon with this (same file). see happiness drug for example - var/special_screen_replace = TRUE //if false, it will be an overlay instead - var/mob/owner - -/datum/mood_event/New(mob/M, 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/category //string of what category this mood was added in as + var/special_screen_obj //if it isn't null, it will replace or add onto the mood icon with this (same file). see happiness drug for example + var/special_screen_replace = TRUE //if false, it will be an overlay instead + var/mob/owner + +/datum/mood_event/New(mob/M, 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 12e0560f6cd8..c87e416a8c3d 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -1,98 +1,98 @@ -//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 - -//charge -/datum/mood_event/charged - description = "I feel the power in my veins!\n" - mood_change = 6 - -/datum/mood_event/lowpower - description = "My power is running low, I should go charge up somewhere.\n" - mood_change = -7 - -/datum/mood_event/decharged - description = "I'm in desperate need of some electricity!\n" - mood_change = -12 - -//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 - -//Hygiene Events -/datum/mood_event/neat - description = "I'm so clean, I love it.\n" - mood_change = 3 - -/datum/mood_event/dirty - description = "I smell horrid.\n" - mood_change = -5 - -/datum/mood_event/happy_neet - description = "I smell horrid.\n" - mood_change = 2 - -//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/breakfast - description = "Nothing like a hearty breakfast to start the shift.\n" - mood_change = 2 - timeout = 10 MINUTES - -/datum/mood_event/nice_shower - description = "I have recently had a nice shower.\n" - mood_change = 2 - timeout = 1800 - -/datum/mood_event/fresh_laundry - description = "There's nothing like the feeling of a freshly laundered jumpsuit.\n" - mood_change = 2 - timeout = 10 MINUTES +//nutrition +/datum/mood_event/fat + description = "I'm so fat...\n" //muh fatshaming + mood_change = -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 + +//charge +/datum/mood_event/charged + description = "I feel the power in my veins!\n" + mood_change = 6 + +/datum/mood_event/lowpower + description = "My power is running low, I should go charge up somewhere.\n" + mood_change = -7 + +/datum/mood_event/decharged + description = "I'm in desperate need of some electricity!\n" + mood_change = -12 + +//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 + +//Hygiene Events +/datum/mood_event/neat + description = "I'm so clean, I love it.\n" + mood_change = 3 + +/datum/mood_event/dirty + description = "I smell horrid.\n" + mood_change = -5 + +/datum/mood_event/happy_neet + description = "I smell horrid.\n" + mood_change = 2 + +//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/breakfast + description = "Nothing like a hearty breakfast to start the shift.\n" + mood_change = 2 + timeout = 10 MINUTES + +/datum/mood_event/nice_shower + description = "I have recently had a nice shower.\n" + mood_change = 2 + timeout = 1800 + +/datum/mood_event/fresh_laundry + description = "There's nothing like the feeling of a freshly laundered jumpsuit.\n" + mood_change = 2 + timeout = 10 MINUTES diff --git a/code/datums/numbered_display.dm b/code/datums/numbered_display.dm index 84ab35a8defd..9aa880aa75d9 100644 --- a/code/datums/numbered_display.dm +++ b/code/datums/numbered_display.dm @@ -1,10 +1,10 @@ -//Used in storage. -/datum/numbered_display - var/obj/item/sample_object - var/number - -/datum/numbered_display/New(obj/item/sample, _number = 1) - if(!istype(sample)) - qdel(src) - sample_object = sample - number = _number +//Used in storage. +/datum/numbered_display + var/obj/item/sample_object + var/number + +/datum/numbered_display/New(obj/item/sample, _number = 1) + if(!istype(sample)) + qdel(src) + sample_object = sample + number = _number diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index a33d0d222525..44ff87e8ae60 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/recipe.dm b/code/datums/recipe.dm index 0cd636bfeb8f..11141e3452e1 100644 --- a/code/datums/recipe.dm +++ b/code/datums/recipe.dm @@ -1,120 +1,120 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * - * /datum/recipe by rastaf0 13 apr 2011 * - * * * * * * * * * * * * * * * * * * * * * * * * * * - * This is powerful and flexible recipe system. - * It exists not only for food. - * supports both reagents and objects as prerequisites. - * In order to use this system you have to define a deriative from /datum/recipe - * * reagents are reagents. Acid, milc, booze, etc. - * * items are objects. Fruits, tools, circuit boards. - * * result is type to create as new object - * * time is optional parameter, you shall use in in your machine, - default /datum/recipe/ procs does not rely on this parameter. - * - * Functions you need: - * /datum/recipe/proc/make(var/obj/container as obj) - * Creates result inside container, - * deletes prerequisite reagents, - * transfers reagents from prerequisite objects, - * deletes all prerequisite objects (even not needed for recipe at the moment). - * - * /proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj as obj, exact = 1) - * Wonderful function that select suitable recipe for you. - * obj is a machine (or magik hat) with prerequisites, - * exact = 0 forces algorithm to ignore superfluous stuff. - * - * - * Functions you do not need to call directly but could: - * /datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) - * //1=precisely, 0=insufficiently, -1=superfluous - * - * /datum/recipe/proc/check_items(var/obj/container as obj) - * //1=precisely, 0=insufficiently, -1=superfluous - * - * */ - -/datum/recipe - var/list/reagents_list // example: = list(/datum/reagent/consumable/berryjuice = 5) // do not list same reagent twice - var/list/items // example: =list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo - var/result //example: = /obj/item/reagent_containers/food/snacks/donut - var/time = 100 // 1/10 part of second - - -/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous - . = 1 - for (var/r_r in reagents_list) - var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) - if (!(abs(aval_r_amnt - reagents_list[r_r])<0.5)) //if NOT equals - if (aval_r_amnt>reagents_list[r_r]) - . = -1 - else - return 0 - if ((reagents_list?(reagents_list.len):(0)) < avail_reagents.reagent_list.len) - return -1 - return . - -/datum/recipe/proc/check_items(obj/container) //1=precisely, 0=insufficiently, -1=superfluous - if (!items) - if (locate(/obj/) in container) - return -1 - else - return 1 - . = 1 - var/list/checklist = items.Copy() - for (var/obj/O in container) - var/found = 0 - for (var/type in checklist) - if (istype(O,type)) - checklist-=type - found = 1 - break - if (!found) - . = -1 - if (checklist.len) - return 0 - return . - -//general version -/datum/recipe/proc/make(obj/container) - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - O.reagents.trans_to(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -// food-related -/datum/recipe/proc/make_food(obj/container) - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - if (O.reagents) - O.reagents.del_reagent(/datum/reagent/consumable/nutriment) - O.reagents.update_total() - O.reagents.trans_to(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -/proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj, exact = 1 as num) - if (!exact) - exact = -1 - var/list/datum/recipe/possible_recipes = new - for (var/datum/recipe/recipe in avaiable_recipes) - if (recipe.check_reagents(obj.reagents)==exact && recipe.check_items(obj)==exact) - possible_recipes+=recipe - if (possible_recipes.len==0) - return null - else if (possible_recipes.len==1) - return possible_recipes[1] - else //okay, let's select the most complicated recipe - var/r_count = 0 - var/i_count = 0 - . = possible_recipes[1] - for (var/datum/recipe/recipe in possible_recipes) - var/N_i = (recipe.items)?(recipe.items.len):0 - var/N_r = (recipe.reagents_list)?(recipe.reagents_list.len):0 - if (N_i > i_count || (N_i== i_count && N_r > r_count )) - r_count = N_r - i_count = N_i - . = recipe - return . +/* * * * * * * * * * * * * * * * * * * * * * * * * * + * /datum/recipe by rastaf0 13 apr 2011 * + * * * * * * * * * * * * * * * * * * * * * * * * * * + * This is powerful and flexible recipe system. + * It exists not only for food. + * supports both reagents and objects as prerequisites. + * In order to use this system you have to define a deriative from /datum/recipe + * * reagents are reagents. Acid, milc, booze, etc. + * * items are objects. Fruits, tools, circuit boards. + * * result is type to create as new object + * * time is optional parameter, you shall use in in your machine, + default /datum/recipe/ procs does not rely on this parameter. + * + * Functions you need: + * /datum/recipe/proc/make(var/obj/container as obj) + * Creates result inside container, + * deletes prerequisite reagents, + * transfers reagents from prerequisite objects, + * deletes all prerequisite objects (even not needed for recipe at the moment). + * + * /proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj as obj, exact = 1) + * Wonderful function that select suitable recipe for you. + * obj is a machine (or magik hat) with prerequisites, + * exact = 0 forces algorithm to ignore superfluous stuff. + * + * + * Functions you do not need to call directly but could: + * /datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) + * //1=precisely, 0=insufficiently, -1=superfluous + * + * /datum/recipe/proc/check_items(var/obj/container as obj) + * //1=precisely, 0=insufficiently, -1=superfluous + * + * */ + +/datum/recipe + var/list/reagents_list // example: = list(/datum/reagent/consumable/berryjuice = 5) // do not list same reagent twice + var/list/items // example: =list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo + var/result //example: = /obj/item/reagent_containers/food/snacks/donut + var/time = 100 // 1/10 part of second + + +/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous + . = 1 + for (var/r_r in reagents_list) + var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) + if (!(abs(aval_r_amnt - reagents_list[r_r])<0.5)) //if NOT equals + if (aval_r_amnt>reagents_list[r_r]) + . = -1 + else + return 0 + if ((reagents_list?(reagents_list.len):(0)) < avail_reagents.reagent_list.len) + return -1 + return . + +/datum/recipe/proc/check_items(obj/container) //1=precisely, 0=insufficiently, -1=superfluous + if (!items) + if (locate(/obj/) in container) + return -1 + else + return 1 + . = 1 + var/list/checklist = items.Copy() + for (var/obj/O in container) + var/found = 0 + for (var/type in checklist) + if (istype(O,type)) + checklist-=type + found = 1 + break + if (!found) + . = -1 + if (checklist.len) + return 0 + return . + +//general version +/datum/recipe/proc/make(obj/container) + var/obj/result_obj = new result(container) + for (var/obj/O in (container.contents-result_obj)) + O.reagents.trans_to(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +// food-related +/datum/recipe/proc/make_food(obj/container) + var/obj/result_obj = new result(container) + for (var/obj/O in (container.contents-result_obj)) + if (O.reagents) + O.reagents.del_reagent(/datum/reagent/consumable/nutriment) + O.reagents.update_total() + O.reagents.trans_to(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +/proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj, exact = 1 as num) + if (!exact) + exact = -1 + var/list/datum/recipe/possible_recipes = new + for (var/datum/recipe/recipe in avaiable_recipes) + if (recipe.check_reagents(obj.reagents)==exact && recipe.check_items(obj)==exact) + possible_recipes+=recipe + if (possible_recipes.len==0) + return null + else if (possible_recipes.len==1) + return possible_recipes[1] + else //okay, let's select the most complicated recipe + var/r_count = 0 + var/i_count = 0 + . = possible_recipes[1] + for (var/datum/recipe/recipe in possible_recipes) + var/N_i = (recipe.items)?(recipe.items.len):0 + var/N_r = (recipe.reagents_list)?(recipe.reagents_list.len):0 + if (N_i > i_count || (N_i== i_count && N_r > r_count )) + r_count = N_r + i_count = N_i + . = recipe + return . diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm index 91a81247598d..6721b7e16812 100644 --- a/code/datums/spawners_menu.dm +++ b/code/datums/spawners_menu.dm @@ -1,58 +1,58 @@ -/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/group_name = params["name"] - if(!group_name || !(group_name in GLOB.mob_spawners)) - return - var/list/spawnerlist = GLOB.mob_spawners[group_name] - if(!spawnerlist.len) - return - var/obj/effect/mob_spawn/MS = pick(spawnerlist) - if(!istype(MS) || !(MS in GLOB.poi_list)) - return - switch(action) - if("jump") - if(MS) - owner.forceMove(get_turf(MS)) - . = TRUE - if("spawn") - if(MS) - MS.attack_ghost(owner) - . = TRUE +/datum/spawners_menu + var/mob/dead/observer/owner + +/datum/spawners_menu/New(mob/dead/observer/new_owner) + if(!istype(new_owner)) + qdel(src) + owner = new_owner + +/datum/spawners_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "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/group_name = params["name"] + if(!group_name || !(group_name in GLOB.mob_spawners)) + return + var/list/spawnerlist = GLOB.mob_spawners[group_name] + if(!spawnerlist.len) + return + var/obj/effect/mob_spawn/MS = pick(spawnerlist) + if(!istype(MS) || !(MS in GLOB.poi_list)) + return + switch(action) + if("jump") + if(MS) + owner.forceMove(get_turf(MS)) + . = TRUE + if("spawn") + if(MS) + MS.attack_ghost(owner) + . = TRUE diff --git a/code/datums/verbs.dm b/code/datums/verbs.dm index d8d7ee743348..442997288a02 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/_wires.dm b/code/datums/wires/_wires.dm index ce8cb3185609..596200396cd2 100644 --- a/code/datums/wires/_wires.dm +++ b/code/datums/wires/_wires.dm @@ -1,300 +1,300 @@ -#define MAXIMUM_EMP_WIRES 3 - -/proc/is_wire_tool(obj/item/I) - if(!I) - return - - if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL) - return TRUE - if(istype(I, /obj/item/assembly)) - var/obj/item/assembly/A = I - if(A.attachable) - return TRUE - -/atom - var/datum/wires/wires = null - -/atom/proc/attempt_wire_interaction(mob/user) - if(!wires) - return WIRE_INTERACTION_FAIL - if(!user.CanReach(src)) - return WIRE_INTERACTION_FAIL - wires.interact(user) - return WIRE_INTERACTION_BLOCK - -/datum/wires - var/atom/holder = null // The holder (atom that contains these wires). - var/holder_type = null // The holder's typepath (used to make wire colors common to all holders). - var/proper_name = "Unknown" // The display name for the wire set shown in station blueprints. Not used if randomize is true or it's an item NT wouldn't know about (Explosives/Nuke) - - var/list/wires = list() // List of wires. - var/list/cut_wires = list() // List of wires that have been cut. - var/list/colors = list() // Dictionary of colors to wire. - var/list/assemblies = list() // List of attached assemblies. - var/randomize = 0 // If every instance of these wires should be random. - // Prevents wires from showing up in station blueprints - -/datum/wires/New(atom/holder) - ..() - if(!istype(holder, holder_type)) - CRASH("Wire holder is not of the expected type!") - return - - src.holder = holder - if(randomize) - randomize() - else - if(!GLOB.wire_color_directory[holder_type]) - randomize() - GLOB.wire_color_directory[holder_type] = colors - GLOB.wire_name_directory[holder_type] = proper_name - else - colors = GLOB.wire_color_directory[holder_type] - -/datum/wires/Destroy() - holder = null - assemblies = list() - return ..() - -/datum/wires/proc/add_duds(duds) - while(duds) - var/dud = WIRE_DUD_PREFIX + "[--duds]" - if(dud in wires) - continue - wires += dud - -/datum/wires/proc/randomize() - var/static/list/possible_colors = list( - "blue", - "brown", - "crimson", - "cyan", - "gold", - "grey", - "green", - "magenta", - "orange", - "pink", - "purple", - "red", - "silver", - "violet", - "white", - "yellow" - ) - - var/list/my_possible_colors = possible_colors.Copy() - - for(var/wire in shuffle(wires)) - colors[pick_n_take(my_possible_colors)] = wire - -/datum/wires/proc/shuffle_wires() - colors.Cut() - randomize() - -/datum/wires/proc/repair() - cut_wires.Cut() - -/datum/wires/proc/get_wire(color) - return colors[color] - -/datum/wires/proc/get_color_of_wire(wire_type) - for(var/color in colors) - var/other_type = colors[color] - if(wire_type == other_type) - return color - -/datum/wires/proc/get_attached(color) - if(assemblies[color]) - return assemblies[color] - return null - -/datum/wires/proc/is_attached(color) - if(assemblies[color]) - return TRUE - -/datum/wires/proc/is_cut(wire) - return (wire in cut_wires) - -/datum/wires/proc/is_color_cut(color) - return is_cut(get_wire(color)) - -/datum/wires/proc/is_all_cut() - if(cut_wires.len == wires.len) - return TRUE - -/datum/wires/proc/is_dud(wire) - return dd_hasprefix(wire, WIRE_DUD_PREFIX) - -/datum/wires/proc/is_dud_color(color) - return is_dud(get_wire(color)) - -/datum/wires/proc/cut(wire) - if(is_cut(wire)) - cut_wires -= wire - on_cut(wire, mend = TRUE) - else - cut_wires += wire - on_cut(wire, mend = FALSE) - -/datum/wires/proc/cut_color(color) - cut(get_wire(color)) - -/datum/wires/proc/cut_random() - cut(wires[rand(1, wires.len)]) - -/datum/wires/proc/cut_all() - for(var/wire in wires) - cut(wire) - -/datum/wires/proc/pulse(wire, user) - if(is_cut(wire)) - return - on_pulse(wire, user) - -/datum/wires/proc/pulse_color(color, mob/living/user) - pulse(get_wire(color), user) - -/datum/wires/proc/pulse_assembly(obj/item/assembly/S) - for(var/color in assemblies) - if(S == assemblies[color]) - pulse_color(color) - return TRUE - -/datum/wires/proc/attach_assembly(color, obj/item/assembly/S) - if(S && istype(S) && S.attachable && !is_attached(color)) - assemblies[color] = S - S.forceMove(holder) - S.connected = src - return S - -/datum/wires/proc/detach_assembly(color) - var/obj/item/assembly/S = get_attached(color) - if(S && istype(S)) - assemblies -= color - S.connected = null - S.forceMove(holder.drop_location()) - return S - -/datum/wires/proc/emp_pulse() - var/list/possible_wires = shuffle(wires) - var/remaining_pulses = MAXIMUM_EMP_WIRES - - for(var/wire in possible_wires) - if(prob(33)) - pulse(wire) - remaining_pulses-- - if(!remaining_pulses) - break - -// Overridable Procs -/datum/wires/proc/interactable(mob/user) - return TRUE - -/datum/wires/proc/get_status() - return list() - -/datum/wires/proc/on_cut(wire, mend = FALSE) - return - -/datum/wires/proc/on_pulse(wire, user) - return -// End Overridable Procs - -/datum/wires/proc/interact(mob/user) - if(!interactable(user)) - return - ui_interact(user) - for(var/A in assemblies) - var/obj/item/I = assemblies[A] - if(istype(I) && I.on_found(user)) - return - -/datum/wires/ui_host() - return holder - -/datum/wires/ui_status(mob/user) - if(interactable(user)) - return ..() - return UI_CLOSE - -/datum/wires/ui_interact(mob/user, ui_key = "wires", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if (!ui) - ui = new(user, src, ui_key, "wires", "[holder.name] wires", 350, 150 + wires.len * 30, master_ui, state) - ui.open() - -/datum/wires/ui_data(mob/user) - var/list/data = list() - var/list/payload = list() - var/reveal_wires = FALSE - - // Admin ghost can see a purpose of each wire. - if(IsAdminGhost(user)) - reveal_wires = TRUE - - // Same for anyone with an abductor multitool. - else if(user.is_holding_item_of_type(/obj/item/multitool/abductor)) - reveal_wires = TRUE - - // Station blueprints do that too, but only if the wires are not randomized. - else if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints) && !randomize) - reveal_wires = TRUE - - for(var/color in colors) - payload.Add(list(list( - "color" = color, - "wire" = ((reveal_wires && !is_dud_color(color)) ? get_wire(color) : null), - "cut" = is_color_cut(color), - "attached" = is_attached(color) - ))) - data["wires"] = payload - data["status"] = get_status() - return data - -/datum/wires/ui_act(action, params) - if(..() || !interactable(usr)) - return - var/target_wire = params["wire"] - var/mob/living/L = usr - var/obj/item/I - switch(action) - if("cut") - I = L.is_holding_tool_quality(TOOL_WIRECUTTER) - if(I || IsAdminGhost(usr)) - if(I && holder) - I.play_tool_sound(holder, 20) - cut_color(target_wire) - . = TRUE - else - to_chat(L, "You need wirecutters!") - if("pulse") - I = L.is_holding_tool_quality(TOOL_MULTITOOL) - if(I || IsAdminGhost(usr)) - if(I && holder) - I.play_tool_sound(holder, 20) - pulse_color(target_wire, L) - . = TRUE - else - to_chat(L, "You need a multitool!") - if("attach") - if(is_attached(target_wire)) - I = detach_assembly(target_wire) - if(I) - L.put_in_hands(I) - . = TRUE - else - I = L.get_active_held_item() - if(istype(I, /obj/item/assembly)) - var/obj/item/assembly/A = I - if(A.attachable) - if(!L.temporarilyRemoveItemFromInventory(A)) - return - if(!attach_assembly(target_wire, A)) - A.forceMove(L.drop_location()) - . = TRUE - else - to_chat(L, "You need an attachable assembly!") - -#undef MAXIMUM_EMP_WIRES +#define MAXIMUM_EMP_WIRES 3 + +/proc/is_wire_tool(obj/item/I) + if(!I) + return + + if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL) + return TRUE + if(istype(I, /obj/item/assembly)) + var/obj/item/assembly/A = I + if(A.attachable) + return TRUE + +/atom + var/datum/wires/wires = null + +/atom/proc/attempt_wire_interaction(mob/user) + if(!wires) + return WIRE_INTERACTION_FAIL + if(!user.CanReach(src)) + return WIRE_INTERACTION_FAIL + wires.interact(user) + return WIRE_INTERACTION_BLOCK + +/datum/wires + var/atom/holder = null // The holder (atom that contains these wires). + var/holder_type = null // The holder's typepath (used to make wire colors common to all holders). + var/proper_name = "Unknown" // The display name for the wire set shown in station blueprints. Not used if randomize is true or it's an item NT wouldn't know about (Explosives/Nuke) + + var/list/wires = list() // List of wires. + var/list/cut_wires = list() // List of wires that have been cut. + var/list/colors = list() // Dictionary of colors to wire. + var/list/assemblies = list() // List of attached assemblies. + var/randomize = 0 // If every instance of these wires should be random. + // Prevents wires from showing up in station blueprints + +/datum/wires/New(atom/holder) + ..() + if(!istype(holder, holder_type)) + CRASH("Wire holder is not of the expected type!") + return + + src.holder = holder + if(randomize) + randomize() + else + if(!GLOB.wire_color_directory[holder_type]) + randomize() + GLOB.wire_color_directory[holder_type] = colors + GLOB.wire_name_directory[holder_type] = proper_name + else + colors = GLOB.wire_color_directory[holder_type] + +/datum/wires/Destroy() + holder = null + assemblies = list() + return ..() + +/datum/wires/proc/add_duds(duds) + while(duds) + var/dud = WIRE_DUD_PREFIX + "[--duds]" + if(dud in wires) + continue + wires += dud + +/datum/wires/proc/randomize() + var/static/list/possible_colors = list( + "blue", + "brown", + "crimson", + "cyan", + "gold", + "grey", + "green", + "magenta", + "orange", + "pink", + "purple", + "red", + "silver", + "violet", + "white", + "yellow" + ) + + var/list/my_possible_colors = possible_colors.Copy() + + for(var/wire in shuffle(wires)) + colors[pick_n_take(my_possible_colors)] = wire + +/datum/wires/proc/shuffle_wires() + colors.Cut() + randomize() + +/datum/wires/proc/repair() + cut_wires.Cut() + +/datum/wires/proc/get_wire(color) + return colors[color] + +/datum/wires/proc/get_color_of_wire(wire_type) + for(var/color in colors) + var/other_type = colors[color] + if(wire_type == other_type) + return color + +/datum/wires/proc/get_attached(color) + if(assemblies[color]) + return assemblies[color] + return null + +/datum/wires/proc/is_attached(color) + if(assemblies[color]) + return TRUE + +/datum/wires/proc/is_cut(wire) + return (wire in cut_wires) + +/datum/wires/proc/is_color_cut(color) + return is_cut(get_wire(color)) + +/datum/wires/proc/is_all_cut() + if(cut_wires.len == wires.len) + return TRUE + +/datum/wires/proc/is_dud(wire) + return dd_hasprefix(wire, WIRE_DUD_PREFIX) + +/datum/wires/proc/is_dud_color(color) + return is_dud(get_wire(color)) + +/datum/wires/proc/cut(wire) + if(is_cut(wire)) + cut_wires -= wire + on_cut(wire, mend = TRUE) + else + cut_wires += wire + on_cut(wire, mend = FALSE) + +/datum/wires/proc/cut_color(color) + cut(get_wire(color)) + +/datum/wires/proc/cut_random() + cut(wires[rand(1, wires.len)]) + +/datum/wires/proc/cut_all() + for(var/wire in wires) + cut(wire) + +/datum/wires/proc/pulse(wire, user) + if(is_cut(wire)) + return + on_pulse(wire, user) + +/datum/wires/proc/pulse_color(color, mob/living/user) + pulse(get_wire(color), user) + +/datum/wires/proc/pulse_assembly(obj/item/assembly/S) + for(var/color in assemblies) + if(S == assemblies[color]) + pulse_color(color) + return TRUE + +/datum/wires/proc/attach_assembly(color, obj/item/assembly/S) + if(S && istype(S) && S.attachable && !is_attached(color)) + assemblies[color] = S + S.forceMove(holder) + S.connected = src + return S + +/datum/wires/proc/detach_assembly(color) + var/obj/item/assembly/S = get_attached(color) + if(S && istype(S)) + assemblies -= color + S.connected = null + S.forceMove(holder.drop_location()) + return S + +/datum/wires/proc/emp_pulse() + var/list/possible_wires = shuffle(wires) + var/remaining_pulses = MAXIMUM_EMP_WIRES + + for(var/wire in possible_wires) + if(prob(33)) + pulse(wire) + remaining_pulses-- + if(!remaining_pulses) + break + +// Overridable Procs +/datum/wires/proc/interactable(mob/user) + return TRUE + +/datum/wires/proc/get_status() + return list() + +/datum/wires/proc/on_cut(wire, mend = FALSE) + return + +/datum/wires/proc/on_pulse(wire, user) + return +// End Overridable Procs + +/datum/wires/proc/interact(mob/user) + if(!interactable(user)) + return + ui_interact(user) + for(var/A in assemblies) + var/obj/item/I = assemblies[A] + if(istype(I) && I.on_found(user)) + return + +/datum/wires/ui_host() + return holder + +/datum/wires/ui_status(mob/user) + if(interactable(user)) + return ..() + return UI_CLOSE + +/datum/wires/ui_interact(mob/user, ui_key = "wires", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + ui = new(user, src, ui_key, "wires", "[holder.name] wires", 350, 150 + wires.len * 30, master_ui, state) + ui.open() + +/datum/wires/ui_data(mob/user) + var/list/data = list() + var/list/payload = list() + var/reveal_wires = FALSE + + // Admin ghost can see a purpose of each wire. + if(IsAdminGhost(user)) + reveal_wires = TRUE + + // Same for anyone with an abductor multitool. + else if(user.is_holding_item_of_type(/obj/item/multitool/abductor)) + reveal_wires = TRUE + + // Station blueprints do that too, but only if the wires are not randomized. + else if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints) && !randomize) + reveal_wires = TRUE + + for(var/color in colors) + payload.Add(list(list( + "color" = color, + "wire" = ((reveal_wires && !is_dud_color(color)) ? get_wire(color) : null), + "cut" = is_color_cut(color), + "attached" = is_attached(color) + ))) + data["wires"] = payload + data["status"] = get_status() + return data + +/datum/wires/ui_act(action, params) + if(..() || !interactable(usr)) + return + var/target_wire = params["wire"] + var/mob/living/L = usr + var/obj/item/I + switch(action) + if("cut") + I = L.is_holding_tool_quality(TOOL_WIRECUTTER) + if(I || IsAdminGhost(usr)) + if(I && holder) + I.play_tool_sound(holder, 20) + cut_color(target_wire) + . = TRUE + else + to_chat(L, "You need wirecutters!") + if("pulse") + I = L.is_holding_tool_quality(TOOL_MULTITOOL) + if(I || IsAdminGhost(usr)) + if(I && holder) + I.play_tool_sound(holder, 20) + pulse_color(target_wire, L) + . = TRUE + else + to_chat(L, "You need a multitool!") + if("attach") + if(is_attached(target_wire)) + I = detach_assembly(target_wire) + if(I) + L.put_in_hands(I) + . = TRUE + else + I = L.get_active_held_item() + if(istype(I, /obj/item/assembly)) + var/obj/item/assembly/A = I + if(A.attachable) + if(!L.temporarilyRemoveItemFromInventory(A)) + return + if(!attach_assembly(target_wire, A)) + A.forceMove(L.drop_location()) + . = TRUE + else + to_chat(L, "You need an attachable assembly!") + +#undef MAXIMUM_EMP_WIRES diff --git a/code/datums/wires/airalarm.dm b/code/datums/wires/airalarm.dm index 237d36f51bd3..c46e06a4adfa 100644 --- a/code/datums/wires/airalarm.dm +++ b/code/datums/wires/airalarm.dm @@ -1,74 +1,74 @@ -/datum/wires/airalarm - holder_type = /obj/machinery/airalarm - proper_name = "Air Alarm" - -/datum/wires/airalarm/New(atom/holder) - wires = list( - WIRE_POWER, - WIRE_IDSCAN, WIRE_AI, - WIRE_PANIC, WIRE_ALARM - ) - add_duds(3) - ..() - -/datum/wires/airalarm/interactable(mob/user) - var/obj/machinery/airalarm/A = holder - if(A.panel_open && A.buildstage == 2) - return TRUE - -/datum/wires/airalarm/get_status() - var/obj/machinery/airalarm/A = holder - var/list/status = list() - status += "The interface light is [A.locked ? "red" : "green"]." - status += "The short indicator is [A.shorted ? "lit" : "off"]." - status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." - return status - -/datum/wires/airalarm/on_pulse(wire) - var/obj/machinery/airalarm/A = holder - switch(wire) - if(WIRE_POWER) // Short out for a long time. - if(!A.shorted) - A.shorted = TRUE - A.update_icon() - addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 1200) - if(WIRE_IDSCAN) // Toggle lock. - A.locked = !A.locked - if(WIRE_AI) // Disable AI control for a while. - if(!A.aidisabled) - A.aidisabled = TRUE - addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 100) - if(WIRE_PANIC) // Toggle panic siphon. - if(!A.shorted) - if(A.mode == 1) // AALARM_MODE_SCRUB - A.mode = 3 // AALARM_MODE_PANIC - else - A.mode = 1 // AALARM_MODE_SCRUB - A.apply_mode(usr) - if(WIRE_ALARM) // Clear alarms. - var/area/AA = get_area(A) - if(AA.atmosalert(0, holder)) - A.post_alert(0) - A.update_icon() - -/datum/wires/airalarm/on_cut(wire, mend) - var/obj/machinery/airalarm/A = holder - switch(wire) - if(WIRE_POWER) // Short out forever. - A.shock(usr, 50) - A.shorted = !mend - A.update_icon() - if(WIRE_IDSCAN) - if(!mend) - A.locked = TRUE - if(WIRE_AI) - A.aidisabled = mend // Enable/disable AI control. - if(WIRE_PANIC) // Force panic syphon on. - if(!mend && !A.shorted) - A.mode = 3 // AALARM_MODE_PANIC - A.apply_mode(usr) - if(WIRE_ALARM) // Post alarm. - var/area/AA = get_area(A) - if(AA.atmosalert(2, holder)) - A.post_alert(2) - A.update_icon() +/datum/wires/airalarm + holder_type = /obj/machinery/airalarm + proper_name = "Air Alarm" + +/datum/wires/airalarm/New(atom/holder) + wires = list( + WIRE_POWER, + WIRE_IDSCAN, WIRE_AI, + WIRE_PANIC, WIRE_ALARM + ) + add_duds(3) + ..() + +/datum/wires/airalarm/interactable(mob/user) + var/obj/machinery/airalarm/A = holder + if(A.panel_open && A.buildstage == 2) + return TRUE + +/datum/wires/airalarm/get_status() + var/obj/machinery/airalarm/A = holder + var/list/status = list() + status += "The interface light is [A.locked ? "red" : "green"]." + status += "The short indicator is [A.shorted ? "lit" : "off"]." + status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." + return status + +/datum/wires/airalarm/on_pulse(wire) + var/obj/machinery/airalarm/A = holder + switch(wire) + if(WIRE_POWER) // Short out for a long time. + if(!A.shorted) + A.shorted = TRUE + A.update_icon() + addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 1200) + if(WIRE_IDSCAN) // Toggle lock. + A.locked = !A.locked + if(WIRE_AI) // Disable AI control for a while. + if(!A.aidisabled) + A.aidisabled = TRUE + addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 100) + if(WIRE_PANIC) // Toggle panic siphon. + if(!A.shorted) + if(A.mode == 1) // AALARM_MODE_SCRUB + A.mode = 3 // AALARM_MODE_PANIC + else + A.mode = 1 // AALARM_MODE_SCRUB + A.apply_mode(usr) + if(WIRE_ALARM) // Clear alarms. + var/area/AA = get_area(A) + if(AA.atmosalert(0, holder)) + A.post_alert(0) + A.update_icon() + +/datum/wires/airalarm/on_cut(wire, mend) + var/obj/machinery/airalarm/A = holder + switch(wire) + if(WIRE_POWER) // Short out forever. + A.shock(usr, 50) + A.shorted = !mend + A.update_icon() + if(WIRE_IDSCAN) + if(!mend) + A.locked = TRUE + if(WIRE_AI) + A.aidisabled = mend // Enable/disable AI control. + if(WIRE_PANIC) // Force panic syphon on. + if(!mend && !A.shorted) + A.mode = 3 // AALARM_MODE_PANIC + A.apply_mode(usr) + if(WIRE_ALARM) // Post alarm. + var/area/AA = get_area(A) + if(AA.atmosalert(2, holder)) + A.post_alert(2) + A.update_icon() diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm index 56d9266fd813..3dd09e1e756f 100644 --- a/code/datums/wires/airlock.dm +++ b/code/datums/wires/airlock.dm @@ -1,144 +1,144 @@ -/datum/wires/airlock - holder_type = /obj/machinery/door/airlock - proper_name = "Airlock" - -/datum/wires/airlock/secure - randomize = TRUE - -/datum/wires/airlock/New(atom/holder) - wires = list( - WIRE_POWER1, WIRE_POWER2, - WIRE_BACKUP1, WIRE_BACKUP2, - WIRE_OPEN, WIRE_BOLTS, WIRE_IDSCAN, WIRE_AI, - WIRE_SHOCK, WIRE_SAFETY, WIRE_TIMING, WIRE_LIGHT, - WIRE_ZAP1, WIRE_ZAP2 - ) - add_duds(2) - ..() - -/datum/wires/airlock/interactable(mob/user) - var/obj/machinery/door/airlock/A = holder - if(!issilicon(user) && A.isElectrified() && A.shock(user, 100)) - return FALSE - if(A.panel_open) - return TRUE - -/datum/wires/airlock/get_status() - var/obj/machinery/door/airlock/A = holder - var/list/status = list() - status += "The door bolts [A.locked ? "have fallen!" : "look up."]" - status += "The test light is [A.hasPower() ? "on" : "off"]." - status += "The AI connection light is [A.aiControlDisabled || (A.obj_flags & EMAGGED) ? "off" : "on"]." - status += "The check wiring light is [A.safe ? "off" : "on"]." - status += "The timer is powered [A.autoclose ? "on" : "off"]." - status += "The speed light is [A.normalspeed ? "on" : "off"]." - status += "The emergency light is [A.emergency ? "on" : "off"]." - return status - -/datum/wires/airlock/on_pulse(wire) - set waitfor = FALSE - var/obj/machinery/door/airlock/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Pulse to loose power. - A.loseMainPower() - if(WIRE_BACKUP1, WIRE_BACKUP2) // Pulse to loose backup power. - A.loseBackupPower() - if(WIRE_OPEN) // Pulse to open door (only works not emagged and ID wire is cut or no access is required). - if(A.obj_flags & EMAGGED) - return - if(!A.requiresID() || A.check_access(null)) - if(A.density) - INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/open) - else - INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/close) - if(WIRE_BOLTS) // Pulse to toggle bolts (but only raise if power is on). - if(!A.locked) - A.bolt() - else - if(A.hasPower()) - A.unbolt() - A.update_icon() - if(WIRE_IDSCAN) // Pulse to disable emergency access and flash red lights. - if(A.hasPower() && A.density) - A.do_animate("deny") - if(A.emergency) - A.emergency = FALSE - A.update_icon() - if(WIRE_AI) // Pulse to disable WIRE_AI control for 10 ticks (follows same rules as cutting). - if(A.aiControlDisabled == 0) - A.aiControlDisabled = 1 - else if(A.aiControlDisabled == -1) - A.aiControlDisabled = 2 - sleep(10) - if(A) - if(A.aiControlDisabled == 1) - A.aiControlDisabled = 0 - else if(A.aiControlDisabled == 2) - A.aiControlDisabled = -1 - if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks. - if(!A.secondsElectrified) - A.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, usr) - if(WIRE_SAFETY) - A.safe = !A.safe - if(!A.density) - A.close() - if(WIRE_TIMING) - A.normalspeed = !A.normalspeed - if(WIRE_LIGHT) - A.lights = !A.lights - A.update_icon() - -/datum/wires/airlock/on_cut(wire, mend) - var/obj/machinery/door/airlock/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Cut to loose power, repair all to gain power. - if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) - A.regainMainPower() - 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(MACHINE_NOT_ELECTRIFIED, usr) - else - if(A.secondsElectrified != MACHINE_ELECTRIFIED_PERMANENT) - A.set_electrified(MACHINE_ELECTRIFIED_PERMANENT, usr) - if(WIRE_SAFETY) // Cut to disable safeties, mend to re-enable. - A.safe = mend - if(WIRE_TIMING) // Cut to disable auto-close, mend to re-enable. - A.autoclose = mend - if(A.autoclose && !A.density) - A.close() - if(WIRE_LIGHT) // Cut to disable lights, mend to re-enable. - A.lights = mend - A.update_icon() - if(WIRE_ZAP1, WIRE_ZAP2) // Ouch. - if(usr) - A.shock(usr, 50) +/datum/wires/airlock + holder_type = /obj/machinery/door/airlock + proper_name = "Airlock" + +/datum/wires/airlock/secure + randomize = TRUE + +/datum/wires/airlock/New(atom/holder) + wires = list( + WIRE_POWER1, WIRE_POWER2, + WIRE_BACKUP1, WIRE_BACKUP2, + WIRE_OPEN, WIRE_BOLTS, WIRE_IDSCAN, WIRE_AI, + WIRE_SHOCK, WIRE_SAFETY, WIRE_TIMING, WIRE_LIGHT, + WIRE_ZAP1, WIRE_ZAP2 + ) + add_duds(2) + ..() + +/datum/wires/airlock/interactable(mob/user) + var/obj/machinery/door/airlock/A = holder + if(!issilicon(user) && A.isElectrified() && A.shock(user, 100)) + return FALSE + if(A.panel_open) + return TRUE + +/datum/wires/airlock/get_status() + var/obj/machinery/door/airlock/A = holder + var/list/status = list() + status += "The door bolts [A.locked ? "have fallen!" : "look up."]" + status += "The test light is [A.hasPower() ? "on" : "off"]." + status += "The AI connection light is [A.aiControlDisabled || (A.obj_flags & EMAGGED) ? "off" : "on"]." + status += "The check wiring light is [A.safe ? "off" : "on"]." + status += "The timer is powered [A.autoclose ? "on" : "off"]." + status += "The speed light is [A.normalspeed ? "on" : "off"]." + status += "The emergency light is [A.emergency ? "on" : "off"]." + return status + +/datum/wires/airlock/on_pulse(wire) + set waitfor = FALSE + var/obj/machinery/door/airlock/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Pulse to loose power. + A.loseMainPower() + if(WIRE_BACKUP1, WIRE_BACKUP2) // Pulse to loose backup power. + A.loseBackupPower() + if(WIRE_OPEN) // Pulse to open door (only works not emagged and ID wire is cut or no access is required). + if(A.obj_flags & EMAGGED) + return + if(!A.requiresID() || A.check_access(null)) + if(A.density) + INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/open) + else + INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/close) + if(WIRE_BOLTS) // Pulse to toggle bolts (but only raise if power is on). + if(!A.locked) + A.bolt() + else + if(A.hasPower()) + A.unbolt() + A.update_icon() + if(WIRE_IDSCAN) // Pulse to disable emergency access and flash red lights. + if(A.hasPower() && A.density) + A.do_animate("deny") + if(A.emergency) + A.emergency = FALSE + A.update_icon() + if(WIRE_AI) // Pulse to disable WIRE_AI control for 10 ticks (follows same rules as cutting). + if(A.aiControlDisabled == 0) + A.aiControlDisabled = 1 + else if(A.aiControlDisabled == -1) + A.aiControlDisabled = 2 + sleep(10) + if(A) + if(A.aiControlDisabled == 1) + A.aiControlDisabled = 0 + else if(A.aiControlDisabled == 2) + A.aiControlDisabled = -1 + if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks. + if(!A.secondsElectrified) + A.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, usr) + if(WIRE_SAFETY) + A.safe = !A.safe + if(!A.density) + A.close() + if(WIRE_TIMING) + A.normalspeed = !A.normalspeed + if(WIRE_LIGHT) + A.lights = !A.lights + A.update_icon() + +/datum/wires/airlock/on_cut(wire, mend) + var/obj/machinery/door/airlock/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Cut to loose power, repair all to gain power. + if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) + A.regainMainPower() + 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(MACHINE_NOT_ELECTRIFIED, usr) + else + if(A.secondsElectrified != MACHINE_ELECTRIFIED_PERMANENT) + A.set_electrified(MACHINE_ELECTRIFIED_PERMANENT, usr) + if(WIRE_SAFETY) // Cut to disable safeties, mend to re-enable. + A.safe = mend + if(WIRE_TIMING) // Cut to disable auto-close, mend to re-enable. + A.autoclose = mend + if(A.autoclose && !A.density) + A.close() + if(WIRE_LIGHT) // Cut to disable lights, mend to re-enable. + A.lights = mend + A.update_icon() + if(WIRE_ZAP1, WIRE_ZAP2) // Ouch. + if(usr) + A.shock(usr, 50) diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm index e371803d03e5..dc126f612e12 100644 --- a/code/datums/wires/apc.dm +++ b/code/datums/wires/apc.dm @@ -1,55 +1,55 @@ -/datum/wires/apc - holder_type = /obj/machinery/power/apc - proper_name = "APC" - -/datum/wires/apc/New(atom/holder) - wires = list( - WIRE_POWER1, WIRE_POWER2, - WIRE_IDSCAN, WIRE_AI - ) - add_duds(6) - ..() - -/datum/wires/apc/interactable(mob/user) - var/obj/machinery/power/apc/A = holder - if(A.panel_open && !A.opened) - return TRUE - -/datum/wires/apc/get_status() - var/obj/machinery/power/apc/A = holder - var/list/status = list() - status += "The interface light is [A.locked ? "red" : "green"]." - status += "The short indicator is [A.shorted ? "lit" : "off"]." - status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." - return status - -/datum/wires/apc/on_pulse(wire) - var/obj/machinery/power/apc/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Short for a long while. - if(!A.shorted) - A.shorted = TRUE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 1200) - if(WIRE_IDSCAN) // Unlock for a little while. - A.locked = FALSE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 300) - if(WIRE_AI) // Disable AI control for a very short time. - if(!A.aidisabled) - A.aidisabled = TRUE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 10) - -/datum/wires/apc/on_cut(index, mend) - var/obj/machinery/power/apc/A = holder - switch(index) - if(WIRE_POWER1, WIRE_POWER2) // Short out. - if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) - A.shorted = FALSE - A.shock(usr, 50) - else - A.shorted = TRUE - A.shock(usr, 50) - if(WIRE_AI) // Disable AI control. - if(mend) - A.aidisabled = FALSE - else +/datum/wires/apc + holder_type = /obj/machinery/power/apc + proper_name = "APC" + +/datum/wires/apc/New(atom/holder) + wires = list( + WIRE_POWER1, WIRE_POWER2, + WIRE_IDSCAN, WIRE_AI + ) + add_duds(6) + ..() + +/datum/wires/apc/interactable(mob/user) + var/obj/machinery/power/apc/A = holder + if(A.panel_open && !A.opened) + return TRUE + +/datum/wires/apc/get_status() + var/obj/machinery/power/apc/A = holder + var/list/status = list() + status += "The interface light is [A.locked ? "red" : "green"]." + status += "The short indicator is [A.shorted ? "lit" : "off"]." + status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." + return status + +/datum/wires/apc/on_pulse(wire) + var/obj/machinery/power/apc/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Short for a long while. + if(!A.shorted) + A.shorted = TRUE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 1200) + if(WIRE_IDSCAN) // Unlock for a little while. + A.locked = FALSE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 300) + if(WIRE_AI) // Disable AI control for a very short time. + if(!A.aidisabled) + A.aidisabled = TRUE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 10) + +/datum/wires/apc/on_cut(index, mend) + var/obj/machinery/power/apc/A = holder + switch(index) + if(WIRE_POWER1, WIRE_POWER2) // Short out. + if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) + A.shorted = FALSE + A.shock(usr, 50) + else + A.shorted = TRUE + A.shock(usr, 50) + if(WIRE_AI) // Disable AI control. + if(mend) + A.aidisabled = FALSE + else A.aidisabled = TRUE \ No newline at end of file diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm index 5ba76353269f..ebad7d146955 100644 --- a/code/datums/wires/autolathe.dm +++ b/code/datums/wires/autolathe.dm @@ -1,48 +1,48 @@ -/datum/wires/autolathe - holder_type = /obj/machinery/autolathe - proper_name = "Autolathe" - -/datum/wires/autolathe/New(atom/holder) - wires = list( - WIRE_HACK, WIRE_DISABLE, - WIRE_SHOCK, WIRE_ZAP - ) - add_duds(6) - ..() - -/datum/wires/autolathe/interactable(mob/user) - var/obj/machinery/autolathe/A = holder - if(A.panel_open) - return TRUE - -/datum/wires/autolathe/get_status() - var/obj/machinery/autolathe/A = holder - var/list/status = list() - status += "The red light is [A.disabled ? "on" : "off"]." - status += "The blue light is [A.hacked ? "on" : "off"]." - return status - -/datum/wires/autolathe/on_pulse(wire) - var/obj/machinery/autolathe/A = holder - switch(wire) - if(WIRE_HACK) - A.adjust_hacked(!A.hacked) - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - if(WIRE_SHOCK) - A.shocked = !A.shocked - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - if(WIRE_DISABLE) - A.disabled = !A.disabled - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - -/datum/wires/autolathe/on_cut(wire, mend) - var/obj/machinery/autolathe/A = holder - switch(wire) - if(WIRE_HACK) - A.adjust_hacked(!mend) - if(WIRE_HACK) - A.shocked = !mend - if(WIRE_DISABLE) - A.disabled = !mend - if(WIRE_ZAP) +/datum/wires/autolathe + holder_type = /obj/machinery/autolathe + proper_name = "Autolathe" + +/datum/wires/autolathe/New(atom/holder) + wires = list( + WIRE_HACK, WIRE_DISABLE, + WIRE_SHOCK, WIRE_ZAP + ) + add_duds(6) + ..() + +/datum/wires/autolathe/interactable(mob/user) + var/obj/machinery/autolathe/A = holder + if(A.panel_open) + return TRUE + +/datum/wires/autolathe/get_status() + var/obj/machinery/autolathe/A = holder + var/list/status = list() + status += "The red light is [A.disabled ? "on" : "off"]." + status += "The blue light is [A.hacked ? "on" : "off"]." + return status + +/datum/wires/autolathe/on_pulse(wire) + var/obj/machinery/autolathe/A = holder + switch(wire) + if(WIRE_HACK) + A.adjust_hacked(!A.hacked) + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + if(WIRE_SHOCK) + A.shocked = !A.shocked + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + if(WIRE_DISABLE) + A.disabled = !A.disabled + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + +/datum/wires/autolathe/on_cut(wire, mend) + var/obj/machinery/autolathe/A = holder + switch(wire) + if(WIRE_HACK) + A.adjust_hacked(!mend) + if(WIRE_HACK) + A.shocked = !mend + if(WIRE_DISABLE) + A.disabled = !mend + if(WIRE_ZAP) A.shock(usr, 50) \ No newline at end of file diff --git a/code/datums/wires/explosive.dm b/code/datums/wires/explosive.dm index 38f1ef163272..7feeb7eecc19 100644 --- a/code/datums/wires/explosive.dm +++ b/code/datums/wires/explosive.dm @@ -1,122 +1,122 @@ -/datum/wires/explosive - var/duds_number = 2 - -/datum/wires/explosive/New(atom/holder) - add_duds(duds_number) // In this case duds actually explode. - ..() - -/datum/wires/explosive/on_pulse(index) - explode() - -/datum/wires/explosive/on_cut(index, mend) - explode() - -/datum/wires/explosive/proc/explode() - return - -/datum/wires/explosive/chem_grenade - duds_number = 1 - holder_type = /obj/item/grenade/chem_grenade - randomize = TRUE - var/fingerprint - -/datum/wires/explosive/chem_grenade/interactable(mob/user) - var/obj/item/grenade/chem_grenade/G = holder - if(G.stage == GRENADE_WIRED) - return TRUE - -/datum/wires/explosive/chem_grenade/attach_assembly(color, obj/item/assembly/S) - if(istype(S,/obj/item/assembly/timer)) - var/obj/item/grenade/chem_grenade/G = holder - var/obj/item/assembly/timer/T = S - G.det_time = T.saved_time*10 - else if(istype(S,/obj/item/assembly/prox_sensor)) - var/obj/item/grenade/chem_grenade/G = holder - G.landminemode = S - S.proximity_monitor.wire = TRUE - fingerprint = S.fingerprintslast - return ..() - -/datum/wires/explosive/chem_grenade/explode() - var/obj/item/grenade/chem_grenade/G = holder - var/obj/item/assembly/assembly = get_attached(get_wire(1)) - message_admins("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") - log_game("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") - G.prime() - -/datum/wires/explosive/chem_grenade/detach_assembly(color) - var/obj/item/assembly/S = get_attached(color) - if(S && istype(S)) - assemblies -= color - S.connected = null - S.forceMove(holder.drop_location()) - var/obj/item/grenade/chem_grenade/G = holder - G.landminemode = null - return S - -/datum/wires/explosive/c4 - holder_type = /obj/item/grenade/plastic/c4 - randomize = TRUE //Same behaviour since no wire actually disarms it - -/datum/wires/explosive/c4/interactable(mob/user) - var/obj/item/grenade/plastic/c4/P = holder - if(P.open_panel) - return TRUE - -/datum/wires/explosive/c4/explode() - var/obj/item/grenade/plastic/c4/P = holder - P.prime() - - -/datum/wires/explosive/pizza - holder_type = /obj/item/pizzabox - randomize = TRUE - -/datum/wires/explosive/pizza/New(atom/holder) - wires = list( - WIRE_DISARM - ) - add_duds(3) // Duds also explode here. - ..() - -/datum/wires/explosive/pizza/interactable(mob/user) - var/obj/item/pizzabox/P = holder - if(P.open && P.bomb) - return TRUE - -/datum/wires/explosive/pizza/get_status() - var/obj/item/pizzabox/P = holder - var/list/status = list() - status += "The red light is [P.bomb_active ? "on" : "off"]." - status += "The green light is [P.bomb_defused ? "on": "off"]." - return status - -/datum/wires/explosive/pizza/on_pulse(wire) - var/obj/item/pizzabox/P = holder - switch(wire) - if(WIRE_DISARM) // Pulse to toggle - P.bomb_defused = !P.bomb_defused - else // Boom - explode() - -/datum/wires/explosive/pizza/on_cut(wire, mend) - var/obj/item/pizzabox/P = holder - switch(wire) - if(WIRE_DISARM) // Disarm and untrap the box. - if(!mend) - P.bomb_defused = TRUE - else - if(!mend && !P.bomb_defused) - explode() - -/datum/wires/explosive/pizza/explode() - var/obj/item/pizzabox/P = holder - P.bomb.detonate() - - -/datum/wires/explosive/gibtonite - holder_type = /obj/item/twohanded/required/gibtonite - -/datum/wires/explosive/gibtonite/explode() - var/obj/item/twohanded/required/gibtonite/P = holder - P.GibtoniteReaction(null, 2) +/datum/wires/explosive + var/duds_number = 2 + +/datum/wires/explosive/New(atom/holder) + add_duds(duds_number) // In this case duds actually explode. + ..() + +/datum/wires/explosive/on_pulse(index) + explode() + +/datum/wires/explosive/on_cut(index, mend) + explode() + +/datum/wires/explosive/proc/explode() + return + +/datum/wires/explosive/chem_grenade + duds_number = 1 + holder_type = /obj/item/grenade/chem_grenade + randomize = TRUE + var/fingerprint + +/datum/wires/explosive/chem_grenade/interactable(mob/user) + var/obj/item/grenade/chem_grenade/G = holder + if(G.stage == GRENADE_WIRED) + return TRUE + +/datum/wires/explosive/chem_grenade/attach_assembly(color, obj/item/assembly/S) + if(istype(S,/obj/item/assembly/timer)) + var/obj/item/grenade/chem_grenade/G = holder + var/obj/item/assembly/timer/T = S + G.det_time = T.saved_time*10 + else if(istype(S,/obj/item/assembly/prox_sensor)) + var/obj/item/grenade/chem_grenade/G = holder + G.landminemode = S + S.proximity_monitor.wire = TRUE + fingerprint = S.fingerprintslast + return ..() + +/datum/wires/explosive/chem_grenade/explode() + var/obj/item/grenade/chem_grenade/G = holder + var/obj/item/assembly/assembly = get_attached(get_wire(1)) + message_admins("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") + log_game("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") + G.prime() + +/datum/wires/explosive/chem_grenade/detach_assembly(color) + var/obj/item/assembly/S = get_attached(color) + if(S && istype(S)) + assemblies -= color + S.connected = null + S.forceMove(holder.drop_location()) + var/obj/item/grenade/chem_grenade/G = holder + G.landminemode = null + return S + +/datum/wires/explosive/c4 + holder_type = /obj/item/grenade/plastic/c4 + randomize = TRUE //Same behaviour since no wire actually disarms it + +/datum/wires/explosive/c4/interactable(mob/user) + var/obj/item/grenade/plastic/c4/P = holder + if(P.open_panel) + return TRUE + +/datum/wires/explosive/c4/explode() + var/obj/item/grenade/plastic/c4/P = holder + P.prime() + + +/datum/wires/explosive/pizza + holder_type = /obj/item/pizzabox + randomize = TRUE + +/datum/wires/explosive/pizza/New(atom/holder) + wires = list( + WIRE_DISARM + ) + add_duds(3) // Duds also explode here. + ..() + +/datum/wires/explosive/pizza/interactable(mob/user) + var/obj/item/pizzabox/P = holder + if(P.open && P.bomb) + return TRUE + +/datum/wires/explosive/pizza/get_status() + var/obj/item/pizzabox/P = holder + var/list/status = list() + status += "The red light is [P.bomb_active ? "on" : "off"]." + status += "The green light is [P.bomb_defused ? "on": "off"]." + return status + +/datum/wires/explosive/pizza/on_pulse(wire) + var/obj/item/pizzabox/P = holder + switch(wire) + if(WIRE_DISARM) // Pulse to toggle + P.bomb_defused = !P.bomb_defused + else // Boom + explode() + +/datum/wires/explosive/pizza/on_cut(wire, mend) + var/obj/item/pizzabox/P = holder + switch(wire) + if(WIRE_DISARM) // Disarm and untrap the box. + if(!mend) + P.bomb_defused = TRUE + else + if(!mend && !P.bomb_defused) + explode() + +/datum/wires/explosive/pizza/explode() + var/obj/item/pizzabox/P = holder + P.bomb.detonate() + + +/datum/wires/explosive/gibtonite + holder_type = /obj/item/twohanded/required/gibtonite + +/datum/wires/explosive/gibtonite/explode() + var/obj/item/twohanded/required/gibtonite/P = holder + P.GibtoniteReaction(null, 2) diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index 9aa2ca81e825..a452b46830d9 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 4bf49dd81445..af89f0d6f150 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/radio.dm b/code/datums/wires/radio.dm index f950d7a7ce9c..a1118da6d73c 100644 --- a/code/datums/wires/radio.dm +++ b/code/datums/wires/radio.dm @@ -1,25 +1,25 @@ -/datum/wires/radio - holder_type = /obj/item/radio - proper_name = "Radio" - -/datum/wires/radio/New(atom/holder) - wires = list( - WIRE_SIGNAL, - WIRE_RX, WIRE_TX - ) - ..() - -/datum/wires/radio/interactable(mob/user) - var/obj/item/radio/R = holder - return R.unscrewed - -/datum/wires/radio/on_pulse(index) - var/obj/item/radio/R = holder - switch(index) - if(WIRE_SIGNAL) - R.listening = !R.listening - R.broadcasting = R.listening - if(WIRE_RX) - R.listening = !R.listening - if(WIRE_TX) - R.broadcasting = !R.broadcasting +/datum/wires/radio + holder_type = /obj/item/radio + proper_name = "Radio" + +/datum/wires/radio/New(atom/holder) + wires = list( + WIRE_SIGNAL, + WIRE_RX, WIRE_TX + ) + ..() + +/datum/wires/radio/interactable(mob/user) + var/obj/item/radio/R = holder + return R.unscrewed + +/datum/wires/radio/on_pulse(index) + var/obj/item/radio/R = holder + switch(index) + if(WIRE_SIGNAL) + R.listening = !R.listening + R.broadcasting = R.listening + if(WIRE_RX) + R.listening = !R.listening + if(WIRE_TX) + R.broadcasting = !R.broadcasting diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm index e464cbfd5e24..46fb8200ea63 100644 --- a/code/datums/wires/robot.dm +++ b/code/datums/wires/robot.dm @@ -1,86 +1,86 @@ -/datum/wires/robot - holder_type = /mob/living/silicon/robot - randomize = TRUE - -/datum/wires/robot/New(atom/holder) - wires = list( - WIRE_AI, WIRE_CAMERA, - WIRE_LAWSYNC, WIRE_LOCKDOWN, - WIRE_RESET_MODULE - ) - add_duds(2) - ..() - -/datum/wires/robot/interactable(mob/user) - var/mob/living/silicon/robot/R = holder - if(R.wiresexposed) - return TRUE - -/datum/wires/robot/get_status() - var/mob/living/silicon/robot/R = holder - var/list/status = list() - status += "The law sync module is [R.lawupdate ? "on" : "off"]." - status += "The intelligence link display shows [R.connected_ai ? R.connected_ai.name : "NULL"]." - status += "The camera light is [!isnull(R.builtInCamera) && R.builtInCamera.status ? "on" : "off"]." - status += "The lockdown indicator is [R.lockcharge ? "on" : "off"]." - status += "There is a star symbol above the [get_color_of_wire(WIRE_RESET_MODULE)] wire." - return status - -/datum/wires/robot/on_pulse(wire, user) - var/mob/living/silicon/robot/R = holder - switch(wire) - if(WIRE_AI) // Pulse to pick a new AI. - if(!R.emagged) - var/new_ai - if(user) - new_ai = select_active_ai(user) - 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 += "There is a star symbol above the [get_color_of_wire(WIRE_RESET_MODULE)] wire." + return status + +/datum/wires/robot/on_pulse(wire, user) + var/mob/living/silicon/robot/R = holder + switch(wire) + if(WIRE_AI) // Pulse to pick a new AI. + if(!R.emagged) + var/new_ai + if(user) + new_ai = select_active_ai(user) + 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/suit_storage_unit.dm b/code/datums/wires/suit_storage_unit.dm index 4813ad8d207f..eb7781203b2b 100644 --- a/code/datums/wires/suit_storage_unit.dm +++ b/code/datums/wires/suit_storage_unit.dm @@ -1,45 +1,45 @@ -/datum/wires/suit_storage_unit - holder_type = /obj/machinery/suit_storage_unit - proper_name = "Suit Storage Unit" - -/datum/wires/suit_storage_unit/New(atom/holder) - wires = list( - WIRE_HACK, WIRE_SAFETY, - WIRE_ZAP - ) - add_duds(2) - ..() - -/datum/wires/suit_storage_unit/interactable(mob/user) - var/obj/machinery/suit_storage_unit/SSU = holder - if(SSU.panel_open) - return TRUE - -/datum/wires/suit_storage_unit/get_status() - var/obj/machinery/suit_storage_unit/SSU = holder - var/list/status = list() - status += "The UV bulb is [SSU.uv_super ? "glowing" : "dim"]." - status += "The service light is [SSU.safeties ? "off" : "on"]." - return status - -/datum/wires/suit_storage_unit/on_pulse(wire) - var/obj/machinery/suit_storage_unit/SSU = holder - switch(wire) - if(WIRE_HACK) - SSU.uv_super = !SSU.uv_super - if(WIRE_SAFETY) - SSU.safeties = !SSU.safeties - if(WIRE_ZAP) - if(usr) - SSU.shock(usr) - -/datum/wires/suit_storage_unit/on_cut(wire, mend) - var/obj/machinery/suit_storage_unit/SSU = holder - switch(wire) - if(WIRE_HACK) - SSU.uv_super = !mend - if(WIRE_SAFETY) - SSU.safeties = mend - if(WIRE_ZAP) - if(usr) - SSU.shock(usr) +/datum/wires/suit_storage_unit + holder_type = /obj/machinery/suit_storage_unit + proper_name = "Suit Storage Unit" + +/datum/wires/suit_storage_unit/New(atom/holder) + wires = list( + WIRE_HACK, WIRE_SAFETY, + WIRE_ZAP + ) + add_duds(2) + ..() + +/datum/wires/suit_storage_unit/interactable(mob/user) + var/obj/machinery/suit_storage_unit/SSU = holder + if(SSU.panel_open) + return TRUE + +/datum/wires/suit_storage_unit/get_status() + var/obj/machinery/suit_storage_unit/SSU = holder + var/list/status = list() + status += "The UV bulb is [SSU.uv_super ? "glowing" : "dim"]." + status += "The service light is [SSU.safeties ? "off" : "on"]." + return status + +/datum/wires/suit_storage_unit/on_pulse(wire) + var/obj/machinery/suit_storage_unit/SSU = holder + switch(wire) + if(WIRE_HACK) + SSU.uv_super = !SSU.uv_super + if(WIRE_SAFETY) + SSU.safeties = !SSU.safeties + if(WIRE_ZAP) + if(usr) + SSU.shock(usr) + +/datum/wires/suit_storage_unit/on_cut(wire, mend) + var/obj/machinery/suit_storage_unit/SSU = holder + switch(wire) + if(WIRE_HACK) + SSU.uv_super = !mend + if(WIRE_SAFETY) + SSU.safeties = mend + if(WIRE_ZAP) + if(usr) + SSU.shock(usr) diff --git a/code/datums/wires/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm index 2fcccf266311..b24362163aa8 100644 --- a/code/datums/wires/syndicatebomb.dm +++ b/code/datums/wires/syndicatebomb.dm @@ -1,91 +1,91 @@ -/datum/wires/syndicatebomb - holder_type = /obj/machinery/syndicatebomb - randomize = TRUE - -/datum/wires/syndicatebomb/New(atom/holder) - wires = list( - WIRE_BOOM, WIRE_UNBOLT, - WIRE_ACTIVATE, WIRE_DELAY, WIRE_PROCEED - ) - ..() - -/datum/wires/syndicatebomb/interactable(mob/user) - var/obj/machinery/syndicatebomb/P = holder - if(P.open_panel) - return TRUE - -/datum/wires/syndicatebomb/on_pulse(wire) - var/obj/machinery/syndicatebomb/B = holder - switch(wire) - if(WIRE_BOOM) - if(B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - else - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - if(WIRE_UNBOLT) - holder.visible_message("[icon2html(B, viewers(holder))] The bolts spin in place for a moment.") - if(WIRE_DELAY) - if(B.delayedbig) - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - else - holder.visible_message("[icon2html(B, viewers(holder))] The bomb chirps.") - playsound(B, 'sound/machines/chime.ogg', 30, 1) - B.detonation_timer += 300 - if(B.active) - B.delayedbig = TRUE - if(WIRE_PROCEED) - holder.visible_message("[icon2html(B, viewers(holder))] The bomb buzzes ominously!") - playsound(B, 'sound/machines/buzz-sigh.ogg', 30, 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) - holder.visible_message("[icon2html(B, viewers(holder))] You hear the bomb start ticking!") - B.activate() - B.update_icon() - else if(B.delayedlittle) - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - else - holder.visible_message("[icon2html(B, viewers(holder))] The bomb seems to hesitate for a moment.") - B.detonation_timer += 100 - B.delayedlittle = TRUE - -/datum/wires/syndicatebomb/on_cut(wire, mend) - var/obj/machinery/syndicatebomb/B = holder - switch(wire) - if(WIRE_BOOM) - if(!mend && B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - if(WIRE_UNBOLT) - if(!mend && B.anchored) - holder.visible_message("[icon2html(B, viewers(holder))] The bolts lift out of the ground!") - playsound(B, 'sound/effects/stealthoff.ogg', 30, 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.delayedlittle = FALSE - B.delayedbig = FALSE - B.update_icon() - -/datum/wires/syndicatebomb/proc/tell_admins(obj/machinery/syndicatebomb/B) - if(istype(B, /obj/machinery/syndicatebomb/training)) - return - var/turf/T = get_turf(B) - log_game("\A [B] was detonated via boom wire at [AREACOORD(T)].") - message_admins("A [B.name] was detonated via boom wire at [ADMIN_VERBOSEJMP(T)].") +/datum/wires/syndicatebomb + holder_type = /obj/machinery/syndicatebomb + randomize = TRUE + +/datum/wires/syndicatebomb/New(atom/holder) + wires = list( + WIRE_BOOM, WIRE_UNBOLT, + WIRE_ACTIVATE, WIRE_DELAY, WIRE_PROCEED + ) + ..() + +/datum/wires/syndicatebomb/interactable(mob/user) + var/obj/machinery/syndicatebomb/P = holder + if(P.open_panel) + return TRUE + +/datum/wires/syndicatebomb/on_pulse(wire) + var/obj/machinery/syndicatebomb/B = holder + switch(wire) + if(WIRE_BOOM) + if(B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + else + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + if(WIRE_UNBOLT) + holder.visible_message("[icon2html(B, viewers(holder))] The bolts spin in place for a moment.") + if(WIRE_DELAY) + if(B.delayedbig) + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + else + holder.visible_message("[icon2html(B, viewers(holder))] The bomb chirps.") + playsound(B, 'sound/machines/chime.ogg', 30, 1) + B.detonation_timer += 300 + if(B.active) + B.delayedbig = TRUE + if(WIRE_PROCEED) + holder.visible_message("[icon2html(B, viewers(holder))] The bomb buzzes ominously!") + playsound(B, 'sound/machines/buzz-sigh.ogg', 30, 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) + holder.visible_message("[icon2html(B, viewers(holder))] You hear the bomb start ticking!") + B.activate() + B.update_icon() + else if(B.delayedlittle) + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + else + holder.visible_message("[icon2html(B, viewers(holder))] The bomb seems to hesitate for a moment.") + B.detonation_timer += 100 + B.delayedlittle = TRUE + +/datum/wires/syndicatebomb/on_cut(wire, mend) + var/obj/machinery/syndicatebomb/B = holder + switch(wire) + if(WIRE_BOOM) + if(!mend && B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + if(WIRE_UNBOLT) + if(!mend && B.anchored) + holder.visible_message("[icon2html(B, viewers(holder))] The bolts lift out of the ground!") + playsound(B, 'sound/effects/stealthoff.ogg', 30, 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.delayedlittle = FALSE + B.delayedbig = FALSE + B.update_icon() + +/datum/wires/syndicatebomb/proc/tell_admins(obj/machinery/syndicatebomb/B) + if(istype(B, /obj/machinery/syndicatebomb/training)) + return + var/turf/T = get_turf(B) + log_game("\A [B] was detonated via boom wire at [AREACOORD(T)].") + message_admins("A [B.name] was detonated via boom wire at [ADMIN_VERBOSEJMP(T)].") diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm index 3e1a58a1e5be..078c940b041f 100644 --- a/code/datums/wires/vending.dm +++ b/code/datums/wires/vending.dm @@ -1,59 +1,59 @@ -/datum/wires/vending - holder_type = /obj/machinery/vending - proper_name = "Vending Unit" - -/datum/wires/vending/New(atom/holder) - wires = list( - WIRE_THROW, WIRE_SHOCK, WIRE_SPEAKER, - WIRE_CONTRABAND, WIRE_IDSCAN - ) - add_duds(1) - ..() - -/datum/wires/vending/interactable(mob/user) - var/obj/machinery/vending/V = holder - if(!issilicon(user) && V.seconds_electrified && V.shock(user, 100)) - return FALSE - if(V.panel_open) - return TRUE - -/datum/wires/vending/get_status() - var/obj/machinery/vending/V = holder - var/list/status = list() - status += "The orange light is [V.seconds_electrified ? "on" : "off"]." - status += "The red light is [V.shoot_inventory ? "off" : "blinking"]." - status += "The green light is [V.extended_inventory ? "on" : "off"]." - status += "A [V.scan_id ? "purple" : "yellow"] light is on." - status += "The speaker light is [V.shut_up ? "off" : "on"]." - return status - -/datum/wires/vending/on_pulse(wire) - var/obj/machinery/vending/V = holder - switch(wire) - if(WIRE_THROW) - V.shoot_inventory = !V.shoot_inventory - if(WIRE_CONTRABAND) - V.extended_inventory = !V.extended_inventory - if(WIRE_SHOCK) - V.seconds_electrified = MACHINE_DEFAULT_ELECTRIFY_TIME - if(WIRE_IDSCAN) - V.scan_id = !V.scan_id - if(WIRE_SPEAKER) - V.shut_up = !V.shut_up - -/datum/wires/vending/on_cut(wire, mend) - var/obj/machinery/vending/V = holder - switch(wire) - if(WIRE_THROW) - V.shoot_inventory = !mend - if(WIRE_CONTRABAND) - V.extended_inventory = FALSE - if(WIRE_SHOCK) - if(mend) - V.seconds_electrified = MACHINE_NOT_ELECTRIFIED - else - V.seconds_electrified = MACHINE_ELECTRIFIED_PERMANENT - if(WIRE_IDSCAN) - V.scan_id = mend - if(WIRE_SPEAKER) - V.shut_up = mend +/datum/wires/vending + holder_type = /obj/machinery/vending + proper_name = "Vending Unit" + +/datum/wires/vending/New(atom/holder) + wires = list( + WIRE_THROW, WIRE_SHOCK, WIRE_SPEAKER, + WIRE_CONTRABAND, WIRE_IDSCAN + ) + add_duds(1) + ..() + +/datum/wires/vending/interactable(mob/user) + var/obj/machinery/vending/V = holder + if(!issilicon(user) && V.seconds_electrified && V.shock(user, 100)) + return FALSE + if(V.panel_open) + return TRUE + +/datum/wires/vending/get_status() + var/obj/machinery/vending/V = holder + var/list/status = list() + status += "The orange light is [V.seconds_electrified ? "on" : "off"]." + status += "The red light is [V.shoot_inventory ? "off" : "blinking"]." + status += "The green light is [V.extended_inventory ? "on" : "off"]." + status += "A [V.scan_id ? "purple" : "yellow"] light is on." + status += "The speaker light is [V.shut_up ? "off" : "on"]." + return status + +/datum/wires/vending/on_pulse(wire) + var/obj/machinery/vending/V = holder + switch(wire) + if(WIRE_THROW) + V.shoot_inventory = !V.shoot_inventory + if(WIRE_CONTRABAND) + V.extended_inventory = !V.extended_inventory + if(WIRE_SHOCK) + V.seconds_electrified = MACHINE_DEFAULT_ELECTRIFY_TIME + if(WIRE_IDSCAN) + V.scan_id = !V.scan_id + if(WIRE_SPEAKER) + V.shut_up = !V.shut_up + +/datum/wires/vending/on_cut(wire, mend) + var/obj/machinery/vending/V = holder + switch(wire) + if(WIRE_THROW) + V.shoot_inventory = !mend + if(WIRE_CONTRABAND) + V.extended_inventory = FALSE + if(WIRE_SHOCK) + if(mend) + V.seconds_electrified = MACHINE_NOT_ELECTRIFIED + else + V.seconds_electrified = MACHINE_ELECTRIFIED_PERMANENT + if(WIRE_IDSCAN) + V.scan_id = mend + if(WIRE_SPEAKER) + V.shut_up = mend diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index fed7545428de..20bc825f5e4a 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -1,189 +1,189 @@ -// SETUP - -/proc/TopicHandlers() - . = list() - var/list/all_handlers = subtypesof(/datum/world_topic) - for(var/I in all_handlers) - var/datum/world_topic/WT = I - var/keyword = initial(WT.keyword) - if(!keyword) - warning("[WT] has no keyword! Ignoring...") - continue - var/existing_path = .[keyword] - if(existing_path) - warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") - else if(keyword == "key") - warning("[WT] has keyword 'key'! Ignoring...") - else - .[keyword] = WT - -// DATUM - -/datum/world_topic - var/keyword - var/log = TRUE - var/key_valid - var/require_comms_key = FALSE - -/datum/world_topic/proc/TryRun(list/input) - key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) - if(require_comms_key && !key_valid) - return "Bad Key" - input -= "key" - . = Run(input) - if(islist(.)) - . = list2params(.) - -/datum/world_topic/proc/Run(list/input) - CRASH("Run() not implemented for [type]!") - -// TOPICS - -/datum/world_topic/ping - keyword = "ping" - log = FALSE - -/datum/world_topic/ping/Run(list/input) - . = 0 - for (var/client/C in GLOB.clients) - ++. - -/datum/world_topic/playing - keyword = "playing" - log = FALSE - -/datum/world_topic/playing/Run(list/input) - return GLOB.player_list.len - -/datum/world_topic/pr_announce - keyword = "announce" - require_comms_key = TRUE - var/static/list/PRcounts = list() //PR id -> number of times announced this round - -/datum/world_topic/pr_announce/Run(list/input) - var/list/payload = json_decode(input["payload"]) - var/id = "[payload["pull_request"]["id"]]" - if(!PRcounts[id]) - PRcounts[id] = 1 - else - ++PRcounts[id] - if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) - return - - var/final_composed = "PR: [input[keyword]]" - for(var/client/C in GLOB.clients) - C.AnnouncePR(final_composed) - -/datum/world_topic/ahelp_relay - keyword = "Ahelp" - require_comms_key = TRUE - -/datum/world_topic/ahelp_relay/Run(list/input) - relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") - -/datum/world_topic/comms_console - keyword = "Comms_Console" - require_comms_key = TRUE - -/datum/world_topic/comms_console/Run(list/input) - minor_announce(input["message"], "Incoming message from [input["message_sender"]]") - for(var/obj/machinery/computer/communications/CM in GLOB.machines) - CM.overrideCooldown() - -/datum/world_topic/news_report - keyword = "News_Report" - require_comms_key = TRUE - -/datum/world_topic/news_report/Run(list/input) - minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") - -/datum/world_topic/server_hop - keyword = "server_hop" - -/datum/world_topic/server_hop/Run(list/input) - var/expected_key = input[keyword] - for(var/mob/dead/observer/O in GLOB.player_list) - if(O.key == expected_key) - if(O.client) - new /obj/screen/splash(O.client, TRUE) - break - -/datum/world_topic/adminmsg - keyword = "adminmsg" - require_comms_key = TRUE - -/datum/world_topic/adminmsg/Run(list/input) - return IrcPm(input[keyword], input["msg"], input["sender"]) - -/datum/world_topic/namecheck - keyword = "namecheck" - require_comms_key = TRUE - -/datum/world_topic/namecheck/Run(list/input) - //Oh this is a hack, someone refactor the functionality out of the chat command PLS - var/datum/tgs_chat_command/namecheck/NC = new - var/datum/tgs_chat_user/user = new - user.friendly_name = input["sender"] - user.mention = user.friendly_name - return NC.Run(user, input["namecheck"]) - -/datum/world_topic/adminwho - keyword = "adminwho" - require_comms_key = TRUE - -/datum/world_topic/adminwho/Run(list/input) - return ircadminwho() - -/datum/world_topic/status - keyword = "status" - -/datum/world_topic/status/Run(list/input) - . = list() - .["version"] = GLOB.game_version - .["mode"] = GLOB.master_mode - .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE - .["enter"] = GLOB.enter_allowed - .["vote"] = CONFIG_GET(flag/allow_vote_mode) - .["ai"] = CONFIG_GET(flag/allow_ai) - .["host"] = world.host ? world.host : null - .["round_id"] = GLOB.round_id - .["players"] = GLOB.clients.len - .["revision"] = GLOB.revdata.commit - .["revision_date"] = GLOB.revdata.date - - var/list/adm = get_admin_counts() - var/list/presentmins = adm["present"] - var/list/afkmins = adm["afk"] - .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho - .["gamestate"] = SSticker.current_state - - .["map_name"] = SSmapping.config?.map_name || "Loading..." - - if(key_valid) - .["active_players"] = get_active_player_count() - if(SSticker.HasRoundStarted()) - .["real_mode"] = SSticker.mode.name - // Key-authed callers may know the truth behind the "secret" - - .["security_level"] = get_security_level() - .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 - // Amount of world's ticks in seconds, useful for calculating round duration - - //Time dilation stats. - .["time_dilation_current"] = SStime_track.time_dilation_current - .["time_dilation_avg"] = SStime_track.time_dilation_avg - .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow - .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast - - //pop cap stats - .["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0 - .["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0 - .["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0 - .["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases - - if(SSshuttle && SSshuttle.emergency) - .["shuttle_mode"] = SSshuttle.emergency.mode - // Shuttle status, see /__DEFINES/stat.dm - .["shuttle_timer"] = SSshuttle.emergency.timeLeft() - // Shuttle timer, in seconds - +// SETUP + +/proc/TopicHandlers() + . = list() + var/list/all_handlers = subtypesof(/datum/world_topic) + for(var/I in all_handlers) + var/datum/world_topic/WT = I + var/keyword = initial(WT.keyword) + if(!keyword) + warning("[WT] has no keyword! Ignoring...") + continue + var/existing_path = .[keyword] + if(existing_path) + warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") + else if(keyword == "key") + warning("[WT] has keyword 'key'! Ignoring...") + else + .[keyword] = WT + +// DATUM + +/datum/world_topic + var/keyword + var/log = TRUE + var/key_valid + var/require_comms_key = FALSE + +/datum/world_topic/proc/TryRun(list/input) + key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) + if(require_comms_key && !key_valid) + return "Bad Key" + input -= "key" + . = Run(input) + if(islist(.)) + . = list2params(.) + +/datum/world_topic/proc/Run(list/input) + CRASH("Run() not implemented for [type]!") + +// TOPICS + +/datum/world_topic/ping + keyword = "ping" + log = FALSE + +/datum/world_topic/ping/Run(list/input) + . = 0 + for (var/client/C in GLOB.clients) + ++. + +/datum/world_topic/playing + keyword = "playing" + log = FALSE + +/datum/world_topic/playing/Run(list/input) + return GLOB.player_list.len + +/datum/world_topic/pr_announce + keyword = "announce" + require_comms_key = TRUE + var/static/list/PRcounts = list() //PR id -> number of times announced this round + +/datum/world_topic/pr_announce/Run(list/input) + var/list/payload = json_decode(input["payload"]) + var/id = "[payload["pull_request"]["id"]]" + if(!PRcounts[id]) + PRcounts[id] = 1 + else + ++PRcounts[id] + if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) + return + + var/final_composed = "PR: [input[keyword]]" + for(var/client/C in GLOB.clients) + C.AnnouncePR(final_composed) + +/datum/world_topic/ahelp_relay + keyword = "Ahelp" + require_comms_key = TRUE + +/datum/world_topic/ahelp_relay/Run(list/input) + relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") + +/datum/world_topic/comms_console + keyword = "Comms_Console" + require_comms_key = TRUE + +/datum/world_topic/comms_console/Run(list/input) + minor_announce(input["message"], "Incoming message from [input["message_sender"]]") + for(var/obj/machinery/computer/communications/CM in GLOB.machines) + CM.overrideCooldown() + +/datum/world_topic/news_report + keyword = "News_Report" + require_comms_key = TRUE + +/datum/world_topic/news_report/Run(list/input) + minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") + +/datum/world_topic/server_hop + keyword = "server_hop" + +/datum/world_topic/server_hop/Run(list/input) + var/expected_key = input[keyword] + for(var/mob/dead/observer/O in GLOB.player_list) + if(O.key == expected_key) + if(O.client) + new /obj/screen/splash(O.client, TRUE) + break + +/datum/world_topic/adminmsg + keyword = "adminmsg" + require_comms_key = TRUE + +/datum/world_topic/adminmsg/Run(list/input) + return IrcPm(input[keyword], input["msg"], input["sender"]) + +/datum/world_topic/namecheck + keyword = "namecheck" + require_comms_key = TRUE + +/datum/world_topic/namecheck/Run(list/input) + //Oh this is a hack, someone refactor the functionality out of the chat command PLS + var/datum/tgs_chat_command/namecheck/NC = new + var/datum/tgs_chat_user/user = new + user.friendly_name = input["sender"] + user.mention = user.friendly_name + return NC.Run(user, input["namecheck"]) + +/datum/world_topic/adminwho + keyword = "adminwho" + require_comms_key = TRUE + +/datum/world_topic/adminwho/Run(list/input) + return ircadminwho() + +/datum/world_topic/status + keyword = "status" + +/datum/world_topic/status/Run(list/input) + . = list() + .["version"] = GLOB.game_version + .["mode"] = GLOB.master_mode + .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE + .["enter"] = GLOB.enter_allowed + .["vote"] = CONFIG_GET(flag/allow_vote_mode) + .["ai"] = CONFIG_GET(flag/allow_ai) + .["host"] = world.host ? world.host : null + .["round_id"] = GLOB.round_id + .["players"] = GLOB.clients.len + .["revision"] = GLOB.revdata.commit + .["revision_date"] = GLOB.revdata.date + + var/list/adm = get_admin_counts() + var/list/presentmins = adm["present"] + var/list/afkmins = adm["afk"] + .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho + .["gamestate"] = SSticker.current_state + + .["map_name"] = SSmapping.config?.map_name || "Loading..." + + if(key_valid) + .["active_players"] = get_active_player_count() + if(SSticker.HasRoundStarted()) + .["real_mode"] = SSticker.mode.name + // Key-authed callers may know the truth behind the "secret" + + .["security_level"] = get_security_level() + .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 + // Amount of world's ticks in seconds, useful for calculating round duration + + //Time dilation stats. + .["time_dilation_current"] = SStime_track.time_dilation_current + .["time_dilation_avg"] = SStime_track.time_dilation_avg + .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow + .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast + + //pop cap stats + .["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0 + .["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0 + .["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0 + .["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases + + if(SSshuttle && SSshuttle.emergency) + .["shuttle_mode"] = SSshuttle.emergency.mode + // Shuttle status, see /__DEFINES/stat.dm + .["shuttle_timer"] = SSshuttle.emergency.timeLeft() + // Shuttle timer, in seconds + diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm index 9e388511fa85..f7e85d7c06b0 100644 --- a/code/game/area/Space_Station_13_areas.dm +++ b/code/game/area/Space_Station_13_areas.dm @@ -1,1096 +1,1096 @@ -/* - -### This file contains a list of all the areas in your station. Format is as follows: - -/area/CATEGORY/OR/DESCRIPTOR/NAME (you can make as many subdivisions as you want) - name = "NICE NAME" (not required but makes things really nice) - icon = 'ICON FILENAME' (defaults to 'icons/turf/areas.dmi') - icon_state = "NAME OF ICON" (defaults to "unknown" (blank)) - requires_power = FALSE (defaults to true) - ambientsounds = list() (defaults to GENERIC from sound.dm. override it as "ambientsounds = list('sound/ambience/signal.ogg')" or using another define. - -NOTE: there are two lists of areas in the end of this file: centcom and station itself. Please maintain these lists valid. --rastaf0 - -*/ - - -/*-----------------------------------------------------------------------------*/ - -/area/ai_monitored //stub defined ai_monitored.dm - -/area/ai_monitored/turret_protected - -/area/space - icon_state = "space" - requires_power = TRUE - always_unpowered = TRUE - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED - power_light = FALSE - power_equip = FALSE - power_environ = FALSE - valid_territory = FALSE - outdoors = TRUE - ambientsounds = SPACE - blob_allowed = FALSE //Eating up space doesn't count for victory as a blob. - -/area/space/nearstation - icon_state = "space_near" - dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT - -/area/start - name = "start area" - icon_state = "start" - requires_power = FALSE - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED - has_gravity = STANDARD_GRAVITY - - -/area/testroom - requires_power = FALSE - name = "Test Room" - icon_state = "storage" - -//EXTRA - -/area/asteroid - name = "Asteroid" - icon_state = "asteroid" - requires_power = FALSE - has_gravity = STANDARD_GRAVITY - blob_allowed = FALSE //Nope, no winning on the asteroid as a blob. Gotta eat the station. - valid_territory = FALSE - ambientsounds = MINING - -/area/asteroid/nearstation - dynamic_lighting = DYNAMIC_LIGHTING_FORCED - ambientsounds = RUINS - always_unpowered = FALSE - requires_power = TRUE - blob_allowed = TRUE - -/area/asteroid/nearstation/bomb_site - name = "Bomb Testing Asteroid" - -//STATION13 - -//Maintenance - -/area/maintenance - ambientsounds = MAINTENANCE - valid_territory = FALSE - - -//Departments - -/area/maintenance/department/chapel - name = "Chapel Maintenance" - icon_state = "maint_chapel" - -/area/maintenance/department/chapel/monastery - name = "Monastery Maintenance" - icon_state = "maint_monastery" - -/area/maintenance/department/crew_quarters/bar - name = "Bar Maintenance" - icon_state = "maint_bar" - -/area/maintenance/department/crew_quarters/dorms - name = "Dormitory Maintenance" - icon_state = "maint_dorms" - -/area/maintenance/department/eva - name = "EVA Maintenance" - icon_state = "maint_eva" - -/area/maintenance/department/electrical - name = "Electrical Maintenance" - icon_state = "maint_electrical" - -/area/maintenance/department/engine/atmos - name = "Atmospherics Maintenance" - icon_state = "maint_atmos" - -/area/maintenance/department/security - name = "Security Maintenance" - icon_state = "maint_sec" - -/area/maintenance/department/security/brig - name = "Brig Maintenance" - icon_state = "maint_brig" - -/area/maintenance/department/medical - name = "Medbay Maintenance" - icon_state = "medbay_maint" - -/area/maintenance/department/medical/central - name = "Central Medbay Maintenance" - icon_state = "medbay_maint_central" - -/area/maintenance/department/medical/morgue - name = "Morgue Maintenance" - icon_state = "morgue_maint" - -/area/maintenance/department/science - name = "Science Maintenance" - icon_state = "maint_sci" - -/area/maintenance/department/science/central - name = "Central Science Maintenance" - icon_state = "maint_sci_central" - -/area/maintenance/department/cargo - name = "Cargo Maintenance" - icon_state = "maint_cargo" - -/area/maintenance/department/bridge - name = "Bridge Maintenance" - icon_state = "maint_bridge" - -/area/maintenance/department/engine - name = "Engineering Maintenance" - icon_state = "maint_engi" - -/area/maintenance/department/science/xenobiology - name = "Xenobiology Maintenance" - icon_state = "xenomaint" - xenobiology_compatible = TRUE - - -//Maintenance - Generic - -/area/maintenance/aft - name = "Aft Maintenance" - icon_state = "amaint" - -/area/maintenance/aft/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/secondary - name = "Secondary Starboard Maintenance" - icon_state = "smaint_2" - -/area/maintenance/starboard/aft - name = "Starboard Quarter Maintenance" - icon_state = "asmaint" - -/area/maintenance/starboard/aft/secondary - name = "Secondary Starboard Quarter Maintenance" - icon_state = "asmaint_2" - -/area/maintenance/starboard/fore - name = "Starboard Bow Maintenance" - icon_state = "fsmaint" - -/area/maintenance/port - name = "Port Maintenance" - icon_state = "pmaint" - -/area/maintenance/port/central - name = "Central Port Maintenance" - icon_state = "maintcentral" - -/area/maintenance/port/aft - name = "Port Quarter Maintenance" - icon_state = "apmaint" - -/area/maintenance/port/fore - name = "Port Bow Maintenance" - icon_state = "fpmaint" - -/area/maintenance/disposal - name = "Waste Disposal" - icon_state = "disposal" - -/area/maintenance/disposal/incinerator - name = "Incinerator" - icon_state = "disposal" - - -//Hallway - -/area/hallway/primary/aft - name = "Aft Primary Hallway" - icon_state = "hallA" - -/area/hallway/primary/fore - name = "Fore Primary Hallway" - icon_state = "hallF" - -/area/hallway/primary/starboard - name = "Starboard Primary Hallway" - icon_state = "hallS" - -/area/hallway/primary/port - name = "Port Primary Hallway" - icon_state = "hallP" - -/area/hallway/primary/central - name = "Central Primary Hallway" - icon_state = "hallC" - -/area/hallway/secondary/command - name = "Command Hallway" - icon_state = "bridge_hallway" - -/area/hallway/secondary/construction - name = "Construction Area" - icon_state = "construction" - -/area/hallway/secondary/exit - name = "Escape Shuttle Hallway" - icon_state = "escape" - -/area/hallway/secondary/exit/departure_lounge - name = "Departure Lounge" - icon_state = "escape_lounge" - -/area/hallway/secondary/entry - name = "Arrival Shuttle Hallway" - icon_state = "entry" - -/area/hallway/secondary/service - name = "Service Hallway" - icon_state = "hall_service" - -//Command - -/area/bridge - name = "Bridge" - icon_state = "bridge" - ambientsounds = list('sound/ambience/signal.ogg') - -/area/bridge/meeting_room - name = "Heads of Staff Meeting Room" - icon_state = "meeting" - -/area/bridge/meeting_room/council - name = "Council Chamber" - icon_state = "meeting" - -/area/bridge/showroom/corporate - name = "Corporate Showroom" - icon_state = "showroom" - -/area/crew_quarters/heads/captain - name = "Captain's Office" - icon_state = "captain" - -/area/crew_quarters/heads/captain/private - name = "Captain's Quarters" - icon_state = "captain" - -/area/crew_quarters/heads/chief - name = "Chief Engineer's Office" - icon_state = "ce_office" - -/area/crew_quarters/heads/cmo - name = "Chief Medical Officer's Office" - icon_state = "cmo_office" - -/area/crew_quarters/heads/hop - name = "Head of Personnel's Office" - icon_state = "hop_office" - -/area/crew_quarters/heads/hos - name = "Head of Security's Office" - icon_state = "hos_office" - -/area/crew_quarters/heads/hor - name = "Research Director's Office" - icon_state = "rd_office" - -/area/comms - name = "Communications Relay" - icon_state = "tcomsatcham" - -/area/server - name = "Messaging Server Room" - icon_state = "server" - -//Crew - -/area/crew_quarters/dorms - name = "Dormitories" - icon_state = "Sleep" - safe = TRUE - -/area/crew_quarters/toilet - name = "Dormitory Toilets" - icon_state = "toilet" - -/area/crew_quarters/toilet/auxiliary - name = "Auxiliary Restrooms" - icon_state = "toilet" - -/area/crew_quarters/toilet/locker - name = "Locker Toilets" - icon_state = "toilet" - -/area/crew_quarters/toilet/restrooms - name = "Restrooms" - icon_state = "toilet" - -/area/crew_quarters/locker - name = "Locker Room" - icon_state = "locker" - -/area/crew_quarters/lounge - name = "Lounge" - icon_state = "yellow" - -/area/crew_quarters/fitness - name = "Fitness Room" - icon_state = "fitness" - -/area/crew_quarters/fitness/locker_room - name = "Unisex Locker 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/kitchen - name = "Kitchen" - icon_state = "kitchen" - -/area/crew_quarters/kitchen/coldroom - name = "Kitchen Cold Room" - icon_state = "kitchen_cold" - -/area/crew_quarters/bar - name = "Bar" - icon_state = "bar" - -/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/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/atmos_distro //yogstation specific - name = "Atmospherics Distribution" - icon_state = "atmos" - flags_1 = NONE - -/area/engine/atmospherics_engine - name = "Atmospherics Engine" - icon_state = "atmos_engine" - -/area/engine/engine_room //donut station specific - name = "Engine Room" - icon_state = "atmos_engine" - -/area/engine/engine_room/external - name = "Supermatter External Access" - icon_state = "engine_foyer" - -/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/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" - -//Teleporter - -/area/teleporter - name = "Teleporter Room" - icon_state = "teleporter" - ambientsounds = ENGINEERING - -/area/gateway - name = "Gateway" - icon_state = "gateway" - ambientsounds = ENGINEERING - -//MedBay - -/area/medical - name = "Medical" - icon_state = "medbay3" - ambientsounds = MEDICAL - -/area/medical/abandoned - name = "Abandoned Medbay" - icon_state = "medbay3" - ambientsounds = list('sound/ambience/signal.ogg') - -/area/medical/medbay/central - name = "Medbay Central" - icon_state = "medbay" - -/area/medical/medbay/lobby - name = "Medbay Lobby" - icon_state = "medbay" - - //Medbay is a large area, these additional areas help level out APC load. - -/area/medical/medbay/zone2 - name = "Medbay" - icon_state = "medbay2" - -/area/medical/medbay/aft - name = "Medbay Aft" - icon_state = "medbay3" - -/area/medical/storage - name = "Medbay Storage" - icon_state = "medbay2" - -/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/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/escape - icon_state = "checkpoint_esc" - -/area/security/checkpoint/supply - name = "Security Post - Cargo Bay" - icon_state = "checkpoint_supp" - -/area/security/checkpoint/engineering - name = "Security Post - Engineering" - icon_state = "checkpoint_engi" - -/area/security/checkpoint/medical - name = "Security Post - Medbay" - icon_state = "checkpoint_med" - -/area/security/checkpoint/science - name = "Security Post - Science" - icon_state = "checkpoint_sci" - -/area/security/checkpoint/science/research - name = "Security Post - Research Division" - icon_state = "checkpoint_res" - -/area/security/checkpoint/customs - name = "Customs" - icon_state = "customs_point" - -/area/security/checkpoint/customs/auxiliary - icon_state = "customs_point_aux" - - -//Service - -/area/quartermaster - name = "Quartermasters" - icon_state = "quart" - -/area/quartermaster/sorting - name = "Delivery Office" - icon_state = "cargo_delivery" - -/area/quartermaster/warehouse - name = "Warehouse" - icon_state = "cargo_warehouse" - -/area/quartermaster/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/miningdock - name = "Mining Dock" - icon_state = "mining" - -/area/quartermaster/miningoffice - name = "Mining Office" - icon_state = "mining" - -/area/janitor - name = "Custodial Closet" - icon_state = "janitor" - flags_1 = NONE - -/area/janitor/a //yogs start added two new areas - name = "Custodial Closet A" - icon_state = "janitor" - flags_1 = NONE - -/area/janitor/b - name = "Custodial Closet B" - icon_state = "janitor" - flags_1 = NONE //yogs end added two new areas - -/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/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/lab - name = "Robotics Lab" - icon_state = "ass_line" - -/area/science/research - name = "Research Division" - icon_state = "research_development" //yogs - -/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/art - name = "Art Supply Storage" - icon_state = "storage" - -/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/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" - -//Construction - -/area/construction - name = "Construction Area" - icon_state = "yellow" - ambientsounds = ENGINEERING - -/area/construction/mining/aux_base - name = "Auxiliary Base Construction" - icon_state = "aux_base_construction" - -/area/construction/storage_wing - name = "Storage Wing" - icon_state = "storage_wing" - -// Vacant Rooms -/area/vacant_room - name = "Vacant Room" - icon_state = "yellow" - ambientsounds = MAINTENANCE - icon_state = "vacant_room" - -/area/vacant_room/office - name = "Vacant Office" - icon_state = "vacant_office" - -/area/vacant_room/commissary - name = "Vacant Commissary" - icon_state = "vacant_commissary" - -//AI - -/area/ai_monitored/security/armory - name = "Armory" - icon_state = "armory" - ambientsounds = HIGHSEC - -/area/ai_monitored/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/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/computer - name = "Telecomms Control Room" - icon_state = "tcomsatcomp" - -/area/tcommsat/server - name = "Telecomms Server Room" - icon_state = "tcomsatcham" +/* + +### This file contains a list of all the areas in your station. Format is as follows: + +/area/CATEGORY/OR/DESCRIPTOR/NAME (you can make as many subdivisions as you want) + name = "NICE NAME" (not required but makes things really nice) + icon = 'ICON FILENAME' (defaults to 'icons/turf/areas.dmi') + icon_state = "NAME OF ICON" (defaults to "unknown" (blank)) + requires_power = FALSE (defaults to true) + ambientsounds = list() (defaults to GENERIC from sound.dm. override it as "ambientsounds = list('sound/ambience/signal.ogg')" or using another define. + +NOTE: there are two lists of areas in the end of this file: centcom and station itself. Please maintain these lists valid. --rastaf0 + +*/ + + +/*-----------------------------------------------------------------------------*/ + +/area/ai_monitored //stub defined ai_monitored.dm + +/area/ai_monitored/turret_protected + +/area/space + icon_state = "space" + requires_power = TRUE + always_unpowered = TRUE + dynamic_lighting = DYNAMIC_LIGHTING_DISABLED + power_light = FALSE + power_equip = FALSE + power_environ = FALSE + valid_territory = FALSE + outdoors = TRUE + ambientsounds = SPACE + blob_allowed = FALSE //Eating up space doesn't count for victory as a blob. + +/area/space/nearstation + icon_state = "space_near" + dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT + +/area/start + name = "start area" + icon_state = "start" + requires_power = FALSE + dynamic_lighting = DYNAMIC_LIGHTING_DISABLED + has_gravity = STANDARD_GRAVITY + + +/area/testroom + requires_power = FALSE + name = "Test Room" + icon_state = "storage" + +//EXTRA + +/area/asteroid + name = "Asteroid" + icon_state = "asteroid" + requires_power = FALSE + has_gravity = STANDARD_GRAVITY + blob_allowed = FALSE //Nope, no winning on the asteroid as a blob. Gotta eat the station. + valid_territory = FALSE + ambientsounds = MINING + +/area/asteroid/nearstation + dynamic_lighting = DYNAMIC_LIGHTING_FORCED + ambientsounds = RUINS + always_unpowered = FALSE + requires_power = TRUE + blob_allowed = TRUE + +/area/asteroid/nearstation/bomb_site + name = "Bomb Testing Asteroid" + +//STATION13 + +//Maintenance + +/area/maintenance + ambientsounds = MAINTENANCE + valid_territory = FALSE + + +//Departments + +/area/maintenance/department/chapel + name = "Chapel Maintenance" + icon_state = "maint_chapel" + +/area/maintenance/department/chapel/monastery + name = "Monastery Maintenance" + icon_state = "maint_monastery" + +/area/maintenance/department/crew_quarters/bar + name = "Bar Maintenance" + icon_state = "maint_bar" + +/area/maintenance/department/crew_quarters/dorms + name = "Dormitory Maintenance" + icon_state = "maint_dorms" + +/area/maintenance/department/eva + name = "EVA Maintenance" + icon_state = "maint_eva" + +/area/maintenance/department/electrical + name = "Electrical Maintenance" + icon_state = "maint_electrical" + +/area/maintenance/department/engine/atmos + name = "Atmospherics Maintenance" + icon_state = "maint_atmos" + +/area/maintenance/department/security + name = "Security Maintenance" + icon_state = "maint_sec" + +/area/maintenance/department/security/brig + name = "Brig Maintenance" + icon_state = "maint_brig" + +/area/maintenance/department/medical + name = "Medbay Maintenance" + icon_state = "medbay_maint" + +/area/maintenance/department/medical/central + name = "Central Medbay Maintenance" + icon_state = "medbay_maint_central" + +/area/maintenance/department/medical/morgue + name = "Morgue Maintenance" + icon_state = "morgue_maint" + +/area/maintenance/department/science + name = "Science Maintenance" + icon_state = "maint_sci" + +/area/maintenance/department/science/central + name = "Central Science Maintenance" + icon_state = "maint_sci_central" + +/area/maintenance/department/cargo + name = "Cargo Maintenance" + icon_state = "maint_cargo" + +/area/maintenance/department/bridge + name = "Bridge Maintenance" + icon_state = "maint_bridge" + +/area/maintenance/department/engine + name = "Engineering Maintenance" + icon_state = "maint_engi" + +/area/maintenance/department/science/xenobiology + name = "Xenobiology Maintenance" + icon_state = "xenomaint" + xenobiology_compatible = TRUE + + +//Maintenance - Generic + +/area/maintenance/aft + name = "Aft Maintenance" + icon_state = "amaint" + +/area/maintenance/aft/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/secondary + name = "Secondary Starboard Maintenance" + icon_state = "smaint_2" + +/area/maintenance/starboard/aft + name = "Starboard Quarter Maintenance" + icon_state = "asmaint" + +/area/maintenance/starboard/aft/secondary + name = "Secondary Starboard Quarter Maintenance" + icon_state = "asmaint_2" + +/area/maintenance/starboard/fore + name = "Starboard Bow Maintenance" + icon_state = "fsmaint" + +/area/maintenance/port + name = "Port Maintenance" + icon_state = "pmaint" + +/area/maintenance/port/central + name = "Central Port Maintenance" + icon_state = "maintcentral" + +/area/maintenance/port/aft + name = "Port Quarter Maintenance" + icon_state = "apmaint" + +/area/maintenance/port/fore + name = "Port Bow Maintenance" + icon_state = "fpmaint" + +/area/maintenance/disposal + name = "Waste Disposal" + icon_state = "disposal" + +/area/maintenance/disposal/incinerator + name = "Incinerator" + icon_state = "disposal" + + +//Hallway + +/area/hallway/primary/aft + name = "Aft Primary Hallway" + icon_state = "hallA" + +/area/hallway/primary/fore + name = "Fore Primary Hallway" + icon_state = "hallF" + +/area/hallway/primary/starboard + name = "Starboard Primary Hallway" + icon_state = "hallS" + +/area/hallway/primary/port + name = "Port Primary Hallway" + icon_state = "hallP" + +/area/hallway/primary/central + name = "Central Primary Hallway" + icon_state = "hallC" + +/area/hallway/secondary/command + name = "Command Hallway" + icon_state = "bridge_hallway" + +/area/hallway/secondary/construction + name = "Construction Area" + icon_state = "construction" + +/area/hallway/secondary/exit + name = "Escape Shuttle Hallway" + icon_state = "escape" + +/area/hallway/secondary/exit/departure_lounge + name = "Departure Lounge" + icon_state = "escape_lounge" + +/area/hallway/secondary/entry + name = "Arrival Shuttle Hallway" + icon_state = "entry" + +/area/hallway/secondary/service + name = "Service Hallway" + icon_state = "hall_service" + +//Command + +/area/bridge + name = "Bridge" + icon_state = "bridge" + ambientsounds = list('sound/ambience/signal.ogg') + +/area/bridge/meeting_room + name = "Heads of Staff Meeting Room" + icon_state = "meeting" + +/area/bridge/meeting_room/council + name = "Council Chamber" + icon_state = "meeting" + +/area/bridge/showroom/corporate + name = "Corporate Showroom" + icon_state = "showroom" + +/area/crew_quarters/heads/captain + name = "Captain's Office" + icon_state = "captain" + +/area/crew_quarters/heads/captain/private + name = "Captain's Quarters" + icon_state = "captain" + +/area/crew_quarters/heads/chief + name = "Chief Engineer's Office" + icon_state = "ce_office" + +/area/crew_quarters/heads/cmo + name = "Chief Medical Officer's Office" + icon_state = "cmo_office" + +/area/crew_quarters/heads/hop + name = "Head of Personnel's Office" + icon_state = "hop_office" + +/area/crew_quarters/heads/hos + name = "Head of Security's Office" + icon_state = "hos_office" + +/area/crew_quarters/heads/hor + name = "Research Director's Office" + icon_state = "rd_office" + +/area/comms + name = "Communications Relay" + icon_state = "tcomsatcham" + +/area/server + name = "Messaging Server Room" + icon_state = "server" + +//Crew + +/area/crew_quarters/dorms + name = "Dormitories" + icon_state = "Sleep" + safe = TRUE + +/area/crew_quarters/toilet + name = "Dormitory Toilets" + icon_state = "toilet" + +/area/crew_quarters/toilet/auxiliary + name = "Auxiliary Restrooms" + icon_state = "toilet" + +/area/crew_quarters/toilet/locker + name = "Locker Toilets" + icon_state = "toilet" + +/area/crew_quarters/toilet/restrooms + name = "Restrooms" + icon_state = "toilet" + +/area/crew_quarters/locker + name = "Locker Room" + icon_state = "locker" + +/area/crew_quarters/lounge + name = "Lounge" + icon_state = "yellow" + +/area/crew_quarters/fitness + name = "Fitness Room" + icon_state = "fitness" + +/area/crew_quarters/fitness/locker_room + name = "Unisex Locker 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/kitchen + name = "Kitchen" + icon_state = "kitchen" + +/area/crew_quarters/kitchen/coldroom + name = "Kitchen Cold Room" + icon_state = "kitchen_cold" + +/area/crew_quarters/bar + name = "Bar" + icon_state = "bar" + +/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/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/atmos_distro //yogstation specific + name = "Atmospherics Distribution" + icon_state = "atmos" + flags_1 = NONE + +/area/engine/atmospherics_engine + name = "Atmospherics Engine" + icon_state = "atmos_engine" + +/area/engine/engine_room //donut station specific + name = "Engine Room" + icon_state = "atmos_engine" + +/area/engine/engine_room/external + name = "Supermatter External Access" + icon_state = "engine_foyer" + +/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/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" + +//Teleporter + +/area/teleporter + name = "Teleporter Room" + icon_state = "teleporter" + ambientsounds = ENGINEERING + +/area/gateway + name = "Gateway" + icon_state = "gateway" + ambientsounds = ENGINEERING + +//MedBay + +/area/medical + name = "Medical" + icon_state = "medbay3" + ambientsounds = MEDICAL + +/area/medical/abandoned + name = "Abandoned Medbay" + icon_state = "medbay3" + ambientsounds = list('sound/ambience/signal.ogg') + +/area/medical/medbay/central + name = "Medbay Central" + icon_state = "medbay" + +/area/medical/medbay/lobby + name = "Medbay Lobby" + icon_state = "medbay" + + //Medbay is a large area, these additional areas help level out APC load. + +/area/medical/medbay/zone2 + name = "Medbay" + icon_state = "medbay2" + +/area/medical/medbay/aft + name = "Medbay Aft" + icon_state = "medbay3" + +/area/medical/storage + name = "Medbay Storage" + icon_state = "medbay2" + +/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/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/escape + icon_state = "checkpoint_esc" + +/area/security/checkpoint/supply + name = "Security Post - Cargo Bay" + icon_state = "checkpoint_supp" + +/area/security/checkpoint/engineering + name = "Security Post - Engineering" + icon_state = "checkpoint_engi" + +/area/security/checkpoint/medical + name = "Security Post - Medbay" + icon_state = "checkpoint_med" + +/area/security/checkpoint/science + name = "Security Post - Science" + icon_state = "checkpoint_sci" + +/area/security/checkpoint/science/research + name = "Security Post - Research Division" + icon_state = "checkpoint_res" + +/area/security/checkpoint/customs + name = "Customs" + icon_state = "customs_point" + +/area/security/checkpoint/customs/auxiliary + icon_state = "customs_point_aux" + + +//Service + +/area/quartermaster + name = "Quartermasters" + icon_state = "quart" + +/area/quartermaster/sorting + name = "Delivery Office" + icon_state = "cargo_delivery" + +/area/quartermaster/warehouse + name = "Warehouse" + icon_state = "cargo_warehouse" + +/area/quartermaster/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/miningdock + name = "Mining Dock" + icon_state = "mining" + +/area/quartermaster/miningoffice + name = "Mining Office" + icon_state = "mining" + +/area/janitor + name = "Custodial Closet" + icon_state = "janitor" + flags_1 = NONE + +/area/janitor/a //yogs start added two new areas + name = "Custodial Closet A" + icon_state = "janitor" + flags_1 = NONE + +/area/janitor/b + name = "Custodial Closet B" + icon_state = "janitor" + flags_1 = NONE //yogs end added two new areas + +/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/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/lab + name = "Robotics Lab" + icon_state = "ass_line" + +/area/science/research + name = "Research Division" + icon_state = "research_development" //yogs + +/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/art + name = "Art Supply Storage" + icon_state = "storage" + +/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/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" + +//Construction + +/area/construction + name = "Construction Area" + icon_state = "yellow" + ambientsounds = ENGINEERING + +/area/construction/mining/aux_base + name = "Auxiliary Base Construction" + icon_state = "aux_base_construction" + +/area/construction/storage_wing + name = "Storage Wing" + icon_state = "storage_wing" + +// Vacant Rooms +/area/vacant_room + name = "Vacant Room" + icon_state = "yellow" + ambientsounds = MAINTENANCE + icon_state = "vacant_room" + +/area/vacant_room/office + name = "Vacant Office" + icon_state = "vacant_office" + +/area/vacant_room/commissary + name = "Vacant Commissary" + icon_state = "vacant_commissary" + +//AI + +/area/ai_monitored/security/armory + name = "Armory" + icon_state = "armory" + ambientsounds = HIGHSEC + +/area/ai_monitored/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/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/computer + name = "Telecomms Control Room" + icon_state = "tcomsatcomp" + +/area/tcommsat/server + name = "Telecomms Server Room" + icon_state = "tcomsatcham" diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm index 9d42b67bf93a..87b44291f56e 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 ca24d451b74c..bef76b65261e 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1,743 +1,743 @@ -/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 - - var/datum/proximity_monitor/proximity_monitor - var/buckle_message_cooldown = 0 - var/fingerprintslast - - var/list/filter_data //For handling persistent filters - - var/custom_price - var/custom_premium_price - - 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 - -/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) - - orbiters = null // The component is attached to us normaly and will be deleted elsewhere - - 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 = 0) - 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(mob/user, allowmobs = TRUE) - return reagents && (reagents.flags & (INJECTABLE | REFILLABLE)) - -/atom/proc/is_drawable(mob/user, allowmobs = TRUE) - return reagents && (reagents.flags & (DRAWABLE | DRAINABLE)) - -/atom/proc/is_refillable() - return reagents && (reagents.flags & REFILLABLE) - -/atom/proc/is_drainable() - return reagents && (reagents.flags & DRAINABLE) - - -/atom/proc/AllowDrop() - return FALSE - -/atom/proc/CheckExit() - return 1 - -/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) - -/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 - if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) - . = override.Join("") - -/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.flags & TRANSPARENT) - . += "It contains:" - if(length(reagents.reagent_list)) - if(user.can_see_reagents()) //Show each individual reagent - for(var/datum/reagent/R in reagents.reagent_list) - . += "[R.volume] units of [R.name]" - else //Otherwise, just show the total volume - var/total_volume = 0 - for(var/datum/reagent/R in reagents.reagent_list) - total_volume += R.volume - . += "[total_volume] units of various reagents" - else - . += "Nothing." - else if(reagents.flags & AMOUNT_VISIBLE) - if(reagents.total_volume) - . += "It has [reagents.total_volume] unit\s left." - else - . += "It's empty." - - SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) - -/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/prevent_content_explosion() - return FALSE - -/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, datum/thrownthing/throwingdatum) - if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). - addtimer(CALLBACK(src, .proc/hitby_react, AM), 2) - -/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, paralyze, force_drop) - return - -//returns the mob's dna info as a list, to be inserted in an object's blood_DNA list -/mob/living/proc/get_blood_dna_list() - if(get_blood_id() != /datum/reagent/blood) - return - return list("ANIMAL DNA" = "Y-") - -/mob/living/carbon/get_blood_dna_list() - if(get_blood_id() != /datum/reagent/blood) - return - var/list/blood_dna = list() - if(dna) - blood_dna[dna.unique_enzymes] = dna.blood_type - else - blood_dna["UNKNOWN DNA"] = "X*" - return blood_dna - -/mob/living/carbon/alien/get_blood_dna_list() - return list("UNKNOWN DNA" = "X*") - -/mob/living/silicon/get_blood_dna_list() - return list("MOTOR OIL" = "SAE 5W-30") //just a little flavor text. - -//to add a mob's dna info into an object's blood_DNA list. -/atom/proc/transfer_mob_blood_dna(mob/living/L) - // Returns 0 if we have that blood already - var/new_blood_dna = L.get_blood_dna_list() - if(!new_blood_dna) - return FALSE - var/old_length = blood_DNA_length() - add_blood_DNA(new_blood_dna) - if(blood_DNA_length() == old_length) - return FALSE - return TRUE - -//to add blood from a mob onto something, and transfer their dna info -/atom/proc/add_mob_blood(mob/living/M) - var/list/blood_dna = M.get_blood_dna_list() - if(!blood_dna) - return FALSE - return add_blood_DNA(blood_dna) - -/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() - 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) - //yogs start -- stops things from dumping into themselves - if(STR == src_object) - to_chat(user,"You can't dump the contents of [src_object.parent] into itself!") - return - //yogs end - 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) - 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/multitool_check_buffer(user, obj/item/I, silent = FALSE) - if(!istype(I, /obj/item/multitool)) - if(user && !silent) - to_chat(user, "[I] has no data buffer!") - return FALSE - return TRUE - - -/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 - -/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - return - -// Generic logging helper -/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) - if(!log_globally) - return - - var/log_text = "[key_name(src)] [message] [loc_name(src)]" - switch(message_type) - if(LOG_ATTACK) - log_attack(log_text) - if(LOG_SAY) - log_say(log_text) - if(LOG_WHISPER) - log_whisper(log_text) - if(LOG_EMOTE) - log_emote(log_text) - if(LOG_DSAY) - log_dsay(log_text) - if(LOG_PDA) - log_pda(log_text) - if(LOG_CHAT) - log_chat(log_text) - if(LOG_COMMENT) - log_comment(log_text) - if(LOG_TELECOMMS) - log_telecomms(log_text) - if(LOG_NTSL) - log_ntsl(log_text) - if(LOG_OOC) - log_ooc(log_text) - if(LOG_LOOC) // yogs - LOOC log - log_looc(log_text) // yogs - LOOC log - if(LOG_ADMIN) - log_admin(log_text) - if(LOG_ADMIN_PRIVATE) - log_admin_private(log_text) - if(LOG_ASAY) - log_adminsay(log_text) - if(LOG_OWNERSHIP) - log_game(log_text) - if(LOG_GAME) - log_game(log_text) - if(LOG_MECHA) - log_mecha(log_text) - 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 [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) - if(!filter_data) - filter_data = list() - var/list/p = params.Copy() - p["priority"] = priority - filter_data[name] = p - update_filters() - -/atom/movable/proc/update_filters() - filters = null - sortTim(filter_data,associative = 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/proc/intercept_zImpact(atom/movable/AM, levels = 1) - return FALSE +/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 + + var/datum/proximity_monitor/proximity_monitor + var/buckle_message_cooldown = 0 + var/fingerprintslast + + var/list/filter_data //For handling persistent filters + + var/custom_price + var/custom_premium_price + + 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 + +/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) + + orbiters = null // The component is attached to us normaly and will be deleted elsewhere + + 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 = 0) + 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(mob/user, allowmobs = TRUE) + return reagents && (reagents.flags & (INJECTABLE | REFILLABLE)) + +/atom/proc/is_drawable(mob/user, allowmobs = TRUE) + return reagents && (reagents.flags & (DRAWABLE | DRAINABLE)) + +/atom/proc/is_refillable() + return reagents && (reagents.flags & REFILLABLE) + +/atom/proc/is_drainable() + return reagents && (reagents.flags & DRAINABLE) + + +/atom/proc/AllowDrop() + return FALSE + +/atom/proc/CheckExit() + return 1 + +/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) + +/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 + if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) + . = override.Join("") + +/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.flags & TRANSPARENT) + . += "It contains:" + if(length(reagents.reagent_list)) + if(user.can_see_reagents()) //Show each individual reagent + for(var/datum/reagent/R in reagents.reagent_list) + . += "[R.volume] units of [R.name]" + else //Otherwise, just show the total volume + var/total_volume = 0 + for(var/datum/reagent/R in reagents.reagent_list) + total_volume += R.volume + . += "[total_volume] units of various reagents" + else + . += "Nothing." + else if(reagents.flags & AMOUNT_VISIBLE) + if(reagents.total_volume) + . += "It has [reagents.total_volume] unit\s left." + else + . += "It's empty." + + SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) + +/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/prevent_content_explosion() + return FALSE + +/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, datum/thrownthing/throwingdatum) + if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). + addtimer(CALLBACK(src, .proc/hitby_react, AM), 2) + +/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, paralyze, force_drop) + return + +//returns the mob's dna info as a list, to be inserted in an object's blood_DNA list +/mob/living/proc/get_blood_dna_list() + if(get_blood_id() != /datum/reagent/blood) + return + return list("ANIMAL DNA" = "Y-") + +/mob/living/carbon/get_blood_dna_list() + if(get_blood_id() != /datum/reagent/blood) + return + var/list/blood_dna = list() + if(dna) + blood_dna[dna.unique_enzymes] = dna.blood_type + else + blood_dna["UNKNOWN DNA"] = "X*" + return blood_dna + +/mob/living/carbon/alien/get_blood_dna_list() + return list("UNKNOWN DNA" = "X*") + +/mob/living/silicon/get_blood_dna_list() + return list("MOTOR OIL" = "SAE 5W-30") //just a little flavor text. + +//to add a mob's dna info into an object's blood_DNA list. +/atom/proc/transfer_mob_blood_dna(mob/living/L) + // Returns 0 if we have that blood already + var/new_blood_dna = L.get_blood_dna_list() + if(!new_blood_dna) + return FALSE + var/old_length = blood_DNA_length() + add_blood_DNA(new_blood_dna) + if(blood_DNA_length() == old_length) + return FALSE + return TRUE + +//to add blood from a mob onto something, and transfer their dna info +/atom/proc/add_mob_blood(mob/living/M) + var/list/blood_dna = M.get_blood_dna_list() + if(!blood_dna) + return FALSE + return add_blood_DNA(blood_dna) + +/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() + 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) + //yogs start -- stops things from dumping into themselves + if(STR == src_object) + to_chat(user,"You can't dump the contents of [src_object.parent] into itself!") + return + //yogs end + 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) + 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/multitool_check_buffer(user, obj/item/I, silent = FALSE) + if(!istype(I, /obj/item/multitool)) + if(user && !silent) + to_chat(user, "[I] has no data buffer!") + return FALSE + return TRUE + + +/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 + +/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + return + +// Generic logging helper +/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) + if(!log_globally) + return + + var/log_text = "[key_name(src)] [message] [loc_name(src)]" + switch(message_type) + if(LOG_ATTACK) + log_attack(log_text) + if(LOG_SAY) + log_say(log_text) + if(LOG_WHISPER) + log_whisper(log_text) + if(LOG_EMOTE) + log_emote(log_text) + if(LOG_DSAY) + log_dsay(log_text) + if(LOG_PDA) + log_pda(log_text) + if(LOG_CHAT) + log_chat(log_text) + if(LOG_COMMENT) + log_comment(log_text) + if(LOG_TELECOMMS) + log_telecomms(log_text) + if(LOG_NTSL) + log_ntsl(log_text) + if(LOG_OOC) + log_ooc(log_text) + if(LOG_LOOC) // yogs - LOOC log + log_looc(log_text) // yogs - LOOC log + if(LOG_ADMIN) + log_admin(log_text) + if(LOG_ADMIN_PRIVATE) + log_admin_private(log_text) + if(LOG_ASAY) + log_adminsay(log_text) + if(LOG_OWNERSHIP) + log_game(log_text) + if(LOG_GAME) + log_game(log_text) + if(LOG_MECHA) + log_mecha(log_text) + 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 [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) + if(!filter_data) + filter_data = list() + var/list/p = params.Copy() + p["priority"] = priority + filter_data[name] = p + update_filters() + +/atom/movable/proc/update_filters() + filters = null + sortTim(filter_data,associative = 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/proc/intercept_zImpact(atom/movable/AM, levels = 1) + return FALSE diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 927e0d94b0c4..2d06a6e55175 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1,920 +1,920 @@ -/atom/movable - layer = OBJ_LAYER - var/last_move = null - var/last_move_time = 0 - var/anchored = FALSE - var/move_resist = MOVE_RESIST_DEFAULT - var/move_force = MOVE_FORCE_DEFAULT - var/pull_force = PULL_FORCE_DEFAULT - var/datum/thrownthing/throwing = null - var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported - var/throw_range = 7 - var/mob/pulledby = null - var/initial_language_holder = /datum/language_holder - var/datum/language_holder/language_holder - 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/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before. - var/list/client_mobs_in_contents // This contains all the client mobs within this container - var/list/acted_explosions //for explosion dodging - glide_size = 8 - appearance_flags = TILE_BOUND|PIXEL_SCALE - var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm - var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc. - var/atom/movable/pulling - var/grab_state = 0 - var/throwforce = 0 - var/datum/component/orbiter/orbiting - var/can_be_z_moved = TRUE - - var/zfalling = FALSE - -/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", "bounds") - var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height") - if(var_name in banned_edits) - return FALSE //PLEASE no. - if((var_name in careful_edits) && (var_value % world.icon_size) != 0) - return FALSE - switch(var_name) - if("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, state, force = move_force, supress_message = FALSE) - if(QDELETED(AM)) - return FALSE - if(!(AM.can_be_pulled(src, state, force))) - return FALSE - - // If we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - if(state == 0) - stop_pulling() - return FALSE - // Are we trying to pull something we are already pulling? Then enter grab cycle and end. - if(AM == pulling) - grab_state = state - 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 = state - if(ismob(AM)) - var/mob/M = AM - log_combat(src, M, "grabbed", addition="passive grab") - if(!supress_message) - 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_mobility()// mob gets up if it was lyng down in a chokehold - -/atom/movable/proc/Move_Pulled(atom/A) - if(!pulling) - return - if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src)) - stop_pulling() - return - if(isliving(pulling)) - var/mob/living/L = pulling - if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it - stop_pulling() - return - if(A == loc && pulling.density) - return - if(!Process_Spacemove(get_dir(pulling.loc, A))) - return - step(pulling, get_dir(pulling.loc, A)) - return TRUE - -/mob/living/Move_Pulled(atom/A) - . = ..() - if(!. || !isliving(A)) - return - var/mob/living/L = A - set_pull_offsets(L, grab_state) - -/atom/movable/proc/check_pulling() - if(pulling) - var/atom/movable/pullee = pulling - if(pullee && get_dist(src, pullee) > 1) - stop_pulling() - return - if(!isturf(loc)) - stop_pulling() - return - if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). - log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") - stop_pulling() - return - if(pulling.anchored || pulling.move_resist > move_force) - stop_pulling() - return - if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. - pulledby.stop_pulling() - -//////////////////////////////////////// -// Here's where we rewrite how byond handles movement except slightly different -// To be removed on step_ conversion -// All this work to prevent a second bump -/atom/movable/Move(atom/newloc, direct=0) - . = FALSE - if(!newloc || newloc == loc) - return - - if(!direct) - direct = get_dir(src, newloc) - setDir(direct) - - // yogs start - multi tile object handling - if(bound_width != world.icon_size || bound_height != world.icon_size) - var/list/newlocs = isturf(newloc) ? block(locate(newloc.x+(-bound_x)/world.icon_size,newloc.y+(-bound_y)/world.icon_size,newloc.z),locate(newloc.x+(-bound_x+bound_width)/world.icon_size-1,newloc.y+(-bound_y+bound_height)/world.icon_size-1,newloc.z)) : list(newloc) - if(!newlocs) - return // we're trying to cross into the edge of space - var/bothturfs = isturf(newloc) && isturf(loc) - var/dx = bothturfs ? newloc.x - loc.x : 0 - var/dy = bothturfs ? newloc.y - loc.y : 0 - var/dz = bothturfs ? newloc.z - loc.z : 0 - for(var/atom/A in (locs - newlocs)) - if(!A.Exit(src, bothturfs ? locate(A.x+dx,A.y+dy,A.z+dz) : newloc)) - return - for(var/atom/A in (newlocs - locs)) - if(!A.Enter(src, bothturfs ? locate(A.x-dx,A.y-dy,A.z+dz) : loc)) - return - else - if(!loc.Exit(src, newloc)) - return - - if(!newloc.Enter(src, src.loc)) - return - // yogs end - - if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE) - return - - // Past this is the point of no return - var/atom/oldloc = loc - var/area/oldarea = get_area(oldloc) - var/area/newarea = get_area(newloc) - loc = newloc - . = TRUE - oldloc.Exited(src, newloc) - if(oldarea != newarea) - oldarea.Exited(src, newloc) - - for(var/i in oldloc) - if(i == src) // Multi tile objects - continue - var/atom/movable/thing = i - thing.Uncrossed(src) - - newloc.Entered(src, oldloc) - if(oldarea != newarea) - newarea.Entered(src, oldloc) - - for(var/i in loc) - if(i == src) // Multi tile objects - continue - var/atom/movable/thing = i - thing.Crossed(src) -// -//////////////////////////////////////// - -/atom/movable/Move(atom/newloc, direct) - var/atom/movable/pullee = pulling - var/turf/T = loc - if(!moving_from_pull) - check_pulling() - if(!loc || !newloc) - return FALSE - var/atom/oldloc = loc - - if(loc != newloc) - if (!(direct & (direct - 1))) //Cardinal move - . = ..() - else //Diagonal move, split it into cardinal moves - moving_diagonally = FIRST_DIAG_STEP - var/first_step_dir - // The `&& moving_diagonally` checks are so that a forceMove taking - // place due to a Crossed, Bumped, etc. call will interrupt - // the second half of the diagonal movement, or the second attempt - // at a first half if step() fails because we hit something. - if (direct & NORTH) - if (direct & EAST) - if (step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if (moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if (direct & WEST) - if (step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if (moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if (direct & SOUTH) - if (direct & EAST) - if (step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if (moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - else if (direct & WEST) - if (step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if (moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - if(moving_diagonally == SECOND_DIAG_STEP) - if(!.) - setDir(first_step_dir) - else if (!inertia_moving) - inertia_next_move = world.time + inertia_move_delay - newtonian_move(direct) - moving_diagonally = 0 - return - - if(!loc || (loc == oldloc && oldloc != newloc)) - last_move = 0 - return - - if(.) - Moved(oldloc, direct) - if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. - if(pulling.anchored) - stop_pulling() - else - var/pull_dir = get_dir(src, pulling) - //puller and pullee more than one tile away or in diagonal position - if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir))) - pulling.moving_from_pull = src - pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position - pulling.moving_from_pull = null - check_pulling() - - last_move = direct - setDir(direct) - if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s) - return FALSE - -//Called after a successful Move(). By this point, we've already moved -/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) - 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, throwingdatum=throwingdatum) - -/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) - if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) - step(src, AM.dir) - ..() - -/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) - if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY)) - return - return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force) - -/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) //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.force = force - TT.callback = callback - - 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/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) - return FALSE - -/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE) - . = AM.force_pushed(src, force, direction) - if(!silent && .) - visible_message("[src] forcefully pushes against [AM]!", "You forcefully push against [AM]!") - -/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE) - . = AM.move_crushed(src, force, direction) - if(!silent && .) - visible_message("[src] crushes past [AM]!", "You crush [AM]!") - -/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) - return FALSE - -/atom/movable/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 - - animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) - animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2) - -/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) - var/image/I - if(visual_effect_icon) - I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) - else if(used_item) - I = image(icon = used_item, loc = A, layer = A.layer + 0.1) - I.plane = GAME_PLANE - - // Scale the icon. - I.transform *= 0.75 - // The icon should not rotate. - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - - // Set the direction of the icon animation. - var/direction = get_dir(src, A) - if(direction & NORTH) - I.pixel_y = -16 - else if(direction & SOUTH) - I.pixel_y = 16 - - if(direction & EAST) - I.pixel_x = -16 - else if(direction & WEST) - I.pixel_x = 16 - - if(!direction) // Attacked self?! - I.pixel_z = 16 - - if(!I) - return - - flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second - - // And animate the attack! - var/t_color = "#ffffff" //yogs start - if(ismob(src) && ismob(A) && (!used_item)) - var/mob/M = src - t_color = M.a_intent == INTENT_HARM ? "#ff0000" : "#ffffff" - animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3, color = t_color) //yogs end - -/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, grab_state, force) - if(src == user || !isturf(loc)) - return FALSE - if(anchored || throwing) - return FALSE - if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) - return FALSE - return TRUE - -/obj/item/proc/do_pickup_animation(atom/target) - set waitfor = FALSE - if(!istype(loc, /turf)) - return - var/image/I = image(icon = src, loc = loc, layer = layer + 0.1) - I.plane = GAME_PLANE - I.transform *= 0.75 - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - var/turf/T = get_turf(src) - var/direction - var/to_x = 0 - var/to_y = 0 - - if(!QDELETED(T) && !QDELETED(target)) - direction = get_dir(T, target) - if(direction & NORTH) - to_y = 32 - else if(direction & SOUTH) - to_y = -32 - if(direction & EAST) - to_x = 32 - else if(direction & WEST) - to_x = -32 - if(!direction) - to_y = 16 - flick_overlay(I, GLOB.clients, 6) - var/matrix/M = new - M.Turn(pick(-30, 30)) - animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING) - sleep(1) - animate(I, alpha = 0, transform = matrix(), time = 1) +/atom/movable + layer = OBJ_LAYER + var/last_move = null + var/last_move_time = 0 + var/anchored = FALSE + var/move_resist = MOVE_RESIST_DEFAULT + var/move_force = MOVE_FORCE_DEFAULT + var/pull_force = PULL_FORCE_DEFAULT + var/datum/thrownthing/throwing = null + var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported + var/throw_range = 7 + var/mob/pulledby = null + var/initial_language_holder = /datum/language_holder + var/datum/language_holder/language_holder + 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/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before. + var/list/client_mobs_in_contents // This contains all the client mobs within this container + var/list/acted_explosions //for explosion dodging + glide_size = 8 + appearance_flags = TILE_BOUND|PIXEL_SCALE + var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm + var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc. + var/atom/movable/pulling + var/grab_state = 0 + var/throwforce = 0 + var/datum/component/orbiter/orbiting + var/can_be_z_moved = TRUE + + var/zfalling = FALSE + +/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", "bounds") + var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height") + if(var_name in banned_edits) + return FALSE //PLEASE no. + if((var_name in careful_edits) && (var_value % world.icon_size) != 0) + return FALSE + switch(var_name) + if("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, state, force = move_force, supress_message = FALSE) + if(QDELETED(AM)) + return FALSE + if(!(AM.can_be_pulled(src, state, force))) + return FALSE + + // If we're pulling something then drop what we're currently pulling and pull this instead. + if(pulling) + if(state == 0) + stop_pulling() + return FALSE + // Are we trying to pull something we are already pulling? Then enter grab cycle and end. + if(AM == pulling) + grab_state = state + 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 = state + if(ismob(AM)) + var/mob/M = AM + log_combat(src, M, "grabbed", addition="passive grab") + if(!supress_message) + 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_mobility()// mob gets up if it was lyng down in a chokehold + +/atom/movable/proc/Move_Pulled(atom/A) + if(!pulling) + return + if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src)) + stop_pulling() + return + if(isliving(pulling)) + var/mob/living/L = pulling + if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it + stop_pulling() + return + if(A == loc && pulling.density) + return + if(!Process_Spacemove(get_dir(pulling.loc, A))) + return + step(pulling, get_dir(pulling.loc, A)) + return TRUE + +/mob/living/Move_Pulled(atom/A) + . = ..() + if(!. || !isliving(A)) + return + var/mob/living/L = A + set_pull_offsets(L, grab_state) + +/atom/movable/proc/check_pulling() + if(pulling) + var/atom/movable/pullee = pulling + if(pullee && get_dist(src, pullee) > 1) + stop_pulling() + return + if(!isturf(loc)) + stop_pulling() + return + if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). + log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") + stop_pulling() + return + if(pulling.anchored || pulling.move_resist > move_force) + stop_pulling() + return + if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. + pulledby.stop_pulling() + +//////////////////////////////////////// +// Here's where we rewrite how byond handles movement except slightly different +// To be removed on step_ conversion +// All this work to prevent a second bump +/atom/movable/Move(atom/newloc, direct=0) + . = FALSE + if(!newloc || newloc == loc) + return + + if(!direct) + direct = get_dir(src, newloc) + setDir(direct) + + // yogs start - multi tile object handling + if(bound_width != world.icon_size || bound_height != world.icon_size) + var/list/newlocs = isturf(newloc) ? block(locate(newloc.x+(-bound_x)/world.icon_size,newloc.y+(-bound_y)/world.icon_size,newloc.z),locate(newloc.x+(-bound_x+bound_width)/world.icon_size-1,newloc.y+(-bound_y+bound_height)/world.icon_size-1,newloc.z)) : list(newloc) + if(!newlocs) + return // we're trying to cross into the edge of space + var/bothturfs = isturf(newloc) && isturf(loc) + var/dx = bothturfs ? newloc.x - loc.x : 0 + var/dy = bothturfs ? newloc.y - loc.y : 0 + var/dz = bothturfs ? newloc.z - loc.z : 0 + for(var/atom/A in (locs - newlocs)) + if(!A.Exit(src, bothturfs ? locate(A.x+dx,A.y+dy,A.z+dz) : newloc)) + return + for(var/atom/A in (newlocs - locs)) + if(!A.Enter(src, bothturfs ? locate(A.x-dx,A.y-dy,A.z+dz) : loc)) + return + else + if(!loc.Exit(src, newloc)) + return + + if(!newloc.Enter(src, src.loc)) + return + // yogs end + + if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE) + return + + // Past this is the point of no return + var/atom/oldloc = loc + var/area/oldarea = get_area(oldloc) + var/area/newarea = get_area(newloc) + loc = newloc + . = TRUE + oldloc.Exited(src, newloc) + if(oldarea != newarea) + oldarea.Exited(src, newloc) + + for(var/i in oldloc) + if(i == src) // Multi tile objects + continue + var/atom/movable/thing = i + thing.Uncrossed(src) + + newloc.Entered(src, oldloc) + if(oldarea != newarea) + newarea.Entered(src, oldloc) + + for(var/i in loc) + if(i == src) // Multi tile objects + continue + var/atom/movable/thing = i + thing.Crossed(src) +// +//////////////////////////////////////// + +/atom/movable/Move(atom/newloc, direct) + var/atom/movable/pullee = pulling + var/turf/T = loc + if(!moving_from_pull) + check_pulling() + if(!loc || !newloc) + return FALSE + var/atom/oldloc = loc + + if(loc != newloc) + if (!(direct & (direct - 1))) //Cardinal move + . = ..() + else //Diagonal move, split it into cardinal moves + moving_diagonally = FIRST_DIAG_STEP + var/first_step_dir + // The `&& moving_diagonally` checks are so that a forceMove taking + // place due to a Crossed, Bumped, etc. call will interrupt + // the second half of the diagonal movement, or the second attempt + // at a first half if step() fails because we hit something. + if (direct & NORTH) + if (direct & EAST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & WEST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & SOUTH) + if (direct & EAST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + else if (direct & WEST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + if(moving_diagonally == SECOND_DIAG_STEP) + if(!.) + setDir(first_step_dir) + else if (!inertia_moving) + inertia_next_move = world.time + inertia_move_delay + newtonian_move(direct) + moving_diagonally = 0 + return + + if(!loc || (loc == oldloc && oldloc != newloc)) + last_move = 0 + return + + if(.) + Moved(oldloc, direct) + if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. + if(pulling.anchored) + stop_pulling() + else + var/pull_dir = get_dir(src, pulling) + //puller and pullee more than one tile away or in diagonal position + if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir))) + pulling.moving_from_pull = src + pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position + pulling.moving_from_pull = null + check_pulling() + + last_move = direct + setDir(direct) + if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s) + return FALSE + +//Called after a successful Move(). By this point, we've already moved +/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) + 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, throwingdatum=throwingdatum) + +/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) + if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) + step(src, AM.dir) + ..() + +/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) + if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY)) + return + return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force) + +/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) //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.force = force + TT.callback = callback + + 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/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE) + . = AM.force_pushed(src, force, direction) + if(!silent && .) + visible_message("[src] forcefully pushes against [AM]!", "You forcefully push against [AM]!") + +/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE) + . = AM.move_crushed(src, force, direction) + if(!silent && .) + visible_message("[src] crushes past [AM]!", "You crush [AM]!") + +/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/atom/movable/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 + + animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) + animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2) + +/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) + var/image/I + if(visual_effect_icon) + I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) + else if(used_item) + I = image(icon = used_item, loc = A, layer = A.layer + 0.1) + I.plane = GAME_PLANE + + // Scale the icon. + I.transform *= 0.75 + // The icon should not rotate. + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + + // Set the direction of the icon animation. + var/direction = get_dir(src, A) + if(direction & NORTH) + I.pixel_y = -16 + else if(direction & SOUTH) + I.pixel_y = 16 + + if(direction & EAST) + I.pixel_x = -16 + else if(direction & WEST) + I.pixel_x = 16 + + if(!direction) // Attacked self?! + I.pixel_z = 16 + + if(!I) + return + + flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second + + // And animate the attack! + var/t_color = "#ffffff" //yogs start + if(ismob(src) && ismob(A) && (!used_item)) + var/mob/M = src + t_color = M.a_intent == INTENT_HARM ? "#ff0000" : "#ffffff" + animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3, color = t_color) //yogs end + +/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, grab_state, force) + if(src == user || !isturf(loc)) + return FALSE + if(anchored || throwing) + return FALSE + if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) + return FALSE + return TRUE + +/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 ed470473d954..696b942434f7 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 4dbc9b3baa4a..c7da430ec9d5 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") - required_players = 8 //yogs - just a minor change - - 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") | yogs - redundant - 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") + required_players = 8 //yogs - just a minor change + + 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") | yogs - redundant + 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 da85296e7a36..1c4ba8a4f78c 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -1,138 +1,138 @@ -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" - report_type = "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") //YOGS - added hop - 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() - //Decide if it's ok for the lings to have a team objective - //And then set it up to be handed out in forge_changeling_objectives - var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) - var/list/possible_team_objectives = list() - for(var/T in team_objectives) - var/datum/objective/changeling_team_objective/CTO = T - - if(changelings.len >= initial(CTO.min_lings)) - possible_team_objectives += T - - if(possible_team_objectives.len && prob(20*changelings.len)) - GLOB.changeling_team_objective_type = pick(possible_team_objectives) - - for(var/datum/mind/changeling in changelings) - //log_game("[key_name(changeling)] has been selected as a changeling") | yogs - redundant - var/datum/antagonist/changeling/new_antag = new() - //new_antag.team_mode = TRUE //yogs - lol - 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(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - character.mind.make_Changeling() - changelings += character.mind - -/datum/game_mode/changeling/generate_report() - return "The Gorlex Marauders have announced the successful raid and destruction of Central Command containment ship #S-[rand(1111, 9999)]. This ship housed only a single prisoner - \ - codenamed \"Thing\", and it was highly adaptive and extremely dangerous. We have reason to believe that the Thing has allied with the Syndicate, and you should note that likelihood \ - of the Thing being sent to a station in this sector is highly likely. It may be in the guise of any crew member. Trust nobody - suspect everybody. Do not announce this to the crew, \ - as paranoia may spread and inhibit workplace efficiency." - -/proc/changeling_transform(mob/living/carbon/human/user, datum/changelingprofile/chosen_prof) - var/datum/dna/chosen_dna = chosen_prof.dna - user.real_name = chosen_prof.name - user.underwear = chosen_prof.underwear - user.undershirt = chosen_prof.undershirt - user.socks = chosen_prof.socks - - chosen_dna.transfer_identity(user, 1) - user.updateappearance(mutcolor_update=1) - user.update_body() - user.domutcheck() - - //vars hackery. not pretty, but better than the alternative. - for(var/slot in GLOB.slots) - if(istype(user.vars[slot], GLOB.slot2type[slot]) && !(chosen_prof.exists_list[slot])) //remove unnecessary flesh items - qdel(user.vars[slot]) - continue - - if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot])) - continue - - var/obj/item/C - var/equip = 0 - if(!user.vars[slot]) - var/thetype = GLOB.slot2type[slot] - equip = 1 - C = new thetype(user) - - else if(istype(user.vars[slot], GLOB.slot2type[slot])) - C = user.vars[slot] - - C.appearance = chosen_prof.appearance_list[slot] - C.name = chosen_prof.name_list[slot] - C.flags_cover = chosen_prof.flags_cover_list[slot] - C.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" + report_type = "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") //YOGS - added hop + 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() + //Decide if it's ok for the lings to have a team objective + //And then set it up to be handed out in forge_changeling_objectives + var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) + var/list/possible_team_objectives = list() + for(var/T in team_objectives) + var/datum/objective/changeling_team_objective/CTO = T + + if(changelings.len >= initial(CTO.min_lings)) + possible_team_objectives += T + + if(possible_team_objectives.len && prob(20*changelings.len)) + GLOB.changeling_team_objective_type = pick(possible_team_objectives) + + for(var/datum/mind/changeling in changelings) + //log_game("[key_name(changeling)] has been selected as a changeling") | yogs - redundant + var/datum/antagonist/changeling/new_antag = new() + //new_antag.team_mode = TRUE //yogs - lol + 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(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + character.mind.make_Changeling() + changelings += character.mind + +/datum/game_mode/changeling/generate_report() + return "The Gorlex Marauders have announced the successful raid and destruction of Central Command containment ship #S-[rand(1111, 9999)]. This ship housed only a single prisoner - \ + codenamed \"Thing\", and it was highly adaptive and extremely dangerous. We have reason to believe that the Thing has allied with the Syndicate, and you should note that likelihood \ + of the Thing being sent to a station in this sector is highly likely. It may be in the guise of any crew member. Trust nobody - suspect everybody. Do not announce this to the crew, \ + as paranoia may spread and inhibit workplace efficiency." + +/proc/changeling_transform(mob/living/carbon/human/user, datum/changelingprofile/chosen_prof) + var/datum/dna/chosen_dna = chosen_prof.dna + user.real_name = chosen_prof.name + user.underwear = chosen_prof.underwear + user.undershirt = chosen_prof.undershirt + user.socks = chosen_prof.socks + + chosen_dna.transfer_identity(user, 1) + user.updateappearance(mutcolor_update=1) + user.update_body() + user.domutcheck() + + //vars hackery. not pretty, but better than the alternative. + for(var/slot in GLOB.slots) + if(istype(user.vars[slot], GLOB.slot2type[slot]) && !(chosen_prof.exists_list[slot])) //remove unnecessary flesh items + qdel(user.vars[slot]) + continue + + if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot])) + continue + + var/obj/item/C + var/equip = 0 + if(!user.vars[slot]) + var/thetype = GLOB.slot2type[slot] + equip = 1 + C = new thetype(user) + + else if(istype(user.vars[slot], GLOB.slot2type[slot])) + C = user.vars[slot] + + C.appearance = chosen_prof.appearance_list[slot] + C.name = chosen_prof.name_list[slot] + C.flags_cover = chosen_prof.flags_cover_list[slot] + C.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 ef9b710d7b05..b6f7dbba23f0 100644 --- a/code/game/gamemodes/changeling/traitor_chan.dm +++ b/code/game/gamemodes/changeling/traitor_chan.dm @@ -1,83 +1,83 @@ -/datum/game_mode/traitor/changeling - name = "traitor+changeling" - config_tag = "traitorchan" - report_type = "traitorchan" - false_report_weight = 10 - traitors_possible = 3 //hard limit on traitors if scaling is turned off - restricted_jobs = list("AI", "Cyborg") - required_players = 25 - required_enemies = 1 // how many of each type are required - recommended_enemies = 3 - reroll_friendly = 1 - announce_span = "Traitors and Changelings" - announce_text = "There are alien creatures on the station along with some syndicate operatives out for their own gain! Do not let the changelings or the traitors succeed!" - - var/list/possible_changelings = list() - var/list/changelings = list() - var/const/changeling_amount = 1 //hard limit on changelings if scaling is turned off - -/datum/game_mode/traitor/changeling/can_start() - if(!..()) - return 0 - possible_changelings = get_players_for_role(ROLE_CHANGELING) - if(possible_changelings.len < required_enemies) - return 0 - return 1 - -/datum/game_mode/traitor/changeling/pre_setup() - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) - - var/num_changelings = 1 - - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - if(csc) - num_changelings = max(1, min(round(num_players() / (csc * 4)) + 2, round(num_players() / (csc * 2)))) - else - num_changelings = max(1, min(num_players(), changeling_amount/2)) - - if(possible_changelings.len>0) - for(var/j = 0, j < num_changelings, j++) - if(!possible_changelings.len) - break - var/datum/mind/changeling = antag_pick(possible_changelings) - antag_candidates -= changeling - possible_changelings -= changeling - changeling.special_role = ROLE_CHANGELING - changelings += changeling - changeling.restricted_roles = restricted_jobs - 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(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - character.mind.make_Changeling() - changelings += character.mind - if(QDELETED(character)) - return - ..() - -/datum/game_mode/traitor/changeling/generate_report() - return "The Syndicate has started some experimental research regarding humanoid shapeshifting. There are rumors that this technology will be field tested on a Nanotrasen station \ - for infiltration purposes. Be advised that support personel may also be deployed to defend these shapeshifters. Trust nobody - suspect everybody. Do not announce this to the crew, \ - as paranoia may spread and inhibit workplace efficiency." +/datum/game_mode/traitor/changeling + name = "traitor+changeling" + config_tag = "traitorchan" + report_type = "traitorchan" + false_report_weight = 10 + traitors_possible = 3 //hard limit on traitors if scaling is turned off + restricted_jobs = list("AI", "Cyborg") + required_players = 25 + required_enemies = 1 // how many of each type are required + recommended_enemies = 3 + reroll_friendly = 1 + announce_span = "Traitors and Changelings" + announce_text = "There are alien creatures on the station along with some syndicate operatives out for their own gain! Do not let the changelings or the traitors succeed!" + + var/list/possible_changelings = list() + var/list/changelings = list() + var/const/changeling_amount = 1 //hard limit on changelings if scaling is turned off + +/datum/game_mode/traitor/changeling/can_start() + if(!..()) + return 0 + possible_changelings = get_players_for_role(ROLE_CHANGELING) + if(possible_changelings.len < required_enemies) + return 0 + return 1 + +/datum/game_mode/traitor/changeling/pre_setup() + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) + + var/num_changelings = 1 + + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + if(csc) + num_changelings = max(1, min(round(num_players() / (csc * 4)) + 2, round(num_players() / (csc * 2)))) + else + num_changelings = max(1, min(num_players(), changeling_amount/2)) + + if(possible_changelings.len>0) + for(var/j = 0, j < num_changelings, j++) + if(!possible_changelings.len) + break + var/datum/mind/changeling = antag_pick(possible_changelings) + antag_candidates -= changeling + possible_changelings -= changeling + changeling.special_role = ROLE_CHANGELING + changelings += changeling + changeling.restricted_roles = restricted_jobs + 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(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + character.mind.make_Changeling() + changelings += character.mind + if(QDELETED(character)) + return + ..() + +/datum/game_mode/traitor/changeling/generate_report() + return "The Syndicate has started some experimental research regarding humanoid shapeshifting. There are rumors that this technology will be field tested on a Nanotrasen station \ + for infiltration purposes. Be advised that support personel may also be deployed to defend these shapeshifters. Trust nobody - suspect everybody. Do not announce this to the crew, \ + as paranoia may spread and inhibit workplace efficiency." diff --git a/code/game/gamemodes/events.dm b/code/game/gamemodes/events.dm index ef781c5ee364..8a7181aaa3dd 100644 --- a/code/game/gamemodes/events.dm +++ b/code/game/gamemodes/events.dm @@ -1,66 +1,66 @@ -/proc/power_failure() - priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", 'sound/ai/poweroff.ogg') - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(istype(get_area(S), /area/ai_monitored/turret_protected) || !is_station_level(S.z)) - continue - S.charge = 0 - S.output_level = 0 - S.output_attempt = FALSE - S.update_icon() - S.power_change() - - for(var/area/A in GLOB.the_station_areas) - if(!A.requires_power || A.always_unpowered ) - continue - if(GLOB.typecache_powerfailure_safe_areas[A.type]) - continue - - A.power_light = FALSE - A.power_equip = FALSE - A.power_environ = FALSE - A.power_change() - - for(var/obj/machinery/power/apc/C in GLOB.apcs_list) - if(C.cell && is_station_level(C.z)) - var/area/A = C.area - if(GLOB.typecache_powerfailure_safe_areas[A.type]) - continue - - C.cell.charge = 0 - -/proc/power_restore() - - priority_announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') - for(var/obj/machinery/power/apc/C in GLOB.machines) - if(C.cell && is_station_level(C.z)) - C.cell.charge = C.cell.maxcharge - C.failure_timer = 0 - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(!is_station_level(S.z)) - continue - S.charge = S.capacity - S.output_level = S.output_level_max - S.output_attempt = TRUE - S.update_icon() - S.power_change() - for(var/area/A in GLOB.the_station_areas) - if(!A.requires_power || A.always_unpowered) - continue - if(!istype(A, /area/shuttle)) - A.power_light = TRUE - A.power_equip = TRUE - A.power_environ = TRUE - A.power_change() - -/proc/power_restore_quick() - - priority_announce("All SMESs on [station_name()] have been recharged. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(!is_station_level(S.z)) - continue - S.charge = S.capacity - S.output_level = S.output_level_max - S.output_attempt = TRUE - S.update_icon() - S.power_change() - +/proc/power_failure() + priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", 'sound/ai/poweroff.ogg') + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(istype(get_area(S), /area/ai_monitored/turret_protected) || !is_station_level(S.z)) + continue + S.charge = 0 + S.output_level = 0 + S.output_attempt = FALSE + S.update_icon() + S.power_change() + + for(var/area/A in GLOB.the_station_areas) + if(!A.requires_power || A.always_unpowered ) + continue + if(GLOB.typecache_powerfailure_safe_areas[A.type]) + continue + + A.power_light = FALSE + A.power_equip = FALSE + A.power_environ = FALSE + A.power_change() + + for(var/obj/machinery/power/apc/C in GLOB.apcs_list) + if(C.cell && is_station_level(C.z)) + var/area/A = C.area + if(GLOB.typecache_powerfailure_safe_areas[A.type]) + continue + + C.cell.charge = 0 + +/proc/power_restore() + + priority_announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') + for(var/obj/machinery/power/apc/C in GLOB.machines) + if(C.cell && is_station_level(C.z)) + C.cell.charge = C.cell.maxcharge + C.failure_timer = 0 + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(!is_station_level(S.z)) + continue + S.charge = S.capacity + S.output_level = S.output_level_max + S.output_attempt = TRUE + S.update_icon() + S.power_change() + for(var/area/A in GLOB.the_station_areas) + if(!A.requires_power || A.always_unpowered) + continue + if(!istype(A, /area/shuttle)) + A.power_light = TRUE + A.power_equip = TRUE + A.power_environ = TRUE + A.power_change() + +/proc/power_restore_quick() + + priority_announce("All SMESs on [station_name()] have been recharged. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(!is_station_level(S.z)) + continue + S.charge = S.capacity + S.output_level = S.output_level_max + S.output_attempt = TRUE + S.update_icon() + S.power_change() + diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm index 61a91c79bd01..d1b90b50b54f 100644 --- a/code/game/gamemodes/extended/extended.dm +++ b/code/game/gamemodes/extended/extended.dm @@ -1,29 +1,29 @@ -/datum/game_mode/extended - name = "secret extended" - config_tag = "secret_extended" - report_type = "extended" - false_report_weight = 5 - required_players = 0 - - announce_span = "notice" - announce_text = "Just have fun and enjoy the game!" - -/datum/game_mode/extended/pre_setup() - return 1 - -/datum/game_mode/extended/generate_report() - return "The transmission mostly failed to mention your sector. It is possible that there is nothing in the Syndicate that could threaten your station during this shift." - -/datum/game_mode/extended/announced - name = "extended" - config_tag = "extended" - false_report_weight = 0 - -/datum/game_mode/extended/announced/generate_station_goals() - for(var/T in subtypesof(/datum/station_goal)) - var/datum/station_goal/G = new T - station_goals += G - G.on_report() - -/datum/game_mode/extended/announced/send_intercept(report = 0) - priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", 'sound/ai/commandreport.ogg') +/datum/game_mode/extended + name = "secret extended" + config_tag = "secret_extended" + report_type = "extended" + false_report_weight = 5 + required_players = 0 + + announce_span = "notice" + announce_text = "Just have fun and enjoy the game!" + +/datum/game_mode/extended/pre_setup() + return 1 + +/datum/game_mode/extended/generate_report() + return "The transmission mostly failed to mention your sector. It is possible that there is nothing in the Syndicate that could threaten your station during this shift." + +/datum/game_mode/extended/announced + name = "extended" + config_tag = "extended" + false_report_weight = 0 + +/datum/game_mode/extended/announced/generate_station_goals() + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = new T + station_goals += G + G.on_report() + +/datum/game_mode/extended/announced/send_intercept(report = 0) + priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", 'sound/ai/commandreport.ogg') diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 22323ab1b9b9..6f46f34b7111 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -1,592 +1,592 @@ - - -/* - * GAMEMODES (by Rastaf0) - * - * In the new mode system all special roles are fully supported. - * You can have proper wizards/traitors/changelings/cultists during any mode. - * Only two things really depends on gamemode: - * 1. Starting roles, equipment and preparations - * 2. Conditions of finishing the round. - * - */ - - -/datum/game_mode - var/name = "invalid" - var/config_tag = null - var/votable = 1 - var/probability = 0 - var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report? - var/report_type = "invalid" //gamemodes with the same report type will not show up in the command report together. - var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm - var/nuke_off_station = 0 //Used for tracking where the nuke hit - var/round_ends_with_antag_death = 0 //flags the "one verse the station" antags as such - var/list/datum/mind/antag_candidates = list() // List of possible starting antags goes here - var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist - var/list/protected_jobs = list() // Jobs that can't be traitors because - var/list/required_jobs = list() // alternative required job groups eg list(list(cap=1),list(hos=1,sec=2)) translates to one captain OR one hos and two secmans - var/required_players = 0 - var/maximum_players = -1 // -1 is no maximum, positive numbers limit the selection of a mode on overstaffed stations - var/required_enemies = 0 - var/recommended_enemies = 0 - var/antag_flag = null //preferences flag such as BE_WIZARD that need to be turned on for players to be antag - var/mob/living/living_antag_player = null - var/datum/game_mode/replacementmode = null - var/round_converted = 0 //0: round not converted, 1: round going to convert, 2: round converted - var/reroll_friendly //During mode conversion only these are in the running - var/continuous_sanity_checked //Catches some cases where config options could be used to suggest that modes without antagonists should end when all antagonists die - var/enemy_minimum_age = 7 //How many days must players have been playing before they can play this antagonist - - var/announce_span = "warning" //The gamemode's name will be in this span during announcement. - var/announce_text = "This gamemode forgot to set a descriptive text! Uh oh!" //Used to describe a gamemode when it's announced. - - var/const/waittime_l = 600 - var/const/waittime_h = 1800 // started at 1800 - - var/list/datum/station_goal/station_goals = list() - - var/allow_persistence_save = TRUE - - var/gamemode_ready = FALSE //Is the gamemode all set up and ready to start checking for ending conditions. - var/setup_error //What stopepd setting up the mode. - -/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. - if(!report) - report = !CONFIG_GET(flag/no_intercept_report) - addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) - - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles)) - var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay) - if(delay) - delay = (delay SECONDS) - else - delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined. - addtimer(CALLBACK(GLOBAL_PROC, .proc/reopen_roundstart_suicide_roles), delay) - - if(SSdbcore.Connect()) - var/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.len) - message_admins("Convert_roundtype failed due to no valid modes to convert to. Please report this error to the Coders.") - return null - - replacementmode = pickweight(usable_modes) - - switch(SSshuttle.emergency.mode) //Rounds on the verge of ending don't get new antags, they just run out - if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE) - return 1 - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) < initial(SSshuttle.emergencyCallTime)*0.5) - return 1 - - var/matc = CONFIG_GET(number/midround_antag_time_check) - if(world.time >= (matc * 600)) - message_admins("Convert_roundtype failed due to round length. Limit is [matc] minutes.") - return null - - var/list/antag_candidates = list() - - for(var/mob/living/carbon/human/H in living_crew) - if(H.client && H.client.prefs.allow_midround_antag && !is_centcom_level(H.z)) - antag_candidates += H - - if(!antag_candidates) - message_admins("Convert_roundtype failed due to no antag candidates.") - return null - - antag_candidates = shuffle(antag_candidates) - - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - replacementmode.restricted_jobs += replacementmode.protected_jobs - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - replacementmode.restricted_jobs += "Assistant" - - message_admins("The roundtype will be converted. If you have other plans for the station or feel the station is too messed up to inhabit stop the creation of antags or end the round now.") - log_game("Roundtype converted to [replacementmode.name]") - - . = 1 - - sleep(rand(600,1800)) - if(!SSticker.IsRoundInProgress()) - message_admins("Roundtype conversion cancelled, the game appears to have finished!") - round_converted = 0 - return - //somewhere between 1 and 3 minutes from now - if(!CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag]) - round_converted = 0 - return 1 - for(var/mob/living/carbon/human/H in antag_candidates) - if(H.client) - replacementmode.make_antag_chance(H) - replacementmode.gamemode_ready = TRUE //Awful but we're not doing standard setup here. - round_converted = 2 - message_admins("-- IMPORTANT: The roundtype has been converted to [replacementmode.name], antagonists may have been created! --") - - -///Called by the gameSSticker -/datum/game_mode/process() - return 0 - -//For things that do not die easily -/datum/game_mode/proc/are_special_antags_dead() - return TRUE - - -/datum/game_mode/proc/check_finished(force_ending) //to be called by SSticker - if(!SSticker.setup_done || !gamemode_ready) - return FALSE - if(replacementmode && round_converted == 2) - return replacementmode.check_finished() - if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME)) - return TRUE - if(station_was_nuked) - return TRUE - var/list/continuous = CONFIG_GET(keyed_list/continuous) - var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) - if(!round_converted && (!continuous[config_tag] || (continuous[config_tag] && midround_antag[config_tag]))) //Non-continuous or continous with replacement antags - if(!continuous_sanity_checked) //make sure we have antags to be checking in the first place - for(var/mob/Player in GLOB.mob_list) - if(Player.mind) - if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) - continuous_sanity_checked = 1 - return 0 - if(!continuous_sanity_checked) - message_admins("The roundtype ([config_tag]) has no antagonists, continuous round has been defaulted to on and midround_antag has been defaulted to off.") - continuous[config_tag] = TRUE - midround_antag[config_tag] = FALSE - SSshuttle.clearHostileEnvironment(src) - return 0 - - - if(living_antag_player && living_antag_player.mind && isliving(living_antag_player) && living_antag_player.stat != DEAD && !isnewplayer(living_antag_player) &&!isbrain(living_antag_player) && (living_antag_player.mind.special_role || LAZYLEN(living_antag_player.mind.antag_datums))) - return 0 //A resource saver: once we find someone who has to die for all antags to be dead, we can just keep checking them, cycling over everyone only when we lose our mark. - - for(var/mob/Player in GLOB.alive_mob_list) - if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) &&!isbrain(Player) && Player.client && (Player.mind.special_role || LAZYLEN(Player.mind.antag_datums))) //Someone's still antagging but is their antagonist datum important enough to skip mulligan? - for(var/datum/antagonist/antag_types in Player.mind.antag_datums) - if(antag_types.prevent_roundtype_conversion) - living_antag_player = Player //they were an important antag, they're our new mark - return 0 - - if(!are_special_antags_dead()) - return FALSE - - if(!continuous[config_tag] || force_ending) - return 1 - - else - round_converted = convert_roundtype() - if(!round_converted) - if(round_ends_with_antag_death) - return 1 - else - midround_antag[config_tag] = 0 - return 0 - - return 0 - - -/datum/game_mode/proc/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() - var/intercepttext = "Central Command Status Summary
    " - intercepttext += "Central Command has intercepted and partially decoded a Syndicate transmission with vital information regarding their movements. The following report outlines the most \ - likely threats to appear in your sector." - var/list/report_weights = config.mode_false_report_weight.Copy() - report_weights[report_type] = 0 //Prevent the current mode from being falsely selected. - var/list/reports = list() - var/Count = 0 //To compensate for missing correct report - if(prob(65)) // 65% chance the actual mode will appear on the list - reports += config.mode_reports[report_type] - Count++ - for(var/i in Count to rand(3,5)) //Between three and five wrong entries on the list. - var/false_report_type = pickweightAllowZero(report_weights) - report_weights[false_report_type] = 0 //Make it so the same false report won't be selected twice - reports += config.mode_reports[false_report_type] - - reports = shuffle(reports) //Randomize the order, so the real one is at a random position. - - for(var/report in reports) - intercepttext += "
    " - intercepttext += report - - if(station_goals.len) - intercepttext += "
    Special Orders for [station_name()]:" - for(var/datum/station_goal/G in station_goals) - G.on_report() - intercepttext += G.get_report() - - print_command_report(intercepttext, "Central Command Status Summary", announce=FALSE) - priority_announce("A summary has been copied and printed to all communications consoles.", "Enemy communication intercepted. Security level elevated.", 'sound/ai/intercept.ogg') - if(GLOB.security_level < SEC_LEVEL_BLUE) - set_security_level(SEC_LEVEL_BLUE) - - -// This is a frequency selection system. You may imagine it like a raffle where each player can have some number of tickets. The more tickets you have the more likely you are to -// "win". The default is 100 tickets. If no players use any extra tickets (earned with the antagonist rep system) calling this function should be equivalent to calling the normal -// pick() function. By default you may use up to 100 extra tickets per roll, meaning at maximum a player may double their chances compared to a player who has no extra tickets. -// -// The odds of being picked are simply (your_tickets / total_tickets). Suppose you have one player using fifty (50) extra tickets, and one who uses no extra: -// Player A: 150 tickets -// Player B: 100 tickets -// Total: 250 tickets -// -// The odds become: -// Player A: 150 / 250 = 0.6 = 60% -// Player B: 100 / 250 = 0.4 = 40% -/datum/game_mode/proc/antag_pick(list/datum/candidates) - if(!CONFIG_GET(flag/use_antag_rep)) // || candidates.len <= 1) - return pick(candidates) - - // Tickets start at 100 - var/DEFAULT_ANTAG_TICKETS = CONFIG_GET(number/default_antag_tickets) - - // You may use up to 100 extra tickets (double your odds) - var/MAX_TICKETS_PER_ROLL = CONFIG_GET(number/max_tickets_per_roll) - - - var/total_tickets = 0 - - MAX_TICKETS_PER_ROLL += DEFAULT_ANTAG_TICKETS - - var/p_ckey - var/p_rep - - for(var/datum/mind/mind in candidates) - p_ckey = ckey(mind.key) - total_tickets += min(SSpersistence.antag_rep[p_ckey] + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) - - var/antag_select = rand(1,total_tickets) - var/current = 1 - - for(var/datum/mind/mind in candidates) - p_ckey = ckey(mind.key) - p_rep = SSpersistence.antag_rep[p_ckey] - - var/previous = current - var/spend = min(p_rep + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) - current += spend - - if(antag_select >= previous && antag_select <= (current-1)) - SSpersistence.antag_rep_change[p_ckey] = -(spend - DEFAULT_ANTAG_TICKETS) - -// WARNING("AR_DEBUG: Player [mind.key] won spending [spend] tickets from starting value [SSpersistence.antag_rep[p_ckey]]") - - //yogs start -- quiet mode - if(mind.quiet_round) - to_chat(mind.current,"There aren't enough antag volunteers, so your quiet round setting will not be considered!") - //yogs end - return mind - - WARNING("Something has gone terribly wrong. /datum/game_mode/proc/antag_pick failed to select a candidate. Falling back to pick()") - return pick(candidates) - -/datum/game_mode/proc/get_players_for_role(role) //YOGS -- MIRRORED IN THE YOGSTATION FOLDER! DO NOT EAT, SWALLOW, OR SUBMURGE IN ACID - 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(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) - if(age_check(player.client)) //Must be older than the minimum age - candidates += player.mind // Get a list of all the people who want to be the antagonist for this round - - if(restricted_jobs) - for(var/datum/mind/player in candidates) - for(var/job in restricted_jobs) // Remove people who want to be antagonist but have a job already that precludes it - if(player.assigned_role == job) - candidates -= player - - if(candidates.len < recommended_enemies) - for(var/mob/dead/new_player/player in players) - if(player.client && player.ready == PLAYER_READY_TO_PLAY) - if(!(role in player.client.prefs.be_special)) // We don't have enough people who want to be antagonist, make a separate list of people who don't want to be one - if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) - drafted += player.mind - - if(restricted_jobs) - for(var/datum/mind/player in drafted) // Remove people who can't be an antagonist - for(var/job in restricted_jobs) - if(player.assigned_role == job) - drafted -= player - - drafted = shuffle(drafted) // Will hopefully increase randomness, Donkie - - while(candidates.len < recommended_enemies) // Pick randomlly just the number of people we need and add them to our list of candidates - if(drafted.len > 0) - applicant = pick(drafted) - if(applicant) - candidates += applicant - drafted.Remove(applicant) - - else // Not enough scrubs, ABORT ABORT ABORT - break - - return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies - // recommended_enemies if the number of people with that role set to yes is less than recomended_enemies, - // Less if there are not enough valid players in the game entirely to make recommended_enemies. - - - -/datum/game_mode/proc/num_players() - . = 0 - for(var/mob/dead/new_player/P in GLOB.player_list) - if(P.client && P.ready == PLAYER_READY_TO_PLAY) - . ++ - -/proc/reopen_roundstart_suicide_roles() - var/list/valid_positions = list() - valid_positions += GLOB.engineering_positions - valid_positions += GLOB.medical_positions - valid_positions += GLOB.science_positions - valid_positions += GLOB.supply_positions - valid_positions += GLOB.civilian_positions - valid_positions += GLOB.security_positions - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions)) - valid_positions += GLOB.command_positions //add any remaining command positions - else - valid_positions -= GLOB.command_positions //remove all command positions that were added from their respective department positions lists. - - var/list/reopened_jobs = list() - for(var/X in GLOB.suicided_mob_list) - if(!isliving(X)) - continue - var/mob/living/L = X - if(L.job in valid_positions) - var/datum/job/J = SSjob.GetJob(L.job) - if(!J) - continue - J.current_positions = max(J.current_positions-1, 0) - reopened_jobs += L.job - - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report)) - if(reopened_jobs.len) - var/reopened_job_report_positions - for(var/dead_dudes_job in reopened_jobs) - reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]" - - var/suicide_command_report = "Central Command Human Resources Board
    \ - Notice of Personnel Change

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

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

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

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

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

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

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

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

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

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

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

    \ + [reopened_job_report_positions]
    " + + print_command_report(suicide_command_report, "Central Command Personnel Update") + +////////////////////////// +//Reports player logouts// +////////////////////////// +/proc/display_roundstart_logout_report() + var/list/msg = list("Roundstart logout report\n\n") + for(var/i in GLOB.mob_living_list) + var/mob/living/L = i + var/mob/living/carbon/C = L + if (istype(C) && !C.last_mind) + continue // never had a client + + if(L.ckey && !GLOB.directory[L.ckey]) + msg += "[L.name] ([L.key]), the [L.job] (Disconnected)\n" + + + if(L.ckey && L.client) + var/failed = FALSE + if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something) + msg += "[L.name] ([L.key]), the [L.job] (Connected, Inactive)\n" + failed = TRUE //AFK client + if(!failed && L.stat) + if(L.suiciding) //Suicider + msg += "[L.name] ([L.key]), the [L.job] (Suicide)\n" + failed = TRUE //Disconnected client + if(!failed && L.stat == UNCONSCIOUS) + msg += "[L.name] ([L.key]), the [L.job] (Dying)\n" + failed = TRUE //Unconscious + if(!failed && L.stat == DEAD) + msg += "[L.name] ([L.key]), the [L.job] (Dead)\n" + failed = TRUE //Dead + + var/p_ckey = L.client.ckey +// WARNING("AR_DEBUG: [p_ckey]: failed - [failed], antag_rep_change: [SSpersistence.antag_rep_change[p_ckey]]") + + // people who died or left should not gain any reputation + // people who rolled antagonist still lose it + if(failed && SSpersistence.antag_rep_change[p_ckey] > 0) +// WARNING("AR_DEBUG: Zeroed [p_ckey]'s antag_rep_change") + SSpersistence.antag_rep_change[p_ckey] = 0 + + continue //Happy connected client + for(var/mob/dead/observer/D in GLOB.dead_mob_list) + if(D.mind && D.mind.current == L) + if(L.stat == DEAD) + if(L.suiciding) //Suicider + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Suicide)\n" + continue //Disconnected client + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" + continue //Dead mob, ghost abandoned + else + if(D.can_reenter_corpse) + continue //Adminghost, or cult/wizard ghost + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Ghosted)\n" + continue //Ghosted while alive + + + for (var/C in GLOB.admins) + to_chat(C, msg.Join()) + +//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 +/datum/game_mode/proc/age_check(client/C) + if(get_remaining_days(C) == 0) + return 1 //Available in 0 days = available right now = player is old enough to play. + return 0 + + +/datum/game_mode/proc/get_remaining_days(client/C) + if(!C) + return 0 + if(!CONFIG_GET(flag/use_age_restriction_for_jobs)) + return 0 + if(!isnum(C.player_age)) + return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced + if(!isnum(enemy_minimum_age)) + return 0 + + return max(0, enemy_minimum_age - C.player_age) + +/datum/game_mode/proc/remove_antag_for_borging(datum/mind/newborgie) + SSticker.mode.remove_cultist(newborgie, 0, 0) + var/datum/antagonist/rev/rev = newborgie.has_antag_datum(/datum/antagonist/rev) + if(rev) + rev.remove_revolutionary(TRUE) + +/datum/game_mode/proc/generate_station_goals() + var/list/possible = list() + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = T + if(config_tag in initial(G.gamemode_blacklist)) + continue + possible += T + var/goal_weights = 0 + while(possible.len && goal_weights < STATION_GOAL_BUDGET) + var/datum/station_goal/picked = pick_n_take(possible) + goal_weights += initial(picked.weight) + station_goals += new picked + + +/datum/game_mode/proc/generate_report() //Generates a small text blurb for the gamemode in centcom report + return "Gamemode report for [name] not set. Contact a coder." + +//By default nuke just ends the round +/datum/game_mode/proc/OnNukeExplosion(off_station) + nuke_off_station = off_station + if(off_station < 2) + station_was_nuked = TRUE //Will end the round on next check. + +//Additional report section in roundend report +/datum/game_mode/proc/special_report() + return + +//Set result and news report here +/datum/game_mode/proc/set_round_result() + SSticker.mode_result = "undefined" + if(station_was_nuked) + SSticker.news_report = STATION_DESTROYED_NUKE + if(EMERGENCY_ESCAPED_OR_ENDGAMED) + SSticker.news_report = STATION_EVACUATED + if(SSshuttle.emergency.is_hijacked()) + SSticker.news_report = SHUTTLE_HIJACK diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm index 8bcbb3669b54..dc6eee0997fb 100644 --- a/code/game/gamemodes/meteor/meteor.dm +++ b/code/game/gamemodes/meteor/meteor.dm @@ -1,60 +1,60 @@ -/datum/game_mode/meteor - name = "meteor" - config_tag = "meteor" - report_type = "meteor" - false_report_weight = 1 - var/meteordelay = 2000 - var/nometeors = 0 - var/rampupdelta = 5 - required_players = 0 - - announce_span = "danger" - announce_text = "A major meteor shower is bombarding the station! The crew needs to evacuate or survive the onslaught." - - -/datum/game_mode/meteor/process() - if(nometeors || meteordelay > world.time - SSticker.round_start_time) - return - - var/list/wavetype = GLOB.meteors_normal - var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 - - - if (prob(meteorminutes)) - wavetype = GLOB.meteors_threatening - - if (prob(meteorminutes/2)) - wavetype = GLOB.meteors_catastrophic - - var/ramp_up_final = CLAMP(round(meteorminutes/rampupdelta), 1, 10) - - spawn_meteors(ramp_up_final, wavetype) - - -/datum/game_mode/meteor/special_report() - var/survivors = 0 - var/list/survivor_list = list() - - for(var/mob/living/player in GLOB.player_list) - if(player.stat != DEAD) - ++survivors - - if(player.onCentCom()) - survivor_list += "[player.real_name] escaped to the safety of CentCom." - else if(player.onSyndieBase()) - survivor_list += "[player.real_name] escaped to the (relative) safety of Syndicate Space." - else - survivor_list += "[player.real_name] survived but is stranded without any hope of rescue." - - if(survivors) - return "
    The following survived the meteor storm:
    [survivor_list.Join("
    ")]
    " - else - return "
    Nobody survived the meteor storm!
    " - -/datum/game_mode/meteor/set_round_result() - ..() - SSticker.mode_result = "end - evacuation" - -/datum/game_mode/meteor/generate_report() - return "[pick("Asteroids have", "Meteors have", "Large rocks have", "Stellar minerals have", "Space hail has", "Debris has")] been detected near your station, and a collision is possible, \ - though unlikely. Be prepared for largescale impacts and destruction. Please note that the debris will prevent the escape shuttle from arriving quickly." +/datum/game_mode/meteor + name = "meteor" + config_tag = "meteor" + report_type = "meteor" + false_report_weight = 1 + var/meteordelay = 2000 + var/nometeors = 0 + var/rampupdelta = 5 + required_players = 0 + + announce_span = "danger" + announce_text = "A major meteor shower is bombarding the station! The crew needs to evacuate or survive the onslaught." + + +/datum/game_mode/meteor/process() + if(nometeors || meteordelay > world.time - SSticker.round_start_time) + return + + var/list/wavetype = GLOB.meteors_normal + var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 + + + if (prob(meteorminutes)) + wavetype = GLOB.meteors_threatening + + if (prob(meteorminutes/2)) + wavetype = GLOB.meteors_catastrophic + + var/ramp_up_final = CLAMP(round(meteorminutes/rampupdelta), 1, 10) + + spawn_meteors(ramp_up_final, wavetype) + + +/datum/game_mode/meteor/special_report() + var/survivors = 0 + var/list/survivor_list = list() + + for(var/mob/living/player in GLOB.player_list) + if(player.stat != DEAD) + ++survivors + + if(player.onCentCom()) + survivor_list += "[player.real_name] escaped to the safety of CentCom." + else if(player.onSyndieBase()) + survivor_list += "[player.real_name] escaped to the (relative) safety of Syndicate Space." + else + survivor_list += "[player.real_name] survived but is stranded without any hope of rescue." + + if(survivors) + return "
    The following survived the meteor storm:
    [survivor_list.Join("
    ")]
    " + else + return "
    Nobody survived the meteor storm!
    " + +/datum/game_mode/meteor/set_round_result() + ..() + SSticker.mode_result = "end - evacuation" + +/datum/game_mode/meteor/generate_report() + return "[pick("Asteroids have", "Meteors have", "Large rocks have", "Stellar minerals have", "Space hail has", "Debris has")] been detected near your station, and a collision is possible, \ + though unlikely. Be prepared for largescale impacts and destruction. Please note that the debris will prevent the escape shuttle from arriving quickly." diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index b5383d88b4ff..628ddb8b2d8b 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") - -/datum/objective_item/steal/capmedal - name = "the medal of captaincy." - targetitem = /obj/item/clothing/accessory/medal/gold/captain - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/hypo - name = "the hypospray." - targetitem = /obj/item/reagent_containers/hypospray/CMO - difficulty = 5 - excludefromjob = list("Chief Medical Officer") - -/datum/objective_item/steal/nukedisc - name = "the nuclear authentication disk." - targetitem = /obj/item/disk/nuclear - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/nukedisc/check_special_completion(obj/item/disk/nuclear/N) - return !N.fake - -/datum/objective_item/steal/reflector - name = "a reflector vest." - targetitem = /obj/item/clothing/suit/armor/laserproof - difficulty = 3 - excludefromjob = list("Head of Security", "Warden") - -/datum/objective_item/steal/reactive - name = "the reactive teleport armor." - targetitem = /obj/item/clothing/suit/armor/reactive/teleport - difficulty = 5 - excludefromjob = list("Research Director") - -/datum/objective_item/steal/documents - name = "any set of secret documents of any organization." - targetitem = /obj/item/documents //Any set of secret documents. Doesn't have to be NT's - difficulty = 5 - -/datum/objective_item/steal/nuke_core - name = "the heavily radioactive plutonium core from the onboard self-destruct. Take care to wear the proper safety equipment when extracting the core!" - targetitem = /obj/item/nuke_core - difficulty = 15 - -/datum/objective_item/steal/nuke_core/New() - special_equipment += /obj/item/storage/box/syndie_kit/nuke - ..() - -/datum/objective_item/steal/supermatter - name = "a sliver of a supermatter crystal. Be sure to use the proper safety equipment when extracting the sliver!" - targetitem = /obj/item/nuke_core/supermatter_sliver - difficulty = 15 - -/datum/objective_item/steal/supermatter/New() - special_equipment += /obj/item/storage/box/syndie_kit/supermatter - ..() - -/datum/objective_item/steal/supermatter/TargetExists() - return GLOB.main_supermatter_engine != null - -//Items with special checks! -/datum/objective_item/steal/plasma - name = "28 moles of plasma (full tank)." - targetitem = /obj/item/tank - difficulty = 3 - excludefromjob = list("Chief Engineer","Research Director","Station Engineer","Scientist","Atmospheric Technician") - -/datum/objective_item/steal/plasma/check_special_completion(obj/item/tank/T) - var/target_amount = text2num(name) - var/found_amount = 0 - found_amount += T.air_contents.gases[/datum/gas/plasma] ? T.air_contents.gases[/datum/gas/plasma][MOLES] : 0 - return found_amount>=target_amount - - -/datum/objective_item/steal/functionalai - name = "a functional AI." - targetitem = /obj/item/aicard - difficulty = 20 //beyond the impossible - -/datum/objective_item/steal/functionalai/check_special_completion(obj/item/aicard/C) - for(var/mob/living/silicon/ai/A in C) - if(isAI(A) && A.stat != DEAD) //See if any AI's are alive inside that card. - return 1 - return 0 - -/datum/objective_item/steal/blueprints - name = "the station blueprints." - targetitem = /obj/item/areaeditor/blueprints - difficulty = 10 - excludefromjob = list("Chief Engineer") - altitems = list(/obj/item/photo) - -/datum/objective_item/steal/blueprints/check_special_completion(obj/item/I) - if(istype(I, /obj/item/areaeditor/blueprints)) - return TRUE - if(istype(I, /obj/item/photo)) - var/obj/item/photo/P = I - if(P.picture.has_blueprints) //if the blueprints are in frame - return TRUE - return FALSE - -/datum/objective_item/steal/slime - name = "an unused sample of slime extract." - targetitem = /obj/item/slime_extract - difficulty = 3 - excludefromjob = list("Research Director","Scientist") - -/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E) - if(E.Uses > 0) - return 1 - return 0 - -//Unique Objectives -/datum/objective_item/unique/docs_red - name = "the \"Red\" secret documents." - targetitem = /obj/item/documents/syndicate/red - difficulty = 10 - -/datum/objective_item/unique/docs_blue - name = "the \"Blue\" secret documents." - targetitem = /obj/item/documents/syndicate/blue - difficulty = 10 - -/datum/objective_item/special/New() - ..() - if(TargetExists()) - GLOB.possible_items_special += src - else - qdel(src) - -/datum/objective_item/special/Destroy() - GLOB.possible_items_special -= src - return ..() - -//Old ninja objectives. -/datum/objective_item/special/pinpointer/nuke - name = "the captain's pinpointer." - targetitem = /obj/item/pinpointer - difficulty = 10 - -/datum/objective_item/special/aegun - name = "an advanced energy gun." - targetitem = /obj/item/gun/energy/e_gun/nuclear - difficulty = 10 - -/datum/objective_item/special/ddrill - name = "a diamond drill." - targetitem = /obj/item/pickaxe/drill/diamonddrill - difficulty = 10 - -/datum/objective_item/special/boh - name = "a bag of holding." - targetitem = /obj/item/storage/backpack/holding - difficulty = 10 - -/datum/objective_item/special/hypercell - name = "a hyper-capacity power cell." - targetitem = /obj/item/stock_parts/cell/hyper - difficulty = 5 - -/datum/objective_item/special/laserpointer - name = "a laser pointer." - targetitem = /obj/item/laser_pointer - difficulty = 5 - -/datum/objective_item/special/corgimeat - name = "a piece of corgi meat." - targetitem = /obj/item/reagent_containers/food/snacks/meat/slab/corgi - difficulty = 5 - -/datum/objective_item/stack/New() - ..() - if(TargetExists()) - GLOB.possible_items_special += src - else - qdel(src) - -/datum/objective_item/stack/Destroy() - GLOB.possible_items_special -= src - return ..() - -//Stack objectives get their own subtype -/datum/objective_item/stack - name = "5 cardboard." - targetitem = /obj/item/stack/sheet/cardboard - difficulty = 9001 - -/datum/objective_item/stack/check_special_completion(obj/item/stack/S) - var/target_amount = text2num(name) - var/found_amount = 0 - - if(istype(S, targetitem)) - found_amount = S.amount - return found_amount>=target_amount - -/datum/objective_item/stack/diamond - name = "10 diamonds." - targetitem = /obj/item/stack/sheet/mineral/diamond - difficulty = 10 - -/datum/objective_item/stack/gold - name = "50 gold bars." - targetitem = /obj/item/stack/sheet/mineral/gold - difficulty = 15 - -/datum/objective_item/stack/uranium - name = "25 refined uranium bars." - targetitem = /obj/item/stack/sheet/mineral/uranium - difficulty = 10 +//Contains the target item datums for Steal objectives. + +/datum/objective_item + var/name = "A silly bike horn! Honk!" + var/targetitem = /obj/item/bikehorn //typepath of the objective item + var/difficulty = 9001 //vaguely how hard it is to do this objective + var/list/excludefromjob = list() //If you don't want a job to get a certain objective (no captain stealing his own medal, etcetc) + var/list/altitems = list() //Items which can serve as an alternative to the objective (darn you blueprints) + var/list/special_equipment = list() + +/datum/objective_item/proc/check_special_completion() //for objectives with special checks (is that slime extract unused? does that intellicard have an ai in it? etcetc) + return 1 + +/datum/objective_item/proc/TargetExists() + return TRUE + +/datum/objective_item/steal/New() + ..() + if(TargetExists()) + GLOB.possible_items += src + else + qdel(src) + +/datum/objective_item/steal/Destroy() + GLOB.possible_items -= src + return ..() + +/datum/objective_item/steal/caplaser + name = "the captain's antique laser gun." + targetitem = /obj/item/gun/energy/laser/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/hoslaser + name = "the head of security's personal laser gun." + targetitem = /obj/item/gun/energy/e_gun/hos + difficulty = 10 + excludefromjob = list("Head Of Security") + +/datum/objective_item/steal/handtele + name = "a hand teleporter." + targetitem = /obj/item/hand_tele + difficulty = 5 + excludefromjob = list("Captain", "Research Director") + +/datum/objective_item/steal/jetpack + name = "the Captain's jetpack." + targetitem = /obj/item/tank/jetpack/oxygen/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/magboots + name = "the chief engineer's advanced magnetic boots." + targetitem = /obj/item/clothing/shoes/magboots/advance + difficulty = 5 + excludefromjob = list("Chief Engineer") + +/datum/objective_item/steal/capmedal + name = "the medal of captaincy." + targetitem = /obj/item/clothing/accessory/medal/gold/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/hypo + name = "the hypospray." + targetitem = /obj/item/reagent_containers/hypospray/CMO + difficulty = 5 + excludefromjob = list("Chief Medical Officer") + +/datum/objective_item/steal/nukedisc + name = "the nuclear authentication disk." + targetitem = /obj/item/disk/nuclear + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/nukedisc/check_special_completion(obj/item/disk/nuclear/N) + return !N.fake + +/datum/objective_item/steal/reflector + name = "a reflector vest." + targetitem = /obj/item/clothing/suit/armor/laserproof + difficulty = 3 + excludefromjob = list("Head of Security", "Warden") + +/datum/objective_item/steal/reactive + name = "the reactive teleport armor." + targetitem = /obj/item/clothing/suit/armor/reactive/teleport + difficulty = 5 + excludefromjob = list("Research Director") + +/datum/objective_item/steal/documents + name = "any set of secret documents of any organization." + targetitem = /obj/item/documents //Any set of secret documents. Doesn't have to be NT's + difficulty = 5 + +/datum/objective_item/steal/nuke_core + name = "the heavily radioactive plutonium core from the onboard self-destruct. Take care to wear the proper safety equipment when extracting the core!" + targetitem = /obj/item/nuke_core + difficulty = 15 + +/datum/objective_item/steal/nuke_core/New() + special_equipment += /obj/item/storage/box/syndie_kit/nuke + ..() + +/datum/objective_item/steal/supermatter + name = "a sliver of a supermatter crystal. Be sure to use the proper safety equipment when extracting the sliver!" + targetitem = /obj/item/nuke_core/supermatter_sliver + difficulty = 15 + +/datum/objective_item/steal/supermatter/New() + special_equipment += /obj/item/storage/box/syndie_kit/supermatter + ..() + +/datum/objective_item/steal/supermatter/TargetExists() + return GLOB.main_supermatter_engine != null + +//Items with special checks! +/datum/objective_item/steal/plasma + name = "28 moles of plasma (full tank)." + targetitem = /obj/item/tank + difficulty = 3 + excludefromjob = list("Chief Engineer","Research Director","Station Engineer","Scientist","Atmospheric Technician") + +/datum/objective_item/steal/plasma/check_special_completion(obj/item/tank/T) + var/target_amount = text2num(name) + var/found_amount = 0 + found_amount += T.air_contents.gases[/datum/gas/plasma] ? T.air_contents.gases[/datum/gas/plasma][MOLES] : 0 + return found_amount>=target_amount + + +/datum/objective_item/steal/functionalai + name = "a functional AI." + targetitem = /obj/item/aicard + difficulty = 20 //beyond the impossible + +/datum/objective_item/steal/functionalai/check_special_completion(obj/item/aicard/C) + for(var/mob/living/silicon/ai/A in C) + if(isAI(A) && A.stat != DEAD) //See if any AI's are alive inside that card. + return 1 + return 0 + +/datum/objective_item/steal/blueprints + name = "the station blueprints." + targetitem = /obj/item/areaeditor/blueprints + difficulty = 10 + excludefromjob = list("Chief Engineer") + altitems = list(/obj/item/photo) + +/datum/objective_item/steal/blueprints/check_special_completion(obj/item/I) + if(istype(I, /obj/item/areaeditor/blueprints)) + return TRUE + if(istype(I, /obj/item/photo)) + var/obj/item/photo/P = I + if(P.picture.has_blueprints) //if the blueprints are in frame + return TRUE + return FALSE + +/datum/objective_item/steal/slime + name = "an unused sample of slime extract." + targetitem = /obj/item/slime_extract + difficulty = 3 + excludefromjob = list("Research Director","Scientist") + +/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E) + if(E.Uses > 0) + return 1 + return 0 + +//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 6b988fd07721..ddb622ab08c5 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 64aaeebacd97..c5946719d639 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/global/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/global/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/global/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/global/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 a41a6a185edc..8d4846d579e3 100644 --- a/code/game/gamemodes/sandbox/sandbox.dm +++ b/code/game/gamemodes/sandbox/sandbox.dm @@ -1,22 +1,22 @@ -/datum/game_mode/sandbox - name = "sandbox" - config_tag = "sandbox" - report_type = "sandbox" - required_players = 0 - - announce_span = "info" - announce_text = "Build your own station... or just shoot each other!" - - allow_persistence_save = FALSE - -/datum/game_mode/sandbox/pre_setup() - for(var/mob/M in GLOB.player_list) - M.CanBuild() - return 1 - -/datum/game_mode/sandbox/post_setup() - ..() - SSshuttle.registerHostileEnvironment(src) - -/datum/game_mode/sandbox/generate_report() - return "Sensors indicate that crewmembers have been all given psychic powers from which they can manifest various objects.

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

    This can only end poorly." diff --git a/code/game/gamemodes/traitor/double_agents.dm b/code/game/gamemodes/traitor/double_agents.dm index 9e8b9985cdbf..7e3db6e86c4d 100644 --- a/code/game/gamemodes/traitor/double_agents.dm +++ b/code/game/gamemodes/traitor/double_agents.dm @@ -1,83 +1,83 @@ -/datum/game_mode - var/list/target_list = list() - var/list/late_joining_list = list() - -/datum/game_mode/traitor/internal_affairs - name = "Internal Affairs" - config_tag = "internal_affairs" - report_type = "internal_affairs" - false_report_weight = 10 - required_players = 25 - required_enemies = 5 - recommended_enemies = 8 - reroll_friendly = 0 - traitor_name = "Nanotrasen Internal Affairs Agent" - antag_flag = ROLE_INTERNAL_AFFAIRS - - traitors_possible = 10 //hard limit on traitors if scaling is turned off - num_modifier = 4 // Four additional traitors - antag_datum = /datum/antagonist/traitor/internal_affairs - - announce_text = "There are Nanotrasen Internal Affairs Agents trying to kill each other!\n\ - IAA: Eliminate your targets and protect yourself!\n\ - Crew: Stop the IAA agents before they can cause too much mayhem." - - - -/datum/game_mode/traitor/internal_affairs/post_setup() - var/i = 0 - for(var/datum/mind/traitor in pre_traitors) - i++ - if(i + 1 > pre_traitors.len) - i = 0 - target_list[traitor] = pre_traitors[i+1] - ..() - - -/datum/game_mode/traitor/internal_affairs/add_latejoin_traitor(datum/mind/character) - - check_potential_agents() - - // As soon as we get 3 or 4 extra latejoin traitors, make them traitors and kill each other. - if(late_joining_list.len >= rand(3, 4)) - // True randomness - shuffle_inplace(late_joining_list) - // Reset the target_list, it'll be used again in force_traitor_objectives - target_list = list() - - // Basically setting the target_list for who is killing who - var/i = 0 - for(var/datum/mind/traitor in late_joining_list) - i++ - if(i + 1 > late_joining_list.len) - i = 0 - target_list[traitor] = late_joining_list[i + 1] - traitor.special_role = traitor_name - - // Now, give them their targets - for(var/datum/mind/traitor in target_list) - ..(traitor) - - late_joining_list = list() - else - late_joining_list += character - return - -/datum/game_mode/traitor/internal_affairs/proc/check_potential_agents() - - for(var/M in late_joining_list) - if(istype(M, /datum/mind)) - var/datum/mind/agent_mind = M - if(ishuman(agent_mind.current)) - var/mob/living/carbon/human/H = agent_mind.current - if(H.stat != DEAD) - if(H.client) - continue // It all checks out. - - // If any check fails, remove them from our list - late_joining_list -= M - - -/datum/game_mode/traitor/internal_affairs/generate_report() - return "Nanotrasen denies any accusations of placing internal affairs agents onboard your station to eliminate inconvenient employees. Any further accusations against CentCom for such \ - actions will be met with a conversation with an official internal affairs agent." +/datum/game_mode + var/list/target_list = list() + var/list/late_joining_list = list() + +/datum/game_mode/traitor/internal_affairs + name = "Internal Affairs" + config_tag = "internal_affairs" + report_type = "internal_affairs" + false_report_weight = 10 + required_players = 25 + required_enemies = 5 + recommended_enemies = 8 + reroll_friendly = 0 + traitor_name = "Nanotrasen Internal Affairs Agent" + antag_flag = ROLE_INTERNAL_AFFAIRS + + traitors_possible = 10 //hard limit on traitors if scaling is turned off + num_modifier = 4 // Four additional traitors + antag_datum = /datum/antagonist/traitor/internal_affairs + + announce_text = "There are Nanotrasen Internal Affairs Agents trying to kill each other!\n\ + IAA: Eliminate your targets and protect yourself!\n\ + Crew: Stop the IAA agents before they can cause too much mayhem." + + + +/datum/game_mode/traitor/internal_affairs/post_setup() + var/i = 0 + for(var/datum/mind/traitor in pre_traitors) + i++ + if(i + 1 > pre_traitors.len) + i = 0 + target_list[traitor] = pre_traitors[i+1] + ..() + + +/datum/game_mode/traitor/internal_affairs/add_latejoin_traitor(datum/mind/character) + + check_potential_agents() + + // As soon as we get 3 or 4 extra latejoin traitors, make them traitors and kill each other. + if(late_joining_list.len >= rand(3, 4)) + // True randomness + shuffle_inplace(late_joining_list) + // Reset the target_list, it'll be used again in force_traitor_objectives + target_list = list() + + // Basically setting the target_list for who is killing who + var/i = 0 + for(var/datum/mind/traitor in late_joining_list) + i++ + if(i + 1 > late_joining_list.len) + i = 0 + target_list[traitor] = late_joining_list[i + 1] + traitor.special_role = traitor_name + + // Now, give them their targets + for(var/datum/mind/traitor in target_list) + ..(traitor) + + late_joining_list = list() + else + late_joining_list += character + return + +/datum/game_mode/traitor/internal_affairs/proc/check_potential_agents() + + for(var/M in late_joining_list) + if(istype(M, /datum/mind)) + var/datum/mind/agent_mind = M + if(ishuman(agent_mind.current)) + var/mob/living/carbon/human/H = agent_mind.current + if(H.stat != DEAD) + if(H.client) + continue // It all checks out. + + // If any check fails, remove them from our list + late_joining_list -= M + + +/datum/game_mode/traitor/internal_affairs/generate_report() + return "Nanotrasen denies any accusations of placing internal affairs agents onboard your station to eliminate inconvenient employees. Any further accusations against CentCom for such \ + actions will be met with a conversation with an official internal affairs agent." diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index 34ceb718d2fe..2d1ce67a300a 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -1,100 +1,100 @@ -/datum/game_mode - var/traitor_name = "traitor" - var/list/datum/mind/traitors = list() - - var/datum/mind/exchange_red - var/datum/mind/exchange_blue - -/datum/game_mode/traitor - name = "traitor" - config_tag = "traitor" - report_type = "traitor" - antag_flag = ROLE_TRAITOR - false_report_weight = 20 //Reports of traitors are pretty common. - restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel") //YOGS - added the hop - 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]") | yogs - redundant - 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(!is_banned_from(character.ckey, list(ROLE_TRAITOR, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - add_latejoin_traitor(character.mind) - -/datum/game_mode/traitor/proc/add_latejoin_traitor(datum/mind/character) - var/datum/antagonist/traitor/new_antag = new antag_datum() - character.add_antag_datum(new_antag) - -/datum/game_mode/traitor/generate_report() - return "Although more specific threats are commonplace, you should always remain vigilant for Syndicate agents aboard your station. Syndicate communications have implied that many \ - Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions." +/datum/game_mode + var/traitor_name = "traitor" + var/list/datum/mind/traitors = list() + + var/datum/mind/exchange_red + var/datum/mind/exchange_blue + +/datum/game_mode/traitor + name = "traitor" + config_tag = "traitor" + report_type = "traitor" + antag_flag = ROLE_TRAITOR + false_report_weight = 20 //Reports of traitors are pretty common. + restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel") //YOGS - added the hop + 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]") | yogs - redundant + 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(!is_banned_from(character.ckey, list(ROLE_TRAITOR, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + add_latejoin_traitor(character.mind) + +/datum/game_mode/traitor/proc/add_latejoin_traitor(datum/mind/character) + var/datum/antagonist/traitor/new_antag = new antag_datum() + character.add_antag_datum(new_antag) + +/datum/game_mode/traitor/generate_report() + return "Although more specific threats are commonplace, you should always remain vigilant for Syndicate agents aboard your station. Syndicate communications have implied that many \ + Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions." diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index 7322e3f6ee28..c8a1bfa03378 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -1,74 +1,74 @@ -/datum/game_mode - var/list/datum/mind/wizards = list() - var/list/datum/mind/apprentices = list() - -/datum/game_mode/wizard - name = "wizard" - config_tag = "wizard" - report_type = "wizard" - antag_flag = ROLE_WIZARD - false_report_weight = 10 - required_players = 20 - required_enemies = 1 - recommended_enemies = 1 - enemy_minimum_age = 14 - round_ends_with_antag_death = 1 - announce_span = "danger" - announce_text = "There is a space wizard attacking the station!\n\ - Wizard: Accomplish your objectives and cause mayhem on the station.\n\ - Crew: Eliminate the wizard before they can succeed!" - var/finished = 0 - -/datum/game_mode/wizard/pre_setup() - var/datum/mind/wizard = antag_pick(antag_candidates) - wizards += wizard - wizard.assigned_role = ROLE_WIZARD - wizard.special_role = ROLE_WIZARD - //log_game("[key_name(wizard)] has been selected as a Wizard") //TODO: Move these to base antag datum | yogs - redundant - 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" + report_type = "wizard" + antag_flag = ROLE_WIZARD + false_report_weight = 10 + required_players = 20 + required_enemies = 1 + recommended_enemies = 1 + enemy_minimum_age = 14 + round_ends_with_antag_death = 1 + announce_span = "danger" + announce_text = "There is a space wizard attacking the station!\n\ + Wizard: Accomplish your objectives and cause mayhem on the station.\n\ + Crew: Eliminate the wizard before they can succeed!" + var/finished = 0 + +/datum/game_mode/wizard/pre_setup() + var/datum/mind/wizard = antag_pick(antag_candidates) + wizards += wizard + wizard.assigned_role = ROLE_WIZARD + wizard.special_role = ROLE_WIZARD + //log_game("[key_name(wizard)] has been selected as a Wizard") //TODO: Move these to base antag datum | yogs - redundant + 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 3d0931d534b9..8d849c8c145e 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 - updateicon() - -// update the icon_state -/obj/machinery/bluespace_beacon/proc/updateicon() - 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) - - updateicon() +/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 + updateicon() + +// update the icon_state +/obj/machinery/bluespace_beacon/proc/updateicon() + 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) + + updateicon() diff --git a/code/game/machinery/PDApainter.dm b/code/game/machinery/PDApainter.dm index c230c4af6f19..9325b7d1df36 100644 --- a/code/game/machinery/PDApainter.dm +++ b/code/game/machinery/PDApainter.dm @@ -1,149 +1,149 @@ -/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) - - for(var/P in typesof(/obj/item/pda) - blocked) - var/obj/item/pda/D = new P - - //D.name = "PDA Style [colorlist.len+1]" //Gotta set the name, otherwise it all comes up as "PDA" - D.name = D.icon_state //PDAs don't have unique names, but using the sprite names works. - - src.colorlist += D - -/obj/machinery/pdapainter/Destroy() - QDEL_NULL(storedpda) - return ..() - -/obj/machinery/pdapainter/on_deconstruction() - if(storedpda) - storedpda.forceMove(loc) - storedpda = null - -/obj/machinery/pdapainter/contents_explosion(severity, target) - if(storedpda) - storedpda.ex_act(severity, target) - -/obj/machinery/pdapainter/handle_atom_del(atom/A) - if(A == storedpda) - storedpda = null - update_icon() - -/obj/machinery/pdapainter/attackby(obj/item/O, mob/user, params) - if(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(O.tool_behaviour == TOOL_WELDER && 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) - var/obj/item/pda/P - P = input(user, "Select your color!", "PDA Painting") as null|anything in colorlist - if(!P) - return - if(!in_range(src, user)) - return - if(!storedpda)//is the pda still there? - return - storedpda.icon_state = P.icon_state - storedpda.desc = P.desc - storedpda.light_color = P.light_color //yogs start - pda flashlight colouring - storedpda.icon = P.icon // This is to prevent yogs PDAs from being blank when switched to normal PDAs, and the other way around. - storedpda.update_light() //yogs end - pda flashlight colouring - ejectpda() - - else - to_chat(user, "[src] is empty.") - - -/obj/machinery/pdapainter/verb/ejectpda() - set name = "Eject PDA" - set category = "Object" - set src in oview(1) - - if(usr.stat || usr.restrained()) - return - - if(storedpda) - storedpda.forceMove(drop_location()) - storedpda = null - update_icon() - else - to_chat(usr, "[src] is empty.") - - -/obj/machinery/pdapainter/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) + + for(var/P in typesof(/obj/item/pda) - blocked) + var/obj/item/pda/D = new P + + //D.name = "PDA Style [colorlist.len+1]" //Gotta set the name, otherwise it all comes up as "PDA" + D.name = D.icon_state //PDAs don't have unique names, but using the sprite names works. + + src.colorlist += D + +/obj/machinery/pdapainter/Destroy() + QDEL_NULL(storedpda) + return ..() + +/obj/machinery/pdapainter/on_deconstruction() + if(storedpda) + storedpda.forceMove(loc) + storedpda = null + +/obj/machinery/pdapainter/contents_explosion(severity, target) + if(storedpda) + storedpda.ex_act(severity, target) + +/obj/machinery/pdapainter/handle_atom_del(atom/A) + if(A == storedpda) + storedpda = null + update_icon() + +/obj/machinery/pdapainter/attackby(obj/item/O, mob/user, params) + if(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(O.tool_behaviour == TOOL_WELDER && 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) + var/obj/item/pda/P + P = input(user, "Select your color!", "PDA Painting") as null|anything in colorlist + if(!P) + return + if(!in_range(src, user)) + return + if(!storedpda)//is the pda still there? + return + storedpda.icon_state = P.icon_state + storedpda.desc = P.desc + storedpda.light_color = P.light_color //yogs start - pda flashlight colouring + storedpda.icon = P.icon // This is to prevent yogs PDAs from being blank when switched to normal PDAs, and the other way around. + storedpda.update_light() //yogs end - pda flashlight colouring + ejectpda() + + else + to_chat(user, "[src] is empty.") + + +/obj/machinery/pdapainter/verb/ejectpda() + set name = "Eject PDA" + set category = "Object" + set src in oview(1) + + if(usr.stat || usr.restrained()) + return + + if(storedpda) + storedpda.forceMove(drop_location()) + storedpda = null + update_icon() + else + to_chat(usr, "[src] is empty.") + + +/obj/machinery/pdapainter/power_change() + ..() + update_icon() diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm index 1703ce57d1ed..4935c9d4d308 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 9f51d2e45716..5970e0577bac 100644 --- a/code/game/machinery/airlock_control.dm +++ b/code/game/machinery/airlock_control.dm @@ -1,164 +1,164 @@ -#define AIRLOCK_CONTROL_RANGE 5 - -// This code allows for airlocks to be controlled externally by setting an id_tag and comm frequency (disables ID access) -/obj/machinery/door/airlock - var/id_tag - var/frequency - var/datum/radio_frequency/radio_connection - - -/obj/machinery/door/airlock/receive_signal(datum/signal/signal) - if(!signal) - return - - if(id_tag != signal.data["tag"] || !signal.data["command"]) - return - - switch(signal.data["command"]) - if("open") - open(1) - - if("close") - close(1) - - if("unlock") - locked = FALSE - update_icon() - - if("lock") - locked = TRUE - update_icon() - - if("secure_open") - locked = FALSE - update_icon() - - sleep(2) - open(1) - - locked = TRUE - update_icon() - - if("secure_close") - locked = FALSE - close(1) - - locked = TRUE - sleep(2) - update_icon() - - send_status() - - -/obj/machinery/door/airlock/proc/send_status() - if(radio_connection) - var/datum/signal/signal = new(list( - "tag" = id_tag, - "timestamp" = world.time, - "door_status" = density ? "closed" : "open", - "lock_status" = locked ? "locked" : "unlocked" - )) - radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) - - -/obj/machinery/door/airlock/open(surpress_send) - . = ..() - if(!surpress_send) - send_status() - - -/obj/machinery/door/airlock/close(surpress_send) - . = ..() - if(!surpress_send) - send_status() - - -/obj/machinery/door/airlock/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - if(new_frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) - -/obj/machinery/door/airlock/Destroy() - if(frequency) - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/airlock_sensor - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "airlock_sensor_off" - name = "airlock sensor" - resistance_flags = FIRE_PROOF - - power_channel = 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 = FIRE_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 48b3e9bbe02f..0025c8b06894 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -1,391 +1,391 @@ -#define AUTOLATHE_MAIN_MENU 1 -#define AUTOLATHE_CATEGORY_MENU 2 -#define AUTOLATHE_SEARCH_MENU 3 - -/obj/machinery/autolathe - name = "autolathe" - desc = "It produces items using metal and glass." - icon_state = "autolathe" - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - circuit = /obj/item/circuitboard/machine/autolathe - layer = BELOW_OBJ_LAYER - - var/operating = FALSE - var/list/L = list() - var/list/LL = list() - var/hacked = FALSE - var/disabled = 0 - var/shocked = FALSE - var/hack_wire - var/disable_wire - var/shock_wire - - var/busy = FALSE - var/prod_coeff = 1 - - var/datum/design/being_built - var/datum/techweb/stored_research - var/list/datum/design/matching_designs - var/selected_category - var/screen = 1 - var/base_price = 25 - var/hacked_price = 50 - - var/list/categories = list( - "Tools", - "Electronics", - "Construction", - "T-Comm", - "Security", - "Machinery", - "Medical", - "Misc", - "Dinnerware", - "Imported" - ) - -/obj/machinery/autolathe/Initialize() - AddComponent(/datum/component/material_container, 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/examine(mob/user) - . += ..() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Storing up to [materials.max_amount] material units.
    Material consumption at [prod_coeff*100]%." - -/obj/machinery/autolathe/proc/main_win(mob/user) - var/dat = "

    Autolathe Menu:


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

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

    Browsing [selected_category]:


    " - dat += materials_printout() - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category)) - continue - - if(disabled || !can_build(D)) - dat += "[D.name]" - else - dat += "[D.name]" - - if(ispath(D.build_path, /obj/item/stack)) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/max_multiplier = 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 +#define AUTOLATHE_MAIN_MENU 1 +#define AUTOLATHE_CATEGORY_MENU 2 +#define AUTOLATHE_SEARCH_MENU 3 + +/obj/machinery/autolathe + name = "autolathe" + desc = "It produces items using metal and glass." + icon_state = "autolathe" + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + circuit = /obj/item/circuitboard/machine/autolathe + layer = BELOW_OBJ_LAYER + + var/operating = FALSE + var/list/L = list() + var/list/LL = list() + var/hacked = FALSE + var/disabled = 0 + var/shocked = FALSE + var/hack_wire + var/disable_wire + var/shock_wire + + var/busy = FALSE + var/prod_coeff = 1 + + var/datum/design/being_built + var/datum/techweb/stored_research + var/list/datum/design/matching_designs + var/selected_category + var/screen = 1 + var/base_price = 25 + var/hacked_price = 50 + + var/list/categories = list( + "Tools", + "Electronics", + "Construction", + "T-Comm", + "Security", + "Machinery", + "Medical", + "Misc", + "Dinnerware", + "Imported" + ) + +/obj/machinery/autolathe/Initialize() + AddComponent(/datum/component/material_container, 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/examine(mob/user) + . += ..() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Storing up to [materials.max_amount] material units.
    Material consumption at [prod_coeff*100]%." + +/obj/machinery/autolathe/proc/main_win(mob/user) + var/dat = "

    Autolathe Menu:


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

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

    Browsing [selected_category]:


    " + dat += materials_printout() + + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(selected_category in D.category)) + continue + + if(disabled || !can_build(D)) + dat += "[D.name]" + else + dat += "[D.name]" + + if(ispath(D.build_path, /obj/item/stack)) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/max_multiplier = 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 diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index a494548bc520..29610d956f4e 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -1,289 +1,289 @@ -/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 = LAVA_PROOF | FIRE_PROOF - -/obj/machinery/button/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/Initialize(mapload, ndir = 0, built = 0) - . = ..() - if(built) - setDir(ndir) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 - panel_open = TRUE - update_icon() - - - if(!built && !device && device_type) - device = new device_type(src) - - src.check_access(null) - - if(req_access.len || req_one_access.len) - board = new(src) - if(req_access.len) - board.accesses = req_access - else - board.one_access = 1 - board.accesses = req_one_access - - -/obj/machinery/button/update_icon() - cut_overlays() - if(panel_open) - icon_state = "button-open" - if(device) - add_overlay("button-device") - if(board) - add_overlay("button-board") - - else - if(stat & (NOPOWER|BROKEN)) - icon_state = "[skin]-p" - else - icon_state = skin - -/obj/machinery/button/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(panel_open || allowed(user)) - default_deconstruction_screwdriver(user, "button-open", "[skin]",W) - update_icon() - else - to_chat(user, "Maintenance Access Denied") - flick("[skin]-denied", src) - return - - if(panel_open) - if(!device && istype(W, /obj/item/assembly)) - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to you!") - return - device = W - to_chat(user, "You add [W] to the button.") - - if(!board && istype(W, /obj/item/electronics/airlock)) - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to you!") - return - board = W - if(board.one_access) - req_one_access = board.accesses - else - req_access = board.accesses - to_chat(user, "You add [W] to the button.") - - if(!device && !board && W.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start unsecuring the button frame...") - W.play_tool_sound(src) - if(W.use_tool(src, user, 40)) - to_chat(user, "You unsecure the button frame.") - transfer_fingerprints_to(new /obj/item/wallframe/button(get_turf(src))) - playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) - qdel(src) - - update_icon() - return - - if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) - return attack_hand(user) - else if(istype(W, /obj/item/airlock_scanner)) //yogs start - var/obj/item/airlock_scanner/S = W - S.show_access(src, user) //yogs end - 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 - -/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, .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 - var/sync_doors = TRUE - -/obj/machinery/button/door/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/door/setup_device() - if(!device) - if(normaldoorcontrol) - var/obj/item/assembly/control/airlock/A = new(src) - A.specialfunctions = specialfunctions - device = A - else - var/obj/item/assembly/control/C = new(src) - C.sync_doors = sync_doors - device = C - ..() - -/obj/machinery/button/door/incinerator_vent_toxmix - name = "combustion chamber vent control" - id = INCINERATOR_TOXMIX_VENT - req_access = list(ACCESS_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/massdriver/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/ignition - name = "ignition switch" - desc = "A remote control switch for a mounted igniter." - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/igniter - -/obj/machinery/button/ignition/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/ignition/incinerator - name = "combustion chamber ignition switch" - desc = "A remote control switch for the combustion chamber's igniter." - -/obj/machinery/button/ignition/incinerator/toxmix - id = INCINERATOR_TOXMIX_IGNITER - -/obj/machinery/button/ignition/incinerator/atmos - id = INCINERATOR_ATMOS_IGNITER - -/obj/machinery/button/ignition/incinerator/syndicatelava - id = INCINERATOR_SYNDICATELAVA_IGNITER - -/obj/machinery/button/flasher - name = "flasher button" - desc = "A remote control switch for a mounted flasher." - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/flasher - -/obj/machinery/button/flasher/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/crematorium - name = "crematorium igniter" - desc = "Burn baby burn!" - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/crematorium - req_access = list() - id = 1 - -/obj/machinery/button/crematorium/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/item/wallframe/button - name = "button frame" - desc = "Used for building buttons." - icon_state = "button" - result_path = /obj/machinery/button - 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 = LAVA_PROOF | FIRE_PROOF + +/obj/machinery/button/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/Initialize(mapload, ndir = 0, built = 0) + . = ..() + if(built) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 + panel_open = TRUE + update_icon() + + + if(!built && !device && device_type) + device = new device_type(src) + + src.check_access(null) + + if(req_access.len || req_one_access.len) + board = new(src) + if(req_access.len) + board.accesses = req_access + else + board.one_access = 1 + board.accesses = req_one_access + + +/obj/machinery/button/update_icon() + cut_overlays() + if(panel_open) + icon_state = "button-open" + if(device) + add_overlay("button-device") + if(board) + add_overlay("button-board") + + else + if(stat & (NOPOWER|BROKEN)) + icon_state = "[skin]-p" + else + icon_state = skin + +/obj/machinery/button/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(panel_open || allowed(user)) + default_deconstruction_screwdriver(user, "button-open", "[skin]",W) + update_icon() + else + to_chat(user, "Maintenance Access Denied") + flick("[skin]-denied", src) + return + + if(panel_open) + if(!device && istype(W, /obj/item/assembly)) + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to you!") + return + device = W + to_chat(user, "You add [W] to the button.") + + if(!board && istype(W, /obj/item/electronics/airlock)) + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to you!") + return + board = W + if(board.one_access) + req_one_access = board.accesses + else + req_access = board.accesses + to_chat(user, "You add [W] to the button.") + + if(!device && !board && W.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start unsecuring the button frame...") + W.play_tool_sound(src) + if(W.use_tool(src, user, 40)) + to_chat(user, "You unsecure the button frame.") + transfer_fingerprints_to(new /obj/item/wallframe/button(get_turf(src))) + playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) + qdel(src) + + update_icon() + return + + if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) + return attack_hand(user) + else if(istype(W, /obj/item/airlock_scanner)) //yogs start + var/obj/item/airlock_scanner/S = W + S.show_access(src, user) //yogs end + 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 + +/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, .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 + var/sync_doors = TRUE + +/obj/machinery/button/door/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/door/setup_device() + if(!device) + if(normaldoorcontrol) + var/obj/item/assembly/control/airlock/A = new(src) + A.specialfunctions = specialfunctions + device = A + else + var/obj/item/assembly/control/C = new(src) + C.sync_doors = sync_doors + device = C + ..() + +/obj/machinery/button/door/incinerator_vent_toxmix + name = "combustion chamber vent control" + id = INCINERATOR_TOXMIX_VENT + req_access = list(ACCESS_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/massdriver/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/ignition + name = "ignition switch" + desc = "A remote control switch for a mounted igniter." + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/igniter + +/obj/machinery/button/ignition/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/ignition/incinerator + name = "combustion chamber ignition switch" + desc = "A remote control switch for the combustion chamber's igniter." + +/obj/machinery/button/ignition/incinerator/toxmix + id = INCINERATOR_TOXMIX_IGNITER + +/obj/machinery/button/ignition/incinerator/atmos + id = INCINERATOR_ATMOS_IGNITER + +/obj/machinery/button/ignition/incinerator/syndicatelava + id = INCINERATOR_SYNDICATELAVA_IGNITER + +/obj/machinery/button/flasher + name = "flasher button" + desc = "A remote control switch for a mounted flasher." + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/flasher + +/obj/machinery/button/flasher/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/crematorium + name = "crematorium igniter" + desc = "Burn baby burn!" + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/crematorium + req_access = list() + id = 1 + +/obj/machinery/button/crematorium/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/item/wallframe/button + name = "button frame" + desc = "Used for building buttons." + icon_state = "button" + result_path = /obj/machinery/button + 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 5f8d2942cd9b..d3066739ca89 100644 --- a/code/game/machinery/camera/camera_assembly.dm +++ b/code/game/machinery/camera/camera_assembly.dm @@ -1,285 +1,285 @@ -#define STATE_WRENCHED 1 -#define STATE_WELDED 2 -#define STATE_WIRED 3 -#define STATE_FINISHED 4 - -/obj/item/wallframe/camera - name = "camera assembly" - desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." - icon = 'icons/obj/machines/camera.dmi' - icon_state = "cameracase" - 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 = "camera_assembly" - max_integrity = 150 - // Motion, EMP-Proof, X-ray - var/obj/item/analyzer/xray_module - var/malf_xray_firmware_active //used to keep from revealing malf AI upgrades for user facing isXRay() checks when they use Upgrade Camera Network ability - //will be false if the camera is upgraded with the proper parts. - var/malf_xray_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. - var/obj/item/stack/sheet/mineral/plasma/emp_module - var/malf_emp_firmware_active //used to keep from revealing malf AI upgrades for user facing isEmp() checks after they use Upgrade Camera Network ability - //will be false if the camera is upgraded with the proper parts. - var/malf_emp_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. - var/obj/item/assembly/prox_sensor/proxy_module - var/state = STATE_WRENCHED - -/obj/structure/camera_assembly/examine(mob/user) - . = ..() - //upgrade messages - var/has_upgrades - if(emp_module) - . += "It has electromagnetic interference shielding installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be shielded against electromagnetic interference with some plasma." - if(xray_module) - . += "It has an X-ray photodiode installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be upgraded with an X-ray photodiode with an analyzer." - if(proxy_module) - . += "It has a proximity sensor installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be upgraded with a proximity sensor." - - //construction states - switch(state) - if(STATE_WRENCHED) - . += "You can secure it in place with a welder, or removed with a wrench." - if(STATE_WELDED) - . += "You can add wires to it, or unweld it from the wall." - if(STATE_WIRED) - if(has_upgrades) - . += "You can remove the contained upgrades with a crowbar." - . += "You can complete it with a screwdriver, or unwire it to start removal." - if(STATE_FINISHED) - . += "You shouldn't be seeing this, tell a coder!" - -/obj/structure/camera_assembly/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - -/obj/structure/camera_assembly/update_icon() - icon_state = "[xray_module ? "xray" : null][initial(icon_state)]" - -/obj/structure/camera_assembly/handle_atom_del(atom/A) - if(A == xray_module) - xray_module = null - update_icon() - if(malf_xray_firmware_present) - malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeXRay(malf_xray_firmware_present) //make sure we don't remove MALF upgrades. - - else if(A == emp_module) - emp_module = null - if(malf_emp_firmware_present) - malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeEmpProof(malf_emp_firmware_present) //make sure we don't remove MALF upgrades - - else if(A == proxy_module) - emp_module = null - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeMotion() - - return ..() - - -/obj/structure/camera_assembly/Destroy() - QDEL_NULL(xray_module) - QDEL_NULL(emp_module) - QDEL_NULL(proxy_module) - return ..() - -/obj/structure/camera_assembly/proc/drop_upgrade(obj/item/I) - I.forceMove(drop_location()) - if(I == xray_module) - xray_module = null - if(malf_xray_firmware_present) - malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. - update_icon() - - else if(I == emp_module) - emp_module = null - if(malf_emp_firmware_present) - malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. - - else if(I == proxy_module) - proxy_module = null - - -/obj/structure/camera_assembly/attackby(obj/item/W, mob/living/user, params) - switch(state) - if(STATE_WRENCHED) - if(W.tool_behaviour == TOOL_WELDER) - if(weld(W, user)) - to_chat(user, "You weld [src] securely into place.") - setAnchored(TRUE) - state = STATE_WELDED - return - - if(STATE_WELDED) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = W - if(C.use(2)) - to_chat(user, "You add wires to [src].") - state = STATE_WIRED - else - to_chat(user, "You need two lengths of cable to wire a camera!") - return - return - - else if(W.tool_behaviour == TOOL_WELDER) - - if(weld(W, user)) - to_chat(user, "You unweld [src] from its place.") - state = STATE_WRENCHED - setAnchored(TRUE) - return - - if(STATE_WIRED) // Upgrades! - if(istype(W, /obj/item/stack/sheet/mineral/plasma)) //emp upgrade - if(emp_module) - to_chat(user, "[src] already contains a [emp_module]!") - return - if(!W.use_tool(src, user, 0, amount=1)) //only use one sheet, otherwise the whole stack will be consumed. - return - emp_module = new(src) - if(malf_xray_firmware_active) - malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part - //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. - to_chat(user, "You attach [W] into [src]'s inner circuits.") - return - - else if(istype(W, /obj/item/analyzer)) //xray upgrade - if(xray_module) - to_chat(user, "[src] already contains a [xray_module]!") - return - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You attach [W] into [src]'s inner circuits.") - xray_module = W - if(malf_xray_firmware_active) - malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part - //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. - update_icon() - return - - else if(istype(W, /obj/item/assembly/prox_sensor)) //motion sensing upgrade - if(proxy_module) - to_chat(user, "[src] already contains a [proxy_module]!") - return - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You attach [W] into [src]'s inner circuits.") - proxy_module = W - return - - return ..() - -/obj/structure/camera_assembly/crowbar_act(mob/user, obj/item/tool) - if(state != STATE_WIRED) - return FALSE - var/list/droppable_parts = list() - if(xray_module) - droppable_parts += xray_module - if(emp_module) - droppable_parts += emp_module - if(proxy_module) - droppable_parts += proxy_module - if(!droppable_parts.len) - return - var/obj/item/choice = input(user, "Select a part to remove:", src) as null|obj in droppable_parts - if(!choice || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - to_chat(user, "You remove [choice] from [src].") - drop_upgrade(choice) - tool.play_tool_sound(src) - return TRUE - -/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool) - . = ..() - if(.) - return TRUE - if(state != STATE_WIRED) - return FALSE - - tool.play_tool_sound(src) - var/input = stripped_input(user, "Which networks would you like to connect this camera to? Separate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Set Network", "SS13") - if(!input) - to_chat(user, "No input found, please hang up and try your call again!") - return - var/list/tempnetwork = splittext(input, ",") - if(tempnetwork.len < 1) - to_chat(user, "No network found, please hang up and try your call again!") - return - for(var/i in tempnetwork) - tempnetwork -= i - tempnetwork += lowertext(i) - state = STATE_FINISHED - var/obj/machinery/camera/C = new(loc, src) - forceMove(C) - C.setDir(src.dir) - - C.network = tempnetwork - var/area/A = get_area(src) - C.c_tag = "[A.name] ([rand(1, 999)])" - return TRUE - -/obj/structure/camera_assembly/wirecutter_act(mob/user, obj/item/I) - if(state != STATE_WIRED) - return 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 = STATE_WELDED - return TRUE - -/obj/structure/camera_assembly/wrench_act(mob/user, obj/item/I) - if(state != STATE_WRENCHED) - return FALSE - I.play_tool_sound(src) - to_chat(user, "You detach [src] from its place.") - new /obj/item/wallframe/camera(drop_location()) - //drop upgrades - if(xray_module) - drop_upgrade(xray_module) - if(emp_module) - drop_upgrade(emp_module) - if(proxy_module) - drop_upgrade(proxy_module) - - qdel(src) - return TRUE - -/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user) - if(!W.tool_start_check(user, amount=3)) - return FALSE - to_chat(user, "You start to weld [src]...") - if(W.use_tool(src, user, 20, amount=3, volume = 50)) - return TRUE - return FALSE - -/obj/structure/camera_assembly/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc) - qdel(src) - - -#undef STATE_WRENCHED -#undef STATE_WELDED -#undef STATE_WIRED -#undef STATE_FINISHED +#define STATE_WRENCHED 1 +#define STATE_WELDED 2 +#define STATE_WIRED 3 +#define STATE_FINISHED 4 + +/obj/item/wallframe/camera + name = "camera assembly" + desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." + icon = 'icons/obj/machines/camera.dmi' + icon_state = "cameracase" + 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 = "camera_assembly" + max_integrity = 150 + // Motion, EMP-Proof, X-ray + var/obj/item/analyzer/xray_module + var/malf_xray_firmware_active //used to keep from revealing malf AI upgrades for user facing isXRay() checks when they use Upgrade Camera Network ability + //will be false if the camera is upgraded with the proper parts. + var/malf_xray_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. + var/obj/item/stack/sheet/mineral/plasma/emp_module + var/malf_emp_firmware_active //used to keep from revealing malf AI upgrades for user facing isEmp() checks after they use Upgrade Camera Network ability + //will be false if the camera is upgraded with the proper parts. + var/malf_emp_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. + var/obj/item/assembly/prox_sensor/proxy_module + var/state = STATE_WRENCHED + +/obj/structure/camera_assembly/examine(mob/user) + . = ..() + //upgrade messages + var/has_upgrades + if(emp_module) + . += "It has electromagnetic interference shielding installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be shielded against electromagnetic interference with some plasma." + if(xray_module) + . += "It has an X-ray photodiode installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be upgraded with an X-ray photodiode with an analyzer." + if(proxy_module) + . += "It has a proximity sensor installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be upgraded with a proximity sensor." + + //construction states + switch(state) + if(STATE_WRENCHED) + . += "You can secure it in place with a welder, or removed with a wrench." + if(STATE_WELDED) + . += "You can add wires to it, or unweld it from the wall." + if(STATE_WIRED) + if(has_upgrades) + . += "You can remove the contained upgrades with a crowbar." + . += "You can complete it with a screwdriver, or unwire it to start removal." + if(STATE_FINISHED) + . += "You shouldn't be seeing this, tell a coder!" + +/obj/structure/camera_assembly/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + +/obj/structure/camera_assembly/update_icon() + icon_state = "[xray_module ? "xray" : null][initial(icon_state)]" + +/obj/structure/camera_assembly/handle_atom_del(atom/A) + if(A == xray_module) + xray_module = null + update_icon() + if(malf_xray_firmware_present) + malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeXRay(malf_xray_firmware_present) //make sure we don't remove MALF upgrades. + + else if(A == emp_module) + emp_module = null + if(malf_emp_firmware_present) + malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeEmpProof(malf_emp_firmware_present) //make sure we don't remove MALF upgrades + + else if(A == proxy_module) + emp_module = null + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeMotion() + + return ..() + + +/obj/structure/camera_assembly/Destroy() + QDEL_NULL(xray_module) + QDEL_NULL(emp_module) + QDEL_NULL(proxy_module) + return ..() + +/obj/structure/camera_assembly/proc/drop_upgrade(obj/item/I) + I.forceMove(drop_location()) + if(I == xray_module) + xray_module = null + if(malf_xray_firmware_present) + malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. + update_icon() + + else if(I == emp_module) + emp_module = null + if(malf_emp_firmware_present) + malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. + + else if(I == proxy_module) + proxy_module = null + + +/obj/structure/camera_assembly/attackby(obj/item/W, mob/living/user, params) + switch(state) + if(STATE_WRENCHED) + if(W.tool_behaviour == TOOL_WELDER) + if(weld(W, user)) + to_chat(user, "You weld [src] securely into place.") + setAnchored(TRUE) + state = STATE_WELDED + return + + if(STATE_WELDED) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = W + if(C.use(2)) + to_chat(user, "You add wires to [src].") + state = STATE_WIRED + else + to_chat(user, "You need two lengths of cable to wire a camera!") + return + return + + else if(W.tool_behaviour == TOOL_WELDER) + + if(weld(W, user)) + to_chat(user, "You unweld [src] from its place.") + state = STATE_WRENCHED + setAnchored(TRUE) + return + + if(STATE_WIRED) // Upgrades! + if(istype(W, /obj/item/stack/sheet/mineral/plasma)) //emp upgrade + if(emp_module) + to_chat(user, "[src] already contains a [emp_module]!") + return + if(!W.use_tool(src, user, 0, amount=1)) //only use one sheet, otherwise the whole stack will be consumed. + return + emp_module = new(src) + if(malf_xray_firmware_active) + malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part + //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. + to_chat(user, "You attach [W] into [src]'s inner circuits.") + return + + else if(istype(W, /obj/item/analyzer)) //xray upgrade + if(xray_module) + to_chat(user, "[src] already contains a [xray_module]!") + return + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You attach [W] into [src]'s inner circuits.") + xray_module = W + if(malf_xray_firmware_active) + malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part + //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. + update_icon() + return + + else if(istype(W, /obj/item/assembly/prox_sensor)) //motion sensing upgrade + if(proxy_module) + to_chat(user, "[src] already contains a [proxy_module]!") + return + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You attach [W] into [src]'s inner circuits.") + proxy_module = W + return + + return ..() + +/obj/structure/camera_assembly/crowbar_act(mob/user, obj/item/tool) + if(state != STATE_WIRED) + return FALSE + var/list/droppable_parts = list() + if(xray_module) + droppable_parts += xray_module + if(emp_module) + droppable_parts += emp_module + if(proxy_module) + droppable_parts += proxy_module + if(!droppable_parts.len) + return + var/obj/item/choice = input(user, "Select a part to remove:", src) as null|obj in droppable_parts + if(!choice || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + to_chat(user, "You remove [choice] from [src].") + drop_upgrade(choice) + tool.play_tool_sound(src) + return TRUE + +/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool) + . = ..() + if(.) + return TRUE + if(state != STATE_WIRED) + return FALSE + + tool.play_tool_sound(src) + var/input = stripped_input(user, "Which networks would you like to connect this camera to? Separate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Set Network", "SS13") + if(!input) + to_chat(user, "No input found, please hang up and try your call again!") + return + var/list/tempnetwork = splittext(input, ",") + if(tempnetwork.len < 1) + to_chat(user, "No network found, please hang up and try your call again!") + return + for(var/i in tempnetwork) + tempnetwork -= i + tempnetwork += lowertext(i) + state = STATE_FINISHED + var/obj/machinery/camera/C = new(loc, src) + forceMove(C) + C.setDir(src.dir) + + C.network = tempnetwork + var/area/A = get_area(src) + C.c_tag = "[A.name] ([rand(1, 999)])" + return TRUE + +/obj/structure/camera_assembly/wirecutter_act(mob/user, obj/item/I) + if(state != STATE_WIRED) + return 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 = STATE_WELDED + return TRUE + +/obj/structure/camera_assembly/wrench_act(mob/user, obj/item/I) + if(state != STATE_WRENCHED) + return FALSE + I.play_tool_sound(src) + to_chat(user, "You detach [src] from its place.") + new /obj/item/wallframe/camera(drop_location()) + //drop upgrades + if(xray_module) + drop_upgrade(xray_module) + if(emp_module) + drop_upgrade(emp_module) + if(proxy_module) + drop_upgrade(proxy_module) + + qdel(src) + return TRUE + +/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user) + if(!W.tool_start_check(user, amount=3)) + return FALSE + to_chat(user, "You start to weld [src]...") + if(W.use_tool(src, user, 20, amount=3, volume = 50)) + return TRUE + return FALSE + +/obj/structure/camera_assembly/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc) + qdel(src) + + +#undef STATE_WRENCHED +#undef STATE_WELDED +#undef STATE_WIRED +#undef STATE_FINISHED diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index 6fdfa6421e6a..af5093d6caf9 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -1,112 +1,112 @@ -/obj/machinery/camera - - var/list/datum/weakref/localMotionTargets = list() - var/detectTime = 0 - var/area/ai_monitored/area_motion = null - var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm() - -/obj/machinery/camera/process() - // motion camera event loop - if(!isMotion()) - . = PROCESS_KILL - return - if(stat & EMPED) - return - if (detectTime > 0) - var/elapsed = world.time - detectTime - if (elapsed > alarm_delay) - triggerAlarm() - else if (detectTime == -1) - for (var/datum/weakref/targetref in getTargetList()) - var/mob/target = targetref.resolve() - if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target))) - //If not part of a monitored area and the camera is not in range or the target is dead - lostTargetRef(targetref) - -/obj/machinery/camera/proc/getTargetList() - if(area_motion) - return area_motion.motionTargets - return localMotionTargets - -/obj/machinery/camera/proc/newTarget(mob/target) - if(isAI(target)) - return FALSE - if (detectTime == 0) - detectTime = world.time // start the clock - var/list/targets = getTargetList() - targets |= WEAKREF(target) - return TRUE - -/obj/machinery/camera/Destroy() - var/area/ai_monitored/A = get_area(src) - localMotionTargets = null - if(istype(A)) - A.motioncameras -= src - cancelAlarm() - return ..() - -/obj/machinery/camera/proc/lostTargetRef(datum/weakref/R) - var/list/targets = getTargetList() - targets -= R - if (targets.len == 0) - cancelAlarm() - -/obj/machinery/camera/proc/cancelAlarm() - if (detectTime == -1) - for (var/i in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = i - if (status) - aiPlayer.cancelAlarm("Motion", get_area(src), src) - detectTime = 0 - return TRUE - -/obj/machinery/camera/proc/triggerAlarm() - if (!detectTime) - return FALSE - for (var/mob/living/silicon/aiPlayer in GLOB.player_list) - if (status) - aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src) - visible_message("A red light flashes on the [src]!") - detectTime = -1 - return TRUE - -/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) - // Motion cameras outside of an "ai monitored" area will use this to detect stuff. - if (!area_motion) - if(isliving(AM)) - newTarget(AM) - -/obj/machinery/camera/motion/thunderdome - name = "entertainment camera" - network = list("thunder") - c_tag = "Arena" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF - -/obj/machinery/camera/motion/thunderdome/Initialize() - . = ..() - proximity_monitor.SetRange(7) - -/obj/machinery/camera/motion/thunderdome/HasProximity(atom/movable/AM as mob|obj) - if (!isliving(AM) || get_area(AM) != get_area(src)) - return - localMotionTargets |= WEAKREF(AM) - if (!detectTime) - for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) - TV.notify(TRUE) - detectTime = world.time + 30 SECONDS - -/obj/machinery/camera/motion/thunderdome/process() - if (!detectTime) - return - - for (var/datum/weakref/targetref in localMotionTargets) - var/mob/target = targetref.resolve() - if(QDELETED(target) || target.stat == DEAD || get_dist(src, target) > 7 || get_area(src) != get_area(target)) - localMotionTargets -= targetref - - if (localMotionTargets.len) - detectTime = world.time + 30 SECONDS - else if (world.time > detectTime) - detectTime = 0 - for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) - TV.notify(FALSE) +/obj/machinery/camera + + var/list/datum/weakref/localMotionTargets = list() + var/detectTime = 0 + var/area/ai_monitored/area_motion = null + var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm() + +/obj/machinery/camera/process() + // motion camera event loop + if(!isMotion()) + . = PROCESS_KILL + return + if(stat & EMPED) + return + if (detectTime > 0) + var/elapsed = world.time - detectTime + if (elapsed > alarm_delay) + triggerAlarm() + else if (detectTime == -1) + for (var/datum/weakref/targetref in getTargetList()) + var/mob/target = targetref.resolve() + if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target))) + //If not part of a monitored area and the camera is not in range or the target is dead + lostTargetRef(targetref) + +/obj/machinery/camera/proc/getTargetList() + if(area_motion) + return area_motion.motionTargets + return localMotionTargets + +/obj/machinery/camera/proc/newTarget(mob/target) + if(isAI(target)) + return FALSE + if (detectTime == 0) + detectTime = world.time // start the clock + var/list/targets = getTargetList() + targets |= WEAKREF(target) + return TRUE + +/obj/machinery/camera/Destroy() + var/area/ai_monitored/A = get_area(src) + localMotionTargets = null + if(istype(A)) + A.motioncameras -= src + cancelAlarm() + return ..() + +/obj/machinery/camera/proc/lostTargetRef(datum/weakref/R) + var/list/targets = getTargetList() + targets -= R + if (targets.len == 0) + cancelAlarm() + +/obj/machinery/camera/proc/cancelAlarm() + if (detectTime == -1) + for (var/i in GLOB.silicon_mobs) + var/mob/living/silicon/aiPlayer = i + if (status) + aiPlayer.cancelAlarm("Motion", get_area(src), src) + detectTime = 0 + return TRUE + +/obj/machinery/camera/proc/triggerAlarm() + if (!detectTime) + return FALSE + for (var/mob/living/silicon/aiPlayer in GLOB.player_list) + if (status) + aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src) + visible_message("A red light flashes on the [src]!") + detectTime = -1 + return TRUE + +/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) + // Motion cameras outside of an "ai monitored" area will use this to detect stuff. + if (!area_motion) + if(isliving(AM)) + newTarget(AM) + +/obj/machinery/camera/motion/thunderdome + name = "entertainment camera" + network = list("thunder") + c_tag = "Arena" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF + +/obj/machinery/camera/motion/thunderdome/Initialize() + . = ..() + proximity_monitor.SetRange(7) + +/obj/machinery/camera/motion/thunderdome/HasProximity(atom/movable/AM as mob|obj) + if (!isliving(AM) || get_area(AM) != get_area(src)) + return + localMotionTargets |= WEAKREF(AM) + if (!detectTime) + for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) + TV.notify(TRUE) + detectTime = world.time + 30 SECONDS + +/obj/machinery/camera/motion/thunderdome/process() + if (!detectTime) + return + + for (var/datum/weakref/targetref in localMotionTargets) + var/mob/target = targetref.resolve() + if(QDELETED(target) || target.stat == DEAD || get_dist(src, target) > 7 || get_area(src) != get_area(target)) + localMotionTargets -= targetref + + if (localMotionTargets.len) + detectTime = world.time + 30 SECONDS + else if (world.time > detectTime) + detectTime = 0 + for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) + TV.notify(FALSE) diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index 9e1978468b17..dcbff23b21ae 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -1,140 +1,140 @@ -// PRESETS - -// EMP -/obj/machinery/camera/emp_proof - start_active = TRUE - -/obj/machinery/camera/emp_proof/Initialize() - . = ..() - upgradeEmpProof() - -// EMP + Motion - -/obj/machinery/camera/emp_proof/motion/Initialize() - . = ..() - upgradeMotion() - -// X-ray - -/obj/machinery/camera/xray - start_active = TRUE - icon_state = "xraycamera" //mapping icon - Thanks to Krutchen for the icons. - -/obj/machinery/camera/xray/Initialize() - . = ..() - upgradeXRay() - -// MOTION -/obj/machinery/camera/motion - start_active = TRUE - name = "motion-sensitive security camera" - -/obj/machinery/camera/motion/Initialize() - . = ..() - upgradeMotion() - -// ALL UPGRADES -/obj/machinery/camera/all - start_active = TRUE - icon_state = "xraycamera" //mapping icon. - -/obj/machinery/camera/all/Initialize() - . = ..() - upgradeEmpProof() - upgradeXRay() - upgradeMotion() - -// AUTONAME - -/obj/machinery/camera/autoname - var/number = 0 //camera number in area - -//This camera type automatically sets it's name to whatever the area that it's in is called. -/obj/machinery/camera/autoname/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/camera/autoname/LateInitialize() - . = ..() - number = 1 - var/area/A = get_area(src) - if(A) - for(var/obj/machinery/camera/autoname/C in GLOB.machines) - if(C == src) - continue - var/area/CA = get_area(C) - if(CA.type == A.type) - if(C.number) - number = max(number, C.number+1) - c_tag = "[A.name] #[number]" - - -// UPGRADE PROCS - -/obj/machinery/camera/proc/isEmpProof(ignore_malf_upgrades) - return (upgrades & CAMERA_UPGRADE_EMP_PROOF) && (!(ignore_malf_upgrades && assembly.malf_emp_firmware_active)) - -/obj/machinery/camera/proc/upgradeEmpProof(malf_upgrade, ignore_malf_upgrades) - if(isEmpProof(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf module with the normal one - return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. - emp_component = AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) - if(malf_upgrade) - assembly.malf_emp_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. - assembly.malf_emp_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. - - else if(!assembly.emp_module) //only happens via upgrading in camera/attackby() - assembly.emp_module = new(assembly) - if(assembly.malf_emp_firmware_active) - assembly.malf_emp_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. - - upgrades |= CAMERA_UPGRADE_EMP_PROOF - -/obj/machinery/camera/proc/removeEmpProof(ignore_malf_upgrades) - if(ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. - return - emp_component.RemoveComponent() - upgrades &= ~CAMERA_UPGRADE_EMP_PROOF - - - -/obj/machinery/camera/proc/isXRay(ignore_malf_upgrades) - return (upgrades & CAMERA_UPGRADE_XRAY) && (!(ignore_malf_upgrades && assembly.malf_xray_firmware_active)) - -/obj/machinery/camera/proc/upgradeXRay(malf_upgrade, ignore_malf_upgrades) - if(isXRay(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf upgrade with the normal one - return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. - if(malf_upgrade) - assembly.malf_xray_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. - assembly.malf_xray_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. - - else if(!assembly.xray_module) //only happens via upgrading in camera/attackby() - assembly.xray_module = new(assembly) - if(assembly.malf_xray_firmware_active) - assembly.malf_xray_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. - - upgrades |= CAMERA_UPGRADE_XRAY - update_icon() - -/obj/machinery/camera/proc/removeXRay(ignore_malf_upgrades) - if(!ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. - upgrades &= ~CAMERA_UPGRADE_XRAY - update_icon() - - - -/obj/machinery/camera/proc/isMotion() - return upgrades & CAMERA_UPGRADE_MOTION - -/obj/machinery/camera/proc/upgradeMotion() - if(isMotion()) - return - if(name == initial(name)) - name = "motion-sensitive security camera" - if(!assembly.proxy_module) - assembly.proxy_module = new(assembly) - upgrades |= CAMERA_UPGRADE_MOTION - -/obj/machinery/camera/proc/removeMotion() - if(name == "motion-sensitive security camera") - name = "security camera" - upgrades &= ~CAMERA_UPGRADE_MOTION +// PRESETS + +// EMP +/obj/machinery/camera/emp_proof + start_active = TRUE + +/obj/machinery/camera/emp_proof/Initialize() + . = ..() + upgradeEmpProof() + +// EMP + Motion + +/obj/machinery/camera/emp_proof/motion/Initialize() + . = ..() + upgradeMotion() + +// X-ray + +/obj/machinery/camera/xray + start_active = TRUE + icon_state = "xraycamera" //mapping icon - Thanks to Krutchen for the icons. + +/obj/machinery/camera/xray/Initialize() + . = ..() + upgradeXRay() + +// MOTION +/obj/machinery/camera/motion + start_active = TRUE + name = "motion-sensitive security camera" + +/obj/machinery/camera/motion/Initialize() + . = ..() + upgradeMotion() + +// ALL UPGRADES +/obj/machinery/camera/all + start_active = TRUE + icon_state = "xraycamera" //mapping icon. + +/obj/machinery/camera/all/Initialize() + . = ..() + upgradeEmpProof() + upgradeXRay() + upgradeMotion() + +// AUTONAME + +/obj/machinery/camera/autoname + var/number = 0 //camera number in area + +//This camera type automatically sets it's name to whatever the area that it's in is called. +/obj/machinery/camera/autoname/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/camera/autoname/LateInitialize() + . = ..() + number = 1 + var/area/A = get_area(src) + if(A) + for(var/obj/machinery/camera/autoname/C in GLOB.machines) + if(C == src) + continue + var/area/CA = get_area(C) + if(CA.type == A.type) + if(C.number) + number = max(number, C.number+1) + c_tag = "[A.name] #[number]" + + +// UPGRADE PROCS + +/obj/machinery/camera/proc/isEmpProof(ignore_malf_upgrades) + return (upgrades & CAMERA_UPGRADE_EMP_PROOF) && (!(ignore_malf_upgrades && assembly.malf_emp_firmware_active)) + +/obj/machinery/camera/proc/upgradeEmpProof(malf_upgrade, ignore_malf_upgrades) + if(isEmpProof(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf module with the normal one + return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. + emp_component = AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) + if(malf_upgrade) + assembly.malf_emp_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. + assembly.malf_emp_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. + + else if(!assembly.emp_module) //only happens via upgrading in camera/attackby() + assembly.emp_module = new(assembly) + if(assembly.malf_emp_firmware_active) + assembly.malf_emp_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. + + upgrades |= CAMERA_UPGRADE_EMP_PROOF + +/obj/machinery/camera/proc/removeEmpProof(ignore_malf_upgrades) + if(ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. + return + emp_component.RemoveComponent() + upgrades &= ~CAMERA_UPGRADE_EMP_PROOF + + + +/obj/machinery/camera/proc/isXRay(ignore_malf_upgrades) + return (upgrades & CAMERA_UPGRADE_XRAY) && (!(ignore_malf_upgrades && assembly.malf_xray_firmware_active)) + +/obj/machinery/camera/proc/upgradeXRay(malf_upgrade, ignore_malf_upgrades) + if(isXRay(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf upgrade with the normal one + return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. + if(malf_upgrade) + assembly.malf_xray_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. + assembly.malf_xray_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. + + else if(!assembly.xray_module) //only happens via upgrading in camera/attackby() + assembly.xray_module = new(assembly) + if(assembly.malf_xray_firmware_active) + assembly.malf_xray_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. + + upgrades |= CAMERA_UPGRADE_XRAY + update_icon() + +/obj/machinery/camera/proc/removeXRay(ignore_malf_upgrades) + if(!ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. + upgrades &= ~CAMERA_UPGRADE_XRAY + update_icon() + + + +/obj/machinery/camera/proc/isMotion() + return upgrades & CAMERA_UPGRADE_MOTION + +/obj/machinery/camera/proc/upgradeMotion() + if(isMotion()) + return + if(name == initial(name)) + name = "motion-sensitive security camera" + if(!assembly.proxy_module) + assembly.proxy_module = new(assembly) + upgrades |= CAMERA_UPGRADE_MOTION + +/obj/machinery/camera/proc/removeMotion() + if(name == "motion-sensitive security camera") + name = "security camera" + upgrades &= ~CAMERA_UPGRADE_MOTION diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm index 1dbfe9d055b0..bf1b84fe5d4f 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 be496b8635e2..cb5aa575b432 100644 --- a/code/game/machinery/cell_charger.dm +++ b/code/game/machinery/cell_charger.dm @@ -1,132 +1,132 @@ -/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)]%." - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Charge rate at [charge_rate]J per cycle." - -/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/cell) && !panel_open) - if(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)]%." + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Charge rate at [charge_rate]J per cycle." + +/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/cell) && !panel_open) + if(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 e8b78afe5d7b..b8e980f0e9d1 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -1,555 +1,555 @@ -//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 //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 - var/datum/bank_account/current_insurance - fair_market_price = 5 // He nodded, because he knew I was right. Then he swiped his credit card to pay me for arresting him. - payment_department = ACCOUNT_MED -/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() - -/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 - -/obj/machinery/clonepod/examine(mob/user) - . = ..() - . += "The linking device can be scanned with a multitool." - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Cloning speed at [speed_coeff*50]%.
    Predicted amount of cellular damage: [100-heal_level]%." - if(efficiency > 5) - . += "Pod has been upgraded to support autoprocessing and apply beneficial mutations." - -//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/list/mutations = list() - var/max_mutations = 6 - var/read_only = FALSE //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() && istype(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(clonename, ui, mutation_index, mindref, last_death, datum/species/mrace, list/features, factions, list/quirks, datum/bank_account/insurance, list/traumas, empty) - if(panel_open) - return NONE - if(mess || attempting) - return NONE - - if(!empty) //Doesn't matter if we're just making a copy - clonemind = locate(mindref) in SSticker.minds - if(!istype(clonemind)) //not a mind - return NONE - if(clonemind.last_death != last_death) //The soul has advanced, the record has not. - return NONE - if(!QDELETED(clonemind.current)) - if(clonemind.current.stat != DEAD) //mind is associated with a non-dead body - return NONE - if(clonemind.current.suiciding) // Mind is associated with a body that is suiciding. - return NONE - if(!clonemind.active) - // get_ghost() will fail if they're unable to reenter their body - var/mob/dead/observer/G = clonemind.get_ghost() - if(!G) - return NONE - if(G.suiciding) // The ghost came from a body that is suiciding. - return NONE - if(clonemind.damnation_type) //Can't clone the damned. - INVOKE_ASYNC(src, .proc/horrifyingsound) - mess = TRUE - icon_state = "pod_g" - update_icon() - return NONE - current_insurance = insurance - attempting = TRUE //One at a time!! - countdown.start() - - var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) - - H.hardset_dna(ui, mutation_index, H.real_name, null, mrace, features) - - if(!HAS_TRAIT(H, TRAIT_RADIMMUNE))//dont apply mutations if the species is Mutation proof. - if(efficiency > 2) - var/list/unclean_mutations = (GLOB.not_good_mutations|GLOB.bad_mutations) - H.dna.remove_mutation_group(unclean_mutations) - if(efficiency > 5 && prob(20)) - H.easy_randmut(POSITIVE) - if(efficiency < 3 && prob(50)) - var/mob/M = H.easy_randmut(NEGATIVE+MINOR_NEGATIVE) - if(ismob(M)) - H = M - - 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 - - icon_state = "pod_1" - //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) - - if(!empty) - 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 - remove_hivemember(H) - - for(var/V in quirks) - var/datum/quirk/Q = new V(H) - Q.on_clone(quirks[V]) - - for(var/t in traumas) - var/datum/brain_trauma/BT = t - var/datum/brain_trauma/cloned_trauma = BT.on_clone() - if(cloned_trauma) - H.gain_trauma(cloned_trauma, BT.resilience) - - H.set_cloned_appearance() - - H.set_suicide(FALSE) - attempting = FALSE - return CLONING_SUCCESS - -//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) - log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to power loss.") - go_out() - connected_message("Clone Ejected: Loss of power.") - - else if(mob_occupant && (mob_occupant.loc == src)) - if(SSeconomy.full_ancap) - if(!current_insurance) - log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to invalid bank account.") - go_out() - connected_message("Clone Ejected: No bank account.") - if(internal_radio) - SPEAK("The cloning of [mob_occupant.real_name] has been terminated due to no bank account to draw payment from.") - else if(!current_insurance.adjust_money(-fair_market_price)) - log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to insufficient funds.") - go_out() - connected_message("Clone Ejected: Out of Money.") - if(internal_radio) - SPEAK("The cloning of [mob_occupant.real_name] has been ended prematurely due to being unable to pay.") - else - var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department) - if(D) - D.adjust_money(fair_market_price) - if(mob_occupant && (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 of [mob_occupant.real_name] has been \ - aborted due to unrecoverable tissue failure.") - log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] after suiciding.") - go_out() - - else if(mob_occupant && 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) - - use_power(7500) //This might need tweaking. - - else if(mob_occupant && (mob_occupant.cloneloss <= (100 - heal_level))) - connected_message("Cloning Process Complete.") - log_cloning("[key_name(mob_occupant)] completed cloning cycle in [src] at [AREACOORD(src)].") - if(internal_radio) - SPEAK("The cloning cycle of [mob_occupant.real_name] 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() - - else if (!mob_occupant || mob_occupant.loc != src) - occupant = null - if (!mess && !panel_open) - icon_state = "pod_0" - use_power(200) - -//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(W.tool_behaviour == TOOL_MULTITOOL) - if(!multitool_check_buffer(user, W)) - return - 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 - add_fingerprint(user) - log_cloning("[key_name(user)] manually ejected [key_name(mob_occupant)] from [src] at [AREACOORD(src)].") - log_combat(user, mob_occupant, "ejected", W, "from [src]") - connected_message("Emergency Ejection") - SPEAK("An emergency ejection of [clonemind.name] has occurred. Survival not guaranteed.") - to_chat(user, "You force an emergency ejection. ") - go_out() - else - return ..() - -/obj/machinery/clonepod/emag_act(mob/user) - if(!occupant) - return - to_chat(user, "You corrupt the genetic compiler.") - malfunction() - add_fingerprint(user) - log_cloning("[key_name(user)] emagged [src] at [AREACOORD(src)], causing it to malfunction.") - log_combat(user, src, "emagged", null, occupant ? "[occupant] inside, killing them via malfunction." : null) - -//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 - new /obj/effect/gibspawner/generic(get_turf(src), mob_occupant) - audible_message("You hear a splat.") - icon_state = "pod_0" - return - - if(!mob_occupant) - return - current_insurance = null - 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.
    ") - to_chat(occupant, "You do not remember your death, how you died, or who killed you. See rule 1.7.") //yogs - mob_occupant.flash_act() - - occupant.forceMove(T) - icon_state = "pod_0" - 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 - icon_state = "pod_g" - 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, 'sound/machines/warning-buzzer.ogg', 50) - SEND_SOUND(mob_occupant, sound('sound/hallucinations/veryfar_noise.ogg',0,1,50)) - log_cloning("[key_name(mob_occupant)] destroyed within [src] at [AREACOORD(src)] due to malfunction.") - 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))) - log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to EMP pulse.") - connected_message(Gibberish("EMP-caused Accidental Ejection", 0)) - SPEAK(Gibberish("Exposure to electromagnetic fields has caused the ejection of [mob_occupant.real_name] prematurely." ,0)) - 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(src,pick('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg'), 100, rand(0.95,1.05)) - sleep(1) - sleep(10) - playsound(src,'sound/hallucinations/wail.ogg', 100, TRUE) - -/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 - // In addition to being cellularly damaged, they also have no limbs or internal organs. - // Applying brainloss is done when the clone leaves the pod, so application of traumas can happen - // based on the level of damage sustained. - - 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 - -/* - * 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 //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 + var/datum/bank_account/current_insurance + fair_market_price = 5 // He nodded, because he knew I was right. Then he swiped his credit card to pay me for arresting him. + payment_department = ACCOUNT_MED +/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() + +/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 + +/obj/machinery/clonepod/examine(mob/user) + . = ..() + . += "The linking device can be scanned with a multitool." + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Cloning speed at [speed_coeff*50]%.
    Predicted amount of cellular damage: [100-heal_level]%." + if(efficiency > 5) + . += "Pod has been upgraded to support autoprocessing and apply beneficial mutations." + +//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/list/mutations = list() + var/max_mutations = 6 + var/read_only = FALSE //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() && istype(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(clonename, ui, mutation_index, mindref, last_death, datum/species/mrace, list/features, factions, list/quirks, datum/bank_account/insurance, list/traumas, empty) + if(panel_open) + return NONE + if(mess || attempting) + return NONE + + if(!empty) //Doesn't matter if we're just making a copy + clonemind = locate(mindref) in SSticker.minds + if(!istype(clonemind)) //not a mind + return NONE + if(clonemind.last_death != last_death) //The soul has advanced, the record has not. + return NONE + if(!QDELETED(clonemind.current)) + if(clonemind.current.stat != DEAD) //mind is associated with a non-dead body + return NONE + if(clonemind.current.suiciding) // Mind is associated with a body that is suiciding. + return NONE + if(!clonemind.active) + // get_ghost() will fail if they're unable to reenter their body + var/mob/dead/observer/G = clonemind.get_ghost() + if(!G) + return NONE + if(G.suiciding) // The ghost came from a body that is suiciding. + return NONE + if(clonemind.damnation_type) //Can't clone the damned. + INVOKE_ASYNC(src, .proc/horrifyingsound) + mess = TRUE + icon_state = "pod_g" + update_icon() + return NONE + current_insurance = insurance + attempting = TRUE //One at a time!! + countdown.start() + + var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) + + H.hardset_dna(ui, mutation_index, H.real_name, null, mrace, features) + + if(!HAS_TRAIT(H, TRAIT_RADIMMUNE))//dont apply mutations if the species is Mutation proof. + if(efficiency > 2) + var/list/unclean_mutations = (GLOB.not_good_mutations|GLOB.bad_mutations) + H.dna.remove_mutation_group(unclean_mutations) + if(efficiency > 5 && prob(20)) + H.easy_randmut(POSITIVE) + if(efficiency < 3 && prob(50)) + var/mob/M = H.easy_randmut(NEGATIVE+MINOR_NEGATIVE) + if(ismob(M)) + H = M + + 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 + + icon_state = "pod_1" + //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) + + if(!empty) + 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 + remove_hivemember(H) + + for(var/V in quirks) + var/datum/quirk/Q = new V(H) + Q.on_clone(quirks[V]) + + for(var/t in traumas) + var/datum/brain_trauma/BT = t + var/datum/brain_trauma/cloned_trauma = BT.on_clone() + if(cloned_trauma) + H.gain_trauma(cloned_trauma, BT.resilience) + + H.set_cloned_appearance() + + H.set_suicide(FALSE) + attempting = FALSE + return CLONING_SUCCESS + +//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) + log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to power loss.") + go_out() + connected_message("Clone Ejected: Loss of power.") + + else if(mob_occupant && (mob_occupant.loc == src)) + if(SSeconomy.full_ancap) + if(!current_insurance) + log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to invalid bank account.") + go_out() + connected_message("Clone Ejected: No bank account.") + if(internal_radio) + SPEAK("The cloning of [mob_occupant.real_name] has been terminated due to no bank account to draw payment from.") + else if(!current_insurance.adjust_money(-fair_market_price)) + log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to insufficient funds.") + go_out() + connected_message("Clone Ejected: Out of Money.") + if(internal_radio) + SPEAK("The cloning of [mob_occupant.real_name] has been ended prematurely due to being unable to pay.") + else + var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department) + if(D) + D.adjust_money(fair_market_price) + if(mob_occupant && (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 of [mob_occupant.real_name] has been \ + aborted due to unrecoverable tissue failure.") + log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] after suiciding.") + go_out() + + else if(mob_occupant && 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) + + use_power(7500) //This might need tweaking. + + else if(mob_occupant && (mob_occupant.cloneloss <= (100 - heal_level))) + connected_message("Cloning Process Complete.") + log_cloning("[key_name(mob_occupant)] completed cloning cycle in [src] at [AREACOORD(src)].") + if(internal_radio) + SPEAK("The cloning cycle of [mob_occupant.real_name] 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() + + else if (!mob_occupant || mob_occupant.loc != src) + occupant = null + if (!mess && !panel_open) + icon_state = "pod_0" + use_power(200) + +//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(W.tool_behaviour == TOOL_MULTITOOL) + if(!multitool_check_buffer(user, W)) + return + 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 + add_fingerprint(user) + log_cloning("[key_name(user)] manually ejected [key_name(mob_occupant)] from [src] at [AREACOORD(src)].") + log_combat(user, mob_occupant, "ejected", W, "from [src]") + connected_message("Emergency Ejection") + SPEAK("An emergency ejection of [clonemind.name] has occurred. Survival not guaranteed.") + to_chat(user, "You force an emergency ejection. ") + go_out() + else + return ..() + +/obj/machinery/clonepod/emag_act(mob/user) + if(!occupant) + return + to_chat(user, "You corrupt the genetic compiler.") + malfunction() + add_fingerprint(user) + log_cloning("[key_name(user)] emagged [src] at [AREACOORD(src)], causing it to malfunction.") + log_combat(user, src, "emagged", null, occupant ? "[occupant] inside, killing them via malfunction." : null) + +//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 + new /obj/effect/gibspawner/generic(get_turf(src), mob_occupant) + audible_message("You hear a splat.") + icon_state = "pod_0" + return + + if(!mob_occupant) + return + current_insurance = null + 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.
    ") + to_chat(occupant, "You do not remember your death, how you died, or who killed you. See rule 1.7.") //yogs + mob_occupant.flash_act() + + occupant.forceMove(T) + icon_state = "pod_0" + 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 + icon_state = "pod_g" + 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, 'sound/machines/warning-buzzer.ogg', 50) + SEND_SOUND(mob_occupant, sound('sound/hallucinations/veryfar_noise.ogg',0,1,50)) + log_cloning("[key_name(mob_occupant)] destroyed within [src] at [AREACOORD(src)] due to malfunction.") + 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))) + log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to EMP pulse.") + connected_message(Gibberish("EMP-caused Accidental Ejection", 0)) + SPEAK(Gibberish("Exposure to electromagnetic fields has caused the ejection of [mob_occupant.real_name] prematurely." ,0)) + 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(src,pick('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg'), 100, rand(0.95,1.05)) + sleep(1) + sleep(10) + playsound(src,'sound/hallucinations/wail.ogg', 100, TRUE) + +/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 + // In addition to being cellularly damaged, they also have no limbs or internal organs. + // Applying brainloss is done when the clone leaves the pod, so application of traumas can happen + // based on the level of damage sustained. + + 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 + +/* + * 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 45599375659a..528fc502aebc 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -1,191 +1,191 @@ -#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/obj/machinery/stasis/bed - 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 - for(var/direction in GLOB.cardinals) - bed = locate(/obj/machinery/stasis, get_step(src, direction)) - if(bed) - bed.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)) - data["bed"] = bed - if(bed) - 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(bed.check_patient()) - patient = bed.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/obj/machinery/stasis/bed + 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 + for(var/direction in GLOB.cardinals) + bed = locate(/obj/machinery/stasis, get_step(src, direction)) + if(bed) + bed.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)) + data["bed"] = bed + if(bed) + 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(bed.check_patient()) + patient = bed.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 ed8ad3def028..f88b9bf46d90 100644 --- a/code/game/machinery/computer/aifixer.dm +++ b/code/game/machinery/computer/aifixer.dm @@ -1,157 +1,157 @@ -/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/screwdriver_act(mob/living/user, obj/item/I) - if(occupier) - 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 - if(occupier) - var/mob/living/silicon/ai/A = occupier - A.notify_ghost_cloning("Your core files are being restored!", source = src) - 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 = TRUE - AI.radio_enabled = FALSE - 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/on_deconstruction() - if(occupier) - QDEL_NULL(occupier) +/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/screwdriver_act(mob/living/user, obj/item/I) + if(occupier) + 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 + if(occupier) + var/mob/living/silicon/ai/A = occupier + A.notify_ghost_cloning("Your core files are being restored!", source = src) + 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 = TRUE + AI.radio_enabled = FALSE + 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/on_deconstruction() + if(occupier) + QDEL_NULL(occupier) diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm index 94f5e63afb51..1d09e25fa99c 100644 --- a/code/game/machinery/computer/atmos_alert.dm +++ b/code/game/machinery/computer/atmos_alert.dm @@ -1,88 +1,88 @@ -/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() - ..() - if(stat & (NOPOWER|BROKEN)) - return - if(priority_alarms.len) - add_overlay("alert:2") - else if(minor_alarms.len) - add_overlay("alert:1") +/obj/machinery/computer/atmos_alert + name = "atmospheric alert console" + desc = "Used to monitor the station's air alarms." + circuit = /obj/item/circuitboard/computer/atmos_alert + 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() + ..() + if(stat & (NOPOWER|BROKEN)) + return + if(priority_alarms.len) + add_overlay("alert:2") + else if(minor_alarms.len) + add_overlay("alert:1") diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index 97feb65c34d6..8a15e155e82c 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -1,321 +1,321 @@ -///////////////////////////////////////////////////////////// -// AIR SENSOR (found in gas tanks) -///////////////////////////////////////////////////////////// - -/obj/machinery/air_sensor - name = "gas sensor" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "gsensor1" - resistance_flags = FIRE_PROOF - - var/on = TRUE - - var/id_tag - var/frequency = FREQ_ATMOS_STORAGE - var/datum/radio_frequency/radio_connection - -/obj/machinery/air_sensor/atmos/toxin_tank - name = "plasma tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_TOX -/obj/machinery/air_sensor/atmos/toxins_mixing_tank - name = "toxins mixing gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB -/obj/machinery/air_sensor/atmos/oxygen_tank - name = "oxygen tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_O2 -/obj/machinery/air_sensor/atmos/nitrogen_tank - name = "nitrogen tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_N2 -/obj/machinery/air_sensor/atmos/mix_tank - name = "mix tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_MIX -/obj/machinery/air_sensor/atmos/nitrous_tank - name = "nitrous oxide tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_N2O -/obj/machinery/air_sensor/atmos/air_tank - name = "air mix tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_AIR -/obj/machinery/air_sensor/atmos/carbon_tank - name = "carbon dioxide tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_CO2 -/obj/machinery/air_sensor/atmos/incinerator_tank - name = "incinerator chamber gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_INCINERATOR - -/obj/machinery/air_sensor/update_icon() - icon_state = "gsensor[on]" - -/obj/machinery/air_sensor/process_atmos() - if(on) - var/datum/gas_mixture/air_sample = return_air() - - var/datum/signal/signal = new(list( - "sigtype" = "status", - "id_tag" = id_tag, - "timestamp" = world.time, - "pressure" = air_sample.return_pressure(), - "temperature" = air_sample.temperature, - "gases" = list() - )) - var/total_moles = air_sample.total_moles() - if(total_moles) - for(var/gas_id in air_sample.gases) - var/gas_name = air_sample.gases[gas_id][GAS_META][META_GAS_NAME] - signal.data["gases"][gas_name] = air_sample.gases[gas_id][MOLES] / total_moles * 100 - - radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) - - -/obj/machinery/air_sensor/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) - -/obj/machinery/air_sensor/Initialize() - . = ..() - SSair.atmos_machinery += src - set_frequency(frequency) - -/obj/machinery/air_sensor/Destroy() - SSair.atmos_machinery -= src - SSradio.remove_object(src, frequency) - return ..() - -///////////////////////////////////////////////////////////// -// GENERAL AIR CONTROL (a.k.a atmos computer) -///////////////////////////////////////////////////////////// -GLOBAL_LIST_EMPTY(atmos_air_controllers) - -/obj/machinery/computer/atmos_control - name = "atmospherics monitoring" - desc = "Used to monitor the station's atmospherics sensors." - icon_screen = "tank" - icon_keyboard = "atmos_key" - circuit = /obj/item/circuitboard/computer/atmos_control - - 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") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/oxygen_tank - -/obj/machinery/computer/atmos_control/tank/toxin_tank - name = "Plasma Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_TOX - output_tag = ATMOS_GAS_MONITOR_OUTPUT_TOX - sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/toxin_tank - -/obj/machinery/computer/atmos_control/tank/air_tank - name = "Mixed Air Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_AIR - output_tag = ATMOS_GAS_MONITOR_OUTPUT_AIR - sensors = list(ATMOS_GAS_MONITOR_SENSOR_AIR = "Air Mix Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/air_tank - -/obj/machinery/computer/atmos_control/tank/mix_tank - name = "Gas Mix Tank Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_MIX - output_tag = ATMOS_GAS_MONITOR_OUTPUT_MIX - sensors = list(ATMOS_GAS_MONITOR_SENSOR_MIX = "Gas Mix Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/mix_tank - -/obj/machinery/computer/atmos_control/tank/nitrous_tank - name = "Nitrous Oxide Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_N2O - output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2O - sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrous_tank - -/obj/machinery/computer/atmos_control/tank/nitrogen_tank - name = "Nitrogen Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_N2 - output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2 - sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrogen_tank - -/obj/machinery/computer/atmos_control/tank/carbon_tank - name = "Carbon Dioxide Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_CO2 - output_tag = ATMOS_GAS_MONITOR_OUTPUT_CO2 - sensors = list(ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/carbon_tank - -/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") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/incinerator - -// This hacky madness is the evidence of the fact that a lot of machines were never meant to be constructable, im so sorry you had to see this -/obj/machinery/computer/atmos_control/tank/proc/reconnect(mob/user) - var/list/IO = list() - var/datum/radio_frequency/freq = SSradio.return_frequency(frequency) - var/list/devices = freq.devices["_default"] - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) - var/list/text = splittext(U.id_tag, "_") - IO |= text[1] - for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) - var/list/text = splittext(U.id, "_") - IO |= text[1] - if(!IO.len) - to_chat(user, "No machinery detected.") - var/S = input("Select the device set: ", "Selection", IO[1]) as anything in IO - if(src) - src.input_tag = "[S]_in" - src.output_tag = "[S]_out" - name = "[uppertext(S)] Supply Control" - var/list/new_devices = freq.devices["atmosia"] - sensors.Cut() - for(var/obj/machinery/air_sensor/U in new_devices) - var/list/text = splittext(U.id_tag, "_") - if(text[1] == S) - sensors = list("[S]_sensor" = "[S] Tank") - break - - for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) - U.broadcast_status() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) - U.broadcast_status() - -/obj/machinery/computer/atmos_control/tank/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "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("rate") - var/target = input("New target rate:", name, input_info ? input_info["volume_rate"] : 0) as num|null - if(!isnull(target) && !..()) - target = CLAMP(target, 0, MAX_TRANSFER_RATE) - signal.data += list("tag" = input_tag, "set_volume_rate" = target) - . = TRUE - if("output") - signal.data += list("tag" = output_tag, "power_toggle" = TRUE) - . = TRUE - if("pressure") - var/target = 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" + resistance_flags = FIRE_PROOF + + var/on = TRUE + + var/id_tag + var/frequency = FREQ_ATMOS_STORAGE + var/datum/radio_frequency/radio_connection + +/obj/machinery/air_sensor/atmos/toxin_tank + name = "plasma tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_TOX +/obj/machinery/air_sensor/atmos/toxins_mixing_tank + name = "toxins mixing gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB +/obj/machinery/air_sensor/atmos/oxygen_tank + name = "oxygen tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_O2 +/obj/machinery/air_sensor/atmos/nitrogen_tank + name = "nitrogen tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_N2 +/obj/machinery/air_sensor/atmos/mix_tank + name = "mix tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_MIX +/obj/machinery/air_sensor/atmos/nitrous_tank + name = "nitrous oxide tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_N2O +/obj/machinery/air_sensor/atmos/air_tank + name = "air mix tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_AIR +/obj/machinery/air_sensor/atmos/carbon_tank + name = "carbon dioxide tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_CO2 +/obj/machinery/air_sensor/atmos/incinerator_tank + name = "incinerator chamber gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_INCINERATOR + +/obj/machinery/air_sensor/update_icon() + icon_state = "gsensor[on]" + +/obj/machinery/air_sensor/process_atmos() + if(on) + var/datum/gas_mixture/air_sample = return_air() + + var/datum/signal/signal = new(list( + "sigtype" = "status", + "id_tag" = id_tag, + "timestamp" = world.time, + "pressure" = air_sample.return_pressure(), + "temperature" = air_sample.temperature, + "gases" = list() + )) + var/total_moles = air_sample.total_moles() + if(total_moles) + for(var/gas_id in air_sample.gases) + var/gas_name = air_sample.gases[gas_id][GAS_META][META_GAS_NAME] + signal.data["gases"][gas_name] = air_sample.gases[gas_id][MOLES] / total_moles * 100 + + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + + +/obj/machinery/air_sensor/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) + +/obj/machinery/air_sensor/Initialize() + . = ..() + SSair.atmos_machinery += src + set_frequency(frequency) + +/obj/machinery/air_sensor/Destroy() + SSair.atmos_machinery -= src + SSradio.remove_object(src, frequency) + return ..() + +///////////////////////////////////////////////////////////// +// GENERAL AIR CONTROL (a.k.a atmos computer) +///////////////////////////////////////////////////////////// +GLOBAL_LIST_EMPTY(atmos_air_controllers) + +/obj/machinery/computer/atmos_control + name = "atmospherics monitoring" + desc = "Used to monitor the station's atmospherics sensors." + icon_screen = "tank" + icon_keyboard = "atmos_key" + circuit = /obj/item/circuitboard/computer/atmos_control + + 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") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/oxygen_tank + +/obj/machinery/computer/atmos_control/tank/toxin_tank + name = "Plasma Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_TOX + output_tag = ATMOS_GAS_MONITOR_OUTPUT_TOX + sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/toxin_tank + +/obj/machinery/computer/atmos_control/tank/air_tank + name = "Mixed Air Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_AIR + output_tag = ATMOS_GAS_MONITOR_OUTPUT_AIR + sensors = list(ATMOS_GAS_MONITOR_SENSOR_AIR = "Air Mix Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/air_tank + +/obj/machinery/computer/atmos_control/tank/mix_tank + name = "Gas Mix Tank Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_MIX + output_tag = ATMOS_GAS_MONITOR_OUTPUT_MIX + sensors = list(ATMOS_GAS_MONITOR_SENSOR_MIX = "Gas Mix Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/mix_tank + +/obj/machinery/computer/atmos_control/tank/nitrous_tank + name = "Nitrous Oxide Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_N2O + output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2O + sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrous_tank + +/obj/machinery/computer/atmos_control/tank/nitrogen_tank + name = "Nitrogen Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_N2 + output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2 + sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrogen_tank + +/obj/machinery/computer/atmos_control/tank/carbon_tank + name = "Carbon Dioxide Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_CO2 + output_tag = ATMOS_GAS_MONITOR_OUTPUT_CO2 + sensors = list(ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/carbon_tank + +/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") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/incinerator + +// This hacky madness is the evidence of the fact that a lot of machines were never meant to be constructable, im so sorry you had to see this +/obj/machinery/computer/atmos_control/tank/proc/reconnect(mob/user) + var/list/IO = list() + var/datum/radio_frequency/freq = SSradio.return_frequency(frequency) + var/list/devices = freq.devices["_default"] + for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) + var/list/text = splittext(U.id_tag, "_") + IO |= text[1] + for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) + var/list/text = splittext(U.id, "_") + IO |= text[1] + if(!IO.len) + to_chat(user, "No machinery detected.") + var/S = input("Select the device set: ", "Selection", IO[1]) as anything in IO + if(src) + src.input_tag = "[S]_in" + src.output_tag = "[S]_out" + name = "[uppertext(S)] Supply Control" + var/list/new_devices = freq.devices["atmosia"] + sensors.Cut() + for(var/obj/machinery/air_sensor/U in new_devices) + var/list/text = splittext(U.id_tag, "_") + if(text[1] == S) + sensors = list("[S]_sensor" = "[S] Tank") + break + + for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) + U.broadcast_status() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) + U.broadcast_status() + +/obj/machinery/computer/atmos_control/tank/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "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("rate") + var/target = input("New target rate:", name, input_info ? input_info["volume_rate"] : 0) as num|null + if(!isnull(target) && !..()) + target = CLAMP(target, 0, MAX_TRANSFER_RATE) + signal.data += list("tag" = input_tag, "set_volume_rate" = target) + . = TRUE + if("output") + signal.data += list("tag" = output_tag, "power_toggle" = TRUE) + . = TRUE + if("pressure") + var/target = 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 5bbf3d2329fc..bdcba84d663a 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -1,143 +1,143 @@ -/obj/structure/frame/computer - name = "computer frame" - icon_state = "0" - state = 0 - -/obj/structure/frame/computer/attackby(obj/item/P, mob/user, params) - add_fingerprint(user) - switch(state) - if(0) - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start wrenching the frame into place...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You wrench the frame into place.") - setAnchored(TRUE) - state = 1 - return - if(P.tool_behaviour == TOOL_WELDER) - if(!P.tool_start_check(user, amount=0)) - return - - to_chat(user, "You start deconstructing the frame...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You deconstruct the frame.") - var/obj/item/stack/sheet/metal/M = new (drop_location(), 5) - M.add_fingerprint(user) - qdel(src) - return - if(1) - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start to unfasten the frame...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You unfasten the frame.") - setAnchored(FALSE) - state = 0 - return - if(istype(P, /obj/item/circuitboard/computer) && !circuit) - if(!user.transferItemToLoc(P, src)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, 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(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You screw [circuit] into place.") - state = 2 - icon_state = "2" - return - if(P.tool_behaviour == TOOL_CROWBAR && circuit) - P.play_tool_sound(src) - to_chat(user, "You remove [circuit].") - state = 1 - icon_state = "0" - circuit.forceMove(drop_location()) - circuit.add_fingerprint(user) - circuit = null - return - if(2) - if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You unfasten the circuit board.") - state = 1 - icon_state = "1" - return - if(istype(P, /obj/item/stack/cable_coil)) - if(!P.tool_start_check(user, amount=5)) - return - to_chat(user, "You start adding cables to the frame...") - if(P.use_tool(src, user, 20, volume=50, amount=5)) - if(state != 2) - return - to_chat(user, "You add cables to the frame.") - state = 3 - icon_state = "3" - return - if(3) - if(P.tool_behaviour == TOOL_WIRECUTTER) - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = 2 - icon_state = "2" - var/obj/item/stack/cable_coil/A = new (drop_location(), 5) - A.add_fingerprint(user) - return - - if(istype(P, /obj/item/stack/sheet/glass)) - if(!P.tool_start_check(user, amount=2)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, 1) - to_chat(user, "You start to put in the glass panel...") - if(P.use_tool(src, user, 20, amount=2)) - if(state != 3) - return - to_chat(user, "You put in the glass panel.") - state = 4 - src.icon_state = "4" - return - if(4) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the glass panel.") - state = 3 - icon_state = "3" - var/obj/item/stack/sheet/glass/G = new(drop_location(), 2) - G.add_fingerprint(user) - return - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You connect the monitor.") - var/obj/B = new circuit.build_path (loc, circuit) - B.setDir(dir) - transfer_fingerprints_to(B) - qdel(src) - return - if(user.a_intent == INTENT_HARM) - return ..() - - -/obj/structure/frame/computer/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(state == 4) - new /obj/item/shard(drop_location()) - new /obj/item/shard(drop_location()) - if(state >= 3) - new /obj/item/stack/cable_coil(drop_location(), 5) - ..() - -/obj/structure/frame/computer/AltClick(mob/user) - ..() - if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - - if(anchored) - to_chat(usr, "You must unwrench [src] before rotating it!") - return - - setDir(turn(dir, -90)) +/obj/structure/frame/computer + name = "computer frame" + icon_state = "0" + state = 0 + +/obj/structure/frame/computer/attackby(obj/item/P, mob/user, params) + add_fingerprint(user) + switch(state) + if(0) + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start wrenching the frame into place...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You wrench the frame into place.") + setAnchored(TRUE) + state = 1 + return + if(P.tool_behaviour == TOOL_WELDER) + if(!P.tool_start_check(user, amount=0)) + return + + to_chat(user, "You start deconstructing the frame...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You deconstruct the frame.") + var/obj/item/stack/sheet/metal/M = new (drop_location(), 5) + M.add_fingerprint(user) + qdel(src) + return + if(1) + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start to unfasten the frame...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You unfasten the frame.") + setAnchored(FALSE) + state = 0 + return + if(istype(P, /obj/item/circuitboard/computer) && !circuit) + if(!user.transferItemToLoc(P, src)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, 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(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You screw [circuit] into place.") + state = 2 + icon_state = "2" + return + if(P.tool_behaviour == TOOL_CROWBAR && circuit) + P.play_tool_sound(src) + to_chat(user, "You remove [circuit].") + state = 1 + icon_state = "0" + circuit.forceMove(drop_location()) + circuit.add_fingerprint(user) + circuit = null + return + if(2) + if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You unfasten the circuit board.") + state = 1 + icon_state = "1" + return + if(istype(P, /obj/item/stack/cable_coil)) + if(!P.tool_start_check(user, amount=5)) + return + to_chat(user, "You start adding cables to the frame...") + if(P.use_tool(src, user, 20, volume=50, amount=5)) + if(state != 2) + return + to_chat(user, "You add cables to the frame.") + state = 3 + icon_state = "3" + return + if(3) + if(P.tool_behaviour == TOOL_WIRECUTTER) + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = 2 + icon_state = "2" + var/obj/item/stack/cable_coil/A = new (drop_location(), 5) + A.add_fingerprint(user) + return + + if(istype(P, /obj/item/stack/sheet/glass)) + if(!P.tool_start_check(user, amount=2)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You start to put in the glass panel...") + if(P.use_tool(src, user, 20, amount=2)) + if(state != 3) + return + to_chat(user, "You put in the glass panel.") + state = 4 + src.icon_state = "4" + return + if(4) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the glass panel.") + state = 3 + icon_state = "3" + var/obj/item/stack/sheet/glass/G = new(drop_location(), 2) + G.add_fingerprint(user) + return + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You connect the monitor.") + var/obj/B = new circuit.build_path (loc, circuit) + B.setDir(dir) + transfer_fingerprints_to(B) + qdel(src) + return + if(user.a_intent == INTENT_HARM) + return ..() + + +/obj/structure/frame/computer/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(state == 4) + new /obj/item/shard(drop_location()) + new /obj/item/shard(drop_location()) + if(state >= 3) + new /obj/item/stack/cable_coil(drop_location(), 5) + ..() + +/obj/structure/frame/computer/AltClick(mob/user) + ..() + if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if(anchored) + to_chat(usr, "You must unwrench [src] before rotating it!") + return + + setDir(turn(dir, -90)) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 754a41aec970..fda6371a1de9 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -1,310 +1,310 @@ -/obj/machinery/computer/security - name = "security camera console" - desc = "Used to access the various cameras on the station." - icon_screen = "cameras" - icon_keyboard = "security_key" - circuit = /obj/item/circuitboard/computer/security - light_color = LIGHT_COLOR_RED - - 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. - var/long_ranged = FALSE - -/obj/machinery/computer/security/Initialize() - . = ..() - for(var/i in network) - network -= i - network += lowertext(i) - -/obj/machinery/computer/security/check_eye(mob/user) - if( (stat & (NOPOWER|BROKEN)) || user.incapacitated() || user.eye_blind ) - user.unset_machine() - return - if(!(user in watchers)) - user.unset_machine() - return - if(!watchers[user]) - user.unset_machine() - return - var/obj/machinery/camera/C = watchers[user] - if(!C.can_use()) - user.unset_machine() - return - if(iscyborg(user) || long_ranged) - var/list/viewing = viewers(src) - if(!viewing.Find(user)) - user.unset_machine() - return - if(!issilicon(user) && !Adjacent(user)) - 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/interact(mob/user) - if(stat) - return - if (!network) - user.unset_machine() - CRASH("No camera network") - return - if (!(islist(network))) - user.unset_machine() - CRASH("Camera network is not a list") - return - if(..()) - user.unset_machine() - return - - 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)) - if(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 - 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(user.machine != src) //while we were choosing we got disconnected from our computer or are using another machine. - return - if(!t) - user.unset_machine() - playsound(src, 'sound/machines/terminal_off.ogg', 25, 0) - return - - var/obj/machinery/camera/C = camera_list[t] - - if(t == "Cancel") - user.unset_machine() - playsound(src, 'sound/machines/terminal_off.ogg', 25, 0) - return - if(C) - var/camera_fail = 0 - if(!C.can_use() || user.machine != src || user.eye_blind || user.incapacitated()) - camera_fail = 1 - else if(iscyborg(user) || long_ranged) - var/list/viewing = viewers(src) - if(!viewing.Find(user)) - camera_fail = 1 - else if(!issilicon(user) && !Adjacent(user)) - camera_fail = 1 - - if(camera_fail) - user.unset_machine() - return 0 - - 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) - else - user.unset_machine() - -//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 = "\improper Head of Security's camera console" - desc = "A custom security console with added access to the labor camp network." - network = list("ss13", "labor") - circuit = null - -/obj/machinery/computer/security/labor - name = "labor camp monitoring" - desc = "Used to access the various cameras on the labor camp." - network = list("labor") - circuit = null - -/obj/machinery/computer/security/qm - name = "\improper Quartermaster's camera console" - desc = "A console with access to the mining, 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" - layer = SIGN_LAYER - 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_blank" - network = list("thunder") - density = FALSE - circuit = null - long_ranged = TRUE - interaction_flags_atom = NONE // interact() is called by BigClick() - var/icon_state_off = "entertainment_blank" - var/icon_state_on = "entertainment" - -/obj/machinery/computer/security/telescreen/entertainment/Initialize() - . = ..() - RegisterSignal(src, COMSIG_CLICK, .proc/BigClick) - -// Bypass clickchain to allow humans to use the telescreen from a distance -/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick() - interact(usr) - -/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on) - if(on && icon_state == icon_state_off) - say(pick( - "Feats of bravery live now at the thunderdome!", - "Two enter, one leaves! Tune in now!", - "Violence like you've never seen it before!", - "Spears! Camera! Action! LIVE NOW!")) - icon_state = icon_state_on - else - icon_state = icon_state_off - -/obj/machinery/computer/security/telescreen/rd - name = "\improper Research Director's telescreen" - desc = "Used for watching the AI and the RD's goons from the safety of his office." - network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test") - -/obj/machinery/computer/security/telescreen/research - name = "research telescreen" - desc = "A telescreen with access to the research division's camera network." - network = list("rd") - -/obj/machinery/computer/security/telescreen/ce - name = "\improper Chief Engineer's telescreen" - desc = "Used for watching the engine, telecommunications and the minisat." - network = list("engine", "singularity", "tcomms", "minisat") - -/obj/machinery/computer/security/telescreen/cmo - name = "\improper Chief Medical Officer's telescreen" - desc = "A telescreen with access to the medbay's camera network." - network = list("medbay") - -/obj/machinery/computer/security/telescreen/vault - name = "vault monitor" - desc = "A telescreen that connects to the vault's camera network." - network = list("vault") - -/obj/machinery/computer/security/telescreen/toxins - name = "bomb test site monitor" - desc = "A telescreen that connects to the bomb test site's camera." - network = list("toxins") - -/obj/machinery/computer/security/telescreen/engine - name = "engine monitor" - desc = "A telescreen that connects to the engine's camera network." - network = list("engine") - -/obj/machinery/computer/security/telescreen/turbine - name = "turbine monitor" - desc = "A telescreen that connects to the turbine's camera." - network = list("turbine") - -/obj/machinery/computer/security/telescreen/interrogation - name = "interrogation room monitor" - desc = "A telescreen that connects to the interrogation room's camera." - network = list("interrogation") - -/obj/machinery/computer/security/telescreen/prison - name = "prison monitor" - desc = "A telescreen that connects to the permabrig's camera network." - network = list("prison") - -/obj/machinery/computer/security/telescreen/auxbase - name = "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 = "\improper 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 + light_color = LIGHT_COLOR_RED + + 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. + var/long_ranged = FALSE + +/obj/machinery/computer/security/Initialize() + . = ..() + for(var/i in network) + network -= i + network += lowertext(i) + +/obj/machinery/computer/security/check_eye(mob/user) + if( (stat & (NOPOWER|BROKEN)) || user.incapacitated() || user.eye_blind ) + user.unset_machine() + return + if(!(user in watchers)) + user.unset_machine() + return + if(!watchers[user]) + user.unset_machine() + return + var/obj/machinery/camera/C = watchers[user] + if(!C.can_use()) + user.unset_machine() + return + if(iscyborg(user) || long_ranged) + var/list/viewing = viewers(src) + if(!viewing.Find(user)) + user.unset_machine() + return + if(!issilicon(user) && !Adjacent(user)) + 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/interact(mob/user) + if(stat) + return + if (!network) + user.unset_machine() + CRASH("No camera network") + return + if (!(islist(network))) + user.unset_machine() + CRASH("Camera network is not a list") + return + if(..()) + user.unset_machine() + return + + 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)) + if(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 + 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(user.machine != src) //while we were choosing we got disconnected from our computer or are using another machine. + return + if(!t) + user.unset_machine() + playsound(src, 'sound/machines/terminal_off.ogg', 25, 0) + return + + var/obj/machinery/camera/C = camera_list[t] + + if(t == "Cancel") + user.unset_machine() + playsound(src, 'sound/machines/terminal_off.ogg', 25, 0) + return + if(C) + var/camera_fail = 0 + if(!C.can_use() || user.machine != src || user.eye_blind || user.incapacitated()) + camera_fail = 1 + else if(iscyborg(user) || long_ranged) + var/list/viewing = viewers(src) + if(!viewing.Find(user)) + camera_fail = 1 + else if(!issilicon(user) && !Adjacent(user)) + camera_fail = 1 + + if(camera_fail) + user.unset_machine() + return 0 + + 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) + else + user.unset_machine() + +//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 = "\improper Head of Security's camera console" + desc = "A custom security console with added access to the labor camp network." + network = list("ss13", "labor") + circuit = null + +/obj/machinery/computer/security/labor + name = "labor camp monitoring" + desc = "Used to access the various cameras on the labor camp." + network = list("labor") + circuit = null + +/obj/machinery/computer/security/qm + name = "\improper Quartermaster's camera console" + desc = "A console with access to the mining, 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" + layer = SIGN_LAYER + 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_blank" + network = list("thunder") + density = FALSE + circuit = null + long_ranged = TRUE + interaction_flags_atom = NONE // interact() is called by BigClick() + var/icon_state_off = "entertainment_blank" + var/icon_state_on = "entertainment" + +/obj/machinery/computer/security/telescreen/entertainment/Initialize() + . = ..() + RegisterSignal(src, COMSIG_CLICK, .proc/BigClick) + +// Bypass clickchain to allow humans to use the telescreen from a distance +/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick() + interact(usr) + +/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on) + if(on && icon_state == icon_state_off) + say(pick( + "Feats of bravery live now at the thunderdome!", + "Two enter, one leaves! Tune in now!", + "Violence like you've never seen it before!", + "Spears! Camera! Action! LIVE NOW!")) + icon_state = icon_state_on + else + icon_state = icon_state_off + +/obj/machinery/computer/security/telescreen/rd + name = "\improper Research Director's telescreen" + desc = "Used for watching the AI and the RD's goons from the safety of his office." + network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test") + +/obj/machinery/computer/security/telescreen/research + name = "research telescreen" + desc = "A telescreen with access to the research division's camera network." + network = list("rd") + +/obj/machinery/computer/security/telescreen/ce + name = "\improper Chief Engineer's telescreen" + desc = "Used for watching the engine, telecommunications and the minisat." + network = list("engine", "singularity", "tcomms", "minisat") + +/obj/machinery/computer/security/telescreen/cmo + name = "\improper Chief Medical Officer's telescreen" + desc = "A telescreen with access to the medbay's camera network." + network = list("medbay") + +/obj/machinery/computer/security/telescreen/vault + name = "vault monitor" + desc = "A telescreen that connects to the vault's camera network." + network = list("vault") + +/obj/machinery/computer/security/telescreen/toxins + name = "bomb test site monitor" + desc = "A telescreen that connects to the bomb test site's camera." + network = list("toxins") + +/obj/machinery/computer/security/telescreen/engine + name = "engine monitor" + desc = "A telescreen that connects to the engine's camera network." + network = list("engine") + +/obj/machinery/computer/security/telescreen/turbine + name = "turbine monitor" + desc = "A telescreen that connects to the turbine's camera." + network = list("turbine") + +/obj/machinery/computer/security/telescreen/interrogation + name = "interrogation room monitor" + desc = "A telescreen that connects to the interrogation room's camera." + network = list("interrogation") + +/obj/machinery/computer/security/telescreen/prison + name = "prison monitor" + desc = "A telescreen that connects to the permabrig's camera network." + network = list("prison") + +/obj/machinery/computer/security/telescreen/auxbase + name = "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 = "\improper 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 afe93aebcbe7..0496c57abe95 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -1,624 +1,624 @@ - - -//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/obj/item/card/id/scan = null - var/obj/item/card/id/modify = null - var/authenticated = 0 - var/mode = 0 - var/printing = null - var/list/region_access = null - var/list/head_subordinates = 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") - - //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(); - - 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/examine(mob/user) - . = ..() - if(scan || modify) - . += "Alt-click to eject the ID card." - -/obj/machinery/computer/card/Initialize() - . = ..() - change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) - -/obj/machinery/computer/card/attackby(obj/O, mob/user, params)//TODO:SANITY - if(istype(O, /obj/item/card/id)) - var/obj/item/card/id/idcard = O - if(check_access(idcard)) - if(!scan) - if (!user.transferItemToLoc(idcard,src)) - return - scan = idcard - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - else if(!modify) - if (!user.transferItemToLoc(idcard,src)) - return - modify = idcard - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - else - if(!modify) - if (!user.transferItemToLoc(idcard,src)) - return - modify = idcard - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - updateUsrDialog() - else - return ..() - -/obj/machinery/computer/card/Destroy() - if(scan) - qdel(scan) - scan = null - if(modify) - qdel(modify) - modify = null - return ..() - -/obj/machinery/computer/card/handle_atom_del(atom/A) - ..() - if(A == scan) - scan = null - updateUsrDialog() - if(A == modify) - modify = null - updateUsrDialog() - -/obj/machinery/computer/card/on_deconstruction() - if(scan) - scan.forceMove(drop_location()) - scan = null - if(modify) - modify.forceMove(drop_location()) - modify = 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/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 - || Confirm Identity: - [(scan ? html_encode(scan.name) : "--------")] - - "} - var/ID - if(scan && (ACCESS_CHANGE_IDS in scan.access) && !target_dept) - ID = 1 - else - ID = 0 - 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(ID) - 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(ID) - 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(ID) - 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 = scan ? html_encode(scan.name) : "--------" - var/target_name = modify ? html_encode(modify.name) : "--------" - var/target_owner = (modify && modify.registered_name) ? html_encode(modify.registered_name) : "--------" - var/target_rank = (modify && modify.assignment) ? html_encode(modify.assignment) : "Unassigned" - - - if(!authenticated) - header += {"
    Please insert the cards into the slots
    - Target: [target_name]
    - Confirm Identity: [scan_name]
    "} - else - header += {""} - - header += "
    " - - - var/body - - if (authenticated && modify) - 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 modify.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 modify.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 - 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 ("modify") - eject_id_modify(usr) - if ("scan") - eject_id_scan(usr) - if ("auth") - if ((!( authenticated ) && (scan || issilicon(usr)) && (modify || mode))) - if (check_access(scan)) - region_access = list() - head_subordinates = list() - if(ACCESS_CHANGE_IDS in scan.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, 0) - - else - if((ACCESS_HOP in scan.access) && ((target_dept==1) || !target_dept)) - region_access |= 1 - region_access |= 6 - get_subordinates("Head of Personnel") - if((ACCESS_HOS in scan.access) && ((target_dept==2) || !target_dept)) - region_access |= 2 - get_subordinates("Head of Security") - if((ACCESS_CMO in scan.access) && ((target_dept==3) || !target_dept)) - region_access |= 3 - get_subordinates("Chief Medical Officer") - if((ACCESS_RD in scan.access) && ((target_dept==4) || !target_dept)) - region_access |= 4 - get_subordinates("Research Director") - if((ACCESS_CE in scan.access) && ((target_dept==5) || !target_dept)) - region_access |= 5 - get_subordinates("Chief Engineer") - if(region_access) - authenticated = 1 - else if ((!( authenticated ) && issilicon(usr)) && (!modify)) - 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, 0) - - 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())) - modify.access -= access_type - if(access_allowed == 1) - modify.access += access_type - playsound(src, "terminal_type", 50, 0) - 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", modify ? modify.assignment : "Unassigned"), MAX_NAME_LEN) - if(newJob) - t1 = newJob - - else if(t1 == "Unassigned") - modify.access -= get_all_accesses() - - else - var/datum/job/jobdatum - for(var/jobtype in typesof(/datum/job)) - var/datum/job/J = new jobtype - if(ckey(J.title) == ckey(t1)) - jobdatum = J - updateUsrDialog() - break - if(!jobdatum) - to_chat(usr, "No log exists for this job.") - updateUsrDialog() - return - if(modify.registered_account) - modify.registered_account.account_job = jobdatum // this is a terrible idea and people will grief but sure whatever - - modify.access = ( istype(src, /obj/machinery/computer/card/centcom) ? get_centcom_access(t1) : jobdatum.get_access() ) - if (modify) - log_game("[modify.registered_name]'s ID had its job changed to [t1] from [modify.assignment] by [usr.name]") // yogs - ID card change logging - modify.assignment = t1 - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - if ("demote") - if(modify.assignment in head_subordinates || modify.assignment == "Assistant") - modify.assignment = "Unassigned" - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - else - to_chat(usr, "You are not authorized to demote this position.") - if ("reg") - if (authenticated) - var/t2 = modify - if ((authenticated && modify == t2 && (in_range(src, usr) || issilicon(usr)) && isturf(loc))) - var/newName = reject_bad_name(href_list["reg"]) - if(newName) - modify.registered_name = newName - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - 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, 0) - - if("make_job_available") - // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS - if(scan && (ACCESS_CHANGE_IDS in scan.access) && !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, 0) - - if("make_job_unavailable") - // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS - if(scan && (ACCESS_CHANGE_IDS in scan.access) && !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, 0) - - if ("prioritize_job") - // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY - if(scan && (ACCESS_CHANGE_IDS in scan.access) && !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, 0) - - 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, 0) - if (modify) - modify.update_label() - updateUsrDialog() - -/obj/machinery/computer/card/AltClick(mob/user) - if(!user.canUseTopic(src, !issilicon(user)) || !is_operational()) - return - if(scan) - eject_id_scan(user) - if(modify) - eject_id_modify(user) - -/obj/machinery/computer/card/proc/eject_id_scan(mob/user) - if(scan) - scan.forceMove(drop_location()) - if(!issilicon(user) && Adjacent(user)) - user.put_in_hands(scan) - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - scan = null - else //switching the ID with the one you're holding - if(issilicon(user) || !Adjacent(user)) - return - var/obj/item/I = user.get_active_held_item() - if(istype(I, /obj/item/card/id)) - if(!user.transferItemToLoc(I,src)) - return - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - scan = I - authenticated = FALSE - updateUsrDialog() - -/obj/machinery/computer/card/proc/eject_id_modify(mob/user) - if(modify) - GLOB.data_core.manifest_modify(modify.registered_name, modify.assignment) - modify.update_label() - modify.forceMove(drop_location()) - if(!issilicon(user) && Adjacent(user)) - user.put_in_hands(modify) - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - modify = null - region_access = null - head_subordinates = null - else //switching the ID with the one you're holding - if(issilicon(user) || !Adjacent(user)) - return - var/obj/item/I = user.get_active_held_item() - if(istype(I, /obj/item/card/id)) - if (!user.transferItemToLoc(I,src)) - return - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - modify = I - authenticated = FALSE - updateUsrDialog() - -/obj/machinery/computer/card/proc/get_subordinates(rank) - for(var/datum/job/job in SSjob.occupations) - if(rank in job.department_head) - head_subordinates += job.title - -/obj/machinery/computer/card/centcom - name = "\improper CentCom identification console" - circuit = /obj/item/circuitboard/computer/card/centcom - req_access = list(ACCESS_CENT_CAPTAIN) - -/obj/machinery/computer/card/minor - name = "department management console" - desc = "You can use this to change ID's for specific departments." - icon_screen = "idminor" - circuit = /obj/item/circuitboard/computer/card/minor - -/obj/machinery/computer/card/minor/Initialize() - . = ..() - var/obj/item/circuitboard/computer/card/minor/typed_circuit = circuit - if(target_dept) - typed_circuit.target_dept = target_dept - else - target_dept = typed_circuit.target_dept - var/list/dept_list = list("general","security","medical","science","engineering") - name = "[dept_list[target_dept]] department console" - -/obj/machinery/computer/card/minor/hos - target_dept = 2 - icon_screen = "idhos" - - light_color = LIGHT_COLOR_RED - -/obj/machinery/computer/card/minor/cmo - target_dept = 3 - icon_screen = "idcmo" - -/obj/machinery/computer/card/minor/rd - target_dept = 4 - icon_screen = "idrd" - - light_color = LIGHT_COLOR_PINK - -/obj/machinery/computer/card/minor/ce - target_dept = 5 - icon_screen = "idce" - - light_color = LIGHT_COLOR_YELLOW - -#undef JOB_ALLOWED -#undef JOB_COOLDOWN -#undef JOB_MAX_POSITIONS -#undef JOB_DENIED + + +//Keeps track of the time for the ID console. Having it as a global variable prevents people from dismantling/reassembling it to +//increase the slots of many jobs. +GLOBAL_VAR_INIT(time_last_changed_position, 0) + +#define JOB_ALLOWED 1 +#define JOB_COOLDOWN -2 +#define JOB_MAX_POSITIONS -1 // Trying to reduce the number of slots below that of current holders of that job, or trying to open more slots than allowed +#define JOB_DENIED 0 + +/obj/machinery/computer/card + name = "identification console" + desc = "You can use this to manage jobs and ID access." + icon_screen = "id" + icon_keyboard = "id_key" + req_one_access = list(ACCESS_HEADS, ACCESS_CHANGE_IDS) + circuit = /obj/item/circuitboard/computer/card + var/obj/item/card/id/scan = null + var/obj/item/card/id/modify = null + var/authenticated = 0 + var/mode = 0 + var/printing = null + var/list/region_access = null + var/list/head_subordinates = 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") + + //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(); + + 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/examine(mob/user) + . = ..() + if(scan || modify) + . += "Alt-click to eject the ID card." + +/obj/machinery/computer/card/Initialize() + . = ..() + change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) + +/obj/machinery/computer/card/attackby(obj/O, mob/user, params)//TODO:SANITY + if(istype(O, /obj/item/card/id)) + var/obj/item/card/id/idcard = O + if(check_access(idcard)) + if(!scan) + if (!user.transferItemToLoc(idcard,src)) + return + scan = idcard + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + else if(!modify) + if (!user.transferItemToLoc(idcard,src)) + return + modify = idcard + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + else + if(!modify) + if (!user.transferItemToLoc(idcard,src)) + return + modify = idcard + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + updateUsrDialog() + else + return ..() + +/obj/machinery/computer/card/Destroy() + if(scan) + qdel(scan) + scan = null + if(modify) + qdel(modify) + modify = null + return ..() + +/obj/machinery/computer/card/handle_atom_del(atom/A) + ..() + if(A == scan) + scan = null + updateUsrDialog() + if(A == modify) + modify = null + updateUsrDialog() + +/obj/machinery/computer/card/on_deconstruction() + if(scan) + scan.forceMove(drop_location()) + scan = null + if(modify) + modify.forceMove(drop_location()) + modify = 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/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 + || Confirm Identity: + [(scan ? html_encode(scan.name) : "--------")] + + "} + var/ID + if(scan && (ACCESS_CHANGE_IDS in scan.access) && !target_dept) + ID = 1 + else + ID = 0 + 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(ID) + 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(ID) + 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(ID) + 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 = scan ? html_encode(scan.name) : "--------" + var/target_name = modify ? html_encode(modify.name) : "--------" + var/target_owner = (modify && modify.registered_name) ? html_encode(modify.registered_name) : "--------" + var/target_rank = (modify && modify.assignment) ? html_encode(modify.assignment) : "Unassigned" + + + if(!authenticated) + header += {"
    Please insert the cards into the slots
    + Target: [target_name]
    + Confirm Identity: [scan_name]
    "} + else + header += {""} + + header += "
    " + + + var/body + + if (authenticated && modify) + 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 modify.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 modify.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 + 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 ("modify") + eject_id_modify(usr) + if ("scan") + eject_id_scan(usr) + if ("auth") + if ((!( authenticated ) && (scan || issilicon(usr)) && (modify || mode))) + if (check_access(scan)) + region_access = list() + head_subordinates = list() + if(ACCESS_CHANGE_IDS in scan.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, 0) + + else + if((ACCESS_HOP in scan.access) && ((target_dept==1) || !target_dept)) + region_access |= 1 + region_access |= 6 + get_subordinates("Head of Personnel") + if((ACCESS_HOS in scan.access) && ((target_dept==2) || !target_dept)) + region_access |= 2 + get_subordinates("Head of Security") + if((ACCESS_CMO in scan.access) && ((target_dept==3) || !target_dept)) + region_access |= 3 + get_subordinates("Chief Medical Officer") + if((ACCESS_RD in scan.access) && ((target_dept==4) || !target_dept)) + region_access |= 4 + get_subordinates("Research Director") + if((ACCESS_CE in scan.access) && ((target_dept==5) || !target_dept)) + region_access |= 5 + get_subordinates("Chief Engineer") + if(region_access) + authenticated = 1 + else if ((!( authenticated ) && issilicon(usr)) && (!modify)) + 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, 0) + + 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())) + modify.access -= access_type + if(access_allowed == 1) + modify.access += access_type + playsound(src, "terminal_type", 50, 0) + 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", modify ? modify.assignment : "Unassigned"), MAX_NAME_LEN) + if(newJob) + t1 = newJob + + else if(t1 == "Unassigned") + modify.access -= get_all_accesses() + + else + var/datum/job/jobdatum + for(var/jobtype in typesof(/datum/job)) + var/datum/job/J = new jobtype + if(ckey(J.title) == ckey(t1)) + jobdatum = J + updateUsrDialog() + break + if(!jobdatum) + to_chat(usr, "No log exists for this job.") + updateUsrDialog() + return + if(modify.registered_account) + modify.registered_account.account_job = jobdatum // this is a terrible idea and people will grief but sure whatever + + modify.access = ( istype(src, /obj/machinery/computer/card/centcom) ? get_centcom_access(t1) : jobdatum.get_access() ) + if (modify) + log_game("[modify.registered_name]'s ID had its job changed to [t1] from [modify.assignment] by [usr.name]") // yogs - ID card change logging + modify.assignment = t1 + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + if ("demote") + if(modify.assignment in head_subordinates || modify.assignment == "Assistant") + modify.assignment = "Unassigned" + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + else + to_chat(usr, "You are not authorized to demote this position.") + if ("reg") + if (authenticated) + var/t2 = modify + if ((authenticated && modify == t2 && (in_range(src, usr) || issilicon(usr)) && isturf(loc))) + var/newName = reject_bad_name(href_list["reg"]) + if(newName) + modify.registered_name = newName + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + 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, 0) + + if("make_job_available") + // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS + if(scan && (ACCESS_CHANGE_IDS in scan.access) && !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, 0) + + if("make_job_unavailable") + // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS + if(scan && (ACCESS_CHANGE_IDS in scan.access) && !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, 0) + + if ("prioritize_job") + // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY + if(scan && (ACCESS_CHANGE_IDS in scan.access) && !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, 0) + + 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, 0) + if (modify) + modify.update_label() + updateUsrDialog() + +/obj/machinery/computer/card/AltClick(mob/user) + if(!user.canUseTopic(src, !issilicon(user)) || !is_operational()) + return + if(scan) + eject_id_scan(user) + if(modify) + eject_id_modify(user) + +/obj/machinery/computer/card/proc/eject_id_scan(mob/user) + if(scan) + scan.forceMove(drop_location()) + if(!issilicon(user) && Adjacent(user)) + user.put_in_hands(scan) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + scan = null + else //switching the ID with the one you're holding + if(issilicon(user) || !Adjacent(user)) + return + var/obj/item/I = user.get_active_held_item() + if(istype(I, /obj/item/card/id)) + if(!user.transferItemToLoc(I,src)) + return + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + scan = I + authenticated = FALSE + updateUsrDialog() + +/obj/machinery/computer/card/proc/eject_id_modify(mob/user) + if(modify) + GLOB.data_core.manifest_modify(modify.registered_name, modify.assignment) + modify.update_label() + modify.forceMove(drop_location()) + if(!issilicon(user) && Adjacent(user)) + user.put_in_hands(modify) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + modify = null + region_access = null + head_subordinates = null + else //switching the ID with the one you're holding + if(issilicon(user) || !Adjacent(user)) + return + var/obj/item/I = user.get_active_held_item() + if(istype(I, /obj/item/card/id)) + if (!user.transferItemToLoc(I,src)) + return + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + modify = I + authenticated = FALSE + updateUsrDialog() + +/obj/machinery/computer/card/proc/get_subordinates(rank) + for(var/datum/job/job in SSjob.occupations) + if(rank in job.department_head) + head_subordinates += job.title + +/obj/machinery/computer/card/centcom + name = "\improper CentCom identification console" + circuit = /obj/item/circuitboard/computer/card/centcom + req_access = list(ACCESS_CENT_CAPTAIN) + +/obj/machinery/computer/card/minor + name = "department management console" + desc = "You can use this to change ID's for specific departments." + icon_screen = "idminor" + circuit = /obj/item/circuitboard/computer/card/minor + +/obj/machinery/computer/card/minor/Initialize() + . = ..() + var/obj/item/circuitboard/computer/card/minor/typed_circuit = circuit + if(target_dept) + typed_circuit.target_dept = target_dept + else + target_dept = typed_circuit.target_dept + var/list/dept_list = list("general","security","medical","science","engineering") + name = "[dept_list[target_dept]] department console" + +/obj/machinery/computer/card/minor/hos + target_dept = 2 + icon_screen = "idhos" + + light_color = LIGHT_COLOR_RED + +/obj/machinery/computer/card/minor/cmo + target_dept = 3 + icon_screen = "idcmo" + +/obj/machinery/computer/card/minor/rd + target_dept = 4 + icon_screen = "idrd" + + light_color = LIGHT_COLOR_PINK + +/obj/machinery/computer/card/minor/ce + target_dept = 5 + icon_screen = "idce" + + light_color = LIGHT_COLOR_YELLOW + +#undef JOB_ALLOWED +#undef JOB_COOLDOWN +#undef JOB_MAX_POSITIONS +#undef JOB_DENIED diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index a428115c6954..d832d8190483 100755 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -1,748 +1,748 @@ -// 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/authenticated = 0 - 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, !issilicon(usr))) - return - if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13 - to_chat(usr, "Unable to establish a connection: \black You're too far away from the station!") - return - usr.set_machine(src) - - - if(!href_list["operation"]) - return - var/obj/item/circuitboard/computer/communications/CM = circuit - switch(href_list["operation"]) - // main interface - if("main") - state = STATE_DEFAULT - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 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((20 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 //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(" has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", 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(" has sent an outgoing message to the other station(s).
    ", "[usr.real_name]", 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 - var/points_to_check - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - points_to_check = D.account_balance - if(points_to_check >= S.credit_cost) - SSshuttle.shuttle_purchased = TRUE - SSshuttle.unload_preview() - SSshuttle.load_template(S) - SSshuttle.existing_shuttle = SSshuttle.emergency - SSshuttle.action_load(S) - D.adjust_money(-S.credit_cost) - minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits." , "Shuttle Purchase") - message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].") - SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]") - 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) - SSshuttle.cancelEvac(usr) - state = STATE_DEFAULT - if("messagelist") - currmsg = 0 - state = STATE_MESSAGELIST - if("viewmessage") - state = STATE_VIEWMESSAGE - if (!currmsg) - if(href_list["message-num"]) - var/msgnum = text2num(href_list["message-num"]) - currmsg = messages[msgnum] - else - state = STATE_MESSAGELIST - if("delmessage") - state = currmsg ? STATE_DELMESSAGE : STATE_MESSAGELIST - if("delmessage2") - if(authenticated) - if(currmsg) - if(aicurrmsg == currmsg) - aicurrmsg = null - messages -= currmsg - currmsg = null - state = STATE_MESSAGELIST - else - state = STATE_VIEWMESSAGE - if("respond") - var/answer = text2num(href_list["answer"]) - if(!currmsg || !answer || currmsg.possible_answers.len < answer) - state = STATE_MESSAGELIST - else - if(!currmsg.answered) - currmsg.answered = answer - log_game("[key_name(usr)] answered [currmsg.title] comm message. Answer : [currmsg.answered]") - if(currmsg) - currmsg.answer_callback.InvokeAsync() - state = STATE_VIEWMESSAGE - updateDialog() - if("status") - state = STATE_STATUSDISPLAY - if("securitylevel") - tmp_alertlevel = text2num( href_list["newalertlevel"] ) - if(!tmp_alertlevel) - tmp_alertlevel = 0 - state = STATE_CONFIRM_LEVEL - if("changeseclevel") - state = STATE_ALERT_LEVEL - - if("emergencyaccess") - state = STATE_TOGGLE_EMERGENCY - if("enableemergency") - make_maint_all_access() - log_game("[key_name(usr)] enabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") - deadchat_broadcast(" enabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) - state = STATE_DEFAULT - if("disableemergency") - revoke_maint_all_access() - log_game("[key_name(usr)] disabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") - deadchat_broadcast(" disabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) - 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(" has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) - CM.lastTimeUsed = world.time - - // OMG SYNDICATE ...LETTERHEAD - if("MessageSyndicate") - if((authenticated) && (obj_flags & EMAGGED)) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 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(" has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", 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",'sound/ai/commandreport.ogg') - 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 //Cannot engage delta with this - set_security_level(tmp_alertlevel) - if(GLOB.security_level != old_level) - //Only notify people if an actual change happened - var/security_level = get_security_level() - log_game("[key_name(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") - message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") - deadchat_broadcast(" has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) - tmp_alertlevel = 0 - aistate = STATE_DEFAULT - if("ai-changeseclevel") - aistate = STATE_ALERT_LEVEL - if("ai-emergencyaccess") - aistate = STATE_TOGGLE_EMERGENCY - if("ai-enableemergency") - make_maint_all_access() - log_game("[key_name(usr)] enabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") - deadchat_broadcast(" enabled emergency maintenance access.
    ", "[usr.real_name]", usr) - aistate = STATE_DEFAULT - if("ai-disableemergency") - revoke_maint_all_access() - log_game("[key_name(usr)] disabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") - deadchat_broadcast(" disabled emergency maintenance access.", "[usr.real_name]", usr) - aistate = STATE_DEFAULT - - updateUsrDialog() - -/obj/machinery/computer/communications/attackby(obj/I, mob/user, params) - if(istype(I, /obj/item/card/id)) - attack_hand(user) - else - return ..() - -/obj/machinery/computer/communications/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - if(authenticated == 1) - authenticated = 2 - to_chat(user, "You scramble the communication routing circuits!") - playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) - -/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 += "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) - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - dat += "Budget: [D.account_balance] Credits.
    " - dat += "
    " - dat += "Caution: Purchasing dangerous shuttles may lead to mutiny and/or death.
    " - dat += "
    " - for(var/shuttle_id in SSmapping.shuttle_templates) - var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] - if(S.can_be_bought && S.credit_cost < INFINITY) - dat += "[S.name] | [S.credit_cost] Credits
    " - dat += "[S.description]
    " - if(S.prerequisites) - dat += "Prerequisites: [S.prerequisites]
    " - dat += "(Purchase)

    " - - dat += "

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

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

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

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

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

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

    \[ [(aistate != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]" - return dat - -/obj/machinery/computer/communications/proc/make_announcement(mob/living/user, is_silicon) - if(!SScommunications.can_announce(user, is_silicon)) - to_chat(user, "Intercomms recharging. Please stand by.") - return - var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?") - if(!input || !user.canUseTopic(src, !issilicon(usr))) - return - SScommunications.make_announcement(user, is_silicon, input) - deadchat_broadcast(" made a priority announcement from [get_area_name(usr, TRUE)].", "[user.real_name]", 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/authenticated = 0 + 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, !issilicon(usr))) + return + if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13 + to_chat(usr, "Unable to establish a connection: \black You're too far away from the station!") + return + usr.set_machine(src) + + + if(!href_list["operation"]) + return + var/obj/item/circuitboard/computer/communications/CM = circuit + switch(href_list["operation"]) + // main interface + if("main") + state = STATE_DEFAULT + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 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((20 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 //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(" has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", 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(" has sent an outgoing message to the other station(s).", "[usr.real_name]", 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 + var/points_to_check + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + points_to_check = D.account_balance + if(points_to_check >= S.credit_cost) + SSshuttle.shuttle_purchased = TRUE + SSshuttle.unload_preview() + SSshuttle.load_template(S) + SSshuttle.existing_shuttle = SSshuttle.emergency + SSshuttle.action_load(S) + D.adjust_money(-S.credit_cost) + minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits." , "Shuttle Purchase") + message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].") + SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]") + 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) + SSshuttle.cancelEvac(usr) + state = STATE_DEFAULT + if("messagelist") + currmsg = 0 + state = STATE_MESSAGELIST + if("viewmessage") + state = STATE_VIEWMESSAGE + if (!currmsg) + if(href_list["message-num"]) + var/msgnum = text2num(href_list["message-num"]) + currmsg = messages[msgnum] + else + state = STATE_MESSAGELIST + if("delmessage") + state = currmsg ? STATE_DELMESSAGE : STATE_MESSAGELIST + if("delmessage2") + if(authenticated) + if(currmsg) + if(aicurrmsg == currmsg) + aicurrmsg = null + messages -= currmsg + currmsg = null + state = STATE_MESSAGELIST + else + state = STATE_VIEWMESSAGE + if("respond") + var/answer = text2num(href_list["answer"]) + if(!currmsg || !answer || currmsg.possible_answers.len < answer) + state = STATE_MESSAGELIST + else + if(!currmsg.answered) + currmsg.answered = answer + log_game("[key_name(usr)] answered [currmsg.title] comm message. Answer : [currmsg.answered]") + if(currmsg) + currmsg.answer_callback.InvokeAsync() + state = STATE_VIEWMESSAGE + updateDialog() + if("status") + state = STATE_STATUSDISPLAY + if("securitylevel") + tmp_alertlevel = text2num( href_list["newalertlevel"] ) + if(!tmp_alertlevel) + tmp_alertlevel = 0 + state = STATE_CONFIRM_LEVEL + if("changeseclevel") + state = STATE_ALERT_LEVEL + + if("emergencyaccess") + state = STATE_TOGGLE_EMERGENCY + if("enableemergency") + make_maint_all_access() + log_game("[key_name(usr)] enabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") + deadchat_broadcast(" enabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) + state = STATE_DEFAULT + if("disableemergency") + revoke_maint_all_access() + log_game("[key_name(usr)] disabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") + deadchat_broadcast(" disabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) + 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(" has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) + CM.lastTimeUsed = world.time + + // OMG SYNDICATE ...LETTERHEAD + if("MessageSyndicate") + if((authenticated) && (obj_flags & EMAGGED)) + if(!checkCCcooldown()) + to_chat(usr, "Arrays recycling. Please stand by.") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 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(" has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", 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",'sound/ai/commandreport.ogg') + 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 //Cannot engage delta with this + set_security_level(tmp_alertlevel) + if(GLOB.security_level != old_level) + //Only notify people if an actual change happened + var/security_level = get_security_level() + log_game("[key_name(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") + message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") + deadchat_broadcast(" has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) + tmp_alertlevel = 0 + aistate = STATE_DEFAULT + if("ai-changeseclevel") + aistate = STATE_ALERT_LEVEL + if("ai-emergencyaccess") + aistate = STATE_TOGGLE_EMERGENCY + if("ai-enableemergency") + make_maint_all_access() + log_game("[key_name(usr)] enabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") + deadchat_broadcast(" enabled emergency maintenance access.", "[usr.real_name]", usr) + aistate = STATE_DEFAULT + if("ai-disableemergency") + revoke_maint_all_access() + log_game("[key_name(usr)] disabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") + deadchat_broadcast(" disabled emergency maintenance access.", "[usr.real_name]", usr) + aistate = STATE_DEFAULT + + updateUsrDialog() + +/obj/machinery/computer/communications/attackby(obj/I, mob/user, params) + if(istype(I, /obj/item/card/id)) + attack_hand(user) + else + return ..() + +/obj/machinery/computer/communications/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + if(authenticated == 1) + authenticated = 2 + to_chat(user, "You scramble the communication routing circuits!") + playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) + +/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 += "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) + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + dat += "Budget: [D.account_balance] Credits.
    " + dat += "
    " + dat += "Caution: Purchasing dangerous shuttles may lead to mutiny and/or death.
    " + dat += "
    " + for(var/shuttle_id in SSmapping.shuttle_templates) + var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] + if(S.can_be_bought && S.credit_cost < INFINITY) + dat += "[S.name] | [S.credit_cost] Credits
    " + dat += "[S.description]
    " + if(S.prerequisites) + dat += "Prerequisites: [S.prerequisites]
    " + dat += "(Purchase)

    " + + dat += "

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

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

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

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

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

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

    \[ [(aistate != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]" + return dat + +/obj/machinery/computer/communications/proc/make_announcement(mob/living/user, is_silicon) + if(!SScommunications.can_announce(user, is_silicon)) + to_chat(user, "Intercomms recharging. Please stand by.") + return + var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?") + if(!input || !user.canUseTopic(src, !issilicon(usr))) + return + SScommunications.make_announcement(user, is_silicon, input) + deadchat_broadcast(" made a priority announcement from [get_area_name(usr, TRUE)].", "[user.real_name]", 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 f9185023b8c0..5c7a2ce8b284 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -1,195 +1,195 @@ -#define SENSORS_UPDATE_PERIOD 100 //How often the sensor data updates. - -/obj/machinery/computer/crew - name = "crew monitoring console" - desc = "Used to monitor active health sensors built into most of the crew's uniforms." - icon_screen = "crew" - icon_keyboard = "med_key" - use_power = IDLE_POWER_USE - idle_power_usage = 250 - active_power_usage = 500 - circuit = /obj/item/circuitboard/computer/crew - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/crew/syndie - icon_keyboard = "syndie_key" - -/obj/machinery/computer/crew/interact(mob/user) - GLOB.crewmonitor.show(user,src) - -GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) - -/datum/crewmonitor - var/list/ui_sources = list() //List of user -> ui source - var/list/jobs - var/list/data_by_z = list() - var/list/last_update = list() - -/datum/crewmonitor/New() - . = ..() - - var/list/jobs = new/list() - jobs["Captain"] = 00 - jobs["Head of Personnel"] = 50 - jobs["Head of Security"] = 10 - jobs["Warden"] = 11 - jobs["Security Officer"] = 12 - jobs["Detective"] = 13 - jobs["Chief Medical Officer"] = 20 - jobs["Chemist"] = 21 - jobs["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"] = 51 - jobs["Shaft Miner"] = 52 - jobs["Cargo Technician"] = 53 - jobs["Bartender"] = 61 - jobs["Cook"] = 62 - jobs["Botanist"] = 63 - jobs["Curator"] = 64 - jobs["Chaplain"] = 65 - jobs["Clown"] = 66 - jobs["Mime"] = 67 - jobs["Janitor"] = 68 - jobs["Lawyer"] = 69 - jobs["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 = list() - var/obj/item/clothing/under/U - var/obj/item/card/id/I - var/turf/pos - var/ijob - var/name - var/assignment - var/oxydam - var/toxdam - var/burndam - var/brutedam - var/area - var/pos_x - var/pos_y - var/life_status - - for(var/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) - else - oxydam = null - toxdam = null - burndam = null - brutedam = null - - 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 - - results[++results.len] = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) - - data_by_z["[z]"] = sortTim(results,/proc/sensor_compare) - last_update["[z]"] = world.time - - return results - -/proc/sensor_compare(list/a,list/b) - return a["ijob"] - b["ijob"] - -/datum/crewmonitor/ui_act(action,params) - var/mob/living/silicon/ai/AI = usr - if(!istype(AI)) - return - switch (action) - if ("select_person") - AI.ai_camera_track(params["name"]) - +#define SENSORS_UPDATE_PERIOD 100 //How often the sensor data updates. + +/obj/machinery/computer/crew + name = "crew monitoring console" + desc = "Used to monitor active health sensors built into most of the crew's uniforms." + icon_screen = "crew" + icon_keyboard = "med_key" + use_power = IDLE_POWER_USE + idle_power_usage = 250 + active_power_usage = 500 + circuit = /obj/item/circuitboard/computer/crew + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/crew/syndie + icon_keyboard = "syndie_key" + +/obj/machinery/computer/crew/interact(mob/user) + GLOB.crewmonitor.show(user,src) + +GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) + +/datum/crewmonitor + var/list/ui_sources = list() //List of user -> ui source + var/list/jobs + var/list/data_by_z = list() + var/list/last_update = list() + +/datum/crewmonitor/New() + . = ..() + + var/list/jobs = new/list() + jobs["Captain"] = 00 + jobs["Head of Personnel"] = 50 + jobs["Head of Security"] = 10 + jobs["Warden"] = 11 + jobs["Security Officer"] = 12 + jobs["Detective"] = 13 + jobs["Chief Medical Officer"] = 20 + jobs["Chemist"] = 21 + jobs["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"] = 51 + jobs["Shaft Miner"] = 52 + jobs["Cargo Technician"] = 53 + jobs["Bartender"] = 61 + jobs["Cook"] = 62 + jobs["Botanist"] = 63 + jobs["Curator"] = 64 + jobs["Chaplain"] = 65 + jobs["Clown"] = 66 + jobs["Mime"] = 67 + jobs["Janitor"] = 68 + jobs["Lawyer"] = 69 + jobs["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 = list() + var/obj/item/clothing/under/U + var/obj/item/card/id/I + var/turf/pos + var/ijob + var/name + var/assignment + var/oxydam + var/toxdam + var/burndam + var/brutedam + var/area + var/pos_x + var/pos_y + var/life_status + + for(var/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) + else + oxydam = null + toxdam = null + burndam = null + brutedam = null + + 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 + + results[++results.len] = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) + + data_by_z["[z]"] = sortTim(results,/proc/sensor_compare) + last_update["[z]"] = world.time + + return results + +/proc/sensor_compare(list/a,list/b) + return a["ijob"] - b["ijob"] + +/datum/crewmonitor/ui_act(action,params) + var/mob/living/silicon/ai/AI = usr + if(!istype(AI)) + return + switch (action) + if ("select_person") + AI.ai_camera_track(params["name"]) + #undef SENSORS_UPDATE_PERIOD \ No newline at end of file diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm index ea93fe27a1a8..2b56af897770 100644 --- a/code/game/machinery/computer/law.dm +++ b/code/game/machinery/computer/law.dm @@ -1,89 +1,89 @@ - - -/obj/machinery/computer/upload - var/mob/living/silicon/current = null //The target of future law uploads - icon_screen = "command" - var/obj/item/gps/internal/ai_upload/embedded_gps - var/obj/item/gps/internal/ai_upload/embedded_gps_type = /obj/item/gps/internal/ai_upload - time_to_scewdrive = 60 - -/obj/item/gps/internal/ai_upload - icon_state = null - gpstag = "Encrypted Upload Signal" - desc = "Signal used to connect remotely with silicons." - invisibility = 100 - -/obj/machinery/computer/upload/Initialize() - embedded_gps = new embedded_gps_type(src) - return ..() - -/obj/machinery/computer/upload/Destroy() - QDEL_NULL(embedded_gps) - return ..() - -/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/aiModule)) - var/obj/item/aiModule/M = O - if(stat & (NOPOWER|BROKEN|MAINT)) - return - if(!current) - to_chat(user, "You haven't selected anything to transmit laws to!") - return - if(!can_upload_to(current)) - to_chat(user, "Upload failed! Check to make sure [current.name] is functioning properly.") - current = null - return - var/turf/currentloc = get_turf(current) - if(currentloc && user.z != currentloc.z) - to_chat(user, "Upload failed! Unable to establish a connection to [current.name]. You're too far away!") - current = null - return - M.install(current.laws, user) - else - return ..() - -/obj/machinery/computer/upload/proc/can_upload_to(mob/living/silicon/S) - if(S.stat == DEAD) - return FALSE - return TRUE - -/obj/machinery/computer/upload/ai - name = "\improper AI upload console" - desc = "Used to upload laws to the AI." - circuit = /obj/item/circuitboard/computer/aiupload - -/obj/machinery/computer/upload/ai/interact(mob/user) - current = select_active_ai(user) - - if (!current) - to_chat(user, "No active AIs detected!") - else - to_chat(user, "[current.name] selected for law changes.") - -/obj/machinery/computer/upload/ai/can_upload_to(mob/living/silicon/ai/A) - if(!A || !isAI(A)) - return FALSE - if(A.control_disabled) - return FALSE - return ..() - - -/obj/machinery/computer/upload/borg - name = "cyborg upload console" - desc = "Used to upload laws to Cyborgs." - circuit = /obj/item/circuitboard/computer/borgupload - -/obj/machinery/computer/upload/borg/interact(mob/user) - current = select_active_free_borg(user) - - if(!current) - to_chat(user, "No active unslaved cyborgs detected!") - else - to_chat(user, "[current.name] selected for law changes.") - -/obj/machinery/computer/upload/borg/can_upload_to(mob/living/silicon/robot/B) - if(!B || !iscyborg(B)) - return FALSE - if(B.scrambledcodes || B.emagged) - return FALSE - return ..() + + +/obj/machinery/computer/upload + var/mob/living/silicon/current = null //The target of future law uploads + icon_screen = "command" + var/obj/item/gps/internal/ai_upload/embedded_gps + var/obj/item/gps/internal/ai_upload/embedded_gps_type = /obj/item/gps/internal/ai_upload + time_to_scewdrive = 60 + +/obj/item/gps/internal/ai_upload + icon_state = null + gpstag = "Encrypted Upload Signal" + desc = "Signal used to connect remotely with silicons." + invisibility = 100 + +/obj/machinery/computer/upload/Initialize() + embedded_gps = new embedded_gps_type(src) + return ..() + +/obj/machinery/computer/upload/Destroy() + QDEL_NULL(embedded_gps) + return ..() + +/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/aiModule)) + var/obj/item/aiModule/M = O + if(stat & (NOPOWER|BROKEN|MAINT)) + return + if(!current) + to_chat(user, "You haven't selected anything to transmit laws to!") + return + if(!can_upload_to(current)) + to_chat(user, "Upload failed! Check to make sure [current.name] is functioning properly.") + current = null + return + var/turf/currentloc = get_turf(current) + if(currentloc && user.z != currentloc.z) + to_chat(user, "Upload failed! Unable to establish a connection to [current.name]. You're too far away!") + current = null + return + M.install(current.laws, user) + else + return ..() + +/obj/machinery/computer/upload/proc/can_upload_to(mob/living/silicon/S) + if(S.stat == DEAD) + return FALSE + return TRUE + +/obj/machinery/computer/upload/ai + name = "\improper AI upload console" + desc = "Used to upload laws to the AI." + circuit = /obj/item/circuitboard/computer/aiupload + +/obj/machinery/computer/upload/ai/interact(mob/user) + current = select_active_ai(user) + + if (!current) + to_chat(user, "No active AIs detected!") + else + to_chat(user, "[current.name] selected for law changes.") + +/obj/machinery/computer/upload/ai/can_upload_to(mob/living/silicon/ai/A) + if(!A || !isAI(A)) + return FALSE + if(A.control_disabled) + return FALSE + return ..() + + +/obj/machinery/computer/upload/borg + name = "cyborg upload console" + desc = "Used to upload laws to Cyborgs." + circuit = /obj/item/circuitboard/computer/borgupload + +/obj/machinery/computer/upload/borg/interact(mob/user) + current = select_active_free_borg(user) + + if(!current) + to_chat(user, "No active unslaved cyborgs detected!") + else + to_chat(user, "[current.name] selected for law changes.") + +/obj/machinery/computer/upload/borg/can_upload_to(mob/living/silicon/robot/B) + if(!B || !iscyborg(B)) + return FALSE + if(B.scrambledcodes || B.emagged) + return FALSE + return ..() diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index aea8961c732c..f1055b6fb78e 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -1,602 +1,602 @@ - - -/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/obj/item/card/id/scan = null - var/authenticated = null - var/rank = null - var/screen = null - var/datum/data/record/active1 - var/datum/data/record/active2 - var/a_id = null - 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/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/card/id) && !scan) - if(!user.transferItemToLoc(O, src)) - return - scan = O - to_chat(user, "You insert [O].") - else - return ..() - -/obj/machinery/computer/med_data/ui_interact(mob/user) - . = ..() - var/dat - if(temp) - dat = text("[temp]

    Clear Screen") - else - dat = text("Confirm Identity: []
    ", (src.scan ? text("[]", src.scan.name) : "----------")) - if(src.authenticated) - switch(src.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(src.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 != src.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(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/med_data/Topic(href, href_list) - . = ..() - if(.) - return . - if(!(active1 in GLOB.data_core.general)) - src.active1 = null - if(!(active2 in GLOB.data_core.medical)) - src.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"]) - src.temp = null - if(href_list["scan"]) - if(src.scan) - usr.put_in_hands(scan) - scan = null - else - var/obj/item/I = usr.is_holding_item_of_type(/obj/item/card/id) - if(I) - if(!usr.transferItemToLoc(I, src)) - return - src.scan = I - else if(href_list["logout"]) - src.authenticated = null - src.screen = null - src.active1 = null - src.active2 = null - 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"]) - if(issilicon(usr)) - src.active1 = null - src.active2 = null - src.authenticated = 1 - src.rank = "AI" - src.screen = 1 - else if(IsAdminGhost(usr)) - src.active1 = null - src.active2 = null - src.authenticated = 1 - src.rank = "Central Command" - src.screen = 1 - else if(istype(src.scan, /obj/item/card/id)) - src.active1 = null - src.active2 = null - if(src.check_access(src.scan)) - src.authenticated = src.scan.registered_name - src.rank = src.scan.assignment - src.screen = 1 - if(src.authenticated) - - if(href_list["screen"]) - src.screen = text2num(href_list["screen"]) - if(src.screen < 1) - src.screen = 1 - - src.active1 = null - src.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)];" - src.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"]) - src.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() - src.temp = "All records deleted." - - else if(href_list["field"]) - var/a1 = src.active1 - var/a2 = src.active2 - switch(href_list["field"]) - if("fingerprint") - if(active1) - var/t1 = stripped_input("Please input fingerprint hash:", "Med. records", src.active1.fields["fingerprint"], null) - if(!canUseMedicalRecordsConsole(usr, t1, a1)) - return - src.active1.fields["fingerprint"] = t1 - if("gender") - if(active1) - if(src.active1.fields["gender"] == "Male") - src.active1.fields["gender"] = "Female" - else if(src.active1.fields["gender"] == "Female") - src.active1.fields["gender"] = "Other" - else - src.active1.fields["gender"] = "Male" - if("age") - if(active1) - var/t1 = input("Please input age:", "Med. records", src.active1.fields["age"], null) as num - if(!canUseMedicalRecordsConsole(usr, t1, a1)) - return - src.active1.fields["age"] = t1 - if("species") - if(active1) - var/t1 = stripped_input("Please input species name", "Med. records", src.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", src.active2.fields["mi_dis"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["mi_dis"] = t1 - if("mi_dis_d") - if(active2) - var/t1 = stripped_input("Please summarize minor dis.:", "Med. records", src.active2.fields["mi_dis_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["mi_dis_d"] = t1 - if("ma_dis") - if(active2) - var/t1 = stripped_input("Please input major disabilities list:", "Med. records", src.active2.fields["ma_dis"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["ma_dis"] = t1 - if("ma_dis_d") - if(active2) - var/t1 = stripped_input("Please summarize major dis.:", "Med. records", src.active2.fields["ma_dis_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["ma_dis_d"] = t1 - if("alg") - if(active2) - var/t1 = stripped_input("Please state allergies:", "Med. records", src.active2.fields["alg"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["alg"] = t1 - if("alg_d") - if(active2) - var/t1 = stripped_input("Please summarize allergies:", "Med. records", src.active2.fields["alg_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["alg_d"] = t1 - if("cdi") - if(active2) - var/t1 = stripped_input("Please state diseases:", "Med. records", src.active2.fields["cdi"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["cdi"] = t1 - if("cdi_d") - if(active2) - var/t1 = stripped_input("Please summarize diseases:", "Med. records", src.active2.fields["cdi_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["cdi_d"] = t1 - if("notes") - if(active2) - var/t1 = stripped_input("Please summarize notes:", "Med. records", src.active2.fields["notes"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.active2.fields["notes"] = t1 - if("p_stat") - if(active1) - src.temp = "Physical Condition:
    \n\t*Deceased*
    \n\t*Unconscious*
    \n\tActive
    \n\tPhysically Unfit
    " - if("m_stat") - if(active1) - src.temp = "Mental Condition:
    \n\t*Insane*
    \n\t*Unstable*
    \n\t*Watch*
    \n\tStable
    " - if("blood_type") - if(active2) - src.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", src.active2.fields["b_dna"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - src.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") - src.active1.fields["p_stat"] = "*Deceased*" - if("unconscious") - src.active1.fields["p_stat"] = "*Unconscious*" - if("active") - src.active1.fields["p_stat"] = "Active" - if("unfit") - src.active1.fields["p_stat"] = "Physically Unfit" - - else if(href_list["m_stat"]) - if(active1) - switch(href_list["m_stat"]) - if("insane") - src.active1.fields["m_stat"] = "*Insane*" - if("unstable") - src.active1.fields["m_stat"] = "*Unstable*" - if("watch") - src.active1.fields["m_stat"] = "*Watch*" - if("stable") - src.active1.fields["m_stat"] = "Stable" - - - else if(href_list["blood_type"]) - if(active2) - switch(href_list["blood_type"]) - if("an") - src.active2.fields["blood_type"] = "A-" - if("bn") - src.active2.fields["blood_type"] = "B-" - if("abn") - src.active2.fields["blood_type"] = "AB-" - if("on") - src.active2.fields["blood_type"] = "O-" - if("ap") - src.active2.fields["blood_type"] = "A+" - if("bp") - src.active2.fields["blood_type"] = "B+" - if("abp") - src.active2.fields["blood_type"] = "AB+" - if("op") - src.active2.fields["blood_type"] = "O+" - - - else if(href_list["del_r"]) - if(active2) - src.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(src.active1, /datum/data/record) && !( istype(src.active2, /datum/data/record) ))) - var/datum/data/record/R = new /datum/data/record( ) - R.fields["name"] = src.active1.fields["name"] - R.fields["id"] = src.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 - src.active2 = R - src.screen = 4 - - else if(href_list["add_c"]) - if(!(active2 in GLOB.data_core.medical)) - return - var/a2 = src.active2 - var/t1 = stripped_multiline_input("Add Comment:", "Med. records", null, null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - var/counter = 1 - while(src.active2.fields[text("com_[]", counter)]) - counter++ - src.active2.fields[text("com_[]", counter)] = text("Made by [] ([]) on [] [], []
    []", src.authenticated, src.rank, station_time_timestamp(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) - - else if(href_list["del_c"]) - if((istype(src.active2, /datum/data/record) && src.active2.fields[text("com_[]", href_list["del_c"])])) - src.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 - src.active1 = null - src.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"]))) - src.active2 = R - else - //Foreach continue //goto(3229) - if(!( src.active2 )) - src.temp = text("Could not locate record [].", sanitize(t1)) - else - for(var/datum/data/record/E in GLOB.data_core.general) - if((E.fields["name"] == src.active2.fields["name"] || E.fields["id"] == src.active2.fields["id"])) - src.active1 = E - else - //Foreach continue //goto(3334) - src.screen = 4 - - else if(href_list["print_p"]) - if(!( src.printing )) - src.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( src.loc ) - P.info = "
    Medical Record - (MR-[GLOB.data_core.medicalPrintCount])

    " - if(active1 in GLOB.data_core.general) - P.info += text("Name: [] ID: []
    \nGender: []
    \nAge: []
    ", src.active1.fields["name"], src.active1.fields["id"], src.active1.fields["gender"], src.active1.fields["age"]) - P.info += "\nSpecies: [active1.fields["species"]]
    " - P.info += text("\nFingerprint: []
    \nPhysical Status: []
    \nMental Status: []
    ", src.active1.fields["fingerprint"], src.active1.fields["p_stat"], src.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

    ", src.active2.fields["blood_type"], src.active2.fields["b_dna"], src.active2.fields["mi_dis"], src.active2.fields["mi_dis_d"], src.active2.fields["ma_dis"], src.active2.fields["ma_dis_d"], src.active2.fields["alg"], src.active2.fields["alg_d"], src.active2.fields["cdi"], src.active2.fields["cdi_d"], src.active2.fields["notes"]) - var/counter = 1 - while(src.active2.fields[text("com_[]", counter)]) - P.info += text("[]
    ", src.active2.fields[text("com_[]", counter)]) - counter++ - P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, src.active1.fields["name"]) - else - P.info += "Medical Record Lost!
    " - P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, "Record Lost") - P.info += "" - P.update_icon() - src.printing = null - - src.add_fingerprint(usr) - src.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, BE_CLOSE)) - 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/obj/item/card/id/scan = null + var/authenticated = null + var/rank = null + var/screen = null + var/datum/data/record/active1 + var/datum/data/record/active2 + var/a_id = null + 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/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/card/id) && !scan) + if(!user.transferItemToLoc(O, src)) + return + scan = O + to_chat(user, "You insert [O].") + else + return ..() + +/obj/machinery/computer/med_data/ui_interact(mob/user) + . = ..() + var/dat + if(temp) + dat = text("[temp]

    Clear Screen") + else + dat = text("Confirm Identity: []
    ", (src.scan ? text("[]", src.scan.name) : "----------")) + if(src.authenticated) + switch(src.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(src.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 != src.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(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/med_data/Topic(href, href_list) + . = ..() + if(.) + return . + if(!(active1 in GLOB.data_core.general)) + src.active1 = null + if(!(active2 in GLOB.data_core.medical)) + src.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"]) + src.temp = null + if(href_list["scan"]) + if(src.scan) + usr.put_in_hands(scan) + scan = null + else + var/obj/item/I = usr.is_holding_item_of_type(/obj/item/card/id) + if(I) + if(!usr.transferItemToLoc(I, src)) + return + src.scan = I + else if(href_list["logout"]) + src.authenticated = null + src.screen = null + src.active1 = null + src.active2 = null + 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"]) + if(issilicon(usr)) + src.active1 = null + src.active2 = null + src.authenticated = 1 + src.rank = "AI" + src.screen = 1 + else if(IsAdminGhost(usr)) + src.active1 = null + src.active2 = null + src.authenticated = 1 + src.rank = "Central Command" + src.screen = 1 + else if(istype(src.scan, /obj/item/card/id)) + src.active1 = null + src.active2 = null + if(src.check_access(src.scan)) + src.authenticated = src.scan.registered_name + src.rank = src.scan.assignment + src.screen = 1 + if(src.authenticated) + + if(href_list["screen"]) + src.screen = text2num(href_list["screen"]) + if(src.screen < 1) + src.screen = 1 + + src.active1 = null + src.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)];" + src.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"]) + src.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() + src.temp = "All records deleted." + + else if(href_list["field"]) + var/a1 = src.active1 + var/a2 = src.active2 + switch(href_list["field"]) + if("fingerprint") + if(active1) + var/t1 = stripped_input("Please input fingerprint hash:", "Med. records", src.active1.fields["fingerprint"], null) + if(!canUseMedicalRecordsConsole(usr, t1, a1)) + return + src.active1.fields["fingerprint"] = t1 + if("gender") + if(active1) + if(src.active1.fields["gender"] == "Male") + src.active1.fields["gender"] = "Female" + else if(src.active1.fields["gender"] == "Female") + src.active1.fields["gender"] = "Other" + else + src.active1.fields["gender"] = "Male" + if("age") + if(active1) + var/t1 = input("Please input age:", "Med. records", src.active1.fields["age"], null) as num + if(!canUseMedicalRecordsConsole(usr, t1, a1)) + return + src.active1.fields["age"] = t1 + if("species") + if(active1) + var/t1 = stripped_input("Please input species name", "Med. records", src.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", src.active2.fields["mi_dis"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["mi_dis"] = t1 + if("mi_dis_d") + if(active2) + var/t1 = stripped_input("Please summarize minor dis.:", "Med. records", src.active2.fields["mi_dis_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["mi_dis_d"] = t1 + if("ma_dis") + if(active2) + var/t1 = stripped_input("Please input major disabilities list:", "Med. records", src.active2.fields["ma_dis"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["ma_dis"] = t1 + if("ma_dis_d") + if(active2) + var/t1 = stripped_input("Please summarize major dis.:", "Med. records", src.active2.fields["ma_dis_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["ma_dis_d"] = t1 + if("alg") + if(active2) + var/t1 = stripped_input("Please state allergies:", "Med. records", src.active2.fields["alg"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["alg"] = t1 + if("alg_d") + if(active2) + var/t1 = stripped_input("Please summarize allergies:", "Med. records", src.active2.fields["alg_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["alg_d"] = t1 + if("cdi") + if(active2) + var/t1 = stripped_input("Please state diseases:", "Med. records", src.active2.fields["cdi"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["cdi"] = t1 + if("cdi_d") + if(active2) + var/t1 = stripped_input("Please summarize diseases:", "Med. records", src.active2.fields["cdi_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["cdi_d"] = t1 + if("notes") + if(active2) + var/t1 = stripped_input("Please summarize notes:", "Med. records", src.active2.fields["notes"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.active2.fields["notes"] = t1 + if("p_stat") + if(active1) + src.temp = "Physical Condition:
    \n\t*Deceased*
    \n\t*Unconscious*
    \n\tActive
    \n\tPhysically Unfit
    " + if("m_stat") + if(active1) + src.temp = "Mental Condition:
    \n\t*Insane*
    \n\t*Unstable*
    \n\t*Watch*
    \n\tStable
    " + if("blood_type") + if(active2) + src.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", src.active2.fields["b_dna"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + src.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") + src.active1.fields["p_stat"] = "*Deceased*" + if("unconscious") + src.active1.fields["p_stat"] = "*Unconscious*" + if("active") + src.active1.fields["p_stat"] = "Active" + if("unfit") + src.active1.fields["p_stat"] = "Physically Unfit" + + else if(href_list["m_stat"]) + if(active1) + switch(href_list["m_stat"]) + if("insane") + src.active1.fields["m_stat"] = "*Insane*" + if("unstable") + src.active1.fields["m_stat"] = "*Unstable*" + if("watch") + src.active1.fields["m_stat"] = "*Watch*" + if("stable") + src.active1.fields["m_stat"] = "Stable" + + + else if(href_list["blood_type"]) + if(active2) + switch(href_list["blood_type"]) + if("an") + src.active2.fields["blood_type"] = "A-" + if("bn") + src.active2.fields["blood_type"] = "B-" + if("abn") + src.active2.fields["blood_type"] = "AB-" + if("on") + src.active2.fields["blood_type"] = "O-" + if("ap") + src.active2.fields["blood_type"] = "A+" + if("bp") + src.active2.fields["blood_type"] = "B+" + if("abp") + src.active2.fields["blood_type"] = "AB+" + if("op") + src.active2.fields["blood_type"] = "O+" + + + else if(href_list["del_r"]) + if(active2) + src.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(src.active1, /datum/data/record) && !( istype(src.active2, /datum/data/record) ))) + var/datum/data/record/R = new /datum/data/record( ) + R.fields["name"] = src.active1.fields["name"] + R.fields["id"] = src.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 + src.active2 = R + src.screen = 4 + + else if(href_list["add_c"]) + if(!(active2 in GLOB.data_core.medical)) + return + var/a2 = src.active2 + var/t1 = stripped_multiline_input("Add Comment:", "Med. records", null, null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + var/counter = 1 + while(src.active2.fields[text("com_[]", counter)]) + counter++ + src.active2.fields[text("com_[]", counter)] = text("Made by [] ([]) on [] [], []
    []", src.authenticated, src.rank, station_time_timestamp(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) + + else if(href_list["del_c"]) + if((istype(src.active2, /datum/data/record) && src.active2.fields[text("com_[]", href_list["del_c"])])) + src.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 + src.active1 = null + src.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"]))) + src.active2 = R + else + //Foreach continue //goto(3229) + if(!( src.active2 )) + src.temp = text("Could not locate record [].", sanitize(t1)) + else + for(var/datum/data/record/E in GLOB.data_core.general) + if((E.fields["name"] == src.active2.fields["name"] || E.fields["id"] == src.active2.fields["id"])) + src.active1 = E + else + //Foreach continue //goto(3334) + src.screen = 4 + + else if(href_list["print_p"]) + if(!( src.printing )) + src.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( src.loc ) + P.info = "
    Medical Record - (MR-[GLOB.data_core.medicalPrintCount])

    " + if(active1 in GLOB.data_core.general) + P.info += text("Name: [] ID: []
    \nGender: []
    \nAge: []
    ", src.active1.fields["name"], src.active1.fields["id"], src.active1.fields["gender"], src.active1.fields["age"]) + P.info += "\nSpecies: [active1.fields["species"]]
    " + P.info += text("\nFingerprint: []
    \nPhysical Status: []
    \nMental Status: []
    ", src.active1.fields["fingerprint"], src.active1.fields["p_stat"], src.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

    ", src.active2.fields["blood_type"], src.active2.fields["b_dna"], src.active2.fields["mi_dis"], src.active2.fields["mi_dis_d"], src.active2.fields["ma_dis"], src.active2.fields["ma_dis_d"], src.active2.fields["alg"], src.active2.fields["alg_d"], src.active2.fields["cdi"], src.active2.fields["cdi_d"], src.active2.fields["notes"]) + var/counter = 1 + while(src.active2.fields[text("com_[]", counter)]) + P.info += text("[]
    ", src.active2.fields[text("com_[]", counter)]) + counter++ + P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, src.active1.fields["name"]) + else + P.info += "Medical Record Lost!
    " + P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, "Record Lost") + P.info += "" + P.update_icon() + src.printing = null + + src.add_fingerprint(usr) + src.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, BE_CLOSE)) + 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 5d60ccd3fb4a..1eb5378c677c 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 SECONDS))] - - + +" - var/temp = "" - var/list/L = list( 0.25, 0.5, 1, 2, 4, 8, 16 ) - for(var/t in L) - if(t == connected.power) - temp += "[t] " - else - temp += "[t] " - dat += "
    \nPower Level: [temp]
    \nFiring Sequence
    \nTest Fire Driver
    \nToggle Outer Door
    " - else - dat += "
    \nToggle Outer Door
    " - dat += "

    Close" - add_fingerprint(usr) - var/datum/browser/popup = new(user, "computer", title, 400, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/computer/pod/process() - if(!..()) - return - if(timing) - if(time > 0) - time = round(time) - 1 - else - alarm() - time = 0 - timing = 0 - updateDialog() - - -/obj/machinery/computer/pod/Topic(href, href_list) - if(..()) - return - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) - usr.set_machine(src) - if(href_list["power"]) - var/t = text2num(href_list["power"]) - t = min(max(0.25, t), 16) - if(connected) - connected.power = t - if(href_list["alarm"]) - alarm() - if(href_list["time"]) - timing = text2num(href_list["time"]) - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 0), 120) - if(href_list["door"]) - for(var/obj/machinery/door/poddoor/M in range(range, src)) - if(M.id == id) - if(M.density) - M.open() - else - M.close() - if(href_list["drive"]) - for(var/obj/machinery/mass_driver/M in range(range, src)) - if(M.id == id) - M.power = connected.power - M.drive() - updateUsrDialog() - -/obj/machinery/computer/pod/old - name = "\improper DoorMex control console" - title = "Door Controls" - icon_state = "oldcomp" - icon_screen = "library" - icon_keyboard = 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 SECONDS))] - - + +" + var/temp = "" + var/list/L = list( 0.25, 0.5, 1, 2, 4, 8, 16 ) + for(var/t in L) + if(t == connected.power) + temp += "[t] " + else + temp += "[t] " + dat += "
    \nPower Level: [temp]
    \nFiring Sequence
    \nTest Fire Driver
    \nToggle Outer Door
    " + else + dat += "
    \nToggle Outer Door
    " + dat += "

    Close" + add_fingerprint(usr) + var/datum/browser/popup = new(user, "computer", title, 400, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/computer/pod/process() + if(!..()) + return + if(timing) + if(time > 0) + time = round(time) - 1 + else + alarm() + time = 0 + timing = 0 + updateDialog() + + +/obj/machinery/computer/pod/Topic(href, href_list) + if(..()) + return + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) + usr.set_machine(src) + if(href_list["power"]) + var/t = text2num(href_list["power"]) + t = min(max(0.25, t), 16) + if(connected) + connected.power = t + if(href_list["alarm"]) + alarm() + if(href_list["time"]) + timing = text2num(href_list["time"]) + if(href_list["tp"]) + var/tp = text2num(href_list["tp"]) + time += tp + time = min(max(round(time), 0), 120) + if(href_list["door"]) + for(var/obj/machinery/door/poddoor/M in range(range, src)) + if(M.id == id) + if(M.density) + M.open() + else + M.close() + if(href_list["drive"]) + for(var/obj/machinery/mass_driver/M in range(range, src)) + if(M.id == id) + M.power = connected.power + M.drive() + updateUsrDialog() + +/obj/machinery/computer/pod/old + name = "\improper DoorMex control console" + title = "Door Controls" + icon_state = "oldcomp" + icon_screen = "library" + icon_keyboard = 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 fff0ddff3bdc..68e1d5837ae4 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -1,183 +1,183 @@ -/obj/machinery/computer/robotics - name = "robotics control console" - desc = "Used to remotely lockdown or detonate linked Cyborgs and Drones." - icon_screen = "robot" - icon_keyboard = "rd_key" - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/computer/robotics - light_color = LIGHT_COLOR_PINK - var/temp = null - - -/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) - . = FALSE - if(!istype(R)) - return - if(isAI(user)) - if (R.connected_ai != user) - return - if(iscyborg(user)) - if (R != user) - return - if(R.scrambledcodes) - return - return TRUE - -/obj/machinery/computer/robotics/ui_interact(mob/user) - . = ..() - if(z > 6) - to_chat(user, "Unable to establish a connection: You're too far away from the station!") - return - user.set_machine(src) - var/dat - var/list/robo_list = list() - var/robot_count - for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) - if(!can_control(user, R)) - continue - robot_count++ - var/unit_sync = "Independent" - if(R.connected_ai) - unit_sync = "Slaved to [R.connected_ai]" - if(!robo_list[unit_sync]) - robo_list[unit_sync] = list() - robo_list[unit_sync] += R - - dat += "

    Cyborgs


    " - if(!robo_list.len) - dat += "
    No cyborg units detected within access parameters.


    " - else - if(robo_list.len > 1) - sortTim(robo_list, /proc/cmp_text_asc) - for(var/ai_unit in robo_list) - dat += "

    [ai_unit]

    " - var/spacer - for(var/robo in robo_list[ai_unit]) - if(spacer) - dat += "

    " - else - spacer = TRUE - var/mob/living/silicon/robot/R = robo - dat += "Name: [R.name]
    " - var/can_move = (R.mobility_flags & MOBILITY_MOVE) - dat += "Status: [R.stat ? "Not Responding" : (can_move ? "Normal" : "Locked Down")]
    " - - if(can_move) - dat += "Cell: [R.cell ? "[R.cell.percent()]%" : "No Cell Detected"]
    " - - dat += "Module: [R.module ? "[R.module.name] Module" : "No Module Detected"]
    " - dat += "Unit Controls: " - if(issilicon(user) && user != R) - var/mob/living/silicon/S = user - if(is_servant_of_ratvar(S) && !is_servant_of_ratvar(R)) - dat += "(Convert) " - else if(S.hack_software && !R.emagged) - dat += "(Hack) " - else if(IsAdminGhost(user) && !R.emagged) - dat += "(Hack) " - dat += "([(R.mobility_flags & MOBILITY_MOVE) ? "Lockdown" : "Release"]) " - dat += "(Destroy)" - dat += "
    " - - dat += "

    Drones

    " - var/drones = 0 - for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) - if(D.hacked) - continue - if(istype(D,/mob/living/simple_animal/drone/derelict) && D.loc.z != src.loc.z) // Yogs -- Exempts derelict drones from being detonated while not on the same Z-level as the RD - continue // Yogs - if(drones) - dat += "

    " - else - dat += "
    " - drones++ - dat += "Name: [D.name]
    " - dat += "Status: [D.stat ? "Not Responding" : "Normal"]
    " - dat += "Unit Controls: " - dat += "(Destroy)" - - if(drones) - dat += "
    " - else - dat += "
    No drone units detected within access parameters.
    " - - var/window_height = min((300+((robot_count+drones) * 110)), 800) - - var/datum/browser/popup = new(user, "computer", "Robotics Control Console", 375, window_height) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/computer/robotics/Topic(href, href_list) - . = ..() - if(.) - return - - if (href_list["temp"]) - temp = null - - else if (href_list["killbot"]) - if(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(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.lockcharge ? "lock down" : "release"] [R.name]?") in list("Confirm", "Abort") - if(choice == "Confirm" && can_control(usr, R) && !..()) - message_admins("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!") - log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!") - R.SetLockdown(!R.lockcharge) - to_chat(R, "[!R.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]") - if(R.connected_ai) - to_chat(R.connected_ai, "[!R.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [R.name]
    ") - - else - to_chat(usr, "Access Denied.") - - 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(TRUE) - - 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(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, TRUE, D) - s.start() - D.visible_message("\the [D] self destructs!") - D.gib() - - - updateUsrDialog() +/obj/machinery/computer/robotics + name = "robotics control console" + desc = "Used to remotely lockdown or detonate linked Cyborgs and Drones." + icon_screen = "robot" + icon_keyboard = "rd_key" + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/computer/robotics + light_color = LIGHT_COLOR_PINK + var/temp = null + + +/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) + . = FALSE + if(!istype(R)) + return + if(isAI(user)) + if (R.connected_ai != user) + return + if(iscyborg(user)) + if (R != user) + return + if(R.scrambledcodes) + return + return TRUE + +/obj/machinery/computer/robotics/ui_interact(mob/user) + . = ..() + if(z > 6) + to_chat(user, "Unable to establish a connection: You're too far away from the station!") + return + user.set_machine(src) + var/dat + var/list/robo_list = list() + var/robot_count + for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) + if(!can_control(user, R)) + continue + robot_count++ + var/unit_sync = "Independent" + if(R.connected_ai) + unit_sync = "Slaved to [R.connected_ai]" + if(!robo_list[unit_sync]) + robo_list[unit_sync] = list() + robo_list[unit_sync] += R + + dat += "

    Cyborgs


    " + if(!robo_list.len) + dat += "
    No cyborg units detected within access parameters.


    " + else + if(robo_list.len > 1) + sortTim(robo_list, /proc/cmp_text_asc) + for(var/ai_unit in robo_list) + dat += "

    [ai_unit]

    " + var/spacer + for(var/robo in robo_list[ai_unit]) + if(spacer) + dat += "

    " + else + spacer = TRUE + var/mob/living/silicon/robot/R = robo + dat += "Name: [R.name]
    " + var/can_move = (R.mobility_flags & MOBILITY_MOVE) + dat += "Status: [R.stat ? "Not Responding" : (can_move ? "Normal" : "Locked Down")]
    " + + if(can_move) + dat += "Cell: [R.cell ? "[R.cell.percent()]%" : "No Cell Detected"]
    " + + dat += "Module: [R.module ? "[R.module.name] Module" : "No Module Detected"]
    " + dat += "Unit Controls: " + if(issilicon(user) && user != R) + var/mob/living/silicon/S = user + if(is_servant_of_ratvar(S) && !is_servant_of_ratvar(R)) + dat += "(Convert) " + else if(S.hack_software && !R.emagged) + dat += "(Hack) " + else if(IsAdminGhost(user) && !R.emagged) + dat += "(Hack) " + dat += "([(R.mobility_flags & MOBILITY_MOVE) ? "Lockdown" : "Release"]) " + dat += "(Destroy)" + dat += "
    " + + dat += "

    Drones

    " + var/drones = 0 + for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) + if(D.hacked) + continue + if(istype(D,/mob/living/simple_animal/drone/derelict) && D.loc.z != src.loc.z) // Yogs -- Exempts derelict drones from being detonated while not on the same Z-level as the RD + continue // Yogs + if(drones) + dat += "

    " + else + dat += "
    " + drones++ + dat += "Name: [D.name]
    " + dat += "Status: [D.stat ? "Not Responding" : "Normal"]
    " + dat += "Unit Controls: " + dat += "(Destroy)" + + if(drones) + dat += "
    " + else + dat += "
    No drone units detected within access parameters.
    " + + var/window_height = min((300+((robot_count+drones) * 110)), 800) + + var/datum/browser/popup = new(user, "computer", "Robotics Control Console", 375, window_height) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/computer/robotics/Topic(href, href_list) + . = ..() + if(.) + return + + if (href_list["temp"]) + temp = null + + else if (href_list["killbot"]) + if(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(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.lockcharge ? "lock down" : "release"] [R.name]?") in list("Confirm", "Abort") + if(choice == "Confirm" && can_control(usr, R) && !..()) + message_admins("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!") + log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!") + R.SetLockdown(!R.lockcharge) + to_chat(R, "[!R.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]") + if(R.connected_ai) + to_chat(R.connected_ai, "[!R.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [R.name]
    ") + + else + to_chat(usr, "Access Denied.") + + 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(TRUE) + + 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(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, TRUE, D) + s.start() + D.visible_message("\the [D] self destructs!") + D.gib() + + + updateUsrDialog() diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index 676924433535..818f85cbf250 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -1,87 +1,87 @@ -/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() - ..() - if(stat & (NOPOWER|BROKEN)) - return - var/active_alarms = FALSE - for(var/cat in alarms) - var/list/L = alarms[cat] - if(L.len) - active_alarms = TRUE - if(active_alarms) - add_overlay("alert:2") +/obj/machinery/computer/station_alert + name = "station alert console" + desc = "Used to access the station's automated alert system." + icon_screen = "alert:0" + icon_keyboard = "atmos_key" + circuit = /obj/item/circuitboard/computer/stationalert + 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() + ..() + if(stat & (NOPOWER|BROKEN)) + return + var/active_alarms = FALSE + for(var/cat in alarms) + var/list/L = alarms[cat] + if(L.len) + active_alarms = TRUE + if(active_alarms) + add_overlay("alert:2") diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 96a36e4cb11b..4ca3348fbb2d 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -1,278 +1,278 @@ -/obj/structure/frame - name = "frame" - icon = 'icons/obj/stock_parts.dmi' - icon_state = "box_0" - density = TRUE - max_integrity = 250 - var/obj/item/circuitboard/machine/circuit = null - var/state = 1 - -/obj/structure/frame/examine(user) - . = ..() - if(circuit) - . += "It has \a [circuit] installed." - - -/obj/structure/frame/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc, 5) - if(circuit) - circuit.forceMove(loc) - circuit = null - qdel(src) - - -/obj/structure/frame/machine - name = "machine frame" - var/list/components = null - var/list/req_components = null - var/list/req_component_names = null // user-friendly names of components - -/obj/structure/frame/machine/examine(user) - . = ..() - if(state == 3 && req_components && req_component_names) - var/hasContent = 0 - var/requires = "It requires" - - for(var/i = 1 to req_components.len) - var/tname = req_components[i] - var/amt = req_components[tname] - if(amt == 0) - continue - var/use_and = i == req_components.len - requires += "[(hasContent ? (use_and ? ", and" : ",") : "")] [amt] [amt == 1 ? req_component_names[tname] : "[req_component_names[tname]]\s"]" - hasContent = 1 - - if(hasContent) - . += "[requires]." - else - . += "It does not require any more components." - -/obj/structure/frame/machine/proc/update_namelist() - if(!req_components) - return - - req_component_names = new() - for(var/tname in req_components) - if(ispath(tname, /obj/item/stack)) - var/obj/item/stack/S = tname - var/singular_name = initial(S.singular_name) - if(singular_name) - req_component_names[tname] = singular_name - else - req_component_names[tname] = initial(S.name) - else - var/obj/O = tname - req_component_names[tname] = initial(O.name) - -/obj/structure/frame/machine/proc/get_req_components_amt() - var/amt = 0 - for(var/path in req_components) - amt += req_components[path] - return amt - -/obj/structure/frame/machine/attackby(obj/item/P, mob/user, params) - switch(state) - if(1) - if(istype(P, /obj/item/circuitboard/machine)) - to_chat(user, "The frame needs wiring first!") - return - else if(istype(P, /obj/item/circuitboard)) - to_chat(user, "This frame does not accept circuit boards of this type!") - return - if(istype(P, /obj/item/stack/cable_coil)) - if(!P.tool_start_check(user, amount=5)) - return - - to_chat(user, "You start to add cables to the frame...") - if(P.use_tool(src, user, 20, volume=50, amount=5)) - to_chat(user, "You add cables to the frame.") - state = 2 - icon_state = "box_1" - - return - if(P.tool_behaviour == TOOL_SCREWDRIVER && !anchored) - user.visible_message("[user] disassembles the frame.", \ - "You start to disassemble the frame...", "You hear banging and clanking.") - if(P.use_tool(src, user, 40, volume=50)) - if(state == 1) - to_chat(user, "You disassemble the frame.") - var/obj/item/stack/sheet/metal/M = new (loc, 5) - M.add_fingerprint(user) - qdel(src) - return - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start [anchored ? "un" : ""]securing [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(P.tool_behaviour == TOOL_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(P.tool_behaviour == TOOL_WIRECUTTER) - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = 1 - icon_state = "box_0" - new /obj/item/stack/cable_coil(drop_location(), 5) - return - - if(3) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - state = 2 - circuit.forceMove(drop_location()) - components.Remove(circuit) - circuit = null - if(components.len == 0) - to_chat(user, "You remove the circuit board.") - else - to_chat(user, "You remove the circuit board and other components.") - for(var/atom/movable/AM in components) - AM.forceMove(drop_location()) - desc = initial(desc) - req_components = null - components = null - icon_state = "box_1" - return - - if(P.tool_behaviour == TOOL_WRENCH && !circuit.needs_anchored) - to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") - if(P.use_tool(src, user, 40, volume=75)) - to_chat(user, "You [anchored ? "un" : ""]secure [name].") - setAnchored(!anchored) - return - - if(P.tool_behaviour == TOOL_SCREWDRIVER) - var/component_check = 1 - for(var/R in req_components) - if(req_components[R] > 0) - component_check = 0 - break - if(component_check) - P.play_tool_sound(src) - var/obj/machinery/new_machine = new circuit.build_path(loc) - 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(P.tool_behaviour == TOOL_SCREWDRIVER && !anchored) + user.visible_message("[user] disassembles the frame.", \ + "You start to disassemble the frame...", "You hear banging and clanking.") + if(P.use_tool(src, user, 40, volume=50)) + if(state == 1) + to_chat(user, "You disassemble the frame.") + var/obj/item/stack/sheet/metal/M = new (loc, 5) + M.add_fingerprint(user) + qdel(src) + return + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start [anchored ? "un" : ""]securing [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(P.tool_behaviour == TOOL_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(P.tool_behaviour == TOOL_WIRECUTTER) + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = 1 + icon_state = "box_0" + new /obj/item/stack/cable_coil(drop_location(), 5) + return + + if(3) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + state = 2 + circuit.forceMove(drop_location()) + components.Remove(circuit) + circuit = null + if(components.len == 0) + to_chat(user, "You remove the circuit board.") + else + to_chat(user, "You remove the circuit board and other components.") + for(var/atom/movable/AM in components) + AM.forceMove(drop_location()) + desc = initial(desc) + req_components = null + components = null + icon_state = "box_1" + return + + if(P.tool_behaviour == TOOL_WRENCH && !circuit.needs_anchored) + to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") + if(P.use_tool(src, user, 40, volume=75)) + to_chat(user, "You [anchored ? "un" : ""]secure [name].") + setAnchored(!anchored) + return + + if(P.tool_behaviour == TOOL_SCREWDRIVER) + var/component_check = 1 + for(var/R in req_components) + if(req_components[R] > 0) + component_check = 0 + break + if(component_check) + P.play_tool_sound(src) + var/obj/machinery/new_machine = new circuit.build_path(loc) + 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 cf46e6b0fd33..42649a9ff257 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 85d8b5acf135..61f65c1ed137 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -1,259 +1,259 @@ -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Small Fonts" -#define MAX_TIMER 12000 //yogs - changed 9000 to 12000 - -#define PRESET_SHORT 1800 //yogs - changed 1200 to 1800 -#define PRESET_MEDIUM 3000 //yogs - changed 1800 to 3000 -#define PRESET_LONG 6000 //yogs - changed 3000 to 6000 - - - -/////////////////////////////////////////////////////////////////////////////////////////////// -// Brig Door control displays. -// Description: This is a controls the timer for the brig doors, displays the timer on itself and -// has a popup window when used, allowing to set the timer. -// Code Notes: Combination of old brigdoor.dm code from rev4407 and the status_display.dm code -// Date: 01/September/2010 -// Programmer: Veryinky -///////////////////////////////////////////////////////////////////////////////////////////////// -/obj/machinery/door_timer - name = "door timer" - icon = 'icons/obj/status_display.dmi' - icon_state = "frame" - desc = "A remote control for a door." - req_access = list(ACCESS_SECURITY) - density = FALSE - var/id = null // id of linked machinery/lockers - - var/activation_time = 0 - var/timer_duration = 0 - - var/timing = FALSE // boolean, true/1 timer is on, false/0 means it's not timing - var/list/obj/machinery/targets = list() - var/obj/item/radio/Radio //needed to send messages to sec radio - - maptext_height = 26 - maptext_width = 32 - maptext_y = -1 - -/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.time - 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.time - timing = TRUE - - for(var/obj/machinery/door/window/brigdoor/door in targets) - if(door.density) - continue - INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/close) - - for(var/obj/structure/closet/secure_closet/brig/C in targets) - if(C.broken) - continue - if(C.opened && !C.close()) - continue - C.locked = TRUE - C.update_icon() - return 1 - - -/obj/machinery/door_timer/proc/timer_end(forced = FALSE) - - if(stat & (NOPOWER|BROKEN)) - return 0 - - if(!forced) - Radio.set_frequency(FREQ_SECURITY) - Radio.talk_into(src, "Timer has expired. Releasing prisoner.", FREQ_SECURITY) - - timing = FALSE - activation_time = null - set_timer(0) - update_icon() - - for(var/obj/machinery/door/window/brigdoor/door in targets) - if(!door.density) - continue - INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/open) - - for(var/obj/structure/closet/secure_closet/brig/C in targets) - if(C.broken) - continue - if(C.opened) - continue - C.locked = FALSE - C.update_icon() - - return 1 - - -/obj/machinery/door_timer/proc/time_left(seconds = FALSE) - . = max(0,timer_duration - (activation_time ? world.time - activation_time : 0)) - if(seconds) - . /= 10 - -/obj/machinery/door_timer/proc/set_timer(value) - var/new_time = CLAMP(value,0,MAX_TIMER) - . = new_time == timer_duration //return 1 on no change - timer_duration = new_time - -/obj/machinery/door_timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "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) - line1 = uppertext(line1) - line2 = uppertext(line2) - var/new_text = {"
    [line1]
    [line2]
    "} - if(maptext != new_text) - maptext = new_text - -/obj/machinery/door_timer/ui_data() - var/list/data = list() - var/time_left = time_left(seconds = TRUE) - data["seconds"] = round(time_left % 60) - data["minutes"] = round((time_left - data["seconds"]) / 60) - data["timing"] = timing - data["flash_charging"] = FALSE - for(var/obj/machinery/flasher/F in targets) - if(F.last_flash && (F.last_flash + 150) > world.time) - data["flash_charging"] = TRUE - break - return data - - -/obj/machinery/door_timer/ui_act(action, params) - if(..()) - return - . = TRUE - - if(!allowed(usr)) - to_chat(usr, "Access denied.") - return FALSE - - switch(action) - if("time") - var/value = text2num(params["adjust"]) - if(value) - . = set_timer(time_left()+value) - if("start") - timer_start() - if("stop") - timer_end(forced = TRUE) - if("flash") - for(var/obj/machinery/flasher/F in targets) - F.flash() - if("preset") - var/preset = params["preset"] - var/preset_time = time_left() - switch(preset) - if("short") - preset_time = PRESET_SHORT - if("medium") - preset_time = PRESET_MEDIUM - if("long") - preset_time = PRESET_LONG - . = set_timer(preset_time) - if(timing) - activation_time = world.time - else - . = FALSE - - -#undef PRESET_SHORT -#undef PRESET_MEDIUM -#undef PRESET_LONG - -#undef MAX_TIMER -#undef FONT_SIZE -#undef FONT_COLOR -#undef FONT_STYLE -#undef CHARS_PER_LINE +#define CHARS_PER_LINE 5 +#define FONT_SIZE "5pt" +#define FONT_COLOR "#09f" +#define FONT_STYLE "Small Fonts" +#define MAX_TIMER 12000 //yogs - changed 9000 to 12000 + +#define PRESET_SHORT 1800 //yogs - changed 1200 to 1800 +#define PRESET_MEDIUM 3000 //yogs - changed 1800 to 3000 +#define PRESET_LONG 6000 //yogs - changed 3000 to 6000 + + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Brig Door control displays. +// Description: This is a controls the timer for the brig doors, displays the timer on itself and +// has a popup window when used, allowing to set the timer. +// Code Notes: Combination of old brigdoor.dm code from rev4407 and the status_display.dm code +// Date: 01/September/2010 +// Programmer: Veryinky +///////////////////////////////////////////////////////////////////////////////////////////////// +/obj/machinery/door_timer + name = "door timer" + icon = 'icons/obj/status_display.dmi' + icon_state = "frame" + desc = "A remote control for a door." + req_access = list(ACCESS_SECURITY) + density = FALSE + var/id = null // id of linked machinery/lockers + + var/activation_time = 0 + var/timer_duration = 0 + + var/timing = FALSE // boolean, true/1 timer is on, false/0 means it's not timing + var/list/obj/machinery/targets = list() + var/obj/item/radio/Radio //needed to send messages to sec radio + + maptext_height = 26 + maptext_width = 32 + maptext_y = -1 + +/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.time - 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.time + timing = TRUE + + for(var/obj/machinery/door/window/brigdoor/door in targets) + if(door.density) + continue + INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/close) + + for(var/obj/structure/closet/secure_closet/brig/C in targets) + if(C.broken) + continue + if(C.opened && !C.close()) + continue + C.locked = TRUE + C.update_icon() + return 1 + + +/obj/machinery/door_timer/proc/timer_end(forced = FALSE) + + if(stat & (NOPOWER|BROKEN)) + return 0 + + if(!forced) + Radio.set_frequency(FREQ_SECURITY) + Radio.talk_into(src, "Timer has expired. Releasing prisoner.", FREQ_SECURITY) + + timing = FALSE + activation_time = null + set_timer(0) + update_icon() + + for(var/obj/machinery/door/window/brigdoor/door in targets) + if(!door.density) + continue + INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/open) + + for(var/obj/structure/closet/secure_closet/brig/C in targets) + if(C.broken) + continue + if(C.opened) + continue + C.locked = FALSE + C.update_icon() + + return 1 + + +/obj/machinery/door_timer/proc/time_left(seconds = FALSE) + . = max(0,timer_duration - (activation_time ? world.time - activation_time : 0)) + if(seconds) + . /= 10 + +/obj/machinery/door_timer/proc/set_timer(value) + var/new_time = CLAMP(value,0,MAX_TIMER) + . = new_time == timer_duration //return 1 on no change + timer_duration = new_time + +/obj/machinery/door_timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "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) + line1 = uppertext(line1) + line2 = uppertext(line2) + var/new_text = {"
    [line1]
    [line2]
    "} + if(maptext != new_text) + maptext = new_text + +/obj/machinery/door_timer/ui_data() + var/list/data = list() + var/time_left = time_left(seconds = TRUE) + data["seconds"] = round(time_left % 60) + data["minutes"] = round((time_left - data["seconds"]) / 60) + data["timing"] = timing + data["flash_charging"] = FALSE + for(var/obj/machinery/flasher/F in targets) + if(F.last_flash && (F.last_flash + 150) > world.time) + data["flash_charging"] = TRUE + break + return data + + +/obj/machinery/door_timer/ui_act(action, params) + if(..()) + return + . = TRUE + + if(!allowed(usr)) + to_chat(usr, "Access denied.") + return FALSE + + switch(action) + if("time") + var/value = text2num(params["adjust"]) + if(value) + . = set_timer(time_left()+value) + if("start") + timer_start() + if("stop") + timer_end(forced = TRUE) + if("flash") + for(var/obj/machinery/flasher/F in targets) + F.flash() + if("preset") + var/preset = params["preset"] + var/preset_time = time_left() + switch(preset) + if("short") + preset_time = PRESET_SHORT + if("medium") + preset_time = PRESET_MEDIUM + if("long") + preset_time = PRESET_LONG + . = set_timer(preset_time) + if(timing) + activation_time = world.time + else + . = FALSE + + +#undef PRESET_SHORT +#undef PRESET_MEDIUM +#undef PRESET_LONG + +#undef MAX_TIMER +#undef FONT_SIZE +#undef FONT_COLOR +#undef FONT_STYLE +#undef CHARS_PER_LINE diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index e9f42793008e..83a8b9613257 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -1,389 +1,389 @@ -/obj/machinery/door - name = "door" - desc = "It opens and closes." - icon = 'icons/obj/doors/Doorint.dmi' - icon_state = "door1" - opacity = 1 - density = TRUE - move_resist = MOVE_FORCE_VERY_STRONG - layer = OPEN_DOOR_LAYER - power_channel = 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 - damage_deflection = 10 - - interaction_flags_atom = INTERACT_ATOM_UI_INTERACT - - var/secondsElectrified = MACHINE_NOT_ELECTRIFIED - var/shockedby - var/visible = TRUE - var/operating = FALSE - var/glass = FALSE - var/welded = FALSE - var/normalspeed = 1 - var/heat_proof = FALSE // For rglass-windowed airlocks and firedoors - var/emergency = FALSE // Emergency access override - var/sub_door = FALSE // true if it's meant to go under another door. - var/closingLayer = CLOSED_DOOR_LAYER - var/autoclose = FALSE //does it automatically close after some time - var/safe = TRUE //whether the door detects things and mobs in its way and reopen or crushes them. - var/locked = FALSE //whether the door is bolted or not. - var/assemblytype //the type of door frame to drop during deconstruction - var/datum/effect_system/spark_spread/spark_system - var/real_explosion_block //ignore this, just use explosion_block - var/red_alert_access = FALSE //if TRUE, this door will always open on red alert - var/poddoor = FALSE - var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access - -/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 - - if(isitem(AM)) - var/obj/item/I = AM - if(!density || (I.w_class < WEIGHT_CLASS_NORMAL && !LAZYLEN(I.GetAccess()))) - return - if(check_access(I)) - open() - else - do_animate("deny") - return - -/obj/machinery/door/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/machinery/door/CanPass(atom/movable/mover, turf/target) - if(istype(mover)) //yogs start - if(mover.pass_flags & PASSGLASS) - return !opacity - else if(mover.pass_flags & PASSDOOR) - return TRUE //yogs end - 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 TRUE - if(density) - do_animate("deny") - -/obj/machinery/door/allowed(mob/M) - if(emergency) - return TRUE - if(unrestricted_side(M)) - return TRUE - return ..() - -/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter) - return get_dir(src, M) & unres_sides - -/obj/machinery/door/proc/try_to_weld(obj/item/weldingtool/W, mob/user) - return - -/obj/machinery/door/proc/try_to_crowbar(obj/item/I, mob/user) - return - -/obj/machinery/door/attackby(obj/item/I, mob/user, params) - if(user.a_intent != INTENT_HARM && (I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/twohanded/fireaxe))) - try_to_crowbar(I, user) - return 1 - else if(I.tool_behaviour == TOOL_WELDER) - 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/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 == MACHINE_NOT_ELECTRIFIED) - secondsElectrified = MACHINE_ELECTRIFIED_PERMANENT - LAZYADD(shockedby, "\[[time_stamp()]\]EM Pulse") - addtimer(CALLBACK(src, .proc/unelectrify), 300) - -/obj/machinery/door/proc/unelectrify() - secondsElectrified = MACHINE_NOT_ELECTRIFIED - -/obj/machinery/door/update_icon() - 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.Paralyze(100) - else if(ismonkey(L)) //For monkeys - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - L.Paralyze(100) - else //for simple_animals & borgs - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - var/turf/location = get_turf(src) - //add_blood doesn't work for borgs/xenos, but add_blood_floor does. - L.add_splatter_floor(location) - 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 + move_resist = MOVE_FORCE_VERY_STRONG + 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 + damage_deflection = 10 + + interaction_flags_atom = INTERACT_ATOM_UI_INTERACT + + var/secondsElectrified = MACHINE_NOT_ELECTRIFIED + var/shockedby + var/visible = TRUE + var/operating = FALSE + var/glass = FALSE + var/welded = FALSE + var/normalspeed = 1 + var/heat_proof = FALSE // For rglass-windowed airlocks and firedoors + var/emergency = FALSE // Emergency access override + var/sub_door = FALSE // true if it's meant to go under another door. + var/closingLayer = CLOSED_DOOR_LAYER + var/autoclose = FALSE //does it automatically close after some time + var/safe = TRUE //whether the door detects things and mobs in its way and reopen or crushes them. + var/locked = FALSE //whether the door is bolted or not. + var/assemblytype //the type of door frame to drop during deconstruction + var/datum/effect_system/spark_spread/spark_system + var/real_explosion_block //ignore this, just use explosion_block + var/red_alert_access = FALSE //if TRUE, this door will always open on red alert + var/poddoor = FALSE + var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access + +/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 + + if(isitem(AM)) + var/obj/item/I = AM + if(!density || (I.w_class < WEIGHT_CLASS_NORMAL && !LAZYLEN(I.GetAccess()))) + return + if(check_access(I)) + open() + else + do_animate("deny") + return + +/obj/machinery/door/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/machinery/door/CanPass(atom/movable/mover, turf/target) + if(istype(mover)) //yogs start + if(mover.pass_flags & PASSGLASS) + return !opacity + else if(mover.pass_flags & PASSDOOR) + return TRUE //yogs end + 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 TRUE + if(density) + do_animate("deny") + +/obj/machinery/door/allowed(mob/M) + if(emergency) + return TRUE + if(unrestricted_side(M)) + return TRUE + return ..() + +/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter) + return get_dir(src, M) & unres_sides + +/obj/machinery/door/proc/try_to_weld(obj/item/weldingtool/W, mob/user) + return + +/obj/machinery/door/proc/try_to_crowbar(obj/item/I, mob/user) + return + +/obj/machinery/door/attackby(obj/item/I, mob/user, params) + if(user.a_intent != INTENT_HARM && (I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/twohanded/fireaxe))) + try_to_crowbar(I, user) + return 1 + else if(I.tool_behaviour == TOOL_WELDER) + 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/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 == MACHINE_NOT_ELECTRIFIED) + secondsElectrified = MACHINE_ELECTRIFIED_PERMANENT + LAZYADD(shockedby, "\[[time_stamp()]\]EM Pulse") + addtimer(CALLBACK(src, .proc/unelectrify), 300) + +/obj/machinery/door/proc/unelectrify() + secondsElectrified = MACHINE_NOT_ELECTRIFIED + +/obj/machinery/door/update_icon() + 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.Paralyze(100) + else if(ismonkey(L)) //For monkeys + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + L.Paralyze(100) + else //for simple_animals & borgs + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + var/turf/location = get_turf(src) + //add_blood doesn't work for borgs/xenos, but add_blood_floor does. + L.add_splatter_floor(location) + 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 d5b4ceddaf6c..a8416783d794 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -1,462 +1,462 @@ -#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(C.tool_behaviour == TOOL_WRENCH) - if(boltslocked) - to_chat(user, "There are screws locking the bolts in place!") - return - C.play_tool_sound(src) - user.visible_message("[user] starts undoing [src]'s bolts...", \ - "You start unfastening [src]'s floor bolts...") - if(!C.use_tool(src, user, 50)) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 1) - user.visible_message("[user] unfastens [src]'s bolts.", \ - "You undo [src]'s floor bolts.") - deconstruct(TRUE) - return - if(C.tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message("[user] [boltslocked ? "unlocks" : "locks"] [src]'s bolts.", \ - "You [boltslocked ? "unlock" : "lock"] [src]'s floor bolts.") - C.play_tool_sound(src) - boltslocked = !boltslocked - return - - return ..() - -/obj/machinery/door/firedoor/try_to_activate_door(mob/user) - return - -/obj/machinery/door/firedoor/try_to_weld(obj/item/weldingtool/W, mob/user) - if(!W.tool_start_check(user, amount=0)) - return - user.visible_message("[user] starts [welded ? "unwelding" : "welding"] [src].", "You start welding [src].") - if(W.use_tool(src, user, 40, volume=50)) - welded = !welded - to_chat(user, "[user] [welded?"welds":"unwelds"] [src].", "You [welded ? "weld" : "unweld"] [src].") - update_icon() - -/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user) - if(welded || operating) - return - - if(density) - open() - else - close() - -/obj/machinery/door/firedoor/attack_ai(mob/user) - add_fingerprint(user) - if(welded || operating || 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" - custom_price = 5 - 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(C.tool_behaviour == TOOL_CROWBAR) - C.play_tool_sound(src) - user.visible_message("[user] starts prying something out from [src]...", \ - "You begin prying out the wire cover...") - if(!C.use_tool(src, user, 50)) - return - if(constructionStep != CONSTRUCTION_PANEL_OPEN) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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(C.tool_behaviour == TOOL_WRENCH) - if(locate(/obj/machinery/door/firedoor) in get_turf(src)) - to_chat(user, "There's already a firelock there.") - return - C.play_tool_sound(src) - user.visible_message("[user] starts bolting down [src]...", \ - "You begin bolting [src]...") - if(!C.use_tool(src, user, 30)) - return - if(locate(/obj/machinery/door/firedoor) in get_turf(src)) - return - user.visible_message("[user] finishes the firelock.", \ - "You finish the firelock.") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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(C.tool_behaviour == TOOL_WIRECUTTER) - C.play_tool_sound(src) - user.visible_message("[user] starts cutting the wires from [src]...", \ - "You begin removing [src]'s wires...") - if(!C.use_tool(src, user, 60)) - return - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - user.visible_message("[user] removes the wires from [src].", \ - "You remove the wiring from [src], exposing the circuit board.") - new/obj/item/stack/cable_coil(get_turf(src), 5) - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return - if(C.tool_behaviour == TOOL_CROWBAR) - C.play_tool_sound(src) - user.visible_message("[user] starts prying a metal plate into [src]...", \ - "You begin prying the cover plate back onto [src]...") - if(!C.use_tool(src, user, 80)) - return - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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(C.tool_behaviour == TOOL_CROWBAR) - user.visible_message("[user] begins removing the circuit board from [src]...", \ - "You begin prying out the circuit board from [src]...") - if(!C.use_tool(src, user, 50, volume=50)) - return - if(constructionStep != CONSTRUCTION_GUTTED) - return - user.visible_message("[user] removes [src]'s circuit board.", \ - "You remove the circuit board from [src].") - new /obj/item/electronics/firelock(drop_location()) - constructionStep = CONSTRUCTION_NOCIRCUIT - update_icon() - return - if(istype(C, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/B = C - if(B.get_amount() < 5) - to_chat(user, "You need more wires to add wiring to [src].") - return - user.visible_message("[user] begins wiring [src]...", \ - "You begin adding wires to [src]...") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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(C.tool_behaviour == TOOL_WELDER) - if(!C.tool_start_check(user, amount=1)) - return - user.visible_message("[user] begins cutting apart [src]'s frame...", \ - "You begin slicing [src] apart...") - - if(C.use_tool(src, user, 40, volume=50, amount=1)) - if(constructionStep != CONSTRUCTION_NOCIRCUIT) - return - user.visible_message("[user] cuts apart [src]!", \ - "You cut [src] into metal.") - var/turf/T = get_turf(src) - new /obj/item/stack/sheet/metal(T, 3) - if(reinforced) - new /obj/item/stack/sheet/plasteel(T, 2) - qdel(src) - return - if(istype(C, /obj/item/electronics/firelock)) - user.visible_message("[user] starts adding [C] to [src]...", \ - "You begin adding a circuit board to [src]...") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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/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(C.tool_behaviour == TOOL_WRENCH) + if(boltslocked) + to_chat(user, "There are screws locking the bolts in place!") + return + C.play_tool_sound(src) + user.visible_message("[user] starts undoing [src]'s bolts...", \ + "You start unfastening [src]'s floor bolts...") + if(!C.use_tool(src, user, 50)) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 1) + user.visible_message("[user] unfastens [src]'s bolts.", \ + "You undo [src]'s floor bolts.") + deconstruct(TRUE) + return + if(C.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user] [boltslocked ? "unlocks" : "locks"] [src]'s bolts.", \ + "You [boltslocked ? "unlock" : "lock"] [src]'s floor bolts.") + C.play_tool_sound(src) + boltslocked = !boltslocked + return + + return ..() + +/obj/machinery/door/firedoor/try_to_activate_door(mob/user) + return + +/obj/machinery/door/firedoor/try_to_weld(obj/item/weldingtool/W, mob/user) + if(!W.tool_start_check(user, amount=0)) + return + user.visible_message("[user] starts [welded ? "unwelding" : "welding"] [src].", "You start welding [src].") + if(W.use_tool(src, user, 40, volume=50)) + welded = !welded + to_chat(user, "[user] [welded?"welds":"unwelds"] [src].", "You [welded ? "weld" : "unweld"] [src].") + update_icon() + +/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user) + if(welded || operating) + return + + if(density) + open() + else + close() + +/obj/machinery/door/firedoor/attack_ai(mob/user) + add_fingerprint(user) + if(welded || operating || 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" + custom_price = 5 + 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(C.tool_behaviour == TOOL_CROWBAR) + C.play_tool_sound(src) + user.visible_message("[user] starts prying something out from [src]...", \ + "You begin prying out the wire cover...") + if(!C.use_tool(src, user, 50)) + return + if(constructionStep != CONSTRUCTION_PANEL_OPEN) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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(C.tool_behaviour == TOOL_WRENCH) + if(locate(/obj/machinery/door/firedoor) in get_turf(src)) + to_chat(user, "There's already a firelock there.") + return + C.play_tool_sound(src) + user.visible_message("[user] starts bolting down [src]...", \ + "You begin bolting [src]...") + if(!C.use_tool(src, user, 30)) + return + if(locate(/obj/machinery/door/firedoor) in get_turf(src)) + return + user.visible_message("[user] finishes the firelock.", \ + "You finish the firelock.") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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(C.tool_behaviour == TOOL_WIRECUTTER) + C.play_tool_sound(src) + user.visible_message("[user] starts cutting the wires from [src]...", \ + "You begin removing [src]'s wires...") + if(!C.use_tool(src, user, 60)) + return + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + user.visible_message("[user] removes the wires from [src].", \ + "You remove the wiring from [src], exposing the circuit board.") + new/obj/item/stack/cable_coil(get_turf(src), 5) + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return + if(C.tool_behaviour == TOOL_CROWBAR) + C.play_tool_sound(src) + user.visible_message("[user] starts prying a metal plate into [src]...", \ + "You begin prying the cover plate back onto [src]...") + if(!C.use_tool(src, user, 80)) + return + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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(C.tool_behaviour == TOOL_CROWBAR) + user.visible_message("[user] begins removing the circuit board from [src]...", \ + "You begin prying out the circuit board from [src]...") + if(!C.use_tool(src, user, 50, volume=50)) + return + if(constructionStep != CONSTRUCTION_GUTTED) + return + user.visible_message("[user] removes [src]'s circuit board.", \ + "You remove the circuit board from [src].") + new /obj/item/electronics/firelock(drop_location()) + constructionStep = CONSTRUCTION_NOCIRCUIT + update_icon() + return + if(istype(C, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/B = C + if(B.get_amount() < 5) + to_chat(user, "You need more wires to add wiring to [src].") + return + user.visible_message("[user] begins wiring [src]...", \ + "You begin adding wires to [src]...") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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(C.tool_behaviour == TOOL_WELDER) + if(!C.tool_start_check(user, amount=1)) + return + user.visible_message("[user] begins cutting apart [src]'s frame...", \ + "You begin slicing [src] apart...") + + if(C.use_tool(src, user, 40, volume=50, amount=1)) + if(constructionStep != CONSTRUCTION_NOCIRCUIT) + return + user.visible_message("[user] cuts apart [src]!", \ + "You cut [src] into metal.") + var/turf/T = get_turf(src) + new /obj/item/stack/sheet/metal(T, 3) + if(reinforced) + new /obj/item/stack/sheet/plasteel(T, 2) + qdel(src) + return + if(istype(C, /obj/item/electronics/firelock)) + user.visible_message("[user] starts adding [C] to [src]...", \ + "You begin adding a circuit board to [src]...") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 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/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 ebc1e94e974c..af95ee230cae 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 7ee9891c4c9f..bd58649b9765 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 2e7e7d5cba73..4ff4bd15d714 100644 --- a/code/game/machinery/doors/unpowered.dm +++ b/code/game/machinery/doors/unpowered.dm @@ -1,25 +1,25 @@ -/obj/machinery/door/unpowered - -/obj/machinery/door/unpowered/Bumped(atom/movable/AM) - if(src.locked) - return - ..() - return - - -/obj/machinery/door/unpowered/attackby(obj/item/I, mob/user, params) - if(locked) - return - else - return ..() - -/obj/machinery/door/unpowered/emag_act() - return - -/obj/machinery/door/unpowered/shuttle - icon = 'icons/turf/shuttle.dmi' - name = "door" - icon_state = "door1" - opacity = 1 - density = TRUE +/obj/machinery/door/unpowered + +/obj/machinery/door/unpowered/Bumped(atom/movable/AM) + if(src.locked) + return + ..() + return + + +/obj/machinery/door/unpowered/attackby(obj/item/I, mob/user, params) + if(locked) + return + else + return ..() + +/obj/machinery/door/unpowered/emag_act() + return + +/obj/machinery/door/unpowered/shuttle + icon = 'icons/turf/shuttle.dmi' + name = "door" + icon_state = "door1" + opacity = 1 + density = TRUE explosion_block = 1 \ No newline at end of file diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 75b435b9453b..83f739e8a06a 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -1,540 +1,540 @@ -/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(req_access && req_access.len) - icon_state = "[icon_state]" - base_state = icon_state - for(var/i in 1 to shards) - debris += new /obj/item/shard(src) - if(rods) - debris += new /obj/item/stack/rods(src, rods) - if(cable) - debris += new /obj/item/stack/cable_coil(src, cable) - -/obj/machinery/door/window/ComponentInitialize() - . = ..() - AddComponent(/datum/component/ntnet_interface) - -/obj/machinery/door/window/Destroy() - density = FALSE - QDEL_LIST(debris) - if(obj_integrity == 0) - playsound(src, "shatter", 70, 1) - electronics = null - return ..() - -/obj/machinery/door/window/update_icon() - if(density) - icon_state = base_state - else - icon_state = "[base_state]open" - -/obj/machinery/door/window/proc/open_and_close() - if(!open()) - return - autoclose = TRUE - if(check_access(null)) - sleep(50) - else //secure doors close faster - sleep(20) - if(!density && autoclose) //did someone change state while we slept? - close() - -/obj/machinery/door/window/Bumped(atom/movable/AM) - if( operating || !density ) - return - if (!( ismob(AM) )) - if(ismecha(AM)) - var/obj/mecha/mecha = AM - if(mecha.occupant && allowed(mecha.occupant)) - open_and_close() - else - do_animate("deny") - return - if (!( SSticker )) - return - var/mob/M = AM - if(M.restrained() || ((isdrone(M) || iscyborg(M)) && M.stat)) - return - bumpopen(M) - -/obj/machinery/door/window/bumpopen(mob/user) - if( operating || !density ) - return - add_fingerprint(user) - if(!requiresID()) - user = null - - if(allowed(user)) - open_and_close() - else - do_animate("deny") - return - -/obj/machinery/door/window/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=FALSE) - if (operating) //doors can still open when emag-disabled - return 0 - if(!forced) - if(!hasPower()) - return 0 - if(forced < 2) - if(obj_flags & EMAGGED) - return 0 - if(!operating) //in case of emag - operating = TRUE - do_animate("opening") - playsound(src, 'sound/machines/windowdoor.ogg', 100, 1) - icon_state ="[base_state]open" - sleep(10) - - density = FALSE - air_update_turf(1) - update_freelook_sight() - - if(operating == 1) //emag again - operating = FALSE - return 1 - -/obj/machinery/door/window/close(forced=FALSE) - if (operating) - return 0 - if(!forced) - if(!hasPower()) - return 0 - if(forced < 2) - if(obj_flags & EMAGGED) - return 0 - operating = TRUE - do_animate("closing") - playsound(src, 'sound/machines/windowdoor.ogg', 100, 1) - icon_state = base_state - - density = TRUE - air_update_turf(1) - update_freelook_sight() - sleep(10) - - operating = FALSE - return 1 - -/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src, 'sound/effects/glasshit.ogg', 90, 1) - if(BURN) - playsound(src, '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)) - obj_flags |= EMAGGED - operating = TRUE - flick("[base_state]spark", src) - playsound(src, "sparks", 75, 1) - sleep(6) - operating = FALSE - desc += "
    Its access panel is smoking slightly." - open(2) - -/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) - - if(istype(I, /obj/item/airlock_scanner)) //yogs start - var/obj/item/airlock_scanner/S = I - S.show_access(src, user) //yogs end - - if(operating) - return - - add_fingerprint(user) - if(!(flags_1&NODECONSTRUCT_1)) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(density || operating) - to_chat(user, "You need to open the door to access the maintenance panel!") - return - I.play_tool_sound(src) - panel_open = !panel_open - to_chat(user, "You [panel_open ? "open":"close"] the maintenance panel of the [name].") - return - - if(I.tool_behaviour == TOOL_CROWBAR) - if(panel_open && !density && !operating) - user.visible_message("[user] removes the electronics from the [name].", \ - "You start to remove electronics from the [name]...") - if(I.use_tool(src, user, 40, volume=50)) - if(panel_open && !density && !operating && loc) - var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(loc) - switch(base_state) - if("left") - WA.facing = "l" - if("right") - WA.facing = "r" - if("leftsecure") - WA.facing = "l" - WA.secure = TRUE - if("rightsecure") - WA.facing = "r" - WA.secure = TRUE - WA.setAnchored(TRUE) - WA.state= "02" - WA.setDir(dir) - WA.ini_dir = dir - WA.update_icon() - WA.created_name = name - - if(obj_flags & EMAGGED) - to_chat(user, "You discard the damaged electronics.") - qdel(src) - return - - to_chat(user, "You remove the airlock electronics.") - - var/obj/item/electronics/airlock/ae - if(!electronics) - ae = new/obj/item/electronics/airlock(drop_location()) - if(req_one_access) - ae.one_access = 1 - ae.accesses = req_one_access - else - ae.accesses = req_access - else - ae = electronics - electronics = null - ae.forceMove(drop_location()) - - qdel(src) - return - return ..() - -/obj/machinery/door/window/interact(mob/user) //for sillycones - try_to_activate_door(user) - -/obj/machinery/door/window/try_to_activate_door(mob/user) - if (..()) - autoclose = FALSE - -/obj/machinery/door/window/try_to_crowbar(obj/item/I, mob/user) - if(!hasPower()) - if(density) - open(2) - else - close(2) - else - to_chat(user, "The door's motors resist your efforts to force it!") - -/obj/machinery/door/window/do_animate(animation) - switch(animation) - if("opening") - flick("[base_state]opening", src) - if("closing") - flick("[base_state]closing", src) - if("deny") - flick("[base_state]deny", src) - -/obj/machinery/door/window/check_access_ntnet(datum/netdata/data) - return !requiresID() || ..() - -/obj/machinery/door/window/ntnet_receive(datum/netdata/data) - // Check if the airlock is powered. - if(!hasPower()) - return - - // Check packet access level. - if(!check_access_ntnet(data)) - return - - // Handle received packet. - var/command = lowertext(data.data["data"]) - var/command_value = lowertext(data.data["data_secondary"]) - switch(command) - if("open") - if(command_value == "on" && !density) - return - - if(command_value == "off" && density) - return - - if(density) - INVOKE_ASYNC(src, .proc/open) - else - INVOKE_ASYNC(src, .proc/close) - if("touch") - INVOKE_ASYNC(src, .proc/open_and_close) - -/obj/machinery/door/window/brigdoor - name = "secure door" - icon_state = "leftsecure" - base_state = "leftsecure" - var/id = null - max_integrity = 300 //Stronger doors for prison (regular window door health is 200) - reinf = 1 - explosion_block = 1 - -/obj/machinery/door/window/brigdoor/security/cell - name = "cell door" - desc = "For keeping in criminal scum." - req_access = list(ACCESS_BRIG) - -/obj/machinery/door/window/brigdoor/security/holding - name = "holding cell door" - req_one_access = list(ACCESS_SEC_DOORS, ACCESS_LAWYER) //love for the lawyer - -/obj/machinery/door/window/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(req_access && req_access.len) + icon_state = "[icon_state]" + base_state = icon_state + for(var/i in 1 to shards) + debris += new /obj/item/shard(src) + if(rods) + debris += new /obj/item/stack/rods(src, rods) + if(cable) + debris += new /obj/item/stack/cable_coil(src, cable) + +/obj/machinery/door/window/ComponentInitialize() + . = ..() + AddComponent(/datum/component/ntnet_interface) + +/obj/machinery/door/window/Destroy() + density = FALSE + QDEL_LIST(debris) + if(obj_integrity == 0) + playsound(src, "shatter", 70, 1) + electronics = null + return ..() + +/obj/machinery/door/window/update_icon() + if(density) + icon_state = base_state + else + icon_state = "[base_state]open" + +/obj/machinery/door/window/proc/open_and_close() + if(!open()) + return + autoclose = TRUE + if(check_access(null)) + sleep(50) + else //secure doors close faster + sleep(20) + if(!density && autoclose) //did someone change state while we slept? + close() + +/obj/machinery/door/window/Bumped(atom/movable/AM) + if( operating || !density ) + return + if (!( ismob(AM) )) + if(ismecha(AM)) + var/obj/mecha/mecha = AM + if(mecha.occupant && allowed(mecha.occupant)) + open_and_close() + else + do_animate("deny") + return + if (!( SSticker )) + return + var/mob/M = AM + if(M.restrained() || ((isdrone(M) || iscyborg(M)) && M.stat)) + return + bumpopen(M) + +/obj/machinery/door/window/bumpopen(mob/user) + if( operating || !density ) + return + add_fingerprint(user) + if(!requiresID()) + user = null + + if(allowed(user)) + open_and_close() + else + do_animate("deny") + return + +/obj/machinery/door/window/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=FALSE) + if (operating) //doors can still open when emag-disabled + return 0 + if(!forced) + if(!hasPower()) + return 0 + if(forced < 2) + if(obj_flags & EMAGGED) + return 0 + if(!operating) //in case of emag + operating = TRUE + do_animate("opening") + playsound(src, 'sound/machines/windowdoor.ogg', 100, 1) + icon_state ="[base_state]open" + sleep(10) + + density = FALSE + air_update_turf(1) + update_freelook_sight() + + if(operating == 1) //emag again + operating = FALSE + return 1 + +/obj/machinery/door/window/close(forced=FALSE) + if (operating) + return 0 + if(!forced) + if(!hasPower()) + return 0 + if(forced < 2) + if(obj_flags & EMAGGED) + return 0 + operating = TRUE + do_animate("closing") + playsound(src, 'sound/machines/windowdoor.ogg', 100, 1) + icon_state = base_state + + density = TRUE + air_update_turf(1) + update_freelook_sight() + sleep(10) + + operating = FALSE + return 1 + +/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src, 'sound/effects/glasshit.ogg', 90, 1) + if(BURN) + playsound(src, '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)) + obj_flags |= EMAGGED + operating = TRUE + flick("[base_state]spark", src) + playsound(src, "sparks", 75, 1) + sleep(6) + operating = FALSE + desc += "
    Its access panel is smoking slightly." + open(2) + +/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) + + if(istype(I, /obj/item/airlock_scanner)) //yogs start + var/obj/item/airlock_scanner/S = I + S.show_access(src, user) //yogs end + + if(operating) + return + + add_fingerprint(user) + if(!(flags_1&NODECONSTRUCT_1)) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(density || operating) + to_chat(user, "You need to open the door to access the maintenance panel!") + return + I.play_tool_sound(src) + panel_open = !panel_open + to_chat(user, "You [panel_open ? "open":"close"] the maintenance panel of the [name].") + return + + if(I.tool_behaviour == TOOL_CROWBAR) + if(panel_open && !density && !operating) + user.visible_message("[user] removes the electronics from the [name].", \ + "You start to remove electronics from the [name]...") + if(I.use_tool(src, user, 40, volume=50)) + if(panel_open && !density && !operating && loc) + var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(loc) + switch(base_state) + if("left") + WA.facing = "l" + if("right") + WA.facing = "r" + if("leftsecure") + WA.facing = "l" + WA.secure = TRUE + if("rightsecure") + WA.facing = "r" + WA.secure = TRUE + WA.setAnchored(TRUE) + WA.state= "02" + WA.setDir(dir) + WA.ini_dir = dir + WA.update_icon() + WA.created_name = name + + if(obj_flags & EMAGGED) + to_chat(user, "You discard the damaged electronics.") + qdel(src) + return + + to_chat(user, "You remove the airlock electronics.") + + var/obj/item/electronics/airlock/ae + if(!electronics) + ae = new/obj/item/electronics/airlock(drop_location()) + if(req_one_access) + ae.one_access = 1 + ae.accesses = req_one_access + else + ae.accesses = req_access + else + ae = electronics + electronics = null + ae.forceMove(drop_location()) + + qdel(src) + return + return ..() + +/obj/machinery/door/window/interact(mob/user) //for sillycones + try_to_activate_door(user) + +/obj/machinery/door/window/try_to_activate_door(mob/user) + if (..()) + autoclose = FALSE + +/obj/machinery/door/window/try_to_crowbar(obj/item/I, mob/user) + if(!hasPower()) + if(density) + open(2) + else + close(2) + else + to_chat(user, "The door's motors resist your efforts to force it!") + +/obj/machinery/door/window/do_animate(animation) + switch(animation) + if("opening") + flick("[base_state]opening", src) + if("closing") + flick("[base_state]closing", src) + if("deny") + flick("[base_state]deny", src) + +/obj/machinery/door/window/check_access_ntnet(datum/netdata/data) + return !requiresID() || ..() + +/obj/machinery/door/window/ntnet_receive(datum/netdata/data) + // Check if the airlock is powered. + if(!hasPower()) + return + + // Check packet access level. + if(!check_access_ntnet(data)) + return + + // Handle received packet. + var/command = lowertext(data.data["data"]) + var/command_value = lowertext(data.data["data_secondary"]) + switch(command) + if("open") + if(command_value == "on" && !density) + return + + if(command_value == "off" && density) + return + + if(density) + INVOKE_ASYNC(src, .proc/open) + else + INVOKE_ASYNC(src, .proc/close) + if("touch") + INVOKE_ASYNC(src, .proc/open_and_close) + +/obj/machinery/door/window/brigdoor + name = "secure door" + icon_state = "leftsecure" + base_state = "leftsecure" + var/id = null + max_integrity = 300 //Stronger doors for prison (regular window door health is 200) + reinf = 1 + explosion_block = 1 + +/obj/machinery/door/window/brigdoor/security/cell + name = "cell door" + desc = "For keeping in criminal scum." + req_access = list(ACCESS_BRIG) + +/obj/machinery/door/window/brigdoor/security/holding + name = "holding cell door" + req_one_access = list(ACCESS_SEC_DOORS, ACCESS_LAWYER) //love for the lawyer + +/obj/machinery/door/window/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 5f30e819fae2..ff8a9a18bb0a 100644 --- a/code/game/machinery/doppler_array.dm +++ b/code/game/machinery/doppler_array.dm @@ -1,153 +1,153 @@ -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/cooldown = 10 - var/next_announce = 0 - var/integrated = FALSE - var/max_dist = 150 - verb_say = "states coldly" - -/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/examine(mob/user) - ..() - to_chat(user, "Its dish is facing to the [dir2text(dir)].") - -/obj/machinery/doppler_array/process() - return PROCESS_KILL - -/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - if(!anchored && !isinspace()) - anchored = TRUE - power_change() - to_chat(user, "You fasten [src].") - else if(anchored) - anchored = FALSE - power_change() - to_chat(user, "You unfasten [src].") - I.play_tool_sound(src) - 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 - 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) - return TRUE - -/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 >= INFINITY) // Colton-proofs the doppler array - say("WARNING: INFINITE DENSITY OF TACHYONS DETECTED.") - point_gain = TOXINS_RESEARCH_MAX - else - point_gain = (TOXINS_RESEARCH_MAX * orig_light) / (orig_light + TOXINS_RESEARCH_LAMBDA)//New yogs function has the limit built into it because l'Hopital's rule - - - /*****The Point Capper*****/ - if(point_gain > linked_techweb.largest_bomb_value) - 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 - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) - if(D) - D.adjust_money(point_gain) - linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain) - say("Explosion details and mixture analyzed and sold to the highest bidder for $[point_gain], with a reward of [point_gain] points.") - - else //you've made smaller bombs - say("Data already captured. Aborting.") - return - - -/obj/machinery/doppler_array/research/science/Initialize() - . = ..() - linked_techweb = SSresearch.science_tech +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/cooldown = 10 + var/next_announce = 0 + var/integrated = FALSE + var/max_dist = 150 + verb_say = "states coldly" + +/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/examine(mob/user) + ..() + to_chat(user, "Its dish is facing to the [dir2text(dir)].") + +/obj/machinery/doppler_array/process() + return PROCESS_KILL + +/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) + if(!anchored && !isinspace()) + anchored = TRUE + power_change() + to_chat(user, "You fasten [src].") + else if(anchored) + anchored = FALSE + power_change() + to_chat(user, "You unfasten [src].") + I.play_tool_sound(src) + 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 + 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) + return TRUE + +/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 >= INFINITY) // Colton-proofs the doppler array + say("WARNING: INFINITE DENSITY OF TACHYONS DETECTED.") + point_gain = TOXINS_RESEARCH_MAX + else + point_gain = (TOXINS_RESEARCH_MAX * orig_light) / (orig_light + TOXINS_RESEARCH_LAMBDA)//New yogs function has the limit built into it because l'Hopital's rule + + + /*****The Point Capper*****/ + if(point_gain > linked_techweb.largest_bomb_value) + 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 + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) + if(D) + D.adjust_money(point_gain) + linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain) + say("Explosion details and mixture analyzed and sold to the highest bidder for $[point_gain], with a reward of [point_gain] points.") + + else //you've made smaller bombs + say("Data already captured. Aborting.") + return + + +/obj/machinery/doppler_array/research/science/Initialize() + . = ..() + linked_techweb = SSresearch.science_tech diff --git a/code/game/machinery/embedded_controller/access_controller.dm b/code/game/machinery/embedded_controller/access_controller.dm index 96f6b408cd8c..346ba25ece42 100644 --- a/code/game/machinery/embedded_controller/access_controller.dm +++ b/code/game/machinery/embedded_controller/access_controller.dm @@ -1,312 +1,312 @@ -#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.") - -/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) - if(A.density) - goIdle() - return FALSE - update_icon() - A.safe = FALSE //Door crushies, manual door after all. Set every time in case someone changed it, safe doors can end up waiting forever. - A.unbolt() - if(A.close()) - if(stat & NOPOWER || lostPower || !A || QDELETED(A)) - goIdle(TRUE) - return FALSE - A.bolt() - goIdle(TRUE) - return TRUE - goIdle(TRUE) - return FALSE - -/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(TRUE) - if(A == exteriorAirlock) - if(interiorAirlock) - if(!interiorAirlock.density || !interiorAirlock.locked) - return - else - if(exteriorAirlock) - if(!exteriorAirlock.density || !exteriorAirlock.locked) - return - if(busy != OPENING) - busy = OPENING - openDoor(A) - -/obj/machinery/doorButtons/airlock_controller/proc/openDoor(obj/machinery/door/airlock/A) - if(exteriorAirlock && interiorAirlock && (!exteriorAirlock.density || !interiorAirlock.density)) - goIdle(TRUE) - return - A.unbolt() - spawn() - if(A && A.open()) - if(stat | (NOPOWER) && !lostPower && A && !QDELETED(A)) - A.bolt() - goIdle(TRUE) - -/obj/machinery/doorButtons/airlock_controller/proc/goIdle(update) - lostPower = FALSE - 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 = TRUE - else - if(!busy) - lostPower = FALSE - 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.") + +/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) + if(A.density) + goIdle() + return FALSE + update_icon() + A.safe = FALSE //Door crushies, manual door after all. Set every time in case someone changed it, safe doors can end up waiting forever. + A.unbolt() + if(A.close()) + if(stat & NOPOWER || lostPower || !A || QDELETED(A)) + goIdle(TRUE) + return FALSE + A.bolt() + goIdle(TRUE) + return TRUE + goIdle(TRUE) + return FALSE + +/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(TRUE) + if(A == exteriorAirlock) + if(interiorAirlock) + if(!interiorAirlock.density || !interiorAirlock.locked) + return + else + if(exteriorAirlock) + if(!exteriorAirlock.density || !exteriorAirlock.locked) + return + if(busy != OPENING) + busy = OPENING + openDoor(A) + +/obj/machinery/doorButtons/airlock_controller/proc/openDoor(obj/machinery/door/airlock/A) + if(exteriorAirlock && interiorAirlock && (!exteriorAirlock.density || !interiorAirlock.density)) + goIdle(TRUE) + return + A.unbolt() + spawn() + if(A && A.open()) + if(stat | (NOPOWER) && !lostPower && A && !QDELETED(A)) + A.bolt() + goIdle(TRUE) + +/obj/machinery/doorButtons/airlock_controller/proc/goIdle(update) + lostPower = FALSE + 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 = TRUE + else + if(!busy) + lostPower = FALSE + 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 0036b4180481..9f1ebe67a9c2 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 063cfa6805ac..f1a2ef9acc75 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 9901d51074ce..33be1e3f79ad 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/firealarm.dm b/code/game/machinery/firealarm.dm index d828922c3fee..50a4368dabf1 100644 --- a/code/game/machinery/firealarm.dm +++ b/code/game/machinery/firealarm.dm @@ -1,328 +1,328 @@ -#define FIREALARM_COOLDOWN 67 // Chosen fairly arbitrarily, it is the length of the audio in FireAlarm.ogg. The actual track length is 7 seconds 8ms but but the audio stops at 6s 700ms - -/obj/item/electronics/firealarm - name = "fire alarm electronics" - custom_price = 5 - desc = "A fire alarm circuit. Can handle heat levels up to 40 degrees celsius." - -/obj/item/wallframe/firealarm - name = "fire alarm frame" - desc = "Used for building fire alarms." - icon = 'icons/obj/monitors.dmi' - icon_state = "fire_bitem" - result_path = /obj/machinery/firealarm - -/obj/machinery/firealarm - name = "fire alarm" - desc = "\"Pull this in case of emergency\". Thus, keep pulling it forever." - icon = 'icons/obj/monitors.dmi' - icon_state = "fire0" - max_integrity = 250 - integrity_failure = 100 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 6 - power_channel = ENVIRON - resistance_flags = FIRE_PROOF - - light_power = 0 - light_range = 7 - light_color = "#ff3232" - - var/detecting = 1 - var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone - var/last_alarm = 0 - var/area/myarea = null - -/obj/machinery/firealarm/Initialize(mapload, dir, building) - . = ..() - if(dir) - src.setDir(dir) - if(building) - buildstage = 0 - panel_open = TRUE - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 - update_icon() - myarea = get_area(src) - LAZYADD(myarea.firealarms, src) - -/obj/machinery/firealarm/Destroy() - LAZYREMOVE(myarea.firealarms, src) - return ..() - -/obj/machinery/firealarm/power_change() - ..() - update_icon() - -/obj/machinery/firealarm/update_icon() - cut_overlays() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - - if(panel_open) - icon_state = "fire_b[buildstage]" - return - - if(stat & BROKEN) - icon_state = "firex" - return - - icon_state = "fire0" - - if(stat & NOPOWER) - return - - add_overlay("fire_overlay") - - if(is_station_level(z)) - add_overlay("fire_[GLOB.security_level]") - SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) - else - add_overlay("fire_[SEC_LEVEL_GREEN]") - SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) - - var/area/A = src.loc - A = A.loc - - if(!detecting || !A.fire) - add_overlay("fire_off") - SSvis_overlays.add_vis_overlay(src, icon, "fire_off", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) - else if(obj_flags & EMAGGED) - add_overlay("fire_emagged") - SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) - else - add_overlay("fire_on") - SSvis_overlays.add_vis_overlay(src, icon, "fire_on", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) - -/obj/machinery/firealarm/emp_act(severity) - . = ..() - - if (. & EMP_PROTECT_SELF) - return - - if(prob(50 / severity)) - alarm() - -/obj/machinery/firealarm/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - update_icon() - if(user) - user.visible_message("Sparks fly out of [src]!", - "You emag [src], disabling its thermal sensors.") - playsound(src, "sparks", 50, 1) - -/obj/machinery/firealarm/temperature_expose(datum/gas_mixture/air, temperature, volume) - if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && (last_alarm+FIREALARM_COOLDOWN < world.time) && !(obj_flags & EMAGGED) && detecting && !stat) - alarm() - ..() - -/obj/machinery/firealarm/proc/alarm(mob/user) - if(!is_operational() || (last_alarm+FIREALARM_COOLDOWN > world.time)) - return - last_alarm = world.time - var/area/A = get_area(src) - A.firealert(src) - playsound(loc, 'goon/sound/machinery/FireAlarm.ogg', 75) - if(user) - log_game("[user] triggered a fire alarm at [COORD(src)]") - -/obj/machinery/firealarm/proc/reset(mob/user) - if(!is_operational()) - return - var/area/A = get_area(src) - A.firereset(src) - if(user) - log_game("[user] reset a fire alarm at [COORD(src)]") - -/obj/machinery/firealarm/attack_hand(mob/user) - if(buildstage != 2) - return ..() - add_fingerprint(user) - var/area/A = get_area(src) - if(A.fire) - reset(user) - else - alarm(user) - -/obj/machinery/firealarm/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/firealarm/attack_robot(mob/user) - return attack_hand(user) - -/obj/machinery/firealarm/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - - if(W.tool_behaviour == TOOL_SCREWDRIVER && buildstage == 2) - W.play_tool_sound(src) - panel_open = !panel_open - to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") - update_icon() - return - - if(panel_open) - - if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP) - if(obj_integrity < max_integrity) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin repairing [src]...") - if(W.use_tool(src, user, 40, volume=50)) - obj_integrity = max_integrity - to_chat(user, "You repair [src].") - else - to_chat(user, "[src] is already in good condition!") - return - - switch(buildstage) - if(2) - if(W.tool_behaviour == TOOL_MULTITOOL) - detecting = !detecting - if (src.detecting) - user.visible_message("[user] has reconnected [src]'s detecting unit!", "You reconnect [src]'s detecting unit.") - else - user.visible_message("[user] has disconnected [src]'s detecting unit!", "You disconnect [src]'s detecting unit.") - return - - else if(W.tool_behaviour == TOOL_WIRECUTTER) - buildstage = 1 - W.play_tool_sound(src) - new /obj/item/stack/cable_coil(user.loc, 5) - to_chat(user, "You cut the wires from \the [src].") - update_icon() - return - - else if(W.force) //hit and turn it on - ..() - var/area/A = get_area(src) - if(!A.fire) - alarm() - return - - if(1) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/coil = W - if(coil.get_amount() < 5) - to_chat(user, "You need more cable for this!") - else - coil.use(5) - buildstage = 2 - to_chat(user, "You wire \the [src].") - update_icon() - return - - else if(W.tool_behaviour == TOOL_CROWBAR) - user.visible_message("[user.name] removes the electronics from [src.name].", \ - "You start prying out the circuit...") - if(W.use_tool(src, user, 20, volume=50)) - if(buildstage == 1) - if(stat & BROKEN) - to_chat(user, "You remove the destroyed circuit.") - stat &= ~BROKEN - else - to_chat(user, "You pry out the circuit.") - new /obj/item/electronics/firealarm(user.loc) - buildstage = 0 - update_icon() - return - if(0) - if(istype(W, /obj/item/electronics/firealarm)) - to_chat(user, "You insert the circuit.") - qdel(W) - buildstage = 1 - update_icon() - return - - else if(istype(W, /obj/item/electroadaptive_pseudocircuit)) - var/obj/item/electroadaptive_pseudocircuit/P = W - if(!P.adapt_circuit(user, 15)) - return - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a fire alarm circuit and slot it into the assembly.") - buildstage = 1 - update_icon() - return - - else if(W.tool_behaviour == TOOL_WRENCH) - user.visible_message("[user] removes the fire alarm assembly from the wall.", \ - "You remove the fire alarm assembly from the wall.") - var/obj/item/wallframe/firealarm/frame = new /obj/item/wallframe/firealarm() - frame.forceMove(user.drop_location()) - W.play_tool_sound(src) - qdel(src) - return - - return ..() - - -/obj/machinery/firealarm/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(.) //damage received - if(obj_integrity > 0 && !(stat & BROKEN) && buildstage != 0) - if(prob(33)) - alarm() - -/obj/machinery/firealarm/singularity_pull(S, current_size) - if (current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects, the fire alarm experiences integrity failure - deconstruct() - ..() - -/obj/machinery/firealarm/obj_break(damage_flag) - if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1) && buildstage != 0) //can't break the electronics if there isn't any inside. - LAZYREMOVE(myarea.firealarms, src) - stat |= BROKEN - update_icon() - -/obj/machinery/firealarm/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc, 1) - if(!(stat & BROKEN)) - var/obj/item/I = new /obj/item/electronics/firealarm(loc) - if(!disassembled) - I.obj_integrity = I.max_integrity * 0.5 - new /obj/item/stack/cable_coil(loc, 3) - qdel(src) - -/obj/machinery/firealarm/proc/update_fire_light(fire) - if(fire == !!light_power) - return // do nothing if we're already active - if(fire) - set_light(l_power = 0.8) - else - set_light(l_power = 0) - -/* - * Return of Party button - */ - -/area - var/party = FALSE - -/obj/machinery/firealarm/partyalarm - name = "\improper PARTY BUTTON" - desc = "Cuban Pete is in the house!" - var/static/party_overlay - -/obj/machinery/firealarm/partyalarm/reset() - if (stat & (NOPOWER|BROKEN)) - return - var/area/A = get_area(src) - if (!A || !A.party) - return - A.party = FALSE - A.cut_overlay(party_overlay) - -/obj/machinery/firealarm/partyalarm/alarm() - if (stat & (NOPOWER|BROKEN)) - return - var/area/A = get_area(src) - if (!A || A.party || A.name == "Space") - return - A.party = TRUE - if (!party_overlay) - party_overlay = iconstate2appearance('icons/turf/areas.dmi', "party") - A.add_overlay(party_overlay) +#define FIREALARM_COOLDOWN 67 // Chosen fairly arbitrarily, it is the length of the audio in FireAlarm.ogg. The actual track length is 7 seconds 8ms but but the audio stops at 6s 700ms + +/obj/item/electronics/firealarm + name = "fire alarm electronics" + custom_price = 5 + desc = "A fire alarm circuit. Can handle heat levels up to 40 degrees celsius." + +/obj/item/wallframe/firealarm + name = "fire alarm frame" + desc = "Used for building fire alarms." + icon = 'icons/obj/monitors.dmi' + icon_state = "fire_bitem" + result_path = /obj/machinery/firealarm + +/obj/machinery/firealarm + name = "fire alarm" + desc = "\"Pull this in case of emergency\". Thus, keep pulling it forever." + icon = 'icons/obj/monitors.dmi' + icon_state = "fire0" + max_integrity = 250 + integrity_failure = 100 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 6 + power_channel = ENVIRON + resistance_flags = FIRE_PROOF + + light_power = 0 + light_range = 7 + light_color = "#ff3232" + + var/detecting = 1 + var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone + var/last_alarm = 0 + var/area/myarea = null + +/obj/machinery/firealarm/Initialize(mapload, dir, building) + . = ..() + if(dir) + src.setDir(dir) + if(building) + buildstage = 0 + panel_open = TRUE + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 + update_icon() + myarea = get_area(src) + LAZYADD(myarea.firealarms, src) + +/obj/machinery/firealarm/Destroy() + LAZYREMOVE(myarea.firealarms, src) + return ..() + +/obj/machinery/firealarm/power_change() + ..() + update_icon() + +/obj/machinery/firealarm/update_icon() + cut_overlays() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + + if(panel_open) + icon_state = "fire_b[buildstage]" + return + + if(stat & BROKEN) + icon_state = "firex" + return + + icon_state = "fire0" + + if(stat & NOPOWER) + return + + add_overlay("fire_overlay") + + if(is_station_level(z)) + add_overlay("fire_[GLOB.security_level]") + SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) + else + add_overlay("fire_[SEC_LEVEL_GREEN]") + SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) + + var/area/A = src.loc + A = A.loc + + if(!detecting || !A.fire) + add_overlay("fire_off") + SSvis_overlays.add_vis_overlay(src, icon, "fire_off", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) + else if(obj_flags & EMAGGED) + add_overlay("fire_emagged") + SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) + else + add_overlay("fire_on") + SSvis_overlays.add_vis_overlay(src, icon, "fire_on", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) + +/obj/machinery/firealarm/emp_act(severity) + . = ..() + + if (. & EMP_PROTECT_SELF) + return + + if(prob(50 / severity)) + alarm() + +/obj/machinery/firealarm/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + update_icon() + if(user) + user.visible_message("Sparks fly out of [src]!", + "You emag [src], disabling its thermal sensors.") + playsound(src, "sparks", 50, 1) + +/obj/machinery/firealarm/temperature_expose(datum/gas_mixture/air, temperature, volume) + if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && (last_alarm+FIREALARM_COOLDOWN < world.time) && !(obj_flags & EMAGGED) && detecting && !stat) + alarm() + ..() + +/obj/machinery/firealarm/proc/alarm(mob/user) + if(!is_operational() || (last_alarm+FIREALARM_COOLDOWN > world.time)) + return + last_alarm = world.time + var/area/A = get_area(src) + A.firealert(src) + playsound(loc, 'goon/sound/machinery/FireAlarm.ogg', 75) + if(user) + log_game("[user] triggered a fire alarm at [COORD(src)]") + +/obj/machinery/firealarm/proc/reset(mob/user) + if(!is_operational()) + return + var/area/A = get_area(src) + A.firereset(src) + if(user) + log_game("[user] reset a fire alarm at [COORD(src)]") + +/obj/machinery/firealarm/attack_hand(mob/user) + if(buildstage != 2) + return ..() + add_fingerprint(user) + var/area/A = get_area(src) + if(A.fire) + reset(user) + else + alarm(user) + +/obj/machinery/firealarm/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/firealarm/attack_robot(mob/user) + return attack_hand(user) + +/obj/machinery/firealarm/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + + if(W.tool_behaviour == TOOL_SCREWDRIVER && buildstage == 2) + W.play_tool_sound(src) + panel_open = !panel_open + to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") + update_icon() + return + + if(panel_open) + + if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP) + if(obj_integrity < max_integrity) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin repairing [src]...") + if(W.use_tool(src, user, 40, volume=50)) + obj_integrity = max_integrity + to_chat(user, "You repair [src].") + else + to_chat(user, "[src] is already in good condition!") + return + + switch(buildstage) + if(2) + if(W.tool_behaviour == TOOL_MULTITOOL) + detecting = !detecting + if (src.detecting) + user.visible_message("[user] has reconnected [src]'s detecting unit!", "You reconnect [src]'s detecting unit.") + else + user.visible_message("[user] has disconnected [src]'s detecting unit!", "You disconnect [src]'s detecting unit.") + return + + else if(W.tool_behaviour == TOOL_WIRECUTTER) + buildstage = 1 + W.play_tool_sound(src) + new /obj/item/stack/cable_coil(user.loc, 5) + to_chat(user, "You cut the wires from \the [src].") + update_icon() + return + + else if(W.force) //hit and turn it on + ..() + var/area/A = get_area(src) + if(!A.fire) + alarm() + return + + if(1) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil = W + if(coil.get_amount() < 5) + to_chat(user, "You need more cable for this!") + else + coil.use(5) + buildstage = 2 + to_chat(user, "You wire \the [src].") + update_icon() + return + + else if(W.tool_behaviour == TOOL_CROWBAR) + user.visible_message("[user.name] removes the electronics from [src.name].", \ + "You start prying out the circuit...") + if(W.use_tool(src, user, 20, volume=50)) + if(buildstage == 1) + if(stat & BROKEN) + to_chat(user, "You remove the destroyed circuit.") + stat &= ~BROKEN + else + to_chat(user, "You pry out the circuit.") + new /obj/item/electronics/firealarm(user.loc) + buildstage = 0 + update_icon() + return + if(0) + if(istype(W, /obj/item/electronics/firealarm)) + to_chat(user, "You insert the circuit.") + qdel(W) + buildstage = 1 + update_icon() + return + + else if(istype(W, /obj/item/electroadaptive_pseudocircuit)) + var/obj/item/electroadaptive_pseudocircuit/P = W + if(!P.adapt_circuit(user, 15)) + return + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a fire alarm circuit and slot it into the assembly.") + buildstage = 1 + update_icon() + return + + else if(W.tool_behaviour == TOOL_WRENCH) + user.visible_message("[user] removes the fire alarm assembly from the wall.", \ + "You remove the fire alarm assembly from the wall.") + var/obj/item/wallframe/firealarm/frame = new /obj/item/wallframe/firealarm() + frame.forceMove(user.drop_location()) + W.play_tool_sound(src) + qdel(src) + return + + return ..() + + +/obj/machinery/firealarm/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(.) //damage received + if(obj_integrity > 0 && !(stat & BROKEN) && buildstage != 0) + if(prob(33)) + alarm() + +/obj/machinery/firealarm/singularity_pull(S, current_size) + if (current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects, the fire alarm experiences integrity failure + deconstruct() + ..() + +/obj/machinery/firealarm/obj_break(damage_flag) + if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1) && buildstage != 0) //can't break the electronics if there isn't any inside. + LAZYREMOVE(myarea.firealarms, src) + stat |= BROKEN + update_icon() + +/obj/machinery/firealarm/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc, 1) + if(!(stat & BROKEN)) + var/obj/item/I = new /obj/item/electronics/firealarm(loc) + if(!disassembled) + I.obj_integrity = I.max_integrity * 0.5 + new /obj/item/stack/cable_coil(loc, 3) + qdel(src) + +/obj/machinery/firealarm/proc/update_fire_light(fire) + if(fire == !!light_power) + return // do nothing if we're already active + if(fire) + set_light(l_power = 0.8) + else + set_light(l_power = 0) + +/* + * Return of Party button + */ + +/area + var/party = FALSE + +/obj/machinery/firealarm/partyalarm + name = "\improper PARTY BUTTON" + desc = "Cuban Pete is in the house!" + var/static/party_overlay + +/obj/machinery/firealarm/partyalarm/reset() + if (stat & (NOPOWER|BROKEN)) + return + var/area/A = get_area(src) + if (!A || !A.party) + return + A.party = FALSE + A.cut_overlay(party_overlay) + +/obj/machinery/firealarm/partyalarm/alarm() + if (stat & (NOPOWER|BROKEN)) + return + var/area/A = get_area(src) + if (!A || A.party || A.name == "Space") + return + A.party = TRUE + if (!party_overlay) + party_overlay = iconstate2appearance('icons/turf/areas.dmi', "party") + A.add_overlay(party_overlay) diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm index ee35556a0f8a..2cf4302d9539 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.burnt_out) - 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 (W.tool_behaviour == TOOL_WIRECUTTER) - if (bulb) - user.visible_message("[user] begins to disconnect [src]'s flashbulb.", "You begin to disconnect [src]'s flashbulb...") - if(W.use_tool(src, user, 30, volume=50) && bulb) - user.visible_message("[user] 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 (W.tool_behaviour == TOOL_WRENCH) - if(!bulb) - to_chat(user, "You start unsecuring the flasher frame...") - if(W.use_tool(src, user, 40, volume=50)) - to_chat(user, "You unsecure the flasher frame.") - deconstruct(TRUE) - else - to_chat(user, "Remove a flashbulb from [src] first!") - else - return ..() - -//Let the AI trigger them directly. -/obj/machinery/flasher/attack_ai() - if (anchored) - return flash() - -/obj/machinery/flasher/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.burnt_out || (last_flash && world.time < src.last_flash + 150)) - return - - if(!bulb.flash_recharge(30)) //Bulb can burn out if it's used too often too fast - power_change() - return - - playsound(src.loc, 'sound/weapons/flash.ogg', 100, 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.Paralyze(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 (W.tool_behaviour == TOOL_WRENCH) - W.play_tool_sound(src, 100) - - if (!anchored && !isinspace()) - to_chat(user, "[src] is now secured.") - add_overlay("[base_state]-s") - setAnchored(TRUE) - power_change() - proximity_monitor.SetRange(range) - else - to_chat(user, "[src] can now be moved.") - cut_overlays() - setAnchored(FALSE) - power_change() - proximity_monitor.SetRange(0) - - else - return ..() - -/obj/item/wallframe/flasher - name = "mounted flash frame" - desc = "Used for building wall-mounted flashers." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "mflash_frame" - result_path = /obj/machinery/flasher - var/id = null - -/obj/item/wallframe/flasher/examine(mob/user) - . = ..() - . += "Its channel ID is '[id]'." - -/obj/item/wallframe/flasher/after_attach(var/obj/O) - ..() - var/obj/machinery/flasher/F = O - F.id = id +// It is a gizmo that flashes a small area + +/obj/machinery/flasher + name = "mounted flash" + desc = "A wall-mounted flashbulb device." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mflash1" + max_integrity = 250 + integrity_failure = 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.burnt_out) + 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 (W.tool_behaviour == TOOL_WIRECUTTER) + if (bulb) + user.visible_message("[user] begins to disconnect [src]'s flashbulb.", "You begin to disconnect [src]'s flashbulb...") + if(W.use_tool(src, user, 30, volume=50) && bulb) + user.visible_message("[user] 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 (W.tool_behaviour == TOOL_WRENCH) + if(!bulb) + to_chat(user, "You start unsecuring the flasher frame...") + if(W.use_tool(src, user, 40, volume=50)) + to_chat(user, "You unsecure the flasher frame.") + deconstruct(TRUE) + else + to_chat(user, "Remove a flashbulb from [src] first!") + else + return ..() + +//Let the AI trigger them directly. +/obj/machinery/flasher/attack_ai() + if (anchored) + return flash() + +/obj/machinery/flasher/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.burnt_out || (last_flash && world.time < src.last_flash + 150)) + return + + if(!bulb.flash_recharge(30)) //Bulb can burn out if it's used too often too fast + power_change() + return + + playsound(src.loc, 'sound/weapons/flash.ogg', 100, 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.Paralyze(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 (W.tool_behaviour == TOOL_WRENCH) + W.play_tool_sound(src, 100) + + if (!anchored && !isinspace()) + to_chat(user, "[src] is now secured.") + add_overlay("[base_state]-s") + setAnchored(TRUE) + power_change() + proximity_monitor.SetRange(range) + else + to_chat(user, "[src] can now be moved.") + cut_overlays() + setAnchored(FALSE) + power_change() + proximity_monitor.SetRange(0) + + else + return ..() + +/obj/item/wallframe/flasher + name = "mounted flash frame" + desc = "Used for building wall-mounted flashers." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mflash_frame" + result_path = /obj/machinery/flasher + var/id = null + +/obj/item/wallframe/flasher/examine(mob/user) + . = ..() + . += "Its channel ID is '[id]'." + +/obj/item/wallframe/flasher/after_attach(var/obj/O) + ..() + var/obj/machinery/flasher/F = O + F.id = id diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index 419fc647e73e..edda579b7ec7 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -1,702 +1,702 @@ -/* Holograms! - * Contains: - * Holopad - * Hologram - * Other stuff - */ - -/* -Revised. Original based on space ninja hologram code. Which is also mine. /N -How it works: -AI clicks on holopad in camera view. View centers on holopad. -AI clicks again on the holopad to display a hologram. Hologram stays as long as AI is looking at the pad and it (the hologram) is in range of the pad. -AI can use the directional keys to move the hologram around, provided the above conditions are met and the AI in question is the holopad's master. -Any number of AIs can use a holopad. /Lo6 -AI may cancel the hologram at any time by clicking on the holopad once more. - -Possible to do for anyone motivated enough: - Give an AI variable for different hologram icons. - Itegrate EMP effect to disable the unit. -*/ - - -/* - * Holopad - */ - -#define HOLOPAD_PASSIVE_POWER_USAGE 1 -#define HOLOGRAM_POWER_USAGE 2 - -/obj/machinery/holopad - name = "holopad" - desc = "It's a floor-mounted device for projecting holographic images." - icon_state = "holopad0" - layer = LOW_OBJ_LAYER - plane = FLOOR_PLANE - flags_1 = HEAR_1 - 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/static/list/holopads = list() - 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) - holopads += src - -/obj/machinery/holopad/Destroy() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.ConnectionFailure(src) - - for (var/I in masters) - clear_holo(I) - - if(replay_mode) - replay_stop() - if(record_mode) - record_stop() - - QDEL_NULL(disk) - - holopads -= src - return ..() - -/obj/machinery/holopad/power_change() - if (powered()) - 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/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Current projection range: [holo_range] units." - -/obj/machinery/holopad/attackby(obj/item/P, mob/user, params) - if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P)) - return - - if(default_pry_open(P)) - return - - if(default_unfasten_wrench(user, P)) - return - - if(default_deconstruction_crowbar(P)) - return - - if(istype(P,/obj/item/disk/holodisk)) - if(disk) - to_chat(user,"There's already a disk inside [src]") - return - if (!user.transferItemToLoc(P,src)) - return - to_chat(user,"You insert [P] into [src]") - disk = P - 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 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"]) in holo_calls - 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"]) in holo_calls - 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) - . = ..() - if(speaker && LAZYLEN(masters) && !radio_freq)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios. - for(var/mob/living/silicon/ai/master in masters) - if(masters[master] && speaker != master) - master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad == src && speaker != HC.hologram) - HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - if(outgoing_call && speaker == outgoing_call.user) - outgoing_call.hologram.say(raw_message) - - if(record_mode && speaker == record_user) - record_message(speaker,raw_message,message_language) - -/obj/machinery/holopad/proc/SetLightsAndPower() - var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) - use_power = total_users > 0 ? ACTIVE_POWER_USE : IDLE_POWER_USE - active_power_usage = HOLOPAD_PASSIVE_POWER_USAGE + (HOLOGRAM_POWER_USAGE * total_users) - if(total_users || replay_mode) - set_light(2) - else - set_light(0) - update_icon() - -/obj/machinery/holopad/update_icon() - 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 holopads) - var/obj/machinery/holopad/another = pad - if(another == src) - continue - if(another.validate_location(T)) - unset_holo(holo_owner) - if(another.masters && another.masters[holo_owner]) - another.clear_holo(holo_owner) - another.set_holo(holo_owner, h) - return TRUE - return FALSE - -/obj/machinery/holopad/proc/validate_user(mob/living/user) - if(QDELETED(user) || user.incapacitated() || !user.client) - return FALSE - return TRUE - -//Can we display holos there -//Area check instead of line of sight check because this is a called a lot if AI wants to move around. -/obj/machinery/holopad/proc/validate_location(turf/T,check_los = FALSE) - if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src)) - return TRUE - else - return FALSE - -/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf) - if(LAZYLEN(masters) && masters[user]) - var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] - var/transfered = FALSE - if(!validate_location(new_turf)) - if(!transfer_to_nearby_pad(new_turf,user)) - clear_holo(user) - return FALSE - else - transfered = TRUE - //All is good. - holo.forceMove(new_turf) - if(!transfered) - update_holoray(user,new_turf) - return TRUE - - -/obj/machinery/holopad/proc/update_holoray(mob/living/user, turf/new_turf) - var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] - var/obj/effect/overlay/holoray/ray = holorays[user] - var/disty = holo.y - ray.y - var/distx = holo.x - ray.x - var/newangle - if(!disty) - if(distx >= 0) - newangle = 90 - else - newangle = 270 - else - newangle = arctan(distx/disty) - if(disty < 0) - newangle += 180 - else if(distx < 0) - newangle += 360 - var/matrix/M = matrix() - if (get_dist(get_turf(holo),new_turf) <= 1) - animate(ray, transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle),time = 1) - else - ray.transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle) - -// RECORDED MESSAGES - -/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) - var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. - Hologram.add_overlay(record.caller_image) - Hologram.alpha = 170 - Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) - Hologram.dir = SOUTH //for now - 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 + */ + +#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/static/list/holopads = list() + 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) + holopads += src + +/obj/machinery/holopad/Destroy() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.ConnectionFailure(src) + + for (var/I in masters) + clear_holo(I) + + if(replay_mode) + replay_stop() + if(record_mode) + record_stop() + + QDEL_NULL(disk) + + holopads -= src + return ..() + +/obj/machinery/holopad/power_change() + if (powered()) + 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/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Current projection range: [holo_range] units." + +/obj/machinery/holopad/attackby(obj/item/P, mob/user, params) + if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P)) + return + + if(default_pry_open(P)) + return + + if(default_unfasten_wrench(user, P)) + return + + if(default_deconstruction_crowbar(P)) + return + + if(istype(P,/obj/item/disk/holodisk)) + if(disk) + to_chat(user,"There's already a disk inside [src]") + return + if (!user.transferItemToLoc(P,src)) + return + to_chat(user,"You insert [P] into [src]") + disk = P + 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 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"]) in holo_calls + 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"]) in holo_calls + 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) + . = ..() + if(speaker && LAZYLEN(masters) && !radio_freq)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios. + for(var/mob/living/silicon/ai/master in masters) + if(masters[master] && speaker != master) + master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad == src && speaker != HC.hologram) + HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + + if(outgoing_call && speaker == outgoing_call.user) + outgoing_call.hologram.say(raw_message) + + if(record_mode && speaker == record_user) + record_message(speaker,raw_message,message_language) + +/obj/machinery/holopad/proc/SetLightsAndPower() + var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) + use_power = total_users > 0 ? ACTIVE_POWER_USE : IDLE_POWER_USE + active_power_usage = HOLOPAD_PASSIVE_POWER_USAGE + (HOLOGRAM_POWER_USAGE * total_users) + if(total_users || replay_mode) + set_light(2) + else + set_light(0) + update_icon() + +/obj/machinery/holopad/update_icon() + 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 holopads) + var/obj/machinery/holopad/another = pad + if(another == src) + continue + if(another.validate_location(T)) + unset_holo(holo_owner) + if(another.masters && another.masters[holo_owner]) + another.clear_holo(holo_owner) + another.set_holo(holo_owner, h) + return TRUE + return FALSE + +/obj/machinery/holopad/proc/validate_user(mob/living/user) + if(QDELETED(user) || user.incapacitated() || !user.client) + return FALSE + return TRUE + +//Can we display holos there +//Area check instead of line of sight check because this is a called a lot if AI wants to move around. +/obj/machinery/holopad/proc/validate_location(turf/T,check_los = FALSE) + if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src)) + return TRUE + else + return FALSE + +/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf) + if(LAZYLEN(masters) && masters[user]) + var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] + var/transfered = FALSE + if(!validate_location(new_turf)) + if(!transfer_to_nearby_pad(new_turf,user)) + clear_holo(user) + return FALSE + else + transfered = TRUE + //All is good. + holo.forceMove(new_turf) + if(!transfered) + update_holoray(user,new_turf) + return TRUE + + +/obj/machinery/holopad/proc/update_holoray(mob/living/user, turf/new_turf) + var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] + var/obj/effect/overlay/holoray/ray = holorays[user] + var/disty = holo.y - ray.y + var/distx = holo.x - ray.x + var/newangle + if(!disty) + if(distx >= 0) + newangle = 90 + else + newangle = 270 + else + newangle = arctan(distx/disty) + if(disty < 0) + newangle += 180 + else if(distx < 0) + newangle += 360 + var/matrix/M = matrix() + if (get_dist(get_turf(holo),new_turf) <= 1) + animate(ray, transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle),time = 1) + else + ray.transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle) + +// RECORDED MESSAGES + +/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) + var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. + Hologram.add_overlay(record.caller_image) + Hologram.alpha = 170 + Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) + Hologram.dir = SOUTH //for now + 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 5979732a0ec3..e2070fd5e828 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(1000,500,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 (W.tool_behaviour == TOOL_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,2500,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(1000,500,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 (W.tool_behaviour == TOOL_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,2500,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 3f1aee9edbd6..3f024219652a 100644 --- a/code/game/machinery/lightswitch.dm +++ b/code/game/machinery/lightswitch.dm @@ -1,63 +1,63 @@ -/// The light switch. Can have multiple per area. -/obj/machinery/light_switch - name = "light switch" - icon = 'icons/obj/power.dmi' - icon_state = "light1" - desc = "Make dark." - /// Set this to a string, path, or area instance to control that area - /// instead of the switch's location. - var/area/area = null - -/obj/machinery/light_switch/Initialize() - . = ..() - if(istext(area)) - area = text2path(area) - if(ispath(area)) - area = GLOB.areas_by_type[area] - if(!area) - area = get_area(src) - - if(!name) - name = "light switch ([area.name])" - - update_icon() - -/obj/machinery/light_switch/update_icon() - if(stat & NOPOWER) - icon_state = "light-p" - else - if(area.lightswitch) - icon_state = "light1" - else - icon_state = "light0" - -/obj/machinery/light_switch/examine(mob/user) - . = ..() - . += "It is [area.lightswitch ? "on" : "off"]." - -/obj/machinery/light_switch/interact(mob/user) - . = ..() - - area.lightswitch = !area.lightswitch - area.update_icon() - - for(var/obj/machinery/light_switch/L in area) - L.update_icon() - - area.power_change() - -/obj/machinery/light_switch/power_change() - if(area == get_area(src)) - 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. +/obj/machinery/light_switch + name = "light switch" + icon = 'icons/obj/power.dmi' + icon_state = "light1" + desc = "Make dark." + /// Set this to a string, path, or area instance to control that area + /// instead of the switch's location. + var/area/area = null + +/obj/machinery/light_switch/Initialize() + . = ..() + if(istext(area)) + area = text2path(area) + if(ispath(area)) + area = GLOB.areas_by_type[area] + if(!area) + area = get_area(src) + + if(!name) + name = "light switch ([area.name])" + + update_icon() + +/obj/machinery/light_switch/update_icon() + if(stat & NOPOWER) + icon_state = "light-p" + else + if(area.lightswitch) + icon_state = "light1" + else + icon_state = "light0" + +/obj/machinery/light_switch/examine(mob/user) + . = ..() + . += "It is [area.lightswitch ? "on" : "off"]." + +/obj/machinery/light_switch/interact(mob/user) + . = ..() + + area.lightswitch = !area.lightswitch + area.update_icon() + + for(var/obj/machinery/light_switch/L in area) + L.update_icon() + + area.power_change() + +/obj/machinery/light_switch/power_change() + if(area == get_area(src)) + 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 370621e0c86a..9a10ede1ac05 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 443d8ac581c8..e5df2c29d18a 100644 --- a/code/game/machinery/mass_driver.dm +++ b/code/game/machinery/mass_driver.dm @@ -1,40 +1,40 @@ -/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. - if(ismob(O) && !isliving(O)) - continue - O_limit++ - if(O_limit >= 20) - audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.") - break - use_power(500) - O.throw_at(target, drive_range * power, power) - flick("mass_driver1", src) - - -/obj/machinery/mass_driver/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(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. + if(ismob(O) && !isliving(O)) + continue + O_limit++ + if(O_limit >= 20) + audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.") + break + use_power(500) + O.throw_at(target, drive_range * power, power) + flick("mass_driver1", src) + + +/obj/machinery/mass_driver/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(stat & (BROKEN|NOPOWER)) + return + drive() diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm index ec085157462d..52329ccb97fe 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(I.tool_behaviour == TOOL_SCREWDRIVER) - open = !open - - user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") - - update_icon() - - else if (istype(I, /obj/item/card/id)||istype(I, /obj/item/pda)) - if(open) - if (src.allowed(user)) - src.locked = !src.locked - to_chat(user, "Controls are now [src.locked ? "locked" : "unlocked"].") - else - to_chat(user, "Access denied.") - updateDialog() - else - to_chat(user, "You must open the cover first!") - else - return ..() - -/obj/machinery/navbeacon/attack_ai(mob/user) - interact(user, 1) - -/obj/machinery/navbeacon/attack_paw() - return - -/obj/machinery/navbeacon/ui_interact(mob/user) - . = ..() - var/ai = isAI(user) - var/turf/T = loc - if(T.intact) - return // prevent intraction when T-scanner revealed - - if(!open && !ai) // can't alter controls if not open, unless you're an AI - to_chat(user, "The beacon's control cover is closed!") - return - - - var/t - - if(locked && !ai) - t = {"Navigation Beacon

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

        -(swipe card to lock controls)
        - -
        -Location: [location ? location : "None"]
        -Transponder Codes:
          "} - - for(var/key in codes) - t += "
        • [key] ... [codes[key]]" - t += " Edit" - t += " Delete
          " - t += " Add New
          " - t+= "
            " - - var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) - popup.set_content(t) - popup.open() - return - -/obj/machinery/navbeacon/Topic(href, href_list) - if(..()) - return - if(open && !locked) - usr.set_machine(src) - - if(href_list["locedit"]) - var/newloc = 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(I.tool_behaviour == TOOL_SCREWDRIVER) + open = !open + + user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") + + update_icon() + + else if (istype(I, /obj/item/card/id)||istype(I, /obj/item/pda)) + if(open) + if (src.allowed(user)) + src.locked = !src.locked + to_chat(user, "Controls are now [src.locked ? "locked" : "unlocked"].") + else + to_chat(user, "Access denied.") + updateDialog() + else + to_chat(user, "You must open the cover first!") + else + return ..() + +/obj/machinery/navbeacon/attack_ai(mob/user) + interact(user, 1) + +/obj/machinery/navbeacon/attack_paw() + return + +/obj/machinery/navbeacon/ui_interact(mob/user) + . = ..() + var/ai = isAI(user) + var/turf/T = loc + if(T.intact) + return // prevent intraction when T-scanner revealed + + if(!open && !ai) // can't alter controls if not open, unless you're an AI + to_chat(user, "The beacon's control cover is closed!") + return + + + var/t + + if(locked && !ai) + t = {"Navigation Beacon

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

                +(swipe card to lock controls)
                + +
                +Location: [location ? location : "None"]
                +Transponder Codes:
                  "} + + for(var/key in codes) + t += "
                • [key] ... [codes[key]]" + t += " Edit" + t += " Delete
                  " + t += " Add New
                  " + t+= "
                    " + + var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) + popup.set_content(t) + popup.open() + return + +/obj/machinery/navbeacon/Topic(href, href_list) + if(..()) + return + if(open && !locked) + usr.set_machine(src) + + if(href_list["locedit"]) + var/newloc = 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 8035a3c8d12a..2fad67dca7d3 100644 --- a/code/game/machinery/pipe/construction.dm +++ b/code/game/machinery/pipe/construction.dm @@ -1,235 +1,235 @@ -/*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 - var/disposable = TRUE // yogs - -/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 if(_pipe_type) // yogs - only change if _pipe_type is set - pipe_type = _pipe_type - setDir(_dir) - - update() - pixel_x += rand(-5, 5) - pixel_y += rand(-5, 5) - return ..() - -/obj/item/pipe/proc/make_from_existing(obj/machinery/atmospherics/make_from) - setDir(make_from.dir) - pipename = make_from.name - add_atom_colour(make_from.color, FIXED_COLOUR_PRIORITY) - pipe_type = make_from.type - -/obj/item/pipe/trinary/flippable/make_from_existing(obj/machinery/atmospherics/components/trinary/make_from) - ..() - if(make_from.flipped) - do_a_flip() - -/obj/item/pipe/dropped() - if(loc) - setPipingLayer(piping_layer) - return ..() - -/obj/item/pipe/proc/setPipingLayer(new_layer = PIPING_LAYER_DEFAULT) - var/obj/machinery/atmospherics/fakeA = pipe_type - - if(initial(fakeA.pipe_flags) & PIPING_ALL_LAYER) - new_layer = PIPING_LAYER_DEFAULT - piping_layer = new_layer - - PIPING_LAYER_SHIFT(src, piping_layer) - layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) - -/obj/item/pipe/proc/update() - var/obj/machinery/atmospherics/fakeA = pipe_type - name = "[initial(fakeA.name)] fitting" - icon_state = initial(fakeA.pipe_state) - if(ispath(pipe_type,/obj/machinery/atmospherics/pipe/heat_exchanging)) - resistance_flags |= FIRE_PROOF | LAVA_PROOF - -/obj/item/pipe/verb/flip() - set category = "Object" - set name = "Flip Pipe" - set src in view(1) - - if ( usr.incapacitated() ) - return - - do_a_flip() - -/obj/item/pipe/proc/do_a_flip() - setDir(turn(dir, -180)) - -/obj/item/pipe/trinary/flippable/do_a_flip() - setDir(turn(dir, flipped ? 45 : -45)) - flipped = !flipped - -/obj/item/pipe/Move() - var/old_dir = dir - ..() - setDir(old_dir) //pipes changing direction when moved is just annoying and buggy - -// Convert dir of fitting into dir of built component -/obj/item/pipe/proc/fixed_dir() - return dir - -/obj/item/pipe/binary/fixed_dir() - . = dir - if(dir == SOUTH) - . = NORTH - else if(dir == WEST) - . = EAST - -/obj/item/pipe/trinary/flippable/fixed_dir() - . = dir - if(dir in GLOB.diagonals) - . = turn(dir, 45) - -/obj/item/pipe/attack_self(mob/user) - setDir(turn(dir,-90)) - -/obj/item/pipe/wrench_act(mob/living/user, obj/item/wrench/W) - if(!isturf(loc)) - return TRUE - - add_fingerprint(user) - - var/obj/machinery/atmospherics/fakeA = pipe_type - var/flags = initial(fakeA.pipe_flags) - for(var/obj/machinery/atmospherics/M in loc) - if((M.pipe_flags & flags & PIPING_ONE_PER_TURF)) //Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers. - to_chat(user, "Something is hogging the tile!") - return TRUE - if((M.piping_layer != piping_layer) && !((M.pipe_flags | flags) & PIPING_ALL_LAYER)) //don't continue if either pipe goes across all layers - continue - if(M.GetInitDirections() & SSair.get_init_dirs(pipe_type, fixed_dir())) // matches at least one direction on either type of pipe - to_chat(user, "There is already a pipe at that location!") - return TRUE - // no conflicts found - - var/obj/machinery/atmospherics/A = new pipe_type(loc) - build_pipe(A) - A.on_construction(color, piping_layer) - transfer_fingerprints_to(A) - - W.play_tool_sound(src) - user.visible_message( \ - "[user] fastens \the [src].", \ - "You fasten \the [src].", \ - "You hear ratcheting.") - - qdel(src) - -/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A) - A.setDir(fixed_dir()) - A.SetInitDirections() - - if(pipename) - A.name = pipename - if(A.on) - // Certain pre-mapped subtypes are on by default, we want to preserve - // every other aspect of these subtypes (name, pre-set filters, etc.) - // but they shouldn't turn on automatically when wrenched. - A.on = FALSE - -/obj/item/pipe/trinary/flippable/build_pipe(obj/machinery/atmospherics/components/trinary/T) - ..() - T.flipped = flipped - -/obj/item/pipe/directional/suicide_act(mob/user) - user.visible_message("[user] shoves [src] in [user.p_their()] mouth and turns it on! It looks like [user.p_theyre()] trying to commit suicide!") - if(iscarbon(user)) - var/mob/living/carbon/C = user - for(var/i=1 to 20) - C.vomit(0, TRUE, FALSE, 4, FALSE) - if(prob(20)) - C.spew_organ() - sleep(5) - C.blood_volume = 0 - return(OXYLOSS|BRUTELOSS) - -/obj/item/pipe_meter - name = "meter" - desc = "A meter that can be laid on pipes." - icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' - icon_state = "meter" - 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 - PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer) +/*CONTENTS +Buildable pipes +Buildable meters +*/ + +//construction defines are in __defines/pipe_construction.dm +//update those defines ANY TIME an atmos path is changed... +//...otherwise construction will stop working + +/obj/item/pipe + name = "pipe" + desc = "A pipe." + var/pipe_type + var/pipename + force = 7 + throwforce = 7 + icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' + icon_state = "simple" + item_state = "buildpipe" + w_class = WEIGHT_CLASS_NORMAL + level = 2 + var/piping_layer = PIPING_LAYER_DEFAULT + var/RPD_type + var/disposable = TRUE // yogs + +/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 if(_pipe_type) // yogs - only change if _pipe_type is set + pipe_type = _pipe_type + setDir(_dir) + + update() + pixel_x += rand(-5, 5) + pixel_y += rand(-5, 5) + return ..() + +/obj/item/pipe/proc/make_from_existing(obj/machinery/atmospherics/make_from) + setDir(make_from.dir) + pipename = make_from.name + add_atom_colour(make_from.color, FIXED_COLOUR_PRIORITY) + pipe_type = make_from.type + +/obj/item/pipe/trinary/flippable/make_from_existing(obj/machinery/atmospherics/components/trinary/make_from) + ..() + if(make_from.flipped) + do_a_flip() + +/obj/item/pipe/dropped() + if(loc) + setPipingLayer(piping_layer) + return ..() + +/obj/item/pipe/proc/setPipingLayer(new_layer = PIPING_LAYER_DEFAULT) + var/obj/machinery/atmospherics/fakeA = pipe_type + + if(initial(fakeA.pipe_flags) & PIPING_ALL_LAYER) + new_layer = PIPING_LAYER_DEFAULT + piping_layer = new_layer + + PIPING_LAYER_SHIFT(src, piping_layer) + layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) + +/obj/item/pipe/proc/update() + var/obj/machinery/atmospherics/fakeA = pipe_type + name = "[initial(fakeA.name)] fitting" + icon_state = initial(fakeA.pipe_state) + if(ispath(pipe_type,/obj/machinery/atmospherics/pipe/heat_exchanging)) + resistance_flags |= FIRE_PROOF | LAVA_PROOF + +/obj/item/pipe/verb/flip() + set category = "Object" + set name = "Flip Pipe" + set src in view(1) + + if ( usr.incapacitated() ) + return + + do_a_flip() + +/obj/item/pipe/proc/do_a_flip() + setDir(turn(dir, -180)) + +/obj/item/pipe/trinary/flippable/do_a_flip() + setDir(turn(dir, flipped ? 45 : -45)) + flipped = !flipped + +/obj/item/pipe/Move() + var/old_dir = dir + ..() + setDir(old_dir) //pipes changing direction when moved is just annoying and buggy + +// Convert dir of fitting into dir of built component +/obj/item/pipe/proc/fixed_dir() + return dir + +/obj/item/pipe/binary/fixed_dir() + . = dir + if(dir == SOUTH) + . = NORTH + else if(dir == WEST) + . = EAST + +/obj/item/pipe/trinary/flippable/fixed_dir() + . = dir + if(dir in GLOB.diagonals) + . = turn(dir, 45) + +/obj/item/pipe/attack_self(mob/user) + setDir(turn(dir,-90)) + +/obj/item/pipe/wrench_act(mob/living/user, obj/item/wrench/W) + if(!isturf(loc)) + return TRUE + + add_fingerprint(user) + + var/obj/machinery/atmospherics/fakeA = pipe_type + var/flags = initial(fakeA.pipe_flags) + for(var/obj/machinery/atmospherics/M in loc) + if((M.pipe_flags & flags & PIPING_ONE_PER_TURF)) //Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers. + to_chat(user, "Something is hogging the tile!") + return TRUE + if((M.piping_layer != piping_layer) && !((M.pipe_flags | flags) & PIPING_ALL_LAYER)) //don't continue if either pipe goes across all layers + continue + if(M.GetInitDirections() & SSair.get_init_dirs(pipe_type, fixed_dir())) // matches at least one direction on either type of pipe + to_chat(user, "There is already a pipe at that location!") + return TRUE + // no conflicts found + + var/obj/machinery/atmospherics/A = new pipe_type(loc) + build_pipe(A) + A.on_construction(color, piping_layer) + transfer_fingerprints_to(A) + + W.play_tool_sound(src) + user.visible_message( \ + "[user] fastens \the [src].", \ + "You fasten \the [src].", \ + "You hear ratcheting.") + + qdel(src) + +/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A) + A.setDir(fixed_dir()) + A.SetInitDirections() + + if(pipename) + A.name = pipename + if(A.on) + // Certain pre-mapped subtypes are on by default, we want to preserve + // every other aspect of these subtypes (name, pre-set filters, etc.) + // but they shouldn't turn on automatically when wrenched. + A.on = FALSE + +/obj/item/pipe/trinary/flippable/build_pipe(obj/machinery/atmospherics/components/trinary/T) + ..() + T.flipped = flipped + +/obj/item/pipe/directional/suicide_act(mob/user) + user.visible_message("[user] shoves [src] in [user.p_their()] mouth and turns it on! It looks like [user.p_theyre()] trying to commit suicide!") + if(iscarbon(user)) + var/mob/living/carbon/C = user + for(var/i=1 to 20) + C.vomit(0, TRUE, FALSE, 4, FALSE) + if(prob(20)) + C.spew_organ() + sleep(5) + C.blood_volume = 0 + return(OXYLOSS|BRUTELOSS) + +/obj/item/pipe_meter + name = "meter" + desc = "A meter that can be laid on pipes." + icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' + icon_state = "meter" + 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 + PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer) diff --git a/code/game/machinery/pipe/pipe_dispenser.dm b/code/game/machinery/pipe/pipe_dispenser.dm index 54bed2279804..4721466bf3f9 100644 --- a/code/game/machinery/pipe/pipe_dispenser.dm +++ b/code/game/machinery/pipe/pipe_dispenser.dm @@ -1,220 +1,220 @@ -/obj/machinery/pipedispenser - name = "pipe dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - desc = "Dispenses countless types of pipes. Very useful if you need pipes." - density = TRUE - interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_OFFLINE - var/wait = 0 - var/piping_layer = PIPING_LAYER_DEFAULT - -/obj/machinery/pipedispenser/attack_paw(mob/user) - return attack_hand(user) - -/obj/machinery/pipedispenser/ui_interact(mob/user) - . = ..() - var/dat = "PIPING LAYER: --[piping_layer]++
                    " - - var/recipes = GLOB.atmos_pipe_recipes - - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - dat += "[category]:
                      " - - for(var/i in cat_recipes) - var/datum/pipe_info/I = i - dat += I.Render(src) - - dat += "
                    " - - user << browse("[src][dat]", "window=pipedispenser") - onclose(user, "pipedispenser") - return - -/obj/machinery/pipedispenser/Topic(href, href_list) - if(..()) - return 1 - var/mob/living/L = usr - if(!anchored || (istype(L) && !(L.mobility_flags & MOBILITY_UI)) || usr.stat || usr.restrained() || !in_range(loc, usr)) - usr << browse(null, "window=pipedispenser") - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(href_list["makepipe"]) - if(wait < world.time) - var/p_type = text2path(href_list["makepipe"]) - if (!verify_recipe(GLOB.atmos_pipe_recipes, p_type)) - return - var/p_dir = text2num(href_list["dir"]) - var/obj/item/pipe/P = new (loc, p_type, p_dir) - P.setPipingLayer(piping_layer) - P.add_fingerprint(usr) - wait = world.time + 10 - if(href_list["makemeter"]) - if(wait < world.time ) - new /obj/item/pipe_meter(loc) - wait = world.time + 15 - if(href_list["layer_up"]) - piping_layer = CLAMP(++piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - if(href_list["layer_down"]) - piping_layer = CLAMP(--piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - return - -/obj/machinery/pipedispenser/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if (istype(W, /obj/item/pipe) || istype(W, /obj/item/pipe_meter)) -// yogs start - disposable check - if(istype(W, /obj/item/pipe)) - var/obj/item/pipe/P = W - if(!P.disposable) - to_chat(usr, "[src] is too valuable to dispose of!") - return -// yogs end - to_chat(usr, "You put [W] back into [src].") - qdel(W) - return - else - return ..() - -/obj/machinery/pipedispenser/proc/verify_recipe(recipes, path) - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - for(var/i in cat_recipes) - var/datum/pipe_info/info = i - if (path == info.id) - return TRUE - return FALSE - -/obj/machinery/pipedispenser/wrench_act(mob/living/user, obj/item/I) - if(default_unfasten_wrench(user, I, 40)) - user << browse(null, "window=pipedispenser") - - return TRUE - - -/obj/machinery/pipedispenser/disposal - name = "disposal pipe dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - desc = "Dispenses pipes that will ultimately be used to move trash around." - density = TRUE - - -//Allow you to drag-drop disposal pipes and transit tubes into it -/obj/machinery/pipedispenser/disposal/MouseDrop_T(obj/structure/pipe, mob/usr) - if(!usr.incapacitated()) - return - - if (!istype(pipe, /obj/structure/disposalconstruct) && !istype(pipe, /obj/structure/c_transit_tube) && !istype(pipe, /obj/structure/c_transit_tube_pod)) - return - - if (get_dist(usr, src) > 1 || get_dist(src,pipe) > 1 ) - return - - if (pipe.anchored) - return - - qdel(pipe) - -/obj/machinery/pipedispenser/disposal/interact(mob/user) - - var/dat = "" - var/recipes = GLOB.disposal_pipe_recipes - - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - dat += "[category]:
                      " - - for(var/i in cat_recipes) - var/datum/pipe_info/I = i - dat += I.Render(src) - - dat += "
                    " - - user << browse("[src][dat]", "window=pipedispenser") - return - - -/obj/machinery/pipedispenser/disposal/Topic(href, href_list) - if(..()) - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(href_list["dmake"]) - if(wait < world.time) - var/p_type = text2path(href_list["dmake"]) - if (!verify_recipe(GLOB.disposal_pipe_recipes, p_type)) - return - var/obj/structure/disposalconstruct/C = new (loc, p_type) - - if(!C.can_place()) - to_chat(usr, "There's not enough room to build that here!") - qdel(C) - return - if(href_list["dir"]) - C.setDir(text2num(href_list["dir"])) - C.add_fingerprint(usr) - C.update_icon() - wait = world.time + 15 - return - -//transit tube dispenser -//inherit disposal for the dragging proc -/obj/machinery/pipedispenser/disposal/transit_tube - name = "transit tube dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - density = TRUE - desc = "Dispenses pipes that will move beings around." - -/obj/machinery/pipedispenser/disposal/transit_tube/interact(mob/user) - - var/dat = {"Transit Tubes:
                    -Straight Tube
                    -Straight Tube with Crossing
                    -Curved Tube
                    -Diagonal Tube
                    -Diagonal Tube with Crossing
                    -Junction
                    -Station Equipment:
                    -Through Tube Station
                    -Terminus Tube Station
                    -Transit Tube Pod
                    -"} - - user << browse("[src][dat]", "window=pipedispenser") - return - - -/obj/machinery/pipedispenser/disposal/transit_tube/Topic(href, href_list) - if(..()) - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(wait < world.time) - if(href_list["tube"]) - var/tube_type = text2num(href_list["tube"]) - var/obj/structure/C - switch(tube_type) - if(TRANSIT_TUBE_STRAIGHT) - C = new /obj/structure/c_transit_tube(loc) - if(TRANSIT_TUBE_STRAIGHT_CROSSING) - C = new /obj/structure/c_transit_tube/crossing(loc) - if(TRANSIT_TUBE_CURVED) - C = new /obj/structure/c_transit_tube/curved(loc) - if(TRANSIT_TUBE_DIAGONAL) - C = new /obj/structure/c_transit_tube/diagonal(loc) - if(TRANSIT_TUBE_DIAGONAL_CROSSING) - C = new /obj/structure/c_transit_tube/diagonal/crossing(loc) - if(TRANSIT_TUBE_JUNCTION) - C = new /obj/structure/c_transit_tube/junction(loc) - if(TRANSIT_TUBE_STATION) - C = new /obj/structure/c_transit_tube/station(loc) - if(TRANSIT_TUBE_TERMINUS) - C = new /obj/structure/c_transit_tube/station/reverse(loc) - if(TRANSIT_TUBE_POD) - C = new /obj/structure/c_transit_tube_pod(loc) - if(C) - C.add_fingerprint(usr) - wait = world.time + 15 - return +/obj/machinery/pipedispenser + name = "pipe dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + desc = "Dispenses countless types of pipes. Very useful if you need pipes." + density = TRUE + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_OFFLINE + var/wait = 0 + var/piping_layer = PIPING_LAYER_DEFAULT + +/obj/machinery/pipedispenser/attack_paw(mob/user) + return attack_hand(user) + +/obj/machinery/pipedispenser/ui_interact(mob/user) + . = ..() + var/dat = "PIPING LAYER: --[piping_layer]++
                    " + + var/recipes = GLOB.atmos_pipe_recipes + + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + dat += "[category]:
                      " + + for(var/i in cat_recipes) + var/datum/pipe_info/I = i + dat += I.Render(src) + + dat += "
                    " + + user << browse("[src][dat]", "window=pipedispenser") + onclose(user, "pipedispenser") + return + +/obj/machinery/pipedispenser/Topic(href, href_list) + if(..()) + return 1 + var/mob/living/L = usr + if(!anchored || (istype(L) && !(L.mobility_flags & MOBILITY_UI)) || usr.stat || usr.restrained() || !in_range(loc, usr)) + usr << browse(null, "window=pipedispenser") + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(href_list["makepipe"]) + if(wait < world.time) + var/p_type = text2path(href_list["makepipe"]) + if (!verify_recipe(GLOB.atmos_pipe_recipes, p_type)) + return + var/p_dir = text2num(href_list["dir"]) + var/obj/item/pipe/P = new (loc, p_type, p_dir) + P.setPipingLayer(piping_layer) + P.add_fingerprint(usr) + wait = world.time + 10 + if(href_list["makemeter"]) + if(wait < world.time ) + new /obj/item/pipe_meter(loc) + wait = world.time + 15 + if(href_list["layer_up"]) + piping_layer = CLAMP(++piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + if(href_list["layer_down"]) + piping_layer = CLAMP(--piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + return + +/obj/machinery/pipedispenser/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if (istype(W, /obj/item/pipe) || istype(W, /obj/item/pipe_meter)) +// yogs start - disposable check + if(istype(W, /obj/item/pipe)) + var/obj/item/pipe/P = W + if(!P.disposable) + to_chat(usr, "[src] is too valuable to dispose of!") + return +// yogs end + to_chat(usr, "You put [W] back into [src].") + qdel(W) + return + else + return ..() + +/obj/machinery/pipedispenser/proc/verify_recipe(recipes, path) + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + for(var/i in cat_recipes) + var/datum/pipe_info/info = i + if (path == info.id) + return TRUE + return FALSE + +/obj/machinery/pipedispenser/wrench_act(mob/living/user, obj/item/I) + if(default_unfasten_wrench(user, I, 40)) + user << browse(null, "window=pipedispenser") + + return TRUE + + +/obj/machinery/pipedispenser/disposal + name = "disposal pipe dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + desc = "Dispenses pipes that will ultimately be used to move trash around." + density = TRUE + + +//Allow you to drag-drop disposal pipes and transit tubes into it +/obj/machinery/pipedispenser/disposal/MouseDrop_T(obj/structure/pipe, mob/usr) + if(!usr.incapacitated()) + return + + if (!istype(pipe, /obj/structure/disposalconstruct) && !istype(pipe, /obj/structure/c_transit_tube) && !istype(pipe, /obj/structure/c_transit_tube_pod)) + return + + if (get_dist(usr, src) > 1 || get_dist(src,pipe) > 1 ) + return + + if (pipe.anchored) + return + + qdel(pipe) + +/obj/machinery/pipedispenser/disposal/interact(mob/user) + + var/dat = "" + var/recipes = GLOB.disposal_pipe_recipes + + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + dat += "[category]:
                      " + + for(var/i in cat_recipes) + var/datum/pipe_info/I = i + dat += I.Render(src) + + dat += "
                    " + + user << browse("[src][dat]", "window=pipedispenser") + return + + +/obj/machinery/pipedispenser/disposal/Topic(href, href_list) + if(..()) + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(href_list["dmake"]) + if(wait < world.time) + var/p_type = text2path(href_list["dmake"]) + if (!verify_recipe(GLOB.disposal_pipe_recipes, p_type)) + return + var/obj/structure/disposalconstruct/C = new (loc, p_type) + + if(!C.can_place()) + to_chat(usr, "There's not enough room to build that here!") + qdel(C) + return + if(href_list["dir"]) + C.setDir(text2num(href_list["dir"])) + C.add_fingerprint(usr) + C.update_icon() + wait = world.time + 15 + return + +//transit tube dispenser +//inherit disposal for the dragging proc +/obj/machinery/pipedispenser/disposal/transit_tube + name = "transit tube dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + density = TRUE + desc = "Dispenses pipes that will move beings around." + +/obj/machinery/pipedispenser/disposal/transit_tube/interact(mob/user) + + var/dat = {"Transit Tubes:
                    +Straight Tube
                    +Straight Tube with Crossing
                    +Curved Tube
                    +Diagonal Tube
                    +Diagonal Tube with Crossing
                    +Junction
                    +Station Equipment:
                    +Through Tube Station
                    +Terminus Tube Station
                    +Transit Tube Pod
                    +"} + + user << browse("[src][dat]", "window=pipedispenser") + return + + +/obj/machinery/pipedispenser/disposal/transit_tube/Topic(href, href_list) + if(..()) + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(wait < world.time) + if(href_list["tube"]) + var/tube_type = text2num(href_list["tube"]) + var/obj/structure/C + switch(tube_type) + if(TRANSIT_TUBE_STRAIGHT) + C = new /obj/structure/c_transit_tube(loc) + if(TRANSIT_TUBE_STRAIGHT_CROSSING) + C = new /obj/structure/c_transit_tube/crossing(loc) + if(TRANSIT_TUBE_CURVED) + C = new /obj/structure/c_transit_tube/curved(loc) + if(TRANSIT_TUBE_DIAGONAL) + C = new /obj/structure/c_transit_tube/diagonal(loc) + if(TRANSIT_TUBE_DIAGONAL_CROSSING) + C = new /obj/structure/c_transit_tube/diagonal/crossing(loc) + if(TRANSIT_TUBE_JUNCTION) + C = new /obj/structure/c_transit_tube/junction(loc) + if(TRANSIT_TUBE_STATION) + C = new /obj/structure/c_transit_tube/station(loc) + if(TRANSIT_TUBE_TERMINUS) + C = new /obj/structure/c_transit_tube/station/reverse(loc) + if(TRANSIT_TUBE_POD) + C = new /obj/structure/c_transit_tube_pod(loc) + if(C) + C.add_fingerprint(usr) + wait = world.time + 15 + return diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 6055a3844952..8fcae26b2ec0 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -1,1096 +1,1096 @@ -#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/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(I.tool_behaviour == TOOL_CROWBAR) - //If the turret is destroyed, you can remove it with a crowbar to - //try and salvage its components - to_chat(user, "You begin prying the metal coverings off...") - if(I.use_tool(src, user, 20)) - if(prob(70)) - if(stored_gun) - stored_gun.forceMove(loc) - to_chat(user, "You remove the turret and salvage some components.") - if(prob(50)) - new /obj/item/stack/sheet/metal(loc, rand(1,4)) - if(prob(50)) - new /obj/item/assembly/prox_sensor(loc) - else - to_chat(user, "You remove the turret but did not manage to salvage anything.") - qdel(src) - - else if((I.tool_behaviour == TOOL_WRENCH) && (!on)) - if(raised) - return - - //This code handles moving the turret around. After all, it's a portable turret! - if(!anchored && !isinspace()) - setAnchored(TRUE) - invisibility = INVISIBILITY_MAXIMUM - update_icon() - to_chat(user, "You secure the exterior bolts on the turret.") - if(has_cover) - cover = new /obj/machinery/porta_turret_cover(loc) //create a new turret. While this is handled in process(), this is to workaround a bug where the turret becomes invisible for a split second - cover.parent_turret = src //make the cover's parent src - else if(anchored) - setAnchored(FALSE) - to_chat(user, "You unsecure the exterior bolts on the turret.") - power_change() - invisibility = 0 - qdel(cover) //deletes the cover, and the turret instance itself becomes its own cover. - - else if(I.GetID()) - //Behavior lock/unlock mangement - if(allowed(user)) - locked = !locked - to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") - else - to_chat(user, "Access denied.") - else if(I.tool_behaviour == TOOL_MULTITOOL && !locked) - if(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You add [src] to multitool buffer.") - else - return ..() - -/obj/machinery/porta_turret/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - to_chat(user, "You short out [src]'s threat assessment circuits.") - 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 - - -/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(. && obj_integrity > 0) //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 - continue - - if(issilicon(A)) - var/mob/living/silicon/sillycone = A - - if(ispAI(A)) - continue - - if(sillycone.stat || in_faction(sillycone)) - continue - - if(iscyborg(sillycone)) - var/mob/living/silicon/robot/sillyconerobot = A - if(LAZYLEN(faction) && (ROLE_SYNDICATE in faction) && sillyconerobot.emagged == TRUE) - continue - - targets += sillycone - continue - - if(iscarbon(A)) - var/mob/living/carbon/C = A - //If not emagged, only target carbons that can use items - if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || !(C.mobility_flags & MOBILITY_USE))) - continue - - //If emagged, target all but dead carbons - if(mode == TURRET_LETHAL && C.stat == DEAD) - continue - - //if the target is a human and not in our faction, analyze threat level - if(ishuman(C) && !in_faction(C)) - if(assess_perp(C) >= 4) - targets += C - - else if(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 - // yogs start - for(var/A in GLOB.spacepods_list) - if((get_dist(A, base) < scan_range) && can_see(base, A, scan_range)) - var/obj/spacepod/SP = A - if(SP.pilot && !in_faction(SP.pilot)) - if(assess_perp(SP.pilot) >= 4) - targets += SP - // yogs end - - if(check_anomalies && GLOB.blobs.len && (mode == TURRET_LETHAL)) - for(var/obj/structure/blob/B in view(scan_range, base)) - targets += B - - if(targets.len) - tryToShootAt(targets) - else if(!always_up) - popDown() // no valid targets, close the cover - -/obj/machinery/porta_turret/proc/tryToShootAt(list/atom/movable/targets) - while(targets.len > 0) - var/atom/movable/M = pick(targets) - targets -= M - if(target(M)) - return 1 - - -/obj/machinery/porta_turret/proc/popUp() //pops the turret up - if(!anchored) - return - if(raising || raised) - return - if(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 - - update_icon() - var/obj/item/projectile/A - //any emagged turrets drains 2x power and uses a different projectile? - if(mode == TURRET_STUN) - use_power(reqpower) - A = new stun_projectile(T) - playsound(loc, stun_projectile_sound, 75, 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/setup() - return - -/obj/machinery/porta_turret/syndicate/assess_perp(mob/living/carbon/human/perp) - return 10 //Syndicate turrets shoot everything not in their faction - -/obj/machinery/porta_turret/syndicate/energy - icon_state = "standard_lethal" - base_icon_state = "standard" - stun_projectile = /obj/item/projectile/energy/electrode - stun_projectile_sound = 'sound/weapons/taser.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_lethal" - base_icon_state = "standard" - stun_projectile = /obj/item/projectile/energy/electrode - stun_projectile_sound = 'sound/weapons/taser.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/energy/raven - stun_projectile = /obj/item/projectile/beam/laser - stun_projectile_sound = 'sound/weapons/laser.ogg' - faction = list("neutral","silicon","turret") - - -/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") - -/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 - lethal_projectile = /obj/item/projectile/beam/weak - faction = list("neutral","silicon","turret") - -//////////////////////// -//Turret Control Panel// -//////////////////////// - -/obj/machinery/turretid - name = "turret control panel" - desc = "Used to control a room's automated defenses." - icon = 'icons/obj/machines/turret_control.dmi' - icon_state = "control_standby" - density = FALSE - 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(I.tool_behaviour == TOOL_MULTITOOL) - if(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - if(M.buffer && istype(M.buffer, /obj/machinery/porta_turret)) - turrets |= M.buffer - to_chat(user, "You link \the [M.buffer] with \the [src]") - return - - if (issilicon(user)) - return attack_hand(user) - - if ( get_dist(src, user) == 0 ) // trying to unlock the interface - if (allowed(usr)) - if(obj_flags & EMAGGED) - to_chat(user, "The turret control is unresponsive.") - return - - locked = !locked - to_chat(user, "You [ locked ? "lock" : "unlock"] the panel.") - 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) - -/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(usr) - else if (href_list["toggleLethal"]) - toggle_lethal(usr) - attack_hand(usr) - -/obj/machinery/turretid/proc/toggle_lethal(mob/user) - lethal = !lethal - add_hiddenprint(user) - log_combat(user, src, "[lethal ? "enabled" : "disabled"] lethals on") - updateTurrets() - -/obj/machinery/turretid/proc/toggle_on(mob/user) - enabled = !enabled - add_hiddenprint(user) - log_combat(user, src, "[enabled ? "enabled" : "disabled"]") - updateTurrets() - -/obj/machinery/turretid/proc/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/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(I.tool_behaviour == TOOL_CROWBAR) + //If the turret is destroyed, you can remove it with a crowbar to + //try and salvage its components + to_chat(user, "You begin prying the metal coverings off...") + if(I.use_tool(src, user, 20)) + if(prob(70)) + if(stored_gun) + stored_gun.forceMove(loc) + to_chat(user, "You remove the turret and salvage some components.") + if(prob(50)) + new /obj/item/stack/sheet/metal(loc, rand(1,4)) + if(prob(50)) + new /obj/item/assembly/prox_sensor(loc) + else + to_chat(user, "You remove the turret but did not manage to salvage anything.") + qdel(src) + + else if((I.tool_behaviour == TOOL_WRENCH) && (!on)) + if(raised) + return + + //This code handles moving the turret around. After all, it's a portable turret! + if(!anchored && !isinspace()) + setAnchored(TRUE) + invisibility = INVISIBILITY_MAXIMUM + update_icon() + to_chat(user, "You secure the exterior bolts on the turret.") + if(has_cover) + cover = new /obj/machinery/porta_turret_cover(loc) //create a new turret. While this is handled in process(), this is to workaround a bug where the turret becomes invisible for a split second + cover.parent_turret = src //make the cover's parent src + else if(anchored) + setAnchored(FALSE) + to_chat(user, "You unsecure the exterior bolts on the turret.") + power_change() + invisibility = 0 + qdel(cover) //deletes the cover, and the turret instance itself becomes its own cover. + + else if(I.GetID()) + //Behavior lock/unlock mangement + if(allowed(user)) + locked = !locked + to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") + else + to_chat(user, "Access denied.") + else if(I.tool_behaviour == TOOL_MULTITOOL && !locked) + if(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You add [src] to multitool buffer.") + else + return ..() + +/obj/machinery/porta_turret/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + to_chat(user, "You short out [src]'s threat assessment circuits.") + 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 + + +/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(. && obj_integrity > 0) //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 + continue + + if(issilicon(A)) + var/mob/living/silicon/sillycone = A + + if(ispAI(A)) + continue + + if(sillycone.stat || in_faction(sillycone)) + continue + + if(iscyborg(sillycone)) + var/mob/living/silicon/robot/sillyconerobot = A + if(LAZYLEN(faction) && (ROLE_SYNDICATE in faction) && sillyconerobot.emagged == TRUE) + continue + + targets += sillycone + continue + + if(iscarbon(A)) + var/mob/living/carbon/C = A + //If not emagged, only target carbons that can use items + if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || !(C.mobility_flags & MOBILITY_USE))) + continue + + //If emagged, target all but dead carbons + if(mode == TURRET_LETHAL && C.stat == DEAD) + continue + + //if the target is a human and not in our faction, analyze threat level + if(ishuman(C) && !in_faction(C)) + if(assess_perp(C) >= 4) + targets += C + + else if(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 + // yogs start + for(var/A in GLOB.spacepods_list) + if((get_dist(A, base) < scan_range) && can_see(base, A, scan_range)) + var/obj/spacepod/SP = A + if(SP.pilot && !in_faction(SP.pilot)) + if(assess_perp(SP.pilot) >= 4) + targets += SP + // yogs end + + if(check_anomalies && GLOB.blobs.len && (mode == TURRET_LETHAL)) + for(var/obj/structure/blob/B in view(scan_range, base)) + targets += B + + if(targets.len) + tryToShootAt(targets) + else if(!always_up) + popDown() // no valid targets, close the cover + +/obj/machinery/porta_turret/proc/tryToShootAt(list/atom/movable/targets) + while(targets.len > 0) + var/atom/movable/M = pick(targets) + targets -= M + if(target(M)) + return 1 + + +/obj/machinery/porta_turret/proc/popUp() //pops the turret up + if(!anchored) + return + if(raising || raised) + return + if(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 + + update_icon() + var/obj/item/projectile/A + //any emagged turrets drains 2x power and uses a different projectile? + if(mode == TURRET_STUN) + use_power(reqpower) + A = new stun_projectile(T) + playsound(loc, stun_projectile_sound, 75, 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/setup() + return + +/obj/machinery/porta_turret/syndicate/assess_perp(mob/living/carbon/human/perp) + return 10 //Syndicate turrets shoot everything not in their faction + +/obj/machinery/porta_turret/syndicate/energy + icon_state = "standard_lethal" + base_icon_state = "standard" + stun_projectile = /obj/item/projectile/energy/electrode + stun_projectile_sound = 'sound/weapons/taser.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_lethal" + base_icon_state = "standard" + stun_projectile = /obj/item/projectile/energy/electrode + stun_projectile_sound = 'sound/weapons/taser.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/energy/raven + stun_projectile = /obj/item/projectile/beam/laser + stun_projectile_sound = 'sound/weapons/laser.ogg' + faction = list("neutral","silicon","turret") + + +/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") + +/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 + lethal_projectile = /obj/item/projectile/beam/weak + faction = list("neutral","silicon","turret") + +//////////////////////// +//Turret Control Panel// +//////////////////////// + +/obj/machinery/turretid + name = "turret control panel" + desc = "Used to control a room's automated defenses." + icon = 'icons/obj/machines/turret_control.dmi' + icon_state = "control_standby" + density = FALSE + 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(I.tool_behaviour == TOOL_MULTITOOL) + if(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + if(M.buffer && istype(M.buffer, /obj/machinery/porta_turret)) + turrets |= M.buffer + to_chat(user, "You link \the [M.buffer] with \the [src]") + return + + if (issilicon(user)) + return attack_hand(user) + + if ( get_dist(src, user) == 0 ) // trying to unlock the interface + if (allowed(usr)) + if(obj_flags & EMAGGED) + to_chat(user, "The turret control is unresponsive.") + return + + locked = !locked + to_chat(user, "You [ locked ? "lock" : "unlock"] the panel.") + 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) + +/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(usr) + else if (href_list["toggleLethal"]) + toggle_lethal(usr) + attack_hand(usr) + +/obj/machinery/turretid/proc/toggle_lethal(mob/user) + lethal = !lethal + add_hiddenprint(user) + log_combat(user, src, "[lethal ? "enabled" : "disabled"] lethals on") + updateTurrets() + +/obj/machinery/turretid/proc/toggle_on(mob/user) + enabled = !enabled + add_hiddenprint(user) + log_combat(user, src, "[enabled ? "enabled" : "disabled"]") + updateTurrets() + +/obj/machinery/turretid/proc/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 80e99c45065c..cd987a057b08 100755 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -1,179 +1,179 @@ -/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/machinery/recharger/RefreshParts() - for(var/obj/item/stock_parts/capacitor/C in component_parts) - recharge_coeff = C.rating - -/obj/machinery/recharger/examine(mob/user) - . = ..() - if(!in_range(user, src) && !issilicon(user) && !isobserver(user)) - . += "You're too far away to examine [src]'s contents and display!" - return - - if(charging) - . += {"\The [src] contains: - - \A [charging]."} - - if(!(stat & (NOPOWER|BROKEN))) - . += "The status display reads:" - . += "- Recharging [recharge_coeff*10]% cell charge per cycle." - if(charging) - var/obj/item/stock_parts/cell/C = charging.get_cell() - . += "- \The [charging]'s cell is at [C.percent()]%." - - -/obj/machinery/recharger/proc/setCharging(new_charging) - charging = new_charging - if (new_charging) - START_PROCESSING(SSmachines, src) - use_power = ACTIVE_POWER_USE - update_icon(scan = TRUE) - else - use_power = IDLE_POWER_USE - update_icon() - -/obj/machinery/recharger/attackby(obj/item/G, mob/user, params) - if(G.tool_behaviour == TOOL_WRENCH) - if(charging) - to_chat(user, "Remove the charging item first!") - return - setAnchored(!anchored) - power_change() - to_chat(user, "You [anchored ? "attached" : "detached"] [src].") - G.play_tool_sound(src) - return - - var/allowed = is_type_in_typecache(G, allowed_devices) - - if(allowed) - if(anchored) - if(charging || panel_open) - return 1 - - //Checks to make sure he's not in space doing it, and that the area got proper power. - var/area/a = get_area(src) - if(!isarea(a) || a.power_equip == 0) - to_chat(user, "[src] blinks red as you try to insert [G].") - return 1 - - if (istype(G, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = G - if(!E.can_charge) - to_chat(user, "Your gun has no external power connector.") - return 1 - - if(!user.transferItemToLoc(G, src)) - return 1 - setCharging(G) - - else - to_chat(user, "[src] isn't connected to anything!") - return 1 - - if(anchored && !charging) - if(default_deconstruction_screwdriver(user, "rechargeropen", "recharger0", G)) - return - - if(panel_open && G.tool_behaviour == TOOL_CROWBAR) - default_deconstruction_crowbar(G) - return - - return ..() - -/obj/machinery/recharger/attack_hand(mob/user) - . = ..() - if(.) - return - - add_fingerprint(user) - if(charging) - charging.update_icon() - charging.forceMove(drop_location()) - user.put_in_hands(charging) - setCharging(null) - -/obj/machinery/recharger/attack_tk(mob/user) - if(charging) - charging.update_icon() - charging.forceMove(drop_location()) - setCharging(null) - -/obj/machinery/recharger/process() - if(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/machinery/recharger/RefreshParts() + for(var/obj/item/stock_parts/capacitor/C in component_parts) + recharge_coeff = C.rating + +/obj/machinery/recharger/examine(mob/user) + . = ..() + if(!in_range(user, src) && !issilicon(user) && !isobserver(user)) + . += "You're too far away to examine [src]'s contents and display!" + return + + if(charging) + . += {"\The [src] contains: + - \A [charging]."} + + if(!(stat & (NOPOWER|BROKEN))) + . += "The status display reads:" + . += "- Recharging [recharge_coeff*10]% cell charge per cycle." + if(charging) + var/obj/item/stock_parts/cell/C = charging.get_cell() + . += "- \The [charging]'s cell is at [C.percent()]%." + + +/obj/machinery/recharger/proc/setCharging(new_charging) + charging = new_charging + if (new_charging) + START_PROCESSING(SSmachines, src) + use_power = ACTIVE_POWER_USE + update_icon(scan = TRUE) + else + use_power = IDLE_POWER_USE + update_icon() + +/obj/machinery/recharger/attackby(obj/item/G, mob/user, params) + if(G.tool_behaviour == TOOL_WRENCH) + if(charging) + to_chat(user, "Remove the charging item first!") + return + setAnchored(!anchored) + power_change() + to_chat(user, "You [anchored ? "attached" : "detached"] [src].") + G.play_tool_sound(src) + return + + var/allowed = is_type_in_typecache(G, allowed_devices) + + if(allowed) + if(anchored) + if(charging || panel_open) + return 1 + + //Checks to make sure he's not in space doing it, and that the area got proper power. + var/area/a = get_area(src) + if(!isarea(a) || a.power_equip == 0) + to_chat(user, "[src] blinks red as you try to insert [G].") + return 1 + + if (istype(G, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = G + if(!E.can_charge) + to_chat(user, "Your gun has no external power connector.") + return 1 + + if(!user.transferItemToLoc(G, src)) + return 1 + setCharging(G) + + else + to_chat(user, "[src] isn't connected to anything!") + return 1 + + if(anchored && !charging) + if(default_deconstruction_screwdriver(user, "rechargeropen", "recharger0", G)) + return + + if(panel_open && G.tool_behaviour == TOOL_CROWBAR) + default_deconstruction_crowbar(G) + return + + return ..() + +/obj/machinery/recharger/attack_hand(mob/user) + . = ..() + if(.) + return + + add_fingerprint(user) + if(charging) + charging.update_icon() + charging.forceMove(drop_location()) + user.put_in_hands(charging) + setCharging(null) + +/obj/machinery/recharger/attack_tk(mob/user) + if(charging) + charging.update_icon() + charging.forceMove(drop_location()) + setCharging(null) + +/obj/machinery/recharger/process() + if(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 8fa5524f79d1..189eb7aa89de 100644 --- a/code/game/machinery/rechargestation.dm +++ b/code/game/machinery/rechargestation.dm @@ -1,107 +1,107 @@ -/obj/machinery/recharge_station - name = "cyborg recharging station" - desc = "This device recharges cyborgs and resupplies them with materials." - icon = 'icons/obj/objects.dmi' - icon_state = "borgcharger0" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 1000 - req_access = list(ACCESS_ROBOTICS) - state_open = TRUE - circuit = /obj/item/circuitboard/machine/cyborgrecharger - occupant_typecache = list(/mob/living/silicon/robot, /mob/living/carbon/human) - var/recharge_speed - var/repairs - -/obj/machinery/recharge_station/Initialize() - . = ..() - update_icon() - -/obj/machinery/recharge_station/RefreshParts() - recharge_speed = 0 - repairs = 0 - for(var/obj/item/stock_parts/capacitor/C in component_parts) - recharge_speed += C.rating * 100 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - repairs += M.rating - 1 - for(var/obj/item/stock_parts/cell/C in component_parts) - recharge_speed *= C.maxcharge / 10000 - -/obj/machinery/recharge_station/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Recharging [recharge_speed]J per cycle." - if(repairs) - . += "[src] has been upgraded to support automatic repairs." - -/obj/machinery/recharge_station/process() - if(!is_operational()) - return - - if(occupant) - process_occupant() - return 1 - -/obj/machinery/recharge_station/relaymove(mob/user) - if(user.stat) - return - open_machine() - -/obj/machinery/recharge_station/emp_act(severity) - . = ..() - if(!(stat & (BROKEN|NOPOWER))) - if(occupant && !(. & EMP_PROTECT_CONTENTS)) - occupant.emp_act(severity) - if (!(. & EMP_PROTECT_SELF)) - open_machine() - -/obj/machinery/recharge_station/attackby(obj/item/P, mob/user, params) - if(state_open) - if(default_deconstruction_screwdriver(user, "borgdecon2", "borgcharger0", P)) - return - - if(default_pry_open(P)) - return - - if(default_deconstruction_crowbar(P)) - return - return ..() - -/obj/machinery/recharge_station/interact(mob/user) - toggle_open() - return TRUE - -/obj/machinery/recharge_station/proc/toggle_open() - if(state_open) - close_machine() - else - open_machine() - -/obj/machinery/recharge_station/open_machine() - . = ..() - use_power = IDLE_POWER_USE - -/obj/machinery/recharge_station/close_machine() - . = ..() - if(occupant) - use_power = ACTIVE_POWER_USE //It always tries to charge, even if it can't. - add_fingerprint(occupant) - -/obj/machinery/recharge_station/update_icon() - 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) - return - SEND_SIGNAL(occupant, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, recharge_speed, repairs) +/obj/machinery/recharge_station + name = "cyborg recharging station" + desc = "This device recharges cyborgs and resupplies them with materials." + icon = 'icons/obj/objects.dmi' + icon_state = "borgcharger0" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 1000 + req_access = list(ACCESS_ROBOTICS) + state_open = TRUE + circuit = /obj/item/circuitboard/machine/cyborgrecharger + occupant_typecache = list(/mob/living/silicon/robot, /mob/living/carbon/human) + var/recharge_speed + var/repairs + +/obj/machinery/recharge_station/Initialize() + . = ..() + update_icon() + +/obj/machinery/recharge_station/RefreshParts() + recharge_speed = 0 + repairs = 0 + for(var/obj/item/stock_parts/capacitor/C in component_parts) + recharge_speed += C.rating * 100 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + repairs += M.rating - 1 + for(var/obj/item/stock_parts/cell/C in component_parts) + recharge_speed *= C.maxcharge / 10000 + +/obj/machinery/recharge_station/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Recharging [recharge_speed]J per cycle." + if(repairs) + . += "[src] has been upgraded to support automatic repairs." + +/obj/machinery/recharge_station/process() + if(!is_operational()) + return + + if(occupant) + process_occupant() + return 1 + +/obj/machinery/recharge_station/relaymove(mob/user) + if(user.stat) + return + open_machine() + +/obj/machinery/recharge_station/emp_act(severity) + . = ..() + if(!(stat & (BROKEN|NOPOWER))) + if(occupant && !(. & EMP_PROTECT_CONTENTS)) + occupant.emp_act(severity) + if (!(. & EMP_PROTECT_SELF)) + open_machine() + +/obj/machinery/recharge_station/attackby(obj/item/P, mob/user, params) + if(state_open) + if(default_deconstruction_screwdriver(user, "borgdecon2", "borgcharger0", P)) + return + + if(default_pry_open(P)) + return + + if(default_deconstruction_crowbar(P)) + return + return ..() + +/obj/machinery/recharge_station/interact(mob/user) + toggle_open() + return TRUE + +/obj/machinery/recharge_station/proc/toggle_open() + if(state_open) + close_machine() + else + open_machine() + +/obj/machinery/recharge_station/open_machine() + . = ..() + use_power = IDLE_POWER_USE + +/obj/machinery/recharge_station/close_machine() + . = ..() + if(occupant) + use_power = ACTIVE_POWER_USE //It always tries to charge, even if it can't. + add_fingerprint(occupant) + +/obj/machinery/recharge_station/update_icon() + 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) + return + SEND_SIGNAL(occupant, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, recharge_speed, repairs) diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm index 5c88239e28b5..5df1f45960b7 100644 --- a/code/game/machinery/recycler.dm +++ b/code/game/machinery/recycler.dm @@ -1,205 +1,205 @@ -#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/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) - AddComponent(/datum/component/butchering, 1, amount_produced,amount_produced/5) - . = ..() - 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) - butchering.effectiveness = amount_produced - butchering.bonus_modifier = amount_produced/5 - -/obj/machinery/recycler/examine(mob/user) - . = ..() - . += "Reclaiming [amount_produced]% of materials salvaged." - . += {"The power light is [(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].") - -/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/Bumped(atom/movable/AM) - - if(stat & (BROKEN|NOPOWER)) - return - if(!anchored) - return - if(safety_mode) - return - - var/move_dir = get_dir(loc, AM.loc) - if(move_dir == eat_dir) - eat(AM) - -/obj/machinery/recycler/proc/eat(atom/AM0, sound=TRUE) - var/list/to_eat - if(istype(AM0, /obj/item)) - to_eat = AM0.GetAllContents() - 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) || istype(AM, /mob/living/brain) - 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(istype(AM, /obj/item)) - 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) - if(L.stat == DEAD && (L.butcher_results || L.guaranteed_butcher_results)) - var/datum/component/butchering/butchering = GetComponent(/datum/component/butchering) - butchering.Butcher(src,L) - -/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/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) + AddComponent(/datum/component/butchering, 1, amount_produced,amount_produced/5) + . = ..() + 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) + butchering.effectiveness = amount_produced + butchering.bonus_modifier = amount_produced/5 + +/obj/machinery/recycler/examine(mob/user) + . = ..() + . += "Reclaiming [amount_produced]% of materials salvaged." + . += {"The power light is [(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].") + +/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/Bumped(atom/movable/AM) + + if(stat & (BROKEN|NOPOWER)) + return + if(!anchored) + return + if(safety_mode) + return + + var/move_dir = get_dir(loc, AM.loc) + if(move_dir == eat_dir) + eat(AM) + +/obj/machinery/recycler/proc/eat(atom/AM0, sound=TRUE) + var/list/to_eat + if(istype(AM0, /obj/item)) + to_eat = AM0.GetAllContents() + 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) || istype(AM, /mob/living/brain) + 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(istype(AM, /obj/item)) + 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) + if(L.stat == DEAD && (L.butcher_results || L.guaranteed_butcher_results)) + var/datum/component/butchering/butchering = GetComponent(/datum/component/butchering) + butchering.Butcher(src,L) + +/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/status_display.dm b/code/game/machinery/status_display.dm index 2c06dc9c9445..f6917683b246 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -1,371 +1,371 @@ -// Status display -// (formerly Countdown timer display) - -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Small Fonts" -#define SCROLL_SPEED 2 - -#define SD_BLANK 0 // 0 = Blank -#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer -#define SD_MESSAGE 2 // 2 = Arbitrary message(s) -#define SD_PICTURE 3 // 3 = alert picture - -#define SD_AI_EMOTE 1 // 1 = AI emoticon -#define SD_AI_BSOD 2 // 2 = Blue screen of death - -/// Status display which can show images and scrolling text. -/obj/machinery/status_display - name = "status display" - desc = null - icon = 'icons/obj/status_display.dmi' - icon_state = "frame" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - - maptext_height = 26 - maptext_width = 32 - maptext_y = -1 - - var/message1 = "" // message line 1 - var/message2 = "" // message line 2 - var/index1 // display index for scrolling messages or 0 if non-scrolling - var/index2 - -/// Immediately blank the display. -/obj/machinery/status_display/proc/remove_display() - cut_overlays() - if(maptext) - maptext = "" - -/// Immediately change the display to the given picture. -/obj/machinery/status_display/proc/set_picture(state) - remove_display() - add_overlay(state) - -/// Immediately change the display to the given two lines. -/obj/machinery/status_display/proc/update_display(line1, line2) - line1 = uppertext(line1) - line2 = uppertext(line2) - var/new_text = {"
                    [line1]
                    [line2]
                    "} - if(maptext != new_text) - maptext = new_text - -/// Prepare the display to marquee the given two lines. -/// -/// Call with no arguments to disable. -/obj/machinery/status_display/proc/set_message(m1, m2) - if(m1) - index1 = (length(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(lentext(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/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 "Small Fonts" +#define SCROLL_SPEED 2 + +#define SD_BLANK 0 // 0 = Blank +#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer +#define SD_MESSAGE 2 // 2 = Arbitrary message(s) +#define SD_PICTURE 3 // 3 = alert picture + +#define SD_AI_EMOTE 1 // 1 = AI emoticon +#define SD_AI_BSOD 2 // 2 = Blue screen of death + +/// Status display which can show images and scrolling text. +/obj/machinery/status_display + name = "status display" + desc = null + icon = 'icons/obj/status_display.dmi' + icon_state = "frame" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + + maptext_height = 26 + maptext_width = 32 + maptext_y = -1 + + var/message1 = "" // message line 1 + var/message2 = "" // message line 2 + var/index1 // display index for scrolling messages or 0 if non-scrolling + var/index2 + +/// Immediately blank the display. +/obj/machinery/status_display/proc/remove_display() + cut_overlays() + if(maptext) + maptext = "" + +/// Immediately change the display to the given picture. +/obj/machinery/status_display/proc/set_picture(state) + remove_display() + add_overlay(state) + +/// Immediately change the display to the given two lines. +/obj/machinery/status_display/proc/update_display(line1, line2) + line1 = uppertext(line1) + line2 = uppertext(line2) + var/new_text = {"
                    [line1]
                    [line2]
                    "} + if(maptext != new_text) + maptext = new_text + +/// Prepare the display to marquee the given two lines. +/// +/// Call with no arguments to disable. +/obj/machinery/status_display/proc/set_message(m1, m2) + if(m1) + index1 = (length(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(lentext(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/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 48acb703bbb0..331a66d0dd83 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -1,449 +1,449 @@ -// 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 - // if you add more storage slots, update cook() to clear their radiation too. - - 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/swat/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 //yogs - -/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/clothing/shoes/magboots/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/clothing/shoes/magboots/security - -/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 - 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/living/user) - if(!istype(user) || user.stat || !Adjacent(user) || !Adjacent(A) || !isliving(A)) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - var/mob/living/target = A - if(!state_open) - to_chat(user, "The unit's doors are shut!") - return - if(!is_operational()) - to_chat(user, "The unit is not operational!") - return - if(occupant || helmet || suit || storage) - to_chat(user, "It's too cluttered inside to fit in!") - return - - if(target == user) - user.visible_message("[user] starts squeezing into [src]!", "You start working your way into [src]...") - else - target.visible_message("[user] starts shoving [target] into [src]!", "[user] starts shoving you into [src]!") - - if(do_mob(user, target, 30)) - if(occupant || helmet || suit || storage) - return - if(target == user) - user.visible_message("[user] slips into [src] and closes the door behind [user.p_them()]!", "You slip into [src]'s cramped space and shut its door.") - else - target.visible_message("[user] pushes [target] into [src] and shuts its door!", "[user] shoves you into [src] and shuts the door!") - close_machine(target) - add_fingerprint(user) - -/obj/machinery/suit_storage_unit/proc/cook() - if(uv_cycles) - uv_cycles-- - uv = TRUE - locked = TRUE - update_icon() - if(occupant) - var/mob/living/mob_occupant = 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.") - 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) - 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/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/default_pry_open(obj/item/I)//needs to check if the storage is locked. - . = !(state_open || panel_open || is_operational() || locked || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR - if(.) - I.play_tool_sound(src, 50) - visible_message("[usr] pries open \the [src].", "You pry open \the [src].") - open_machine() - -/obj/machinery/suit_storage_unit/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "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 + // if you add more storage slots, update cook() to clear their radiation too. + + 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/swat/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 //yogs + +/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/clothing/shoes/magboots/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/clothing/shoes/magboots/security + +/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 + 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/living/user) + if(!istype(user) || user.stat || !Adjacent(user) || !Adjacent(A) || !isliving(A)) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + var/mob/living/target = A + if(!state_open) + to_chat(user, "The unit's doors are shut!") + return + if(!is_operational()) + to_chat(user, "The unit is not operational!") + return + if(occupant || helmet || suit || storage) + to_chat(user, "It's too cluttered inside to fit in!") + return + + if(target == user) + user.visible_message("[user] starts squeezing into [src]!", "You start working your way into [src]...") + else + target.visible_message("[user] starts shoving [target] into [src]!", "[user] starts shoving you into [src]!") + + if(do_mob(user, target, 30)) + if(occupant || helmet || suit || storage) + return + if(target == user) + user.visible_message("[user] slips into [src] and closes the door behind [user.p_them()]!", "You slip into [src]'s cramped space and shut its door.") + else + target.visible_message("[user] pushes [target] into [src] and shuts its door!", "[user] shoves you into [src] and shuts the door!") + close_machine(target) + add_fingerprint(user) + +/obj/machinery/suit_storage_unit/proc/cook() + if(uv_cycles) + uv_cycles-- + uv = TRUE + locked = TRUE + update_icon() + if(occupant) + var/mob/living/mob_occupant = 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.") + 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) + 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/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/default_pry_open(obj/item/I)//needs to check if the storage is locked. + . = !(state_open || panel_open || is_operational() || locked || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR + if(.) + I.play_tool_sound(src, 50) + visible_message("[usr] pries open \the [src].", "You pry open \the [src].") + open_machine() + +/obj/machinery/suit_storage_unit/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "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 7e9ad9296974..8bb1a7701a21 100644 --- a/code/game/machinery/syndicatebeacon.dm +++ b/code/game/machinery/syndicatebeacon.dm @@ -1,135 +1,135 @@ -//////////////////////////////////////// -//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 = 0 - var/icontype = "beacon" - - -/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null) - if(surplus() < 1500) - if(user) - to_chat(user, "The connected wire doesn't have enough current.") - return - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.z == z) - singulo.target = src - icon_state = "[icontype]1" - active = 1 - if(user) - to_chat(user, "You activate the beacon.") - - -/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user = null) - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.target == src) - singulo.target = null - icon_state = "[icontype]0" - active = 0 - if(user) - to_chat(user, "You deactivate the beacon.") - - -/obj/machinery/power/singularity_beacon/attack_ai(mob/user) - return - - -/obj/machinery/power/singularity_beacon/attack_hand(mob/user) - . = ..() - if(.) - return - if(anchored) - return active ? Deactivate(user) : Activate(user) - else - to_chat(user, "You need to screw the beacon to the floor first!") - -/obj/machinery/power/singularity_beacon/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_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 - - 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 +//////////////////////////////////////// +//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 = 0 + var/icontype = "beacon" + + +/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null) + if(surplus() < 1500) + if(user) + to_chat(user, "The connected wire doesn't have enough current.") + return + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.z == z) + singulo.target = src + icon_state = "[icontype]1" + active = 1 + if(user) + to_chat(user, "You activate the beacon.") + + +/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user = null) + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.target == src) + singulo.target = null + icon_state = "[icontype]0" + active = 0 + if(user) + to_chat(user, "You deactivate the beacon.") + + +/obj/machinery/power/singularity_beacon/attack_ai(mob/user) + return + + +/obj/machinery/power/singularity_beacon/attack_hand(mob/user) + . = ..() + if(.) + return + if(anchored) + return active ? Deactivate(user) : Activate(user) + else + to_chat(user, "You need to screw the beacon to the floor first!") + +/obj/machinery/power/singularity_beacon/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_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 + + 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 diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm index 2070a69e3864..0245e4314000 100644 --- a/code/game/machinery/telecomms/computers/message.dm +++ b/code/game/machinery/telecomms/computers/message.dm @@ -1,475 +1,475 @@ -/* - The monitoring computer for the messaging server. - Lets you read PDA and request console messages. -*/ - -#define LINKED_SERVER_NONRESPONSIVE (!linkedServer || (linkedServer.stat & (NOPOWER|BROKEN))) - -#define MSG_MON_SCREEN_MAIN 0 -#define MSG_MON_SCREEN_LOGS 1 -#define MSG_MON_SCREEN_HACKED 2 -#define MSG_MON_SCREEN_CUSTOM_MSG 3 -#define MSG_MON_SCREEN_REQUEST_LOGS 4 - -// The monitor itself. -/obj/machinery/computer/message_monitor - name = "message monitor console" - desc = "Used to monitor the crew's PDA messages, as well as request console messages." - icon_screen = "comm_logs" - circuit = /obj/item/circuitboard/computer/message_monitor - //Server linked to. - var/obj/machinery/telecomms/message_server/linkedServer = null - //Sparks effect - For emag - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread - //Messages - Saves me time if I want to change something. - var/noserver = "ALERT: No server detected." - var/incorrectkey = "ALERT: Incorrect decryption key!" - var/defaultmsg = "Welcome. Please select an option." - var/rebootmsg = "%$&(£: Critical %$$@ Error // !RestArting! - ?pLeaSe wAit!" - //Computer properties - var/screen = MSG_MON_SCREEN_MAIN // 0 = Main menu, 1 = Message Logs, 2 = Hacked screen, 3 = Custom Message - var/hacking = FALSE // Is it being hacked into by the AI/Cyborg - var/message = "System bootup complete. Please select an option." // The message that shows on the main menu. - var/auth = FALSE // Are they authenticated? - var/optioncount = 7 - // Custom Message Properties - var/customsender = "System Administrator" - var/obj/item/pda/customrecepient = null - var/customjob = "Admin" - var/custommessage = "This is a test, please ignore." - - light_color = LIGHT_COLOR_GREEN - -/obj/machinery/computer/message_monitor/attackby(obj/item/O, mob/living/user, params) - if(O.tool_behaviour == TOOL_SCREWDRIVER && (obj_flags & EMAGGED)) - //Stops people from just unscrewing the monitor and putting it back to get the console working again. - to_chat(user, "It is too hot to mess with!") - else - return ..() - -/obj/machinery/computer/message_monitor/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - if(!isnull(linkedServer)) - obj_flags |= EMAGGED - screen = MSG_MON_SCREEN_HACKED - spark_system.set_up(5, 0, src) - spark_system.start() - var/obj/item/paper/monitorkey/MK = new(loc, linkedServer) - // Will help make emagging the console not so easy to get away with. - MK.info += "

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

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

                    " - else - dat += "

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

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

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

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

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

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

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

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

                    Daily Key Reset


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

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

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

                    " + else + dat += "

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

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

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

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

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

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

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

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

                    Daily Key Reset


                    The new message monitor key is '[server.decryptkey]'.
                    Please keep this a secret and away from the clown.
                    If necessary, change the password to a more secure one." + info_links = info + add_overlay("paper_words") + +/obj/item/paper/monitorkey/LateInitialize() + for (var/obj/machinery/telecomms/message_server/preset/server in GLOB.telecomms_list) + if (server.decryptkey) + print(server) + break diff --git a/code/game/machinery/telecomms/machines/message_server.dm b/code/game/machinery/telecomms/machines/message_server.dm index f589bb18291c..51f874265396 100644 --- a/code/game/machinery/telecomms/machines/message_server.dm +++ b/code/game/machinery/telecomms/machines/message_server.dm @@ -1,216 +1,216 @@ -/* - 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) - - -#define MESSAGE_SERVER_FUNCTIONING_MESSAGE "This is an automated message. The messaging system is functioning correctly." - -// The message server itself. -/obj/machinery/telecomms/message_server - icon_state = "message_server" - name = "Messaging Server" - desc = "A machine that processes and routes PDA and request console messages." - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - circuit = /obj/item/circuitboard/machine/telecomms/message_server - - var/list/datum/data_pda_msg/pda_msgs = list() - var/list/datum/data_rc_msg/rc_msgs = list() - var/decryptkey = "password" - var/calibrating = 15 MINUTES //Init reads this and adds world.time, then becomes 0 when that time has passed and the machine works - -/obj/machinery/telecomms/message_server/Initialize(mapload) - . = ..() - if (!decryptkey) - decryptkey = GenerateKey() - - if (calibrating) - calibrating += world.time - say("Calibrating... Estimated wait time: [rand(3, 9)] minutes.") - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", "This is an automated message. System calibration started at [station_time_timestamp()]") - else - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) - -/obj/machinery/telecomms/message_server/Destroy() - for(var/obj/machinery/computer/message_monitor/monitor in GLOB.telecomms_list) - if(monitor.linkedServer && monitor.linkedServer == src) - monitor.linkedServer = null - . = ..() - -/obj/machinery/telecomms/message_server/examine(mob/user) - . = ..() - if(calibrating) - . += "It's still calibrating." - -/obj/machinery/telecomms/message_server/proc/GenerateKey() - var/newKey - newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le") - newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai") - newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") - return newKey - -/obj/machinery/telecomms/message_server/process() - . = ..() - if(calibrating && calibrating <= world.time) - calibrating = 0 - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) - -/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/messaging/signal, obj/machinery/telecomms/machine_from) - // can't log non-message signals - if(!istype(signal) || !signal.data["message"] || !on || calibrating) - return - - // log the signal - if(istype(signal, /datum/signal/subspace/messaging/pda)) - var/datum/signal/subspace/messaging/pda/PDAsignal = signal - var/datum/data_pda_msg/M = new(PDAsignal.format_target(), "[PDAsignal.data["name"]] ([PDAsignal.data["job"]])", PDAsignal.data["message"], PDAsignal.data["photo"]) - pda_msgs += M - signal.logged = M - else if(istype(signal, /datum/signal/subspace/messaging/rc)) - var/datum/data_rc_msg/M = new(signal.data["rec_dpt"], signal.data["send_dpt"], signal.data["message"], signal.data["stamped"], signal.data["verified"], signal.data["priority"]) - signal.logged = M - if(signal.data["send_dpt"]) // don't log messages not from a department but allow them to work - rc_msgs += M - signal.data["reject"] = FALSE - - // pass it along to either the hub or the broadcaster - if(!relay_information(signal, /obj/machinery/telecomms/hub)) - relay_information(signal, /obj/machinery/telecomms/broadcaster) - -/obj/machinery/telecomms/message_server/update_icon() - ..() - cut_overlays() - if(calibrating) - add_overlay("message_server_calibrate") - - -// Root messaging signal datum -/datum/signal/subspace/messaging - frequency = FREQ_COMMON - server_type = /obj/machinery/telecomms/message_server - var/datum/logged - -/datum/signal/subspace/messaging/New(init_source, init_data) - source = init_source - data = init_data - var/turf/T = get_turf(source) - levels = list(T.z) - if(!("reject" in data)) - data["reject"] = TRUE - -/datum/signal/subspace/messaging/copy() - var/datum/signal/subspace/messaging/copy = new type(source, data.Copy()) - copy.original = src - copy.levels = levels - return copy - -// PDA signal datum -/datum/signal/subspace/messaging/pda/proc/format_target() - if (length(data["targets"]) > 1) - return "Everyone" - return data["targets"][1] - -/datum/signal/subspace/messaging/pda/proc/format_message() - if (logged && data["photo"]) - return "\"[data["message"]]\" (Photo)" - return "\"[data["message"]]\"" - -/datum/signal/subspace/messaging/pda/broadcast() - if (!logged) // Can only go through if a message server logs it - return - for (var/obj/item/pda/P in GLOB.PDAs) - if ("[P.owner] ([P.ownjob])" in data["targets"]) - P.receive_message(src) - -// Request Console signal datum -/datum/signal/subspace/messaging/rc/broadcast() - if (!logged) // Like /pda, only if logged - return - var/rec_dpt = ckey(data["rec_dpt"]) - for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) - if(ckey(Console.department) == rec_dpt || (data["ore_update"] && Console.receive_ore_updates)) - Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) - -// Log datums stored by the message server. -/datum/data_pda_msg - var/sender = "Unspecified" - var/recipient = "Unspecified" - var/message = "Blank" // transferred message - var/datum/picture/picture // attached photo - var/automated = 0 //automated message - -/datum/data_pda_msg/New(param_rec, param_sender, param_message, param_photo) - if(param_rec) - recipient = param_rec - if(param_sender) - sender = param_sender - if(param_message) - message = param_message - if(param_photo) - picture = param_photo - -/datum/data_pda_msg/Topic(href,href_list) - ..() - if(href_list["photo"]) - var/mob/M = usr - M << browse_rsc(picture.picture_image, "pda_photo.png") - M << browse("PDA Photo" \ - + "" \ - + "" \ - + "", "window=pdaphoto;size=[picture.psize_x]x[picture.psize_y];can-close=true") - onclose(M, "pdaphoto") - -/datum/data_rc_msg - var/rec_dpt = "Unspecified" // receiving department - var/send_dpt = "Unspecified" // sending department - var/message = "Blank" - var/stamp = "Unstamped" - var/id_auth = "Unauthenticated" - var/priority = "Normal" - -/datum/data_rc_msg/New(param_rec, param_sender, param_message, param_stamp, param_id_auth, param_priority) - if(param_rec) - rec_dpt = param_rec - if(param_sender) - send_dpt = param_sender - if(param_message) - message = param_message - if(param_stamp) - stamp = param_stamp - if(param_id_auth) - id_auth = param_id_auth - if(param_priority) - switch(param_priority) - if(REQ_NORMAL_MESSAGE_PRIORITY) - priority = "Normal" - if(REQ_HIGH_MESSAGE_PRIORITY) - priority = "High" - if(REQ_EXTREME_MESSAGE_PRIORITY) - priority = "Extreme" - else - priority = "Undetermined" - -#undef MESSAGE_SERVER_FUNCTIONING_MESSAGE - -/obj/machinery/telecomms/message_server/preset - id = "Messaging Server" - network = "tcommsat" - autolinkers = list("messaging") - decryptkey = null //random - calibrating = 0 +/* + The equivalent of the server, for PDA and request console messages. + Without it, PDA and request console messages cannot be transmitted. + PDAs require the rest of the telecomms setup, but request consoles only + require the message server. +*/ + +// A decorational representation of SSblackbox, usually placed alongside the message server. +/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) + + +#define MESSAGE_SERVER_FUNCTIONING_MESSAGE "This is an automated message. The messaging system is functioning correctly." + +// The message server itself. +/obj/machinery/telecomms/message_server + icon_state = "message_server" + name = "Messaging Server" + desc = "A machine that processes and routes PDA and request console messages." + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + circuit = /obj/item/circuitboard/machine/telecomms/message_server + + var/list/datum/data_pda_msg/pda_msgs = list() + var/list/datum/data_rc_msg/rc_msgs = list() + var/decryptkey = "password" + var/calibrating = 15 MINUTES //Init reads this and adds world.time, then becomes 0 when that time has passed and the machine works + +/obj/machinery/telecomms/message_server/Initialize(mapload) + . = ..() + if (!decryptkey) + decryptkey = GenerateKey() + + if (calibrating) + calibrating += world.time + say("Calibrating... Estimated wait time: [rand(3, 9)] minutes.") + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", "This is an automated message. System calibration started at [station_time_timestamp()]") + else + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) + +/obj/machinery/telecomms/message_server/Destroy() + for(var/obj/machinery/computer/message_monitor/monitor in GLOB.telecomms_list) + if(monitor.linkedServer && monitor.linkedServer == src) + monitor.linkedServer = null + . = ..() + +/obj/machinery/telecomms/message_server/examine(mob/user) + . = ..() + if(calibrating) + . += "It's still calibrating." + +/obj/machinery/telecomms/message_server/proc/GenerateKey() + var/newKey + newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le") + newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai") + newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") + return newKey + +/obj/machinery/telecomms/message_server/process() + . = ..() + if(calibrating && calibrating <= world.time) + calibrating = 0 + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) + +/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/messaging/signal, obj/machinery/telecomms/machine_from) + // can't log non-message signals + if(!istype(signal) || !signal.data["message"] || !on || calibrating) + return + + // log the signal + if(istype(signal, /datum/signal/subspace/messaging/pda)) + var/datum/signal/subspace/messaging/pda/PDAsignal = signal + var/datum/data_pda_msg/M = new(PDAsignal.format_target(), "[PDAsignal.data["name"]] ([PDAsignal.data["job"]])", PDAsignal.data["message"], PDAsignal.data["photo"]) + pda_msgs += M + signal.logged = M + else if(istype(signal, /datum/signal/subspace/messaging/rc)) + var/datum/data_rc_msg/M = new(signal.data["rec_dpt"], signal.data["send_dpt"], signal.data["message"], signal.data["stamped"], signal.data["verified"], signal.data["priority"]) + signal.logged = M + if(signal.data["send_dpt"]) // don't log messages not from a department but allow them to work + rc_msgs += M + signal.data["reject"] = FALSE + + // pass it along to either the hub or the broadcaster + if(!relay_information(signal, /obj/machinery/telecomms/hub)) + relay_information(signal, /obj/machinery/telecomms/broadcaster) + +/obj/machinery/telecomms/message_server/update_icon() + ..() + cut_overlays() + if(calibrating) + add_overlay("message_server_calibrate") + + +// Root messaging signal datum +/datum/signal/subspace/messaging + frequency = FREQ_COMMON + server_type = /obj/machinery/telecomms/message_server + var/datum/logged + +/datum/signal/subspace/messaging/New(init_source, init_data) + source = init_source + data = init_data + var/turf/T = get_turf(source) + levels = list(T.z) + if(!("reject" in data)) + data["reject"] = TRUE + +/datum/signal/subspace/messaging/copy() + var/datum/signal/subspace/messaging/copy = new type(source, data.Copy()) + copy.original = src + copy.levels = levels + return copy + +// PDA signal datum +/datum/signal/subspace/messaging/pda/proc/format_target() + if (length(data["targets"]) > 1) + return "Everyone" + return data["targets"][1] + +/datum/signal/subspace/messaging/pda/proc/format_message() + if (logged && data["photo"]) + return "\"[data["message"]]\" (Photo)" + return "\"[data["message"]]\"" + +/datum/signal/subspace/messaging/pda/broadcast() + if (!logged) // Can only go through if a message server logs it + return + for (var/obj/item/pda/P in GLOB.PDAs) + if ("[P.owner] ([P.ownjob])" in data["targets"]) + P.receive_message(src) + +// Request Console signal datum +/datum/signal/subspace/messaging/rc/broadcast() + if (!logged) // Like /pda, only if logged + return + var/rec_dpt = ckey(data["rec_dpt"]) + for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) + if(ckey(Console.department) == rec_dpt || (data["ore_update"] && Console.receive_ore_updates)) + Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) + +// Log datums stored by the message server. +/datum/data_pda_msg + var/sender = "Unspecified" + var/recipient = "Unspecified" + var/message = "Blank" // transferred message + var/datum/picture/picture // attached photo + var/automated = 0 //automated message + +/datum/data_pda_msg/New(param_rec, param_sender, param_message, param_photo) + if(param_rec) + recipient = param_rec + if(param_sender) + sender = param_sender + if(param_message) + message = param_message + if(param_photo) + picture = param_photo + +/datum/data_pda_msg/Topic(href,href_list) + ..() + if(href_list["photo"]) + var/mob/M = usr + M << browse_rsc(picture.picture_image, "pda_photo.png") + M << browse("PDA Photo" \ + + "" \ + + "" \ + + "", "window=pdaphoto;size=[picture.psize_x]x[picture.psize_y];can-close=true") + onclose(M, "pdaphoto") + +/datum/data_rc_msg + var/rec_dpt = "Unspecified" // receiving department + var/send_dpt = "Unspecified" // sending department + var/message = "Blank" + var/stamp = "Unstamped" + var/id_auth = "Unauthenticated" + var/priority = "Normal" + +/datum/data_rc_msg/New(param_rec, param_sender, param_message, param_stamp, param_id_auth, param_priority) + if(param_rec) + rec_dpt = param_rec + if(param_sender) + send_dpt = param_sender + if(param_message) + message = param_message + if(param_stamp) + stamp = param_stamp + if(param_id_auth) + id_auth = param_id_auth + if(param_priority) + switch(param_priority) + if(REQ_NORMAL_MESSAGE_PRIORITY) + priority = "Normal" + if(REQ_HIGH_MESSAGE_PRIORITY) + priority = "High" + if(REQ_EXTREME_MESSAGE_PRIORITY) + priority = "Extreme" + else + priority = "Undetermined" + +#undef MESSAGE_SERVER_FUNCTIONING_MESSAGE + +/obj/machinery/telecomms/message_server/preset + id = "Messaging Server" + network = "tcommsat" + autolinkers = list("messaging") + decryptkey = null //random + calibrating = 0 diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm index 09f22da609bb..4732e983af0a 100644 --- a/code/game/machinery/telecomms/telecomunications.dm +++ b/code/game/machinery/telecomms/telecomunications.dm @@ -1,160 +1,160 @@ - -/* - 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 - - if(filter && !ispath(filter)) // Yogs -- for debugging telecomms later when I soop up NTSL some more - CRASH("relay_information() was given a path filter that wasn't actually a path!") - 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/m_typeless in links) // Yogs -- God bless typeless for-loops - var/obj/machinery/telecomms/machine = m_typeless - 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 - if (traffic < 0) //yogs start - traffic = 0 //yogs end - -/obj/machinery/telecomms/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(prob(100/severity) && !(stat & EMPED)) - stat |= EMPED - var/duration = (300 * 10)/severity - addtimer(CALLBACK(src, .proc/de_emp), rand(duration - 20, duration + 20)) - -/obj/machinery/telecomms/proc/de_emp() - 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 + + if(filter && !ispath(filter)) // Yogs -- for debugging telecomms later when I soop up NTSL some more + CRASH("relay_information() was given a path filter that wasn't actually a path!") + 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/m_typeless in links) // Yogs -- God bless typeless for-loops + var/obj/machinery/telecomms/machine = m_typeless + 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 + if (traffic < 0) //yogs start + traffic = 0 //yogs end + +/obj/machinery/telecomms/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(prob(100/severity) && !(stat & EMPED)) + stat |= EMPED + var/duration = (300 * 10)/severity + addtimer(CALLBACK(src, .proc/de_emp), rand(duration - 20, duration + 20)) + +/obj/machinery/telecomms/proc/de_emp() + stat &= ~EMPED diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index b78e7bd7abe8..008bcc5a6e0e 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -1,307 +1,307 @@ -/obj/machinery/washing_machine - name = "washing machine" - desc = "Gets rid of those pesky bloodstains, or your money back!" - icon = 'icons/obj/machines/washing_machine.dmi' - icon_state = "wm_1_0" - density = TRUE - state_open = TRUE - var/busy = FALSE - var/bloody_mess = 0 - var/obj/item/color_source - var/max_wash_capacity = 5 - -/obj/machinery/washing_machine/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/machinery/washing_machine/examine(mob/user) - . = ..() - . += "Alt-click it to start a wash cycle." - -/obj/machinery/washing_machine/AltClick(mob/user) - if(!user.canUseTopic(src, !issilicon(user))) - return - - if(busy) - return - - if(state_open) - to_chat(user, "Close the door first") - return - - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first.") - return - - busy = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/wash_cycle), 200) - - START_PROCESSING(SSfastprocess, src) - -/obj/machinery/washing_machine/process() - if(!busy) - animate(src, transform=matrix(), time=2) - return PROCESS_KILL - if(anchored) - if(prob(5)) - var/matrix/M = new - M.Translate(rand(-1, 1), rand(0, 1)) - animate(src, transform=M, time=1) - animate(transform=matrix(), time=1) - else - if(prob(1)) - step(src, pick(GLOB.cardinals)) - var/matrix/M = new - M.Translate(rand(-3, 3), rand(-1, 3)) - animate(src, transform=M, time=2) - -/obj/machinery/washing_machine/proc/clean_blood() - if(!busy) - bloody_mess = FALSE - update_icon() - -/obj/machinery/washing_machine/proc/wash_cycle() - for(var/X in contents) - var/atom/movable/AM = X - SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) - AM.machine_wash(src) - - busy = FALSE - if(color_source) - qdel(color_source) - color_source = null - update_icon() - -//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) - else - if(istype(WM.color_source, /obj/item/reagent_containers/food/snacks/grown/rainbow_flower/)) - var/obj/item/reagent_containers/food/snacks/grown/rainbow_flower/RF = WM.color_source - add_atom_colour(RF.color, WASHABLE_COLOUR_PRIORITY) - -/mob/living/simple_animal/pet/dog/corgi/machine_wash(obj/machinery/washing_machine/WM) - WM.bloody_mess = TRUE - gib() - -/obj/item/clothing/under/machine_wash(obj/machinery/washing_machine/WM) - freshly_laundered = TRUE - addtimer(VARSET_CALLBACK(src, freshly_laundered, FALSE), 5 MINUTES, TIMER_UNIQUE | TIMER_OVERRIDE) - -/obj/item/clothing/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) - dodgy_colours = TRUE - can_adjust = initial(U.can_adjust) - if(!can_adjust && adjusted) //we deadjust the uniform if it's now unadjustable - toggle_jumpsuit_adjust() - -//dyed clothing results// - -/obj/item/storage/belt/fannypack/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/storage/belt/fannypack)) - var/obj/item/storage/belt/fannypack/FP = T - if(wash_color == initial(FP.item_color)) - item_state = initial(FP.item_state) - icon_state = initial(FP.icon_state) - item_color = wash_color - name = initial(FP.name) - desc = "The colors are a bit dodgy." - break - -/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/neck/scarf/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) - else - if(istype(WM.color_source, /obj/item/reagent_containers/food/snacks/grown/rainbow_flower/)) - var/obj/item/reagent_containers/food/snacks/grown/rainbow_flower/RF = WM.color_source - add_atom_colour(RF.color, WASHABLE_COLOUR_PRIORITY) - name = "dyed scarf" - desc = "The colors are a bit dodgy." - -/obj/item/clothing/head/beanie/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) - else - if(istype(WM.color_source, /obj/item/reagent_containers/food/snacks/grown/rainbow_flower/)) - var/obj/item/reagent_containers/food/snacks/grown/rainbow_flower/RF = WM.color_source - add_atom_colour(RF.color, WASHABLE_COLOUR_PRIORITY) - name = "dyed beanie" - desc = "The colors are a bit dodgy." - - -/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 - - 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) || istype(W, /obj/item/reagent_containers/food/snacks/grown/rainbow_flower) || istype(W, /obj/item/stack/ore/bluespace_crystal)) - 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)) - L.forceMove(src) - update_icon() - return - - if(!state_open) - open_machine() - else - state_open = FALSE //close the door - update_icon() - -/obj/machinery/washing_machine/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal(drop_location(), 2) - qdel(src) - -/obj/machinery/washing_machine/open_machine(drop = 1) - ..() - density = TRUE //because machinery/open_machine() sets it to 0 - color_source = null +/obj/machinery/washing_machine + name = "washing machine" + desc = "Gets rid of those pesky bloodstains, or your money back!" + icon = 'icons/obj/machines/washing_machine.dmi' + icon_state = "wm_1_0" + density = TRUE + state_open = TRUE + var/busy = FALSE + var/bloody_mess = 0 + var/obj/item/color_source + var/max_wash_capacity = 5 + +/obj/machinery/washing_machine/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/machinery/washing_machine/examine(mob/user) + . = ..() + . += "Alt-click it to start a wash cycle." + +/obj/machinery/washing_machine/AltClick(mob/user) + if(!user.canUseTopic(src, !issilicon(user))) + return + + if(busy) + return + + if(state_open) + to_chat(user, "Close the door first") + return + + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first.") + return + + busy = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/wash_cycle), 200) + + START_PROCESSING(SSfastprocess, src) + +/obj/machinery/washing_machine/process() + if(!busy) + animate(src, transform=matrix(), time=2) + return PROCESS_KILL + if(anchored) + if(prob(5)) + var/matrix/M = new + M.Translate(rand(-1, 1), rand(0, 1)) + animate(src, transform=M, time=1) + animate(transform=matrix(), time=1) + else + if(prob(1)) + step(src, pick(GLOB.cardinals)) + var/matrix/M = new + M.Translate(rand(-3, 3), rand(-1, 3)) + animate(src, transform=M, time=2) + +/obj/machinery/washing_machine/proc/clean_blood() + if(!busy) + bloody_mess = FALSE + update_icon() + +/obj/machinery/washing_machine/proc/wash_cycle() + for(var/X in contents) + var/atom/movable/AM = X + SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) + AM.machine_wash(src) + + busy = FALSE + if(color_source) + qdel(color_source) + color_source = null + update_icon() + +//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) + else + if(istype(WM.color_source, /obj/item/reagent_containers/food/snacks/grown/rainbow_flower/)) + var/obj/item/reagent_containers/food/snacks/grown/rainbow_flower/RF = WM.color_source + add_atom_colour(RF.color, WASHABLE_COLOUR_PRIORITY) + +/mob/living/simple_animal/pet/dog/corgi/machine_wash(obj/machinery/washing_machine/WM) + WM.bloody_mess = TRUE + gib() + +/obj/item/clothing/under/machine_wash(obj/machinery/washing_machine/WM) + freshly_laundered = TRUE + addtimer(VARSET_CALLBACK(src, freshly_laundered, FALSE), 5 MINUTES, TIMER_UNIQUE | TIMER_OVERRIDE) + +/obj/item/clothing/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) + dodgy_colours = TRUE + can_adjust = initial(U.can_adjust) + if(!can_adjust && adjusted) //we deadjust the uniform if it's now unadjustable + toggle_jumpsuit_adjust() + +//dyed clothing results// + +/obj/item/storage/belt/fannypack/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/storage/belt/fannypack)) + var/obj/item/storage/belt/fannypack/FP = T + if(wash_color == initial(FP.item_color)) + item_state = initial(FP.item_state) + icon_state = initial(FP.icon_state) + item_color = wash_color + name = initial(FP.name) + desc = "The colors are a bit dodgy." + break + +/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/neck/scarf/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) + else + if(istype(WM.color_source, /obj/item/reagent_containers/food/snacks/grown/rainbow_flower/)) + var/obj/item/reagent_containers/food/snacks/grown/rainbow_flower/RF = WM.color_source + add_atom_colour(RF.color, WASHABLE_COLOUR_PRIORITY) + name = "dyed scarf" + desc = "The colors are a bit dodgy." + +/obj/item/clothing/head/beanie/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) + else + if(istype(WM.color_source, /obj/item/reagent_containers/food/snacks/grown/rainbow_flower/)) + var/obj/item/reagent_containers/food/snacks/grown/rainbow_flower/RF = WM.color_source + add_atom_colour(RF.color, WASHABLE_COLOUR_PRIORITY) + name = "dyed beanie" + desc = "The colors are a bit dodgy." + + +/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 + + 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) || istype(W, /obj/item/reagent_containers/food/snacks/grown/rainbow_flower) || istype(W, /obj/item/stack/ore/bluespace_crystal)) + 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)) + L.forceMove(src) + update_icon() + return + + if(!state_open) + open_machine() + else + state_open = FALSE //close the door + update_icon() + +/obj/machinery/washing_machine/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal(drop_location(), 2) + qdel(src) + +/obj/machinery/washing_machine/open_machine(drop = 1) + ..() + density = TRUE //because machinery/open_machine() sets it to 0 + color_source = null diff --git a/code/game/machinery/wishgranter.dm b/code/game/machinery/wishgranter.dm index bec51d1d3055..f618888d9808 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 7bee704768e6..d3347c4af218 100644 --- a/code/game/mecha/combat/combat.dm +++ b/code/game/mecha/combat/combat.dm @@ -1,14 +1,14 @@ -/obj/mecha/combat - force = 30 - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY) - internal_damage_threshold = 50 - armor = list("melee" = 30, "bullet" = 30, "laser" = 15, "energy" = 20, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - mouse_pointer = 'icons/mecha/mecha_mouse.dmi' - destruction_sleep_duration = 40 - exit_delay = 40 - -/obj/mecha/combat/proc/max_ammo() //Max the ammo stored for Nuke Ops mechs, or anyone else that calls this - for(var/obj/item/I in equipment) - if(istype(I, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/)) - var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun = I +/obj/mecha/combat + force = 30 + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY) + internal_damage_threshold = 50 + armor = list("melee" = 30, "bullet" = 30, "laser" = 15, "energy" = 20, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + mouse_pointer = 'icons/mecha/mecha_mouse.dmi' + destruction_sleep_duration = 40 + exit_delay = 40 + +/obj/mecha/combat/proc/max_ammo() //Max the ammo stored for Nuke Ops mechs, or anyone else that calls this + for(var/obj/item/I in equipment) + if(istype(I, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/)) + var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun = I gun.projectiles_cache = gun.projectiles_cache_max \ No newline at end of file diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm index 0ff90cdba323..dfd2a3064715 100644 --- a/code/game/mecha/combat/durand.dm +++ b/code/game/mecha/combat/durand.dm @@ -1,22 +1,22 @@ -/obj/mecha/combat/durand - desc = "An aging combat exosuit utilized by the Nanotrasen corporation. Originally developed to combat hostile alien lifeforms." - name = "\improper Durand" - icon_state = "durand" - step_in = 4 - dir_in = 1 //Facing North. - max_integrity = 400 - deflect_chance = 20 - armor = list("melee" = 40, "bullet" = 35, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) - max_temperature = 30000 - infra_luminosity = 8 - force = 40 - wreckage = /obj/structure/mecha_wreckage/durand - -/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" = 50, "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 dd00adee5054..13b11bcd0291 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 - 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 - -/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 - armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" =20, "fire" = 100, "acid" = 100) - max_temperature = 35000 - leg_overload_coeff = 100 - operation_req_access = list(ACCESS_SYNDICATE) - internals_req_access = list(ACCESS_SYNDICATE) - wreckage = /obj/structure/mecha_wreckage/gygax/dark - max_equip = 4 - destruction_sleep_duration = 20 - -/obj/mecha/combat/gygax/dark/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/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) - max_ammo() - -/obj/mecha/combat/gygax/dark/add_cell(obj/item/stock_parts/cell/C=null) - if(C) - C.forceMove(src) - cell = C - return - cell = new /obj/item/stock_parts/cell/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 + 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 + +/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 + armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" =20, "fire" = 100, "acid" = 100) + max_temperature = 35000 + leg_overload_coeff = 100 + operation_req_access = list(ACCESS_SYNDICATE) + internals_req_access = list(ACCESS_SYNDICATE) + wreckage = /obj/structure/mecha_wreckage/gygax/dark + max_equip = 4 + destruction_sleep_duration = 20 + +/obj/mecha/combat/gygax/dark/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/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) + max_ammo() + +/obj/mecha/combat/gygax/dark/add_cell(obj/item/stock_parts/cell/C=null) + if(C) + C.forceMove(src) + cell = C + return + cell = new /obj/item/stock_parts/cell/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 14e1ec01fd97..bb3e241d3d6f 100644 --- a/code/game/mecha/combat/honker.dm +++ b/code/game/mecha/combat/honker.dm @@ -1,182 +1,182 @@ -/obj/mecha/combat/honker - desc = "Produced by \"Tyranny of Honk, INC\", this exosuit is designed as heavy clown-support. Used to spread the fun and joy of life. HONK!" - name = "\improper H.O.N.K" - icon_state = "honker" - step_in = 3 - max_integrity = 140 - deflect_chance = 60 - internal_damage_threshold = 60 - armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 25000 - infra_luminosity = 5 - operation_req_access = list(ACCESS_THEATRE) - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) - wreckage = /obj/structure/mecha_wreckage/honker - add_req_access = 0 - max_equip = 3 - var/squeak = 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 = {" - "} - 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) - if("reee") - playsound(src, 'sound/effects/reee.ogg', 50) - if("weeoo1") - playsound(src, 'sound/items/weeoo1.ogg', 50) - if("hiss1") - playsound(src, 'sound/voice/hiss1.ogg', 50) - if("armbomb") - playsound(src, 'sound/weapons/armbomb.ogg', 50) - if("saberon") - playsound(src, 'sound/weapons/saberon.ogg', 50) - if("airlock_alien_prying") - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50) - if("lightningbolt") - playsound(src, 'sound/magic/lightningbolt.ogg', 50) - if("explosionfar") - playsound(src, 'sound/effects/explosionfar.ogg', 50) - return - -/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 - - +/obj/mecha/combat/honker + desc = "Produced by \"Tyranny of Honk, INC\", this exosuit is designed as heavy clown-support. Used to spread the fun and joy of life. HONK!" + name = "\improper H.O.N.K" + icon_state = "honker" + step_in = 3 + max_integrity = 140 + deflect_chance = 60 + internal_damage_threshold = 60 + armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 25000 + infra_luminosity = 5 + operation_req_access = list(ACCESS_THEATRE) + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) + wreckage = /obj/structure/mecha_wreckage/honker + add_req_access = 0 + max_equip = 3 + var/squeak = 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 = {" + "} + 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) + if("reee") + playsound(src, 'sound/effects/reee.ogg', 50) + if("weeoo1") + playsound(src, 'sound/items/weeoo1.ogg', 50) + if("hiss1") + playsound(src, 'sound/voice/hiss1.ogg', 50) + if("armbomb") + playsound(src, 'sound/weapons/armbomb.ogg', 50) + if("saberon") + playsound(src, 'sound/weapons/saberon.ogg', 50) + if("airlock_alien_prying") + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50) + if("lightningbolt") + playsound(src, 'sound/magic/lightningbolt.ogg', 50) + if("explosionfar") + playsound(src, 'sound/effects/explosionfar.ogg', 50) + return + +/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 + + diff --git a/code/game/mecha/combat/marauder.dm b/code/game/mecha/combat/marauder.dm index a5eec2ee3238..a3a76d43fa79 100644 --- a/code/game/mecha/combat/marauder.dm +++ b/code/game/mecha/combat/marauder.dm @@ -1,96 +1,96 @@ -/obj/mecha/combat/marauder - desc = "Heavy-duty, combat exosuit, developed after the Durand model. Rarely found among civilian populations." - name = "\improper Marauder" - icon_state = "marauder" - step_in = 5 - max_integrity = 500 - deflect_chance = 25 - armor = list("melee" = 50, "bullet" = 55, "laser" = 40, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 60, "fire" = 100, "acid" = 100) - max_temperature = 60000 - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - infra_luminosity = 3 - operation_req_access = list(ACCESS_CENT_SPECOPS) - internals_req_access = list(ACCESS_CENT_SPECOPS) - wreckage = /obj/structure/mecha_wreckage/marauder - add_req_access = 0 - internal_damage_threshold = 25 - force = 45 - max_equip = 4 - bumpsmash = 1 - -/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) - max_ammo() - -/obj/mecha/combat/marauder/seraph - desc = "Heavy-duty, command-type exosuit. This is a custom model, utilized only by high-ranking military personnel." - name = "\improper Seraph" - icon_state = "seraph" - operation_req_access = list(ACCESS_CENT_SPECOPS) - internals_req_access = list(ACCESS_CENT_SPECOPS) - step_in = 3 - max_integrity = 550 - wreckage = /obj/structure/mecha_wreckage/seraph - internal_damage_threshold = 20 - force = 55 - max_equip = 5 - -/obj/mecha/combat/marauder/seraph/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/teleporter(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - max_ammo() - -/obj/mecha/combat/marauder/mauler - desc = "Heavy-duty, combat exosuit, developed off of the existing Marauder model." - name = "\improper Mauler" - icon_state = "mauler" - operation_req_access = list(ACCESS_SYNDICATE) - internals_req_access = list(ACCESS_SYNDICATE) - wreckage = /obj/structure/mecha_wreckage/mauler - max_equip = 5 - destruction_sleep_duration = 20 - -/obj/mecha/combat/marauder/mauler/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - max_ammo() - - +/obj/mecha/combat/marauder + desc = "Heavy-duty, combat exosuit, developed after the Durand model. Rarely found among civilian populations." + name = "\improper Marauder" + icon_state = "marauder" + step_in = 5 + max_integrity = 500 + deflect_chance = 25 + armor = list("melee" = 50, "bullet" = 55, "laser" = 40, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 60, "fire" = 100, "acid" = 100) + max_temperature = 60000 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + infra_luminosity = 3 + operation_req_access = list(ACCESS_CENT_SPECOPS) + internals_req_access = list(ACCESS_CENT_SPECOPS) + wreckage = /obj/structure/mecha_wreckage/marauder + add_req_access = 0 + internal_damage_threshold = 25 + force = 45 + max_equip = 4 + bumpsmash = 1 + +/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) + max_ammo() + +/obj/mecha/combat/marauder/seraph + desc = "Heavy-duty, command-type exosuit. This is a custom model, utilized only by high-ranking military personnel." + name = "\improper Seraph" + icon_state = "seraph" + operation_req_access = list(ACCESS_CENT_SPECOPS) + internals_req_access = list(ACCESS_CENT_SPECOPS) + step_in = 3 + max_integrity = 550 + wreckage = /obj/structure/mecha_wreckage/seraph + internal_damage_threshold = 20 + force = 55 + max_equip = 5 + +/obj/mecha/combat/marauder/seraph/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/teleporter(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) + ME.attach(src) + max_ammo() + +/obj/mecha/combat/marauder/mauler + desc = "Heavy-duty, combat exosuit, developed off of the existing Marauder model." + name = "\improper Mauler" + icon_state = "mauler" + operation_req_access = list(ACCESS_SYNDICATE) + internals_req_access = list(ACCESS_SYNDICATE) + wreckage = /obj/structure/mecha_wreckage/mauler + max_equip = 5 + destruction_sleep_duration = 20 + +/obj/mecha/combat/marauder/mauler/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) + ME.attach(src) + max_ammo() + + diff --git a/code/game/mecha/combat/phazon.dm b/code/game/mecha/combat/phazon.dm index 4f37ac7f1b97..95b60eba68d8 100644 --- a/code/game/mecha/combat/phazon.dm +++ b/code/game/mecha/combat/phazon.dm @@ -1,30 +1,30 @@ -/obj/mecha/combat/phazon - desc = "This is a Phazon exosuit. The pinnacle of scientific research and pride of Nanotrasen, it uses cutting edge bluespace technology and expensive materials." - name = "\improper Phazon" - icon_state = "phazon" - step_in = 2 - dir_in = 2 //Facing South. - step_energy_drain = 3 - max_integrity = 200 - deflect_chance = 30 - armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) - max_temperature = 25000 - infra_luminosity = 3 - wreckage = /obj/structure/mecha_wreckage/phazon - add_req_access = 1 - internal_damage_threshold = 25 - force = 15 - max_equip = 3 - phase_state = "phazon-phase" - -/obj/mecha/combat/phazon/GrantActions(mob/living/user, human_occupant = 0) - ..() - switch_damtype_action.Grant(user, src) - phasing_action.Grant(user, src) - - -/obj/mecha/combat/phazon/RemoveActions(mob/living/user, human_occupant = 0) - ..() - switch_damtype_action.Remove(user) - phasing_action.Remove(user) - +/obj/mecha/combat/phazon + desc = "This is a Phazon exosuit. The pinnacle of scientific research and pride of Nanotrasen, it uses cutting edge bluespace technology and expensive materials." + name = "\improper Phazon" + icon_state = "phazon" + step_in = 2 + dir_in = 2 //Facing South. + step_energy_drain = 3 + max_integrity = 200 + deflect_chance = 30 + armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) + max_temperature = 25000 + infra_luminosity = 3 + wreckage = /obj/structure/mecha_wreckage/phazon + add_req_access = 1 + internal_damage_threshold = 25 + force = 15 + max_equip = 3 + phase_state = "phazon-phase" + +/obj/mecha/combat/phazon/GrantActions(mob/living/user, human_occupant = 0) + ..() + switch_damtype_action.Grant(user, src) + phasing_action.Grant(user, src) + + +/obj/mecha/combat/phazon/RemoveActions(mob/living/user, human_occupant = 0) + ..() + switch_damtype_action.Remove(user) + phasing_action.Remove(user) + diff --git a/code/game/mecha/combat/reticence.dm b/code/game/mecha/combat/reticence.dm index ad628f2df955..d1beff7265be 100644 --- a/code/game/mecha/combat/reticence.dm +++ b/code/game/mecha/combat/reticence.dm @@ -1,28 +1,28 @@ -/obj/mecha/combat/reticence - desc = "A silent, fast, and nigh-invisible miming exosuit. Popular among mimes and mime assassins." - name = "\improper reticence" - icon_state = "reticence" - step_in = 2 - dir_in = 1 //Facing North. - max_integrity = 100 - deflect_chance = 3 - armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 15000 - wreckage = /obj/structure/mecha_wreckage/reticence - operation_req_access = list(ACCESS_THEATRE) - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) - add_req_access = 0 - internal_damage_threshold = 25 - max_equip = 2 - step_energy_drain = 3 - color = "#87878715" - stepsound = null - turnsound = null - opacity = 0 - -/obj/mecha/combat/reticence/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/rcd //HAHA IT MAKES WALLS GET IT - ME.attach(src) +/obj/mecha/combat/reticence + desc = "A silent, fast, and nigh-invisible miming exosuit. Popular among mimes and mime assassins." + name = "\improper reticence" + icon_state = "reticence" + step_in = 2 + dir_in = 1 //Facing North. + max_integrity = 100 + deflect_chance = 3 + armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 15000 + wreckage = /obj/structure/mecha_wreckage/reticence + operation_req_access = list(ACCESS_THEATRE) + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) + add_req_access = 0 + internal_damage_threshold = 25 + max_equip = 2 + step_energy_drain = 3 + color = "#87878715" + stepsound = null + turnsound = null + opacity = 0 + +/obj/mecha/combat/reticence/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/rcd //HAHA IT MAKES WALLS GET IT + ME.attach(src) diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm index 6e4a4a4c7d8e..baaf07f9e222 100644 --- a/code/game/mecha/equipment/mecha_equipment.dm +++ b/code/game/mecha/equipment/mecha_equipment.dm @@ -1,170 +1,170 @@ -//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!") - log_message("[src] is destroyed.", LOG_MECHA) - 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/try_attach_part(mob/user, obj/mecha/M) - if(can_attach(M)) - if(!user.temporarilyRemoveItemFromInventory(src)) - return FALSE - attach(M) - user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") - return TRUE - to_chat(user, "You are unable to attach [src] to [M]!") - return FALSE - -/obj/item/mecha_parts/mecha_equipment/proc/get_equip_info() - if(!chassis) - return - var/txt = "* " - if(chassis.selected == src) - txt += "[src.name]" - else if(selectable) - txt += "[src.name]" - else - txt += "[src.name]" - - return txt - -/obj/item/mecha_parts/mecha_equipment/proc/is_ranged()//add a distance restricted equipment. Why not? - return range&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(energy_drain && !chassis.has_charge(energy_drain)) - return 0 - if(chassis.is_currently_ejecting) - return 0 - if(chassis.equipment_disabled) - to_chat(chassis.occupant, "Error -- Equipment control unit is unresponsive.") - return 0 - return 1 - -/obj/item/mecha_parts/mecha_equipment/proc/action(atom/target) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown() - set_ready_state(0) - chassis.use_power(energy_drain) - addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) - -/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target) - if(!chassis) - return - var/C = chassis.loc - set_ready_state(0) - chassis.use_power(energy_drain) - . = do_after(chassis.occupant, equip_cooldown, target=target) - set_ready_state(1) - if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/do_after_mecha(atom/target, delay) - if(!chassis) - return - var/C = chassis.loc - . = do_after(chassis.occupant, delay, target=target) - if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/can_attach(obj/mecha/M) - if(M.equipment.len[src] is destroyed!") + log_message("[src] is destroyed.", LOG_MECHA) + 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/try_attach_part(mob/user, obj/mecha/M) + if(can_attach(M)) + if(!user.temporarilyRemoveItemFromInventory(src)) + return FALSE + attach(M) + user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") + return TRUE + to_chat(user, "You are unable to attach [src] to [M]!") + return FALSE + +/obj/item/mecha_parts/mecha_equipment/proc/get_equip_info() + if(!chassis) + return + var/txt = "* " + if(chassis.selected == src) + txt += "[src.name]" + else if(selectable) + txt += "[src.name]" + else + txt += "[src.name]" + + return txt + +/obj/item/mecha_parts/mecha_equipment/proc/is_ranged()//add a distance restricted equipment. Why not? + return range&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(energy_drain && !chassis.has_charge(energy_drain)) + return 0 + if(chassis.is_currently_ejecting) + return 0 + if(chassis.equipment_disabled) + to_chat(chassis.occupant, "Error -- Equipment control unit is unresponsive.") + return 0 + return 1 + +/obj/item/mecha_parts/mecha_equipment/proc/action(atom/target) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown() + set_ready_state(0) + chassis.use_power(energy_drain) + addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) + +/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target) + if(!chassis) + return + var/C = chassis.loc + set_ready_state(0) + chassis.use_power(energy_drain) + . = do_after(chassis.occupant, equip_cooldown, target=target) + set_ready_state(1) + if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/do_after_mecha(atom/target, delay) + if(!chassis) + return + var/C = chassis.loc + . = do_after(chassis.occupant, delay, target=target) + if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/can_attach(obj/mecha/M) + if(M.equipment.lenHONK") - 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 - var/turf/turf_check = get_turf(M) - if(isspaceturf(turf_check) && !turf_check.Adjacent(src)) //in space nobody can hear you honk. - continue - to_chat(M, "HONK") - M.SetSleeping(0) - M.stuttering += 20 - M.adjustEarDamage(0, 30) - M.Paralyze(60) - if(prob(30)) - M.Stun(200) - M.Unconscious(80) - else - M.Jitter(500) - - log_message("Honked from [src.name]. HONK!", LOG_MECHA) - var/turf/T = get_turf(src) - message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] used a Mecha Honker in [ADMIN_VERBOSEJMP(T)]") - log_game("[key_name(chassis.occupant)] used a Mecha Honker in [AREACOORD(T)]") - return 1 - - -//Base ballistic weapon type -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic - name = "general ballistic weapon" - fire_sound = 'sound/weapons/gunshot.ogg' - var/projectiles - var/projectiles_cache //ammo to be loaded in, if possible. - var/projectiles_cache_max - var/projectile_energy_cost - var/disabledreload //For weapons with no cache (like the rockets) which are reloaded by hand - var/ammo_type - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_shot_amount() - return min(projectiles, projectiles_per_shot) - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action_checks(target) - if(!..()) - return 0 - if(projectiles <= 0) - return 0 - return 1 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_equip_info() - return "[..()] \[[src.projectiles][projectiles_cache_max &&!projectile_energy_cost?"/[projectiles_cache]":""]\][!disabledreload &&(src.projectiles < initial(src.projectiles))?" - Rearm":null]" - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/rearm() - if(projectiles < initial(projectiles)) - var/projectiles_to_add = initial(projectiles) - projectiles - - if(projectile_energy_cost) - while(chassis.get_charge() >= projectile_energy_cost && projectiles_to_add) - projectiles++ - projectiles_to_add-- - chassis.use_power(projectile_energy_cost) - - else - if(!projectiles_cache) - return FALSE - if(projectiles_to_add <= projectiles_cache) - projectiles = projectiles + projectiles_to_add - projectiles_cache = projectiles_cache - projectiles_to_add - else - projectiles = projectiles + projectiles_cache - projectiles_cache = 0 - - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) - log_message("Rearmed [src.name].", LOG_MECHA) - return TRUE - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/needs_rearm() - . = !(projectiles > 0) - - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/Topic(href, href_list) - ..() - if (href_list["rearm"]) - src.rearm() - return - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action(atom/target) - if(..()) - projectiles -= get_shot_amount() - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) - return 1 - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine - name = "\improper FNX-99 \"Hades\" Carbine" - desc = "A weapon for combat exosuits. Shoots incendiary bullets." - icon_state = "mecha_carbine" - equip_cooldown = 10 - projectile = /obj/item/projectile/bullet/incendiary/fnx99 - projectiles = 24 - projectiles_cache = 24 - projectiles_cache_max = 96 - harmful = TRUE - ammo_type = "incendiary" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced - name = "\improper S.H.H. \"Quietus\" Carbine" - desc = "A weapon for combat exosuits. A mime invention, field tests have shown that targets cannot even scream before going down." - fire_sound = 'sound/weapons/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 - projectiles_cache = 40 - projectiles_cache_max = 160 - projectiles_per_shot = 4 - variance = 25 - harmful = TRUE - ammo_type = "scattershot" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg - name = "\improper Ultra AC 2" - desc = "A weapon for combat exosuits. Shoots a rapid, three shot burst." - icon_state = "mecha_uac2" - equip_cooldown = 10 - projectile = /obj/item/projectile/bullet/lmg - projectiles = 300 - projectiles_cache = 300 - projectiles_cache_max = 1200 - projectiles_per_shot = 3 - variance = 6 - randomspread = 1 - projectile_delay = 2 - harmful = TRUE - ammo_type = "lmg" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack - name = "\improper SRM-8 missile rack" - desc = "A weapon for combat exosuits. Launches light explosive missiles." - icon_state = "mecha_missilerack" - projectile = /obj/item/projectile/bullet/a84mm_he - fire_sound = 'sound/weapons/grenadelaunch.ogg' - projectiles = 8 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - equip_cooldown = 60 - harmful = TRUE - ammo_type = "missiles_he" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/breaching - name = "\improper BRM-6 missile rack" - desc = "A weapon for combat exosuits. Launches low-explosive breaching missiles designed to explode only when striking a sturdy target." - icon_state = "mecha_missilerack_six" - projectile = /obj/item/projectile/bullet/a84mm_br - fire_sound = 'sound/weapons/grenadelaunch.ogg' - projectiles = 6 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - equip_cooldown = 60 - harmful = TRUE - ammo_type = "missiles_br" - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher - var/missile_speed = 2 - var/missile_range = 30 - var/diags_first = FALSE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/action(target) - if(!action_checks(target)) - return - var/obj/O = new projectile(chassis.loc) - playsound(chassis, fire_sound, 50, 1) - log_message("Launched a [O.name] from [name], targeting [target].", LOG_MECHA) - projectiles-- - proj_init(O) - O.throw_at(target, missile_range, missile_speed, chassis.occupant, FALSE, diagonals_first = diags_first) - return 1 - -//used for projectile initilisation (priming flashbang) and additional logging -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/proc/proj_init(var/obj/O) - return - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang - name = "\improper SGL-6 grenade launcher" - desc = "A weapon for combat exosuits. Launches primed flashbangs." - icon_state = "mecha_grenadelnchr" - projectile = /obj/item/grenade/flashbang - fire_sound = 'sound/weapons/grenadelaunch.ogg' - projectiles = 6 - projectiles_cache = 6 - projectiles_cache_max = 24 - missile_speed = 1.5 - equip_cooldown = 60 - var/det_time = 20 - ammo_type = "flashbang" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/proj_init(var/obj/item/grenade/flashbang/F) - var/turf/T = get_turf(src) - message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] fired a [src] in [ADMIN_VERBOSEJMP(T)]") - log_game("[key_name(chassis.occupant)] fired a [src] in [AREACOORD(T)]") - addtimer(CALLBACK(F, /obj/item/grenade/flashbang.proc/prime), det_time) - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/clusterbang //Because I am a heartless bastard -Sieve //Heartless? for making the poor man's honkblast? - Kaze - name = "\improper SOB-3 grenade launcher" - desc = "A weapon for combat exosuits. Launches primed clusterbangs. You monster." - projectiles = 3 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - projectile = /obj/item/grenade/clusterbuster - equip_cooldown = 90 - ammo_type = "clusterbang" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar - name = "banana mortar" - desc = "Equipment for clown exosuits. Launches banana peels." - icon_state = "mecha_bananamrtr" - projectile = /obj/item/grown/bananapeel - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 15 - missile_speed = 1.5 - projectile_energy_cost = 100 - equip_cooldown = 20 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar - name = "mousetrap mortar" - desc = "Equipment for clown exosuits. Launches armed mousetraps." - icon_state = "mecha_mousetrapmrtr" - projectile = /obj/item/assembly/mousetrap/armed - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 15 - missile_speed = 1.5 - projectile_energy_cost = 100 - equip_cooldown = 10 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/proj_init(var/obj/item/assembly/mousetrap/armed/M) - M.secured = 1 - - -//Classic extending punching glove, but weaponised! -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove - name = "\improper Oingo Boingo Punch-face" - desc = "Equipment for clown exosuits. Delivers fun right to your face!" - icon_state = "mecha_punching_glove" - energy_drain = 250 - equip_cooldown = 20 - range = 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, datum/thrownthing/throwingdatum) - 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/M) + if(!..()) + return FALSE + if(istype(M, /obj/mecha/combat)) + return TRUE + if((locate(/obj/item/mecha_parts/concealed_weapon_bay) in M.contents) && !(locate(/obj/item/mecha_parts/mecha_equipment/weapon) in M.equipment)) + return TRUE + return FALSE + +/obj/item/mecha_parts/mecha_equipment/weapon/proc/get_shot_amount() + return projectiles_per_shot + +/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target, params) + if(!action_checks(target)) + return 0 + + var/turf/curloc = get_turf(chassis) + var/turf/targloc = get_turf(target) + if (!targloc || !istype(targloc) || !curloc) + return 0 + if (targloc == curloc) + return 0 + + set_ready_state(0) + for(var/i=1 to get_shot_amount()) + var/obj/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].", LOG_MECHA) + return 1 + + +//Base energy weapon type +/obj/item/mecha_parts/mecha_equipment/weapon/energy + name = "general energy weapon" + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/get_shot_amount() + return min(round(chassis.cell.charge / energy_drain), projectiles_per_shot) + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/start_cooldown() + set_ready_state(0) + chassis.use_power(energy_drain*get_shot_amount()) + addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser + equip_cooldown = 8 + name = "\improper CH-PS \"Immolator\" laser" + desc = "A weapon for combat exosuits. Shoots basic lasers." + icon_state = "mecha_laser" + energy_drain = 30 + projectile = /obj/item/projectile/beam/laser + fire_sound = 'sound/weapons/laser.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/disabler + equip_cooldown = 8 + name = "\improper CH-DS \"Peacemaker\" disabler" + desc = "A weapon for combat exosuits. Shoots basic disablers." + icon_state = "mecha_disabler" + energy_drain = 30 + projectile = /obj/item/projectile/beam/disabler + fire_sound = 'sound/weapons/taser2.ogg' + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy + equip_cooldown = 15 + name = "\improper CH-LC \"Solaris\" laser cannon" + desc = "A weapon for combat exosuits. Shoots heavy lasers." + icon_state = "mecha_laser" + energy_drain = 60 + projectile = /obj/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 + var/turf/turf_check = get_turf(M) + if(isspaceturf(turf_check) && !turf_check.Adjacent(src)) //in space nobody can hear you honk. + continue + to_chat(M, "HONK") + M.SetSleeping(0) + M.stuttering += 20 + M.adjustEarDamage(0, 30) + M.Paralyze(60) + if(prob(30)) + M.Stun(200) + M.Unconscious(80) + else + M.Jitter(500) + + log_message("Honked from [src.name]. HONK!", LOG_MECHA) + var/turf/T = get_turf(src) + message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] used a Mecha Honker in [ADMIN_VERBOSEJMP(T)]") + log_game("[key_name(chassis.occupant)] used a Mecha Honker in [AREACOORD(T)]") + return 1 + + +//Base ballistic weapon type +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic + name = "general ballistic weapon" + fire_sound = 'sound/weapons/gunshot.ogg' + var/projectiles + var/projectiles_cache //ammo to be loaded in, if possible. + var/projectiles_cache_max + var/projectile_energy_cost + var/disabledreload //For weapons with no cache (like the rockets) which are reloaded by hand + var/ammo_type + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_shot_amount() + return min(projectiles, projectiles_per_shot) + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action_checks(target) + if(!..()) + return 0 + if(projectiles <= 0) + return 0 + return 1 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_equip_info() + return "[..()] \[[src.projectiles][projectiles_cache_max &&!projectile_energy_cost?"/[projectiles_cache]":""]\][!disabledreload &&(src.projectiles < initial(src.projectiles))?" - Rearm":null]" + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/rearm() + if(projectiles < initial(projectiles)) + var/projectiles_to_add = initial(projectiles) - projectiles + + if(projectile_energy_cost) + while(chassis.get_charge() >= projectile_energy_cost && projectiles_to_add) + projectiles++ + projectiles_to_add-- + chassis.use_power(projectile_energy_cost) + + else + if(!projectiles_cache) + return FALSE + if(projectiles_to_add <= projectiles_cache) + projectiles = projectiles + projectiles_to_add + projectiles_cache = projectiles_cache - projectiles_to_add + else + projectiles = projectiles + projectiles_cache + projectiles_cache = 0 + + send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) + log_message("Rearmed [src.name].", LOG_MECHA) + return TRUE + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/needs_rearm() + . = !(projectiles > 0) + + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/Topic(href, href_list) + ..() + if (href_list["rearm"]) + src.rearm() + return + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action(atom/target) + if(..()) + projectiles -= get_shot_amount() + send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) + return 1 + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine + name = "\improper FNX-99 \"Hades\" Carbine" + desc = "A weapon for combat exosuits. Shoots incendiary bullets." + icon_state = "mecha_carbine" + equip_cooldown = 10 + projectile = /obj/item/projectile/bullet/incendiary/fnx99 + projectiles = 24 + projectiles_cache = 24 + projectiles_cache_max = 96 + harmful = TRUE + ammo_type = "incendiary" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced + name = "\improper S.H.H. \"Quietus\" Carbine" + desc = "A weapon for combat exosuits. A mime invention, field tests have shown that targets cannot even scream before going down." + fire_sound = 'sound/weapons/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 + projectiles_cache = 40 + projectiles_cache_max = 160 + projectiles_per_shot = 4 + variance = 25 + harmful = TRUE + ammo_type = "scattershot" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg + name = "\improper Ultra AC 2" + desc = "A weapon for combat exosuits. Shoots a rapid, three shot burst." + icon_state = "mecha_uac2" + equip_cooldown = 10 + projectile = /obj/item/projectile/bullet/lmg + projectiles = 300 + projectiles_cache = 300 + projectiles_cache_max = 1200 + projectiles_per_shot = 3 + variance = 6 + randomspread = 1 + projectile_delay = 2 + harmful = TRUE + ammo_type = "lmg" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack + name = "\improper SRM-8 missile rack" + desc = "A weapon for combat exosuits. Launches light explosive missiles." + icon_state = "mecha_missilerack" + projectile = /obj/item/projectile/bullet/a84mm_he + fire_sound = 'sound/weapons/grenadelaunch.ogg' + projectiles = 8 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + equip_cooldown = 60 + harmful = TRUE + ammo_type = "missiles_he" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/breaching + name = "\improper BRM-6 missile rack" + desc = "A weapon for combat exosuits. Launches low-explosive breaching missiles designed to explode only when striking a sturdy target." + icon_state = "mecha_missilerack_six" + projectile = /obj/item/projectile/bullet/a84mm_br + fire_sound = 'sound/weapons/grenadelaunch.ogg' + projectiles = 6 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + equip_cooldown = 60 + harmful = TRUE + ammo_type = "missiles_br" + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher + var/missile_speed = 2 + var/missile_range = 30 + var/diags_first = FALSE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/action(target) + if(!action_checks(target)) + return + var/obj/O = new projectile(chassis.loc) + playsound(chassis, fire_sound, 50, 1) + log_message("Launched a [O.name] from [name], targeting [target].", LOG_MECHA) + projectiles-- + proj_init(O) + O.throw_at(target, missile_range, missile_speed, chassis.occupant, FALSE, diagonals_first = diags_first) + return 1 + +//used for projectile initilisation (priming flashbang) and additional logging +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/proc/proj_init(var/obj/O) + return + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang + name = "\improper SGL-6 grenade launcher" + desc = "A weapon for combat exosuits. Launches primed flashbangs." + icon_state = "mecha_grenadelnchr" + projectile = /obj/item/grenade/flashbang + fire_sound = 'sound/weapons/grenadelaunch.ogg' + projectiles = 6 + projectiles_cache = 6 + projectiles_cache_max = 24 + missile_speed = 1.5 + equip_cooldown = 60 + var/det_time = 20 + ammo_type = "flashbang" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/proj_init(var/obj/item/grenade/flashbang/F) + var/turf/T = get_turf(src) + message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] fired a [src] in [ADMIN_VERBOSEJMP(T)]") + log_game("[key_name(chassis.occupant)] fired a [src] in [AREACOORD(T)]") + addtimer(CALLBACK(F, /obj/item/grenade/flashbang.proc/prime), det_time) + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/clusterbang //Because I am a heartless bastard -Sieve //Heartless? for making the poor man's honkblast? - Kaze + name = "\improper SOB-3 grenade launcher" + desc = "A weapon for combat exosuits. Launches primed clusterbangs. You monster." + projectiles = 3 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + projectile = /obj/item/grenade/clusterbuster + equip_cooldown = 90 + ammo_type = "clusterbang" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar + name = "banana mortar" + desc = "Equipment for clown exosuits. Launches banana peels." + icon_state = "mecha_bananamrtr" + projectile = /obj/item/grown/bananapeel + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 15 + missile_speed = 1.5 + projectile_energy_cost = 100 + equip_cooldown = 20 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar + name = "mousetrap mortar" + desc = "Equipment for clown exosuits. Launches armed mousetraps." + icon_state = "mecha_mousetrapmrtr" + projectile = /obj/item/assembly/mousetrap/armed + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 15 + missile_speed = 1.5 + projectile_energy_cost = 100 + equip_cooldown = 10 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/proj_init(var/obj/item/assembly/mousetrap/armed/M) + M.secured = 1 + + +//Classic extending punching glove, but weaponised! +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove + name = "\improper Oingo Boingo Punch-face" + desc = "Equipment for clown exosuits. Delivers fun right to your face!" + icon_state = "mecha_punching_glove" + energy_drain = 250 + equip_cooldown = 20 + range = 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, datum/thrownthing/throwingdatum) + 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 23f06398680b..e58ed7997f7a 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", - "Exosuit Ammunition", - "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/examine(mob/user) - . = ..() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Storing up to [materials.max_amount] material units.
                    Material consumption at [component_coeff*100]%.
                    Build time reduced by [100-time_coeff*100]%." - -/obj/machinery/mecha_part_fabricator/emag_act() - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - req_access = list() - say("DB error \[Code 0x00F1\]") - sleep(10) - say("Attempting auto-repair...") - sleep(15) - say("User DB corrupted \[Code 0x00FA\]. Truncating data structure...") - sleep(30) - say("User DB truncated. Please contact your Nanotrasen system operator for future assistance.") - - -/obj/machinery/mecha_part_fabricator/proc/output_parts_list(set_name) - var/output = "" - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(!(set_name in D.category)) - continue - output += "
                    [output_part_info(D)]
                    \[" - if(check_resources(D)) - output += "Build | " - output += "Add to queue\]\[?\]
                    " - return output - -/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D) - var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [get_construction_time_w_coeff(D)/10]sec" - return output - -/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D) - var/i = 0 - var/output - for(var/c in D.materials) - 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 - if(href_list["part_set"]) - var/tpart_set = href_list["part_set"] - if(tpart_set) - if(tpart_set=="clear") - part_set = null - else - part_set = tpart_set - screen = "parts" - if(href_list["part"]) - var/T = href_list["part"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - if(!processing_queue) - build_part(D) - else - add_to_queue(D) - break - if(href_list["add_to_queue"]) - var/T = href_list["add_to_queue"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - add_to_queue(D) - break - return update_queue_on_page() - if(href_list["remove_from_queue"]) - remove_from_queue(text2num(href_list["remove_from_queue"])) - return update_queue_on_page() - if(href_list["partset_to_queue"]) - add_part_set_to_queue(href_list["partset_to_queue"]) - return update_queue_on_page() - if(href_list["process_queue"]) - 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 = text2num(href_list["index"]) - var/new_index = index + text2num(href_list["queue_move"]) - if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index)) - if(ISINRANGE(new_index,1,queue.len)) - queue.Swap(index,new_index) - return update_queue_on_page() - if(href_list["clear_queue"]) - queue = list() - return update_queue_on_page() - if(href_list["sync"]) - sync() - if(href_list["part_desc"]) - var/T = href_list["part_desc"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - var/obj/part = D.build_path - temp = {"

                    [initial(part.name)] description:

                    - [initial(part.desc)]
                    - Return - "} - break - - if(href_list["remove_mat"] && href_list["material"]) - var/datum/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", + "Exosuit Ammunition", + "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/examine(mob/user) + . = ..() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Storing up to [materials.max_amount] material units.
                    Material consumption at [component_coeff*100]%.
                    Build time reduced by [100-time_coeff*100]%." + +/obj/machinery/mecha_part_fabricator/emag_act() + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + req_access = list() + say("DB error \[Code 0x00F1\]") + sleep(10) + say("Attempting auto-repair...") + sleep(15) + say("User DB corrupted \[Code 0x00FA\]. Truncating data structure...") + sleep(30) + say("User DB truncated. Please contact your Nanotrasen system operator for future assistance.") + + +/obj/machinery/mecha_part_fabricator/proc/output_parts_list(set_name) + var/output = "" + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(!(set_name in D.category)) + continue + output += "
                    [output_part_info(D)]
                    \[" + if(check_resources(D)) + output += "Build | " + output += "Add to queue\]\[?\]
                    " + return output + +/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D) + var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [get_construction_time_w_coeff(D)/10]sec" + return output + +/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D) + var/i = 0 + var/output + for(var/c in D.materials) + 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 + if(href_list["part_set"]) + var/tpart_set = href_list["part_set"] + if(tpart_set) + if(tpart_set=="clear") + part_set = null + else + part_set = tpart_set + screen = "parts" + if(href_list["part"]) + var/T = href_list["part"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + if(!processing_queue) + build_part(D) + else + add_to_queue(D) + break + if(href_list["add_to_queue"]) + var/T = href_list["add_to_queue"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + add_to_queue(D) + break + return update_queue_on_page() + if(href_list["remove_from_queue"]) + remove_from_queue(text2num(href_list["remove_from_queue"])) + return update_queue_on_page() + if(href_list["partset_to_queue"]) + add_part_set_to_queue(href_list["partset_to_queue"]) + return update_queue_on_page() + if(href_list["process_queue"]) + 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 = text2num(href_list["index"]) + var/new_index = index + text2num(href_list["queue_move"]) + if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index)) + if(ISINRANGE(new_index,1,queue.len)) + queue.Swap(index,new_index) + return update_queue_on_page() + if(href_list["clear_queue"]) + queue = list() + return update_queue_on_page() + if(href_list["sync"]) + sync() + if(href_list["part_desc"]) + var/T = href_list["part_desc"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + var/obj/part = D.build_path + temp = {"

                    [initial(part.name)] description:

                    + [initial(part.desc)]
                    + Return + "} + break + + if(href_list["remove_mat"] && href_list["material"]) + var/datum/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 f36ae7ef7f41..eec6dd062b44 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -1,1173 +1,1173 @@ -#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/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming. - var/can_move = 0 //time of next allowed movement - var/mob/living/carbon/occupant = null - var/step_in = 10 //make a step in step_in/10 sec. - var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South. - var/normal_step_energy_drain = 10 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain. - var/step_energy_drain = 10 - var/melee_energy_drain = 15 - var/overload_step_energy_drain_min = 100 - max_integrity = 300 //max_integrity is base health - var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. - armor = list("melee" = 20, "bullet" = 10, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - var/list/facing_modifiers = list(FRONT_ARMOUR = 1.5, SIDE_ARMOUR = 1, BACK_ARMOUR = 0.5) - var/equipment_disabled = 0 //disabled due to EMP - var/obj/item/stock_parts/cell/cell - var/state = 0 - var/last_message = 0 - var/add_req_access = 1 - var/maint_access = 0 - var/dna_lock //dna-locking the mech - var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference - var/datum/effect_system/spark_spread/spark_system = new - var/lights = FALSE - var/lights_power = 6 - var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference - var/completely_disabled = FALSE //stops the mech from doing anything - - var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. - //inner atmos - var/use_internal_tank = 0 - var/internal_tank_valve = ONE_ATMOSPHERE - var/obj/machinery/portable_atmospherics/canister/internal_tank - var/datum/gas_mixture/cabin_air - var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port = null - - var/obj/item/radio/mech/radio - var/list/trackers = list() - - var/max_temperature = 25000 - var/internal_damage_threshold = 50 //health percentage below which internal damage is possible - var/internal_damage = 0 //contains bitflags - - var/list/operation_req_access = list()//required access level for mecha operation - var/list/internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT - - var/wreckage - - var/list/equipment = new - var/obj/item/mecha_parts/mecha_equipment/selected - var/max_equip = 3 - var/datum/events/events - - var/stepsound = 'sound/mecha/mechstep.ogg' - var/turnsound = 'sound/mecha/mechturn.ogg' - - var/melee_cooldown = 10 - var/melee_can_hit = 1 - - var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting. - - var/enter_delay = 40 //Time taken to enter the mech - var/exit_delay = 20 //Time to exit mech - var/destruction_sleep_duration = 20 //Time that mech pilot is put to sleep for if mech is destroyed - var/enclosed = TRUE //Set to false for open-cockpit mechs - var/silicon_icon_state = null //if the mech has a different icon when piloted by an AI or MMI - var/is_currently_ejecting = FALSE //Mech cannot use equiptment when true, set to true if pilot is trying to exit mech - - //Action datums - var/datum/action/innate/mecha/mech_eject/eject_action = new - var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new - var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new - var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new - var/datum/action/innate/mecha/mech_view_stats/stats_action = new - var/datum/action/innate/mecha/mech_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() - if (enclosed) - add_airtank() - spark_system.set_up(2, 0, src) - spark_system.attach(src) - smoke_system.set_up(3, src) - smoke_system.attach(src) - add_cell() - START_PROCESSING(SSobj, src) - GLOB.poi_list |= src - log_message("[src.name] created.", LOG_MECHA) - GLOB.mechas_list += src //global mech list - prepare_huds() - for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/obj/mecha/update_icon() - if (silicon_pilot && silicon_icon_state) - icon_state = silicon_icon_state - . = ..() - -/obj/mecha/get_cell() - return cell - -/obj/mecha/Destroy() - if(occupant) - occupant.SetSleeping(destruction_sleep_duration) - go_out() - var/mob/living/silicon/ai/AI - for(var/mob/M in src) //Let's just be ultra sure - if(isAI(M)) - occupant = null - AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck. - else - M.forceMove(loc) - if(wreckage) - 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 * 5)) //Each level of capacitor protects the mech against emp by 5% - 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.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - cabin_air.gases[/datum/gas/oxygen][MOLES] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - cabin_air.gases[/datum/gas/nitrogen][MOLES] = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - return cabin_air - -/obj/mecha/proc/add_radio() - radio = new(src) - radio.name = "[src] radio" - radio.icon = icon - radio.icon_state = icon_state - radio.subspace_transmission = TRUE - -/obj/mecha/proc/can_use(mob/user) - if(user != occupant) - return 0 - if(user && ismob(user)) - if(!user.incapacitated()) - return 1 - return 0 - -//////////////////////////////////////////////////////////////////////////////// - -/obj/mecha/examine(mob/user) - . = ..() - var/integrity = obj_integrity*100/max_integrity - switch(integrity) - if(85 to 100) - . += "It's fully intact." - if(65 to 85) - . += "It's slightly damaged." - if(45 to 65) - . += "It's badly damaged." - if(25 to 45) - . += "It's heavily damaged." - else - . += "It's falling apart." - var/hide_weapon = locate(/obj/item/mecha_parts/concealed_weapon_bay) in contents - var/hidden_weapon = hide_weapon ? (locate(/obj/item/mecha_parts/mecha_equipment/weapon) in equipment) : null - var/list/visible_equipment = equipment - hidden_weapon - if(visible_equipment.len) - . += "It's equipped with:" - for(var/obj/item/mecha_parts/mecha_equipment/ME in visible_equipment) - . += "[icon2html(ME, user)] \A [ME]." - if(!enclosed) - if(silicon_pilot) - . += "[src] appears to be piloting itself..." - else if(occupant && occupant != user) //!silicon_pilot implied - . += "You can see [occupant] inside." - if(ishuman(user)) - var/mob/living/carbon/human/H = user - for(var/O in H.held_items) - if(istype(O, /obj/item/gun)) - . += "It looks like you can hit the pilot directly if you target the center or above." - break //in case user is holding two guns - -//processing internal damage, temperature, air regulation, alert updates, lights power use. -/obj/mecha/process() - var/internal_temp_regulation = 1 - - if(internal_damage) - if(internal_damage & MECHA_INT_FIRE) - if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5)) - clearInternalDamage(MECHA_INT_FIRE) - if(internal_tank) - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH)) - setInternalDamage(MECHA_INT_TANK_BREACH) - if(int_tank_air && int_tank_air.return_volume() > 0) //heat the air_contents - int_tank_air.temperature = min(6000+T0C, int_tank_air.temperature+rand(10,15)) - if(cabin_air && cabin_air.return_volume()>0) - cabin_air.temperature = min(6000+T0C, cabin_air.return_temperature()+rand(10,15)) - if(cabin_air.return_temperature() > max_temperature/2) - take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0) - - if(internal_damage & MECHA_INT_TEMP_CONTROL) - internal_temp_regulation = 0 - - if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank - if(internal_tank) - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.1) - if(loc) - loc.assume_air(leaked_gas) - air_update_turf() - else - qdel(leaked_gas) - - if(internal_damage & MECHA_INT_SHORT_CIRCUIT) - if(get_charge()) - spark_system.start() - cell.charge -= min(20,cell.charge) - cell.maxcharge -= min(20,cell.maxcharge) - - if(internal_temp_regulation) - if(cabin_air && cabin_air.return_volume() > 0) - var/delta = cabin_air.temperature - T20C - cabin_air.temperature -= max(-10, min(10, round(delta/4,0.1))) - - if(internal_tank) - var/datum/gas_mixture/tank_air = internal_tank.return_air() - - var/release_pressure = internal_tank_valve - var/cabin_pressure = cabin_air.return_pressure() - var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) - var/transfer_moles = 0 - if(pressure_delta > 0) //cabin pressure lower than release pressure - if(tank_air.return_temperature() > 0) - transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) - var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) - cabin_air.merge(removed) - else if(pressure_delta < 0) //cabin pressure higher than release pressure - var/datum/gas_mixture/t_air = return_air() - pressure_delta = cabin_pressure - release_pressure - if(t_air) - pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) - if(pressure_delta > 0) //if location pressure is lower than cabin pressure - transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) - var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) - if(t_air) - t_air.merge(removed) - else //just delete the cabin gas, we're in space or some shit - qdel(removed) - - if(occupant) - if(cell) - var/cellcharge = cell.charge/cell.maxcharge - switch(cellcharge) - if(0.75 to INFINITY) - occupant.clear_alert("charge") - if(0.5 to 0.75) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 1) - if(0.25 to 0.5) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2) - if(0.01 to 0.25) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3) - else - occupant.throw_alert("charge", /obj/screen/alert/emptycell) - - var/integrity = obj_integrity/max_integrity*100 - switch(integrity) - if(30 to 45) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1) - if(15 to 35) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2) - if(-INFINITY to 15) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3) - else - occupant.clear_alert("mech damage") - var/atom/checking = occupant.loc - // recursive check to handle all cases regarding very nested occupants, - // such as brainmob inside brainitem inside MMI inside mecha - while (!isnull(checking)) - if (isturf(checking)) - // hit a turf before hitting the mecha, seems like they have - // been moved out - occupant.clear_alert("charge") - occupant.clear_alert("mech damage") - RemoveActions(occupant, human_occupant=1) - occupant = null - break - else if (checking == src) - break // all good - checking = checking.loc - - if(lights) - var/lights_energy_drain = 2 - use_power(lights_energy_drain) - - if(!enclosed && occupant?.incapacitated()) //no sides mean it's easy to just sorta fall out if you're incapacitated. - visible_message("[occupant] tumbles out of the cockpit!") - go_out() //Maybe we should install seat belts? - -//Diagnostic HUD updates - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/obj/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech - . = ..() - if (occupant && !enclosed && !silicon_pilot) - if (occupant.fire_stacks < 5) - occupant.fire_stacks += 1 - occupant.IgniteMob() - -/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. - return - -/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker == occupant) - if(radio?.broadcasting) - radio.talk_into(speaker, text, , spans, message_language) - //flick speech bubble - var/list/speech_bubble_recipients = list() - for(var/mob/M in get_hearers_in_view(7,src)) - if(M.client) - speech_bubble_recipients.Add(M.client) - INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30) - -//////////////////////////// -///// Action processing //// -//////////////////////////// - - -/obj/mecha/proc/click_action(atom/target,mob/user,params) - if(!occupant || occupant != user ) - return - if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled - return - if(completely_disabled) - return - if(is_currently_ejecting) - return - if(phasing) - occupant_message("Unable to interact with objects while phasing") - return - if(user.incapacitated()) - return - if(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.", LOG_MECHA) - -/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(completely_disabled) - return - if(!direction) - return - if(user != occupant) //While not "realistic", this piece is player friendly. - user.forceMove(get_turf(src)) - to_chat(user, "You climb out from [src].") - return 0 - if(internal_tank?.connected_port) - if(world.time - last_message > 20) - occupant_message("Unable to move while connected to the air system port!") - last_message = world.time - return 0 - if(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) - var/turf/newloc = get_step(src,dir) - if(newloc.flags_1 & NOJAUNT_1) - to_chat(occupant, "Some strange aura is blocking the way.") - return - if(phasing && get_charge() >= phasing_energy_drain && !throwing) - spawn() - if(can_move) - can_move = 0 - if(phase_state) - flick(phase_state, src) - forceMove(newloc) - 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,newloc)) - 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_message("Internal damage of type [int_dam_flag].", LOG_MECHA) - SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) - diag_hud_set_mechstat() - 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 = TRUE - AI.radio_enabled = FALSE - AI.disconnect_shell() - RemoveActions(AI, TRUE) - occupant = null - silicon_pilot = FALSE - AI.forceMove(card) - card.AI = AI - AI.controlled_mech = null - AI.remote_control = null - icon_state = initial(icon_state)+"-open" - to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") - - if(AI_MECH_HACK) //Called by AIs on the mech - AI.linked_core = new /obj/structure/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 = FALSE - AI.radio_enabled = TRUE - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - card.AI = null - ai_enter_mech(AI, interaction) - -//Hack and From Card interactions share some code, so leave that here for both to use. -/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction) - AI.ai_restore_power() - AI.forceMove(src) - occupant = AI - silicon_pilot = TRUE - icon_state = initial(icon_state) - update_icon() - playsound(src, 'sound/machines/windowdoor.ogg', 50, 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.mobility_flags = ALL //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. - AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES. - to_chat(AI, AI.can_dominate_mechs ? "Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!" :\ - "You have been uploaded to a mech's onboard computer.") - to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.") - if(interaction == AI_TRANS_FROM_CARD) - GrantActions(AI, FALSE) //No eject/return to core action for AI uploaded by card - else - GrantActions(AI, !AI.can_dominate_mechs) - - -//An actual AI (simple_animal mecha pilot) entering the mech -/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) - if(pilot_mob && pilot_mob.Adjacent(src)) - if(occupant) - return - icon_state = initial(icon_state) - occupant = pilot_mob - pilot_mob.mecha = src - pilot_mob.forceMove(src) - GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob - -/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) - if(occupant == pilot_mob) - occupant = null - if(pilot_mob.mecha == src) - pilot_mob.mecha = null - icon_state = "[initial(icon_state)]-open" - pilot_mob.forceMove(get_turf(src)) - RemoveActions(pilot_mob) - - -///////////////////////////////////// -//////// Atmospheric stuff //////// -///////////////////////////////////// - -/obj/mecha/remove_air(amount) - if(use_internal_tank) - return cabin_air.remove(amount) - return ..() - -/obj/mecha/return_air() - if(use_internal_tank) - return cabin_air - return ..() - -/obj/mecha/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/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.", LOG_MECHA) - if (occupant) - to_chat(usr, "The [name] is already occupied!") - log_message("Permission denied (Occupied).", LOG_MECHA) - return - if(dna_lock) - var/passed = FALSE - if(user.has_dna()) - var/mob/living/carbon/C = user - if(C.dna.unique_enzymes==dna_lock) - passed = TRUE - if (!passed) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - log_message("Permission denied (DNA LOCK).", LOG_MECHA) - return - if(!operation_allowed(user)) - to_chat(user, "Access denied. Insufficient operation keycodes.") - log_message("Permission denied (No keycode).", LOG_MECHA) - return - if(user.buckled) - to_chat(user, "You are currently buckled and cannot move.") - log_message("Permission denied (Buckled).", LOG_MECHA) - return - if(user.has_buckled_mobs()) //mob attached to us - to_chat(user, "You can't enter the exosuit with other creatures attached to you!") - log_message("Permission denied (Attached mobs).", LOG_MECHA) - return - - visible_message("[user] starts to climb into [name].") - - if(do_after(user, enter_delay, target = src)) - if(obj_integrity <= 0) - to_chat(user, "You cannot get in the [name], it has been destroyed!") - else if(occupant) - to_chat(user, "[occupant] was faster! Try better next time, loser.") - else if(user.buckled) - to_chat(user, "You can't enter the exosuit while buckled.") - else if(user.has_buckled_mobs()) - to_chat(user, "You can't enter the exosuit with other creatures attached to you!") - else - moved_inside(user) - else - to_chat(user, "You stop entering the exosuit!") - 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_message("[H] moved in as pilot.", LOG_MECHA) - 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 - silicon_pilot = TRUE - brainmob.forceMove(src) //should allow relaymove - brainmob.reset_perspective(src) - brainmob.remote_control = src - brainmob.update_mobility() - brainmob.update_mouse_pointer() - icon_state = initial(icon_state) - update_icon() - setDir(dir_in) - log_message("[mmi_as_oc] moved in as pilot.", LOG_MECHA) - if(istype(mmi_as_oc, /obj/item/mmi/posibrain)) //yogs start reminder to posibrain to not be shitlers - to_chat(brainmob, "As a synthetic intelligence, you answer to all crewmembers and the AI.\n\ - Remember, the purpose of your existence is to serve the crew and the station. Above all else, do no harm.") - else - to_chat(brainmob, "Remember, you are still member of the crew act like it")//yogs end - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - GrantActions(brainmob) - return TRUE - -/obj/mecha/container_resist(mob/living/user) - is_currently_ejecting = TRUE - to_chat(occupant, "You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting.") - if(do_after(occupant,exit_delay, target = src)) - to_chat(occupant, "You exit the mech.") - go_out() - else - to_chat(occupant, "You stop exiting the mech. Weapons are enabled again.") - is_currently_ejecting = FALSE - -/obj/mecha/Exited(atom/movable/M, atom/newloc) - if(occupant && occupant == M) // The occupant exited the mech without calling go_out() - go_out(TRUE, newloc) - -/obj/mecha/proc/go_out(forced, atom/newloc = loc) - if(!occupant) - return - var/atom/movable/mob_container - occupant.clear_alert("charge") - occupant.clear_alert("mech damage") - if(ishuman(occupant)) - mob_container = occupant - RemoveActions(occupant, human_occupant=1) - else if(isbrain(occupant)) - var/mob/living/brain/brain = occupant - RemoveActions(brain) - mob_container = brain.container - else if(isAI(occupant)) - var/mob/living/silicon/ai/AI = occupant - if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. - RemoveActions(occupant) - occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. - occupant = null - silicon_pilot = FALSE - return - else - if(!AI.linked_core) - to_chat(AI, "Inactive core destroyed. Unable to return.") - AI.linked_core = null - return - to_chat(AI, "Returning to core...") - AI.controlled_mech = null - AI.remote_control = null - RemoveActions(occupant, 1) - mob_container = AI - newloc = get_turf(AI.linked_core) - qdel(AI.linked_core) - else - return - var/mob/living/L = occupant - occupant = null //we need it null when forceMove calls Exited(). - silicon_pilot = FALSE - if(mob_container.forceMove(newloc))//ejecting mob container - log_message("[mob_container] moved out.", LOG_MECHA) - L << browse(null, "window=exosuit") - - if(istype(mob_container, /obj/item/mmi)) - var/obj/item/mmi/mmi = mob_container - if(mmi.brainmob) - L.forceMove(mmi) - L.reset_perspective() - mmi.mecha = null - mmi.update_icon() - L.mobility_flags = NONE - icon_state = initial(icon_state)+"-open" - setDir(dir_in) - - if(L && L.client) - L.update_mouse_pointer() - L.client.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 - -GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) -GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? - -/////////////////////// -///// Power stuff ///// -/////////////////////// - -/obj/mecha/proc/has_charge(amount) - return (get_charge()>=amount) - -/obj/mecha/proc/get_charge() - for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment) - var/relay_charge = R.get_charge() - if(relay_charge) - return relay_charge - if(cell) - return max(0, cell.charge) - -/obj/mecha/proc/use_power(amount) - if(get_charge()) - cell.use(amount) - return 1 - return 0 - -/obj/mecha/proc/give_power(amount) - if(!isnull(get_charge())) - cell.give(amount) - return 1 - return 0 - -/obj/mecha/update_remote_sight(mob/living/user) - if(occupant_sight_flags) - if(user == occupant) - user.sight |= occupant_sight_flags - -/////////////////////// -////// Ammo stuff ///// -/////////////////////// - -/obj/mecha/proc/ammo_resupply(var/obj/item/mecha_ammo/A, mob/user,var/fail_chat_override = FALSE) - if(!A.rounds) - if(!fail_chat_override) - to_chat(user, "This box of ammo is empty!") - return FALSE - var/ammo_needed - var/found_gun - for(var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun in equipment) - ammo_needed = 0 - - if(istype(gun, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic) && gun.ammo_type == A.ammo_type) - found_gun = TRUE - if(A.direct_load) - ammo_needed = initial(gun.projectiles) - gun.projectiles - else - ammo_needed = gun.projectiles_cache_max - gun.projectiles_cache - - if(ammo_needed) - if(ammo_needed < A.rounds) - if(A.direct_load) - gun.projectiles = gun.projectiles + ammo_needed - else - gun.projectiles_cache = gun.projectiles_cache + ammo_needed - playsound(get_turf(user),A.load_audio,50,1) - to_chat(user, "You add [ammo_needed] [A.round_term][ammo_needed > 1?"s":""] to the [gun.name]") - A.rounds = A.rounds - ammo_needed - A.update_name() - return TRUE - - else - if(A.direct_load) - gun.projectiles = gun.projectiles + A.rounds - else - gun.projectiles_cache = gun.projectiles_cache + A.rounds - playsound(get_turf(user),A.load_audio,50,1) - to_chat(user, "You add [A.rounds] [A.round_term][A.rounds > 1?"s":""] to the [gun.name]") - A.rounds = 0 - A.update_name() - return TRUE - if(!fail_chat_override) - if(found_gun) - to_chat(user, "You can't fit any more ammo of this type!") - else - to_chat(user, "None of the equipment on this exosuit can use this ammo!") +#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/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming. + var/can_move = 0 //time of next allowed movement + var/mob/living/carbon/occupant = null + var/step_in = 10 //make a step in step_in/10 sec. + var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South. + var/normal_step_energy_drain = 10 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain. + var/step_energy_drain = 10 + var/melee_energy_drain = 15 + var/overload_step_energy_drain_min = 100 + max_integrity = 300 //max_integrity is base health + var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. + armor = list("melee" = 20, "bullet" = 10, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + var/list/facing_modifiers = list(FRONT_ARMOUR = 1.5, SIDE_ARMOUR = 1, BACK_ARMOUR = 0.5) + var/equipment_disabled = 0 //disabled due to EMP + var/obj/item/stock_parts/cell/cell + var/state = 0 + var/last_message = 0 + var/add_req_access = 1 + var/maint_access = 0 + var/dna_lock //dna-locking the mech + var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference + var/datum/effect_system/spark_spread/spark_system = new + var/lights = FALSE + var/lights_power = 6 + var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference + var/completely_disabled = FALSE //stops the mech from doing anything + + var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. + //inner atmos + var/use_internal_tank = 0 + var/internal_tank_valve = ONE_ATMOSPHERE + var/obj/machinery/portable_atmospherics/canister/internal_tank + var/datum/gas_mixture/cabin_air + var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port = null + + var/obj/item/radio/mech/radio + var/list/trackers = list() + + var/max_temperature = 25000 + var/internal_damage_threshold = 50 //health percentage below which internal damage is possible + var/internal_damage = 0 //contains bitflags + + var/list/operation_req_access = list()//required access level for mecha operation + var/list/internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT + + var/wreckage + + var/list/equipment = new + var/obj/item/mecha_parts/mecha_equipment/selected + var/max_equip = 3 + var/datum/events/events + + var/stepsound = 'sound/mecha/mechstep.ogg' + var/turnsound = 'sound/mecha/mechturn.ogg' + + var/melee_cooldown = 10 + var/melee_can_hit = 1 + + var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting. + + var/enter_delay = 40 //Time taken to enter the mech + var/exit_delay = 20 //Time to exit mech + var/destruction_sleep_duration = 20 //Time that mech pilot is put to sleep for if mech is destroyed + var/enclosed = TRUE //Set to false for open-cockpit mechs + var/silicon_icon_state = null //if the mech has a different icon when piloted by an AI or MMI + var/is_currently_ejecting = FALSE //Mech cannot use equiptment when true, set to true if pilot is trying to exit mech + + //Action datums + var/datum/action/innate/mecha/mech_eject/eject_action = new + var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new + var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new + var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new + var/datum/action/innate/mecha/mech_view_stats/stats_action = new + var/datum/action/innate/mecha/mech_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() + if (enclosed) + add_airtank() + spark_system.set_up(2, 0, src) + spark_system.attach(src) + smoke_system.set_up(3, src) + smoke_system.attach(src) + add_cell() + START_PROCESSING(SSobj, src) + GLOB.poi_list |= src + log_message("[src.name] created.", LOG_MECHA) + GLOB.mechas_list += src //global mech list + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_to_hud(src) + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/obj/mecha/update_icon() + if (silicon_pilot && silicon_icon_state) + icon_state = silicon_icon_state + . = ..() + +/obj/mecha/get_cell() + return cell + +/obj/mecha/Destroy() + if(occupant) + occupant.SetSleeping(destruction_sleep_duration) + go_out() + var/mob/living/silicon/ai/AI + for(var/mob/M in src) //Let's just be ultra sure + if(isAI(M)) + occupant = null + AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck. + else + M.forceMove(loc) + if(wreckage) + 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 * 5)) //Each level of capacitor protects the mech against emp by 5% + 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.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) + cabin_air.gases[/datum/gas/oxygen][MOLES] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + cabin_air.gases[/datum/gas/nitrogen][MOLES] = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + return cabin_air + +/obj/mecha/proc/add_radio() + radio = new(src) + radio.name = "[src] radio" + radio.icon = icon + radio.icon_state = icon_state + radio.subspace_transmission = TRUE + +/obj/mecha/proc/can_use(mob/user) + if(user != occupant) + return 0 + if(user && ismob(user)) + if(!user.incapacitated()) + return 1 + return 0 + +//////////////////////////////////////////////////////////////////////////////// + +/obj/mecha/examine(mob/user) + . = ..() + var/integrity = obj_integrity*100/max_integrity + switch(integrity) + if(85 to 100) + . += "It's fully intact." + if(65 to 85) + . += "It's slightly damaged." + if(45 to 65) + . += "It's badly damaged." + if(25 to 45) + . += "It's heavily damaged." + else + . += "It's falling apart." + var/hide_weapon = locate(/obj/item/mecha_parts/concealed_weapon_bay) in contents + var/hidden_weapon = hide_weapon ? (locate(/obj/item/mecha_parts/mecha_equipment/weapon) in equipment) : null + var/list/visible_equipment = equipment - hidden_weapon + if(visible_equipment.len) + . += "It's equipped with:" + for(var/obj/item/mecha_parts/mecha_equipment/ME in visible_equipment) + . += "[icon2html(ME, user)] \A [ME]." + if(!enclosed) + if(silicon_pilot) + . += "[src] appears to be piloting itself..." + else if(occupant && occupant != user) //!silicon_pilot implied + . += "You can see [occupant] inside." + if(ishuman(user)) + var/mob/living/carbon/human/H = user + for(var/O in H.held_items) + if(istype(O, /obj/item/gun)) + . += "It looks like you can hit the pilot directly if you target the center or above." + break //in case user is holding two guns + +//processing internal damage, temperature, air regulation, alert updates, lights power use. +/obj/mecha/process() + var/internal_temp_regulation = 1 + + if(internal_damage) + if(internal_damage & MECHA_INT_FIRE) + if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5)) + clearInternalDamage(MECHA_INT_FIRE) + if(internal_tank) + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH)) + setInternalDamage(MECHA_INT_TANK_BREACH) + if(int_tank_air && int_tank_air.return_volume() > 0) //heat the air_contents + int_tank_air.temperature = min(6000+T0C, int_tank_air.temperature+rand(10,15)) + if(cabin_air && cabin_air.return_volume()>0) + cabin_air.temperature = min(6000+T0C, cabin_air.return_temperature()+rand(10,15)) + if(cabin_air.return_temperature() > max_temperature/2) + take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0) + + if(internal_damage & MECHA_INT_TEMP_CONTROL) + internal_temp_regulation = 0 + + if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank + if(internal_tank) + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.1) + if(loc) + loc.assume_air(leaked_gas) + air_update_turf() + else + qdel(leaked_gas) + + if(internal_damage & MECHA_INT_SHORT_CIRCUIT) + if(get_charge()) + spark_system.start() + cell.charge -= min(20,cell.charge) + cell.maxcharge -= min(20,cell.maxcharge) + + if(internal_temp_regulation) + if(cabin_air && cabin_air.return_volume() > 0) + var/delta = cabin_air.temperature - T20C + cabin_air.temperature -= max(-10, min(10, round(delta/4,0.1))) + + if(internal_tank) + var/datum/gas_mixture/tank_air = internal_tank.return_air() + + var/release_pressure = internal_tank_valve + var/cabin_pressure = cabin_air.return_pressure() + var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) + var/transfer_moles = 0 + if(pressure_delta > 0) //cabin pressure lower than release pressure + if(tank_air.return_temperature() > 0) + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) + cabin_air.merge(removed) + else if(pressure_delta < 0) //cabin pressure higher than release pressure + var/datum/gas_mixture/t_air = return_air() + pressure_delta = cabin_pressure - release_pressure + if(t_air) + pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) + if(pressure_delta > 0) //if location pressure is lower than cabin pressure + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) + if(t_air) + t_air.merge(removed) + else //just delete the cabin gas, we're in space or some shit + qdel(removed) + + if(occupant) + if(cell) + var/cellcharge = cell.charge/cell.maxcharge + switch(cellcharge) + if(0.75 to INFINITY) + occupant.clear_alert("charge") + if(0.5 to 0.75) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 1) + if(0.25 to 0.5) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2) + if(0.01 to 0.25) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3) + else + occupant.throw_alert("charge", /obj/screen/alert/emptycell) + + var/integrity = obj_integrity/max_integrity*100 + switch(integrity) + if(30 to 45) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1) + if(15 to 35) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2) + if(-INFINITY to 15) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3) + else + occupant.clear_alert("mech damage") + var/atom/checking = occupant.loc + // recursive check to handle all cases regarding very nested occupants, + // such as brainmob inside brainitem inside MMI inside mecha + while (!isnull(checking)) + if (isturf(checking)) + // hit a turf before hitting the mecha, seems like they have + // been moved out + occupant.clear_alert("charge") + occupant.clear_alert("mech damage") + RemoveActions(occupant, human_occupant=1) + occupant = null + break + else if (checking == src) + break // all good + checking = checking.loc + + if(lights) + var/lights_energy_drain = 2 + use_power(lights_energy_drain) + + if(!enclosed && occupant?.incapacitated()) //no sides mean it's easy to just sorta fall out if you're incapacitated. + visible_message("[occupant] tumbles out of the cockpit!") + go_out() //Maybe we should install seat belts? + +//Diagnostic HUD updates + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/obj/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech + . = ..() + if (occupant && !enclosed && !silicon_pilot) + if (occupant.fire_stacks < 5) + occupant.fire_stacks += 1 + occupant.IgniteMob() + +/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. + return + +/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(speaker == occupant) + if(radio?.broadcasting) + radio.talk_into(speaker, text, , spans, message_language) + //flick speech bubble + var/list/speech_bubble_recipients = list() + for(var/mob/M in get_hearers_in_view(7,src)) + if(M.client) + speech_bubble_recipients.Add(M.client) + INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30) + +//////////////////////////// +///// Action processing //// +//////////////////////////// + + +/obj/mecha/proc/click_action(atom/target,mob/user,params) + if(!occupant || occupant != user ) + return + if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled + return + if(completely_disabled) + return + if(is_currently_ejecting) + return + if(phasing) + occupant_message("Unable to interact with objects while phasing") + return + if(user.incapacitated()) + return + if(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.", LOG_MECHA) + +/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(completely_disabled) + return + if(!direction) + return + if(user != occupant) //While not "realistic", this piece is player friendly. + user.forceMove(get_turf(src)) + to_chat(user, "You climb out from [src].") + return 0 + if(internal_tank?.connected_port) + if(world.time - last_message > 20) + occupant_message("Unable to move while connected to the air system port!") + last_message = world.time + return 0 + if(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) + var/turf/newloc = get_step(src,dir) + if(newloc.flags_1 & NOJAUNT_1) + to_chat(occupant, "Some strange aura is blocking the way.") + return + if(phasing && get_charge() >= phasing_energy_drain && !throwing) + spawn() + if(can_move) + can_move = 0 + if(phase_state) + flick(phase_state, src) + forceMove(newloc) + 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,newloc)) + 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_message("Internal damage of type [int_dam_flag].", LOG_MECHA) + SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) + diag_hud_set_mechstat() + 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 = TRUE + AI.radio_enabled = FALSE + AI.disconnect_shell() + RemoveActions(AI, TRUE) + occupant = null + silicon_pilot = FALSE + AI.forceMove(card) + card.AI = AI + AI.controlled_mech = null + AI.remote_control = null + icon_state = initial(icon_state)+"-open" + to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") + + if(AI_MECH_HACK) //Called by AIs on the mech + AI.linked_core = new /obj/structure/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 = FALSE + AI.radio_enabled = TRUE + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + card.AI = null + ai_enter_mech(AI, interaction) + +//Hack and From Card interactions share some code, so leave that here for both to use. +/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction) + AI.ai_restore_power() + AI.forceMove(src) + occupant = AI + silicon_pilot = TRUE + icon_state = initial(icon_state) + update_icon() + playsound(src, 'sound/machines/windowdoor.ogg', 50, 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.mobility_flags = ALL //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. + AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES. + to_chat(AI, AI.can_dominate_mechs ? "Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!" :\ + "You have been uploaded to a mech's onboard computer.") + to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.") + if(interaction == AI_TRANS_FROM_CARD) + GrantActions(AI, FALSE) //No eject/return to core action for AI uploaded by card + else + GrantActions(AI, !AI.can_dominate_mechs) + + +//An actual AI (simple_animal mecha pilot) entering the mech +/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) + if(pilot_mob && pilot_mob.Adjacent(src)) + if(occupant) + return + icon_state = initial(icon_state) + occupant = pilot_mob + pilot_mob.mecha = src + pilot_mob.forceMove(src) + GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob + +/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) + if(occupant == pilot_mob) + occupant = null + if(pilot_mob.mecha == src) + pilot_mob.mecha = null + icon_state = "[initial(icon_state)]-open" + pilot_mob.forceMove(get_turf(src)) + RemoveActions(pilot_mob) + + +///////////////////////////////////// +//////// Atmospheric stuff //////// +///////////////////////////////////// + +/obj/mecha/remove_air(amount) + if(use_internal_tank) + return cabin_air.remove(amount) + return ..() + +/obj/mecha/return_air() + if(use_internal_tank) + return cabin_air + return ..() + +/obj/mecha/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/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.", LOG_MECHA) + if (occupant) + to_chat(usr, "The [name] is already occupied!") + log_message("Permission denied (Occupied).", LOG_MECHA) + return + if(dna_lock) + var/passed = FALSE + if(user.has_dna()) + var/mob/living/carbon/C = user + if(C.dna.unique_enzymes==dna_lock) + passed = TRUE + if (!passed) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + log_message("Permission denied (DNA LOCK).", LOG_MECHA) + return + if(!operation_allowed(user)) + to_chat(user, "Access denied. Insufficient operation keycodes.") + log_message("Permission denied (No keycode).", LOG_MECHA) + return + if(user.buckled) + to_chat(user, "You are currently buckled and cannot move.") + log_message("Permission denied (Buckled).", LOG_MECHA) + return + if(user.has_buckled_mobs()) //mob attached to us + to_chat(user, "You can't enter the exosuit with other creatures attached to you!") + log_message("Permission denied (Attached mobs).", LOG_MECHA) + return + + visible_message("[user] starts to climb into [name].") + + if(do_after(user, enter_delay, target = src)) + if(obj_integrity <= 0) + to_chat(user, "You cannot get in the [name], it has been destroyed!") + else if(occupant) + to_chat(user, "[occupant] was faster! Try better next time, loser.") + else if(user.buckled) + to_chat(user, "You can't enter the exosuit while buckled.") + else if(user.has_buckled_mobs()) + to_chat(user, "You can't enter the exosuit with other creatures attached to you!") + else + moved_inside(user) + else + to_chat(user, "You stop entering the exosuit!") + 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_message("[H] moved in as pilot.", LOG_MECHA) + 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 + silicon_pilot = TRUE + brainmob.forceMove(src) //should allow relaymove + brainmob.reset_perspective(src) + brainmob.remote_control = src + brainmob.update_mobility() + brainmob.update_mouse_pointer() + icon_state = initial(icon_state) + update_icon() + setDir(dir_in) + log_message("[mmi_as_oc] moved in as pilot.", LOG_MECHA) + if(istype(mmi_as_oc, /obj/item/mmi/posibrain)) //yogs start reminder to posibrain to not be shitlers + to_chat(brainmob, "As a synthetic intelligence, you answer to all crewmembers and the AI.\n\ + Remember, the purpose of your existence is to serve the crew and the station. Above all else, do no harm.") + else + to_chat(brainmob, "Remember, you are still member of the crew act like it")//yogs end + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + GrantActions(brainmob) + return TRUE + +/obj/mecha/container_resist(mob/living/user) + is_currently_ejecting = TRUE + to_chat(occupant, "You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting.") + if(do_after(occupant,exit_delay, target = src)) + to_chat(occupant, "You exit the mech.") + go_out() + else + to_chat(occupant, "You stop exiting the mech. Weapons are enabled again.") + is_currently_ejecting = FALSE + +/obj/mecha/Exited(atom/movable/M, atom/newloc) + if(occupant && occupant == M) // The occupant exited the mech without calling go_out() + go_out(TRUE, newloc) + +/obj/mecha/proc/go_out(forced, atom/newloc = loc) + if(!occupant) + return + var/atom/movable/mob_container + occupant.clear_alert("charge") + occupant.clear_alert("mech damage") + if(ishuman(occupant)) + mob_container = occupant + RemoveActions(occupant, human_occupant=1) + else if(isbrain(occupant)) + var/mob/living/brain/brain = occupant + RemoveActions(brain) + mob_container = brain.container + else if(isAI(occupant)) + var/mob/living/silicon/ai/AI = occupant + if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. + RemoveActions(occupant) + occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. + occupant = null + silicon_pilot = FALSE + return + else + if(!AI.linked_core) + to_chat(AI, "Inactive core destroyed. Unable to return.") + AI.linked_core = null + return + to_chat(AI, "Returning to core...") + AI.controlled_mech = null + AI.remote_control = null + RemoveActions(occupant, 1) + mob_container = AI + newloc = get_turf(AI.linked_core) + qdel(AI.linked_core) + else + return + var/mob/living/L = occupant + occupant = null //we need it null when forceMove calls Exited(). + silicon_pilot = FALSE + if(mob_container.forceMove(newloc))//ejecting mob container + log_message("[mob_container] moved out.", LOG_MECHA) + L << browse(null, "window=exosuit") + + if(istype(mob_container, /obj/item/mmi)) + var/obj/item/mmi/mmi = mob_container + if(mmi.brainmob) + L.forceMove(mmi) + L.reset_perspective() + mmi.mecha = null + mmi.update_icon() + L.mobility_flags = NONE + icon_state = initial(icon_state)+"-open" + setDir(dir_in) + + if(L && L.client) + L.update_mouse_pointer() + L.client.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 + +GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) +GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? + +/////////////////////// +///// Power stuff ///// +/////////////////////// + +/obj/mecha/proc/has_charge(amount) + return (get_charge()>=amount) + +/obj/mecha/proc/get_charge() + for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment) + var/relay_charge = R.get_charge() + if(relay_charge) + return relay_charge + if(cell) + return max(0, cell.charge) + +/obj/mecha/proc/use_power(amount) + if(get_charge()) + cell.use(amount) + return 1 + return 0 + +/obj/mecha/proc/give_power(amount) + if(!isnull(get_charge())) + cell.give(amount) + return 1 + return 0 + +/obj/mecha/update_remote_sight(mob/living/user) + if(occupant_sight_flags) + if(user == occupant) + user.sight |= occupant_sight_flags + +/////////////////////// +////// Ammo stuff ///// +/////////////////////// + +/obj/mecha/proc/ammo_resupply(var/obj/item/mecha_ammo/A, mob/user,var/fail_chat_override = FALSE) + if(!A.rounds) + if(!fail_chat_override) + to_chat(user, "This box of ammo is empty!") + return FALSE + var/ammo_needed + var/found_gun + for(var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun in equipment) + ammo_needed = 0 + + if(istype(gun, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic) && gun.ammo_type == A.ammo_type) + found_gun = TRUE + if(A.direct_load) + ammo_needed = initial(gun.projectiles) - gun.projectiles + else + ammo_needed = gun.projectiles_cache_max - gun.projectiles_cache + + if(ammo_needed) + if(ammo_needed < A.rounds) + if(A.direct_load) + gun.projectiles = gun.projectiles + ammo_needed + else + gun.projectiles_cache = gun.projectiles_cache + ammo_needed + playsound(get_turf(user),A.load_audio,50,1) + to_chat(user, "You add [ammo_needed] [A.round_term][ammo_needed > 1?"s":""] to the [gun.name]") + A.rounds = A.rounds - ammo_needed + A.update_name() + return TRUE + + else + if(A.direct_load) + gun.projectiles = gun.projectiles + A.rounds + else + gun.projectiles_cache = gun.projectiles_cache + A.rounds + playsound(get_turf(user),A.load_audio,50,1) + to_chat(user, "You add [A.rounds] [A.round_term][A.rounds > 1?"s":""] to the [gun.name]") + A.rounds = 0 + A.update_name() + return TRUE + if(!fail_chat_override) + if(found_gun) + to_chat(user, "You can't fit any more ammo of this type!") + else + to_chat(user, "None of the equipment on this exosuit can use this ammo!") return FALSE \ No newline at end of file diff --git a/code/game/mecha/mecha_construction_paths.dm b/code/game/mecha/mecha_construction_paths.dm index 8c906600cb68..addf22d7942c 100644 --- a/code/game/mecha/mecha_construction_paths.dm +++ b/code/game/mecha/mecha_construction_paths.dm @@ -1,1940 +1,1940 @@ -//////////////////////////////// -///// 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/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - - //11 - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - - //12 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - - //13 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - - //14 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ), - - //15 - list( - "key" = /obj/item/stack/sheet/metal, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ), - - //16 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Outer plating is installed." - ), - - //17 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Outer Plating is wrenched." - ), - - //18 - list( - "key" = /obj/item/stack/rods, - "amount" = 10, - "back_key" = TOOL_WELDER, - "desc" = "Outer Plating is welded." - ), - - //19 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WIRECUTTER, - "desc" = "Cockpit wire screen is installed." - ), - ) - -/datum/component/construction/mecha/ripley/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures [I].", "You secure [I].") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I].", "You install [I].") - else - user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs the external reinforced armor layer to [parent].", "You install the external reinforced armor layer to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/gygax - result = /datum/component/construction/mecha/gygax - steps = list( - /obj/item/mecha_parts/part/gygax_torso, - /obj/item/mecha_parts/part/gygax_left_arm, - /obj/item/mecha_parts/part/gygax_right_arm, - /obj/item/mecha_parts/part/gygax_left_leg, - /obj/item/mecha_parts/part/gygax_right_leg, - /obj/item/mecha_parts/part/gygax_head - ) - -/datum/component/construction/mecha/gygax - result = /obj/mecha/combat/gygax - base_icon = "gygax" - 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/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - - //11 - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - - //12 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - - //13 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - - //14 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ), - - //15 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ), - - //16 - 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." - ), - - //17 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - - //18 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is being installed." - ), - - //19 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - - //20 - 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 scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] 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(19) - 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(20) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") - if(21) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/honker - result = /datum/component/construction/mecha/honker - steps = list( - /obj/item/mecha_parts/part/honker_torso, - /obj/item/mecha_parts/part/honker_left_arm, - /obj/item/mecha_parts/part/honker_right_arm, - /obj/item/mecha_parts/part/honker_left_leg, - /obj/item/mecha_parts/part/honker_right_leg, - /obj/item/mecha_parts/part/honker_head - ) - -/datum/component/construction/mecha/honker - result = /obj/mecha/combat/honker - steps = list( - //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 - ), - - //6 - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE - ), - - //8 - list( - "key" = /obj/item/bikehorn - ), - - //9 - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE - ), - - //10 - list( - "key" = /obj/item/bikehorn - ), - - //11 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE - ), - - //12 - list( - "key" = /obj/item/bikehorn - ), - - //13 - list( - "key" = /obj/item/clothing/mask/gas/clown_hat, - "action" = ITEM_DELETE - ), - - //14 - list( - "key" = /obj/item/bikehorn - ), - - //15 - list( - "key" = /obj/item/clothing/shoes/clown_shoes, - "action" = ITEM_DELETE - ), - - //16 - 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] installs [I] into [parent].", "You install [I] into [parent].") - if(12) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(14) - user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") - if(16) - user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/durand - result = /datum/component/construction/mecha/durand - steps = list( - /obj/item/mecha_parts/part/durand_torso, - /obj/item/mecha_parts/part/durand_left_arm, - /obj/item/mecha_parts/part/durand_right_arm, - /obj/item/mecha_parts/part/durand_left_leg, - /obj/item/mecha_parts/part/durand_right_leg, - /obj/item/mecha_parts/part/durand_head - ) - -/datum/component/construction/mecha/durand - result = /obj/mecha/combat/durand - base_icon = "durand" - 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 the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs [I].", "You install [I].") - else - user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") - if(16) - if(diff==FORWARD) - user.visible_message("[user] connects the bluespace crystal.", "You connect the bluespace crystal.") - else - user.visible_message("[user] removes the bluespace crystal from [parent].", "You remove the bluespace crystal from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] engages the bluespace crystal.", "You engage the bluespace crystal.") - else - user.visible_message("[user] disconnects the bluespace crystal from [parent].", "You disconnect the bluespace crystal from [parent].") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disengages the bluespace crystal.", "You disengage the bluespace crystal.") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] installs the phase armor layer to [parent].", "You install the phase armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(21) - if(diff==FORWARD) - user.visible_message("[user] secures the phase armor layer.", "You secure the phase armor layer.") - else - user.visible_message("[user] pries the phase armor layer from [parent].", "You pry the phase armor layer from [parent].") - if(22) - if(diff==FORWARD) - user.visible_message("[user] welds the phase armor layer to [parent].", "You weld the phase armor layer to [parent].") - else - user.visible_message("[user] unfastens the phase armor layer.", "You unfasten the phase armor layer.") - if(23) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] cuts phase armor layer from [parent].", "You cut the phase armor layer from [parent].") - if(24) - if(diff==FORWARD) - user.visible_message("[user] secures Phazon Armor Plates.", "You secure Phazon Armor Plates.") - else - user.visible_message("[user] pries Phazon Armor Plates from [parent].", "You pry Phazon Armor Plates from [parent].") - if(25) - if(diff==FORWARD) - user.visible_message("[user] welds Phazon Armor Plates to [parent].", "You weld Phazon Armor Plates to [parent].") - else - user.visible_message("[user] unfastens Phazon Armor Plates.", "You unfasten Phazon Armor Plates.") - if(26) - if(diff==FORWARD) - user.visible_message("[user] carefully inserts the 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/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - - //11 - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - - //12 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - - //13 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - - //11 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ), - - //12 - list( - "key" = /obj/item/stack/sheet/metal, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ), - - //13 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Internal armor is installed." - ), - - //14 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Internal armor is wrenched." - ), - - //15 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - - //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/odysseus/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs the external armor layer to [parent].", "You install the external reinforced armor layer to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries the external armor layer from [parent].", "You pry the external armor layer from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE +//////////////////////////////// +///// Construction datums ////// +//////////////////////////////// +/datum/component/construction/mecha + var/base_icon + +/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/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + + //11 + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + + //12 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + + //13 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + + //14 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ), + + //15 + list( + "key" = /obj/item/stack/sheet/metal, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ), + + //16 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Outer plating is installed." + ), + + //17 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Outer Plating is wrenched." + ), + + //18 + list( + "key" = /obj/item/stack/rods, + "amount" = 10, + "back_key" = TOOL_WELDER, + "desc" = "Outer Plating is welded." + ), + + //19 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WIRECUTTER, + "desc" = "Cockpit wire screen is installed." + ), + ) + +/datum/component/construction/mecha/ripley/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures [I].", "You secure [I].") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I].", "You install [I].") + else + user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs the external reinforced armor layer to [parent].", "You install the external reinforced armor layer to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/gygax + result = /datum/component/construction/mecha/gygax + steps = list( + /obj/item/mecha_parts/part/gygax_torso, + /obj/item/mecha_parts/part/gygax_left_arm, + /obj/item/mecha_parts/part/gygax_right_arm, + /obj/item/mecha_parts/part/gygax_left_leg, + /obj/item/mecha_parts/part/gygax_right_leg, + /obj/item/mecha_parts/part/gygax_head + ) + +/datum/component/construction/mecha/gygax + result = /obj/mecha/combat/gygax + base_icon = "gygax" + 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/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + + //11 + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + + //12 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + + //13 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + + //14 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ), + + //15 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ), + + //16 + 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." + ), + + //17 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + + //18 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is being installed." + ), + + //19 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + + //20 + 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 scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] 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(19) + 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(20) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") + if(21) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/honker + result = /datum/component/construction/mecha/honker + steps = list( + /obj/item/mecha_parts/part/honker_torso, + /obj/item/mecha_parts/part/honker_left_arm, + /obj/item/mecha_parts/part/honker_right_arm, + /obj/item/mecha_parts/part/honker_left_leg, + /obj/item/mecha_parts/part/honker_right_leg, + /obj/item/mecha_parts/part/honker_head + ) + +/datum/component/construction/mecha/honker + result = /obj/mecha/combat/honker + steps = list( + //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 + ), + + //6 + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE + ), + + //8 + list( + "key" = /obj/item/bikehorn + ), + + //9 + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE + ), + + //10 + list( + "key" = /obj/item/bikehorn + ), + + //11 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE + ), + + //12 + list( + "key" = /obj/item/bikehorn + ), + + //13 + list( + "key" = /obj/item/clothing/mask/gas/clown_hat, + "action" = ITEM_DELETE + ), + + //14 + list( + "key" = /obj/item/bikehorn + ), + + //15 + list( + "key" = /obj/item/clothing/shoes/clown_shoes, + "action" = ITEM_DELETE + ), + + //16 + 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] installs [I] into [parent].", "You install [I] into [parent].") + if(12) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(14) + user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") + if(16) + user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/durand + result = /datum/component/construction/mecha/durand + steps = list( + /obj/item/mecha_parts/part/durand_torso, + /obj/item/mecha_parts/part/durand_left_arm, + /obj/item/mecha_parts/part/durand_right_arm, + /obj/item/mecha_parts/part/durand_left_leg, + /obj/item/mecha_parts/part/durand_right_leg, + /obj/item/mecha_parts/part/durand_head + ) + +/datum/component/construction/mecha/durand + result = /obj/mecha/combat/durand + base_icon = "durand" + 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 the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs [I].", "You install [I].") + else + user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") + if(16) + if(diff==FORWARD) + user.visible_message("[user] connects the bluespace crystal.", "You connect the bluespace crystal.") + else + user.visible_message("[user] removes the bluespace crystal from [parent].", "You remove the bluespace crystal from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] engages the bluespace crystal.", "You engage the bluespace crystal.") + else + user.visible_message("[user] disconnects the bluespace crystal from [parent].", "You disconnect the bluespace crystal from [parent].") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disengages the bluespace crystal.", "You disengage the bluespace crystal.") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs the phase armor layer to [parent].", "You install the phase armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(21) + if(diff==FORWARD) + user.visible_message("[user] secures the phase armor layer.", "You secure the phase armor layer.") + else + user.visible_message("[user] pries the phase armor layer from [parent].", "You pry the phase armor layer from [parent].") + if(22) + if(diff==FORWARD) + user.visible_message("[user] welds the phase armor layer to [parent].", "You weld the phase armor layer to [parent].") + else + user.visible_message("[user] unfastens the phase armor layer.", "You unfasten the phase armor layer.") + if(23) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] cuts phase armor layer from [parent].", "You cut the phase armor layer from [parent].") + if(24) + if(diff==FORWARD) + user.visible_message("[user] secures Phazon Armor Plates.", "You secure Phazon Armor Plates.") + else + user.visible_message("[user] pries Phazon Armor Plates from [parent].", "You pry Phazon Armor Plates from [parent].") + if(25) + if(diff==FORWARD) + user.visible_message("[user] welds Phazon Armor Plates to [parent].", "You weld Phazon Armor Plates to [parent].") + else + user.visible_message("[user] unfastens Phazon Armor Plates.", "You unfasten Phazon Armor Plates.") + if(26) + if(diff==FORWARD) + user.visible_message("[user] carefully inserts the 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/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + + //11 + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + + //12 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + + //13 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + + //11 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ), + + //12 + list( + "key" = /obj/item/stack/sheet/metal, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ), + + //13 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Internal armor is installed." + ), + + //14 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Internal armor is wrenched." + ), + + //15 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + + //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/odysseus/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs the external armor layer to [parent].", "You install the external reinforced armor layer to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries the external armor layer from [parent].", "You pry the external armor layer from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm index e35fe51e7975..14e0e8bf2228 100644 --- a/code/game/mecha/mecha_control_console.dm +++ b/code/game/mecha/mecha_control_console.dm @@ -1,129 +1,129 @@ -/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() - -/obj/machinery/computer/mecha/ui_interact(mob/user) - . = ..() - var/dat = {"[src.name]
                    -

                    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 | [TR.recharging?"Recharging EMP Pulse...
                    ":"(EMP Pulse)
                    "]"} - - dat += "
                    " - dat += "(Refresh)
                    " - dat += "" - - user << browse(dat, "window=computer;size=400x500") - onclose(user, "computer") - -/obj/machinery/computer/mecha/Topic(href, href_list) - if(..()) - return - if(href_list["send_message"]) - var/obj/item/mecha_parts/mecha_tracking/MT = locate(href_list["send_message"]) - if (!istype(MT)) - return - 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 = locate(href_list["shock"]) - if (istype(MT)) - MT.shock() - - 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: [round((M.obj_integrity/M.max_integrity*100), 0.01)]%
                    -Cell Charge: [isnull(cell_charge)?"Not Found":"[M.cell.percent()]%"]
                    -Airtank: [M.internal_tank?"[round(M.return_pressure(), 0.01)]":"Not Equipped"] kPa
                    -Pilot: [M.occupant||"None"]
                    -Location: [get_area_name(M, TRUE)||"Unknown"]
                    -Active Equipment: [M.selected||"None"]"} - if(istype(M, /obj/mecha/working/ripley)) - var/obj/mecha/working/ripley/RM = M - answer += "
                    Used Cargo Space: [round((RM.cargo.len/RM.cargo_capacity*100), 0.01)]%" - - return answer - -/obj/item/mecha_parts/mecha_tracking/emp_act() - . = ..() - if(!(. & EMP_PROTECT_SELF)) - qdel(src) - -/obj/item/mecha_parts/mecha_tracking/Destroy() - if(ismecha(loc)) - var/obj/mecha/M = loc - if(src in M.trackers) - M.trackers -= src - return ..() - -/obj/item/mecha_parts/mecha_tracking/try_attach_part(mob/user, obj/mecha/M) - if(!..()) - return - M.trackers += src - M.diag_hud_set_mechtracking() - -/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), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) - recharging = 1 - -/obj/item/mecha_parts/mecha_tracking/proc/recharge() - recharging = 0 - -/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() + +/obj/machinery/computer/mecha/ui_interact(mob/user) + . = ..() + var/dat = {"[src.name]
                    +

                    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 | [TR.recharging?"Recharging EMP Pulse...
                    ":"(EMP Pulse)
                    "]"} + + dat += "
                    " + dat += "(Refresh)
                    " + dat += "" + + user << browse(dat, "window=computer;size=400x500") + onclose(user, "computer") + +/obj/machinery/computer/mecha/Topic(href, href_list) + if(..()) + return + if(href_list["send_message"]) + var/obj/item/mecha_parts/mecha_tracking/MT = locate(href_list["send_message"]) + if (!istype(MT)) + return + 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 = locate(href_list["shock"]) + if (istype(MT)) + MT.shock() + + 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: [round((M.obj_integrity/M.max_integrity*100), 0.01)]%
                    +Cell Charge: [isnull(cell_charge)?"Not Found":"[M.cell.percent()]%"]
                    +Airtank: [M.internal_tank?"[round(M.return_pressure(), 0.01)]":"Not Equipped"] kPa
                    +Pilot: [M.occupant||"None"]
                    +Location: [get_area_name(M, TRUE)||"Unknown"]
                    +Active Equipment: [M.selected||"None"]"} + if(istype(M, /obj/mecha/working/ripley)) + var/obj/mecha/working/ripley/RM = M + answer += "
                    Used Cargo Space: [round((RM.cargo.len/RM.cargo_capacity*100), 0.01)]%" + + return answer + +/obj/item/mecha_parts/mecha_tracking/emp_act() + . = ..() + if(!(. & EMP_PROTECT_SELF)) + qdel(src) + +/obj/item/mecha_parts/mecha_tracking/Destroy() + if(ismecha(loc)) + var/obj/mecha/M = loc + if(src in M.trackers) + M.trackers -= src + return ..() + +/obj/item/mecha_parts/mecha_tracking/try_attach_part(mob/user, obj/mecha/M) + if(!..()) + return + M.trackers += src + M.diag_hud_set_mechtracking() + +/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), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + recharging = 1 + +/obj/item/mecha_parts/mecha_tracking/proc/recharge() + recharging = 0 + +/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 57ba26ce0b65..0f6d1bb637ce 100644 --- a/code/game/mecha/mecha_parts.dm +++ b/code/game/mecha/mecha_parts.dm @@ -1,349 +1,349 @@ -///////////////////////// -////// Mecha Parts ////// -///////////////////////// - -/obj/item/mecha_parts - name = "mecha part" - icon = 'icons/mecha/mech_construct.dmi' - icon_state = "blank" - w_class = WEIGHT_CLASS_GIGANTIC - flags_1 = CONDUCT_1 - -/obj/item/mecha_parts/proc/try_attach_part(mob/user, obj/mecha/M) //For attaching parts to a finished mech - if(!user.transferItemToLoc(src, M)) - to_chat(user, "\The [src] is stuck to your hand, you cannot put it in \the [M]!") - return FALSE - user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") - return TRUE - -/obj/item/mecha_parts/part/try_attach_part(mob/user, obj/mecha/M) - return - -/obj/item/mecha_parts/chassis - name = "Mecha Chassis" - icon_state = "backbone" - interaction_flags_item = NONE //Don't pick us up!! - var/construct_type - -/obj/item/mecha_parts/chassis/Initialize() - . = ..() - if(construct_type) - AddComponent(construct_type) - -/////////// Ripley - -/obj/item/mecha_parts/chassis/ripley - name = "\improper Ripley chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/ripley - -/obj/item/mecha_parts/part/ripley_torso - name = "\improper Ripley torso" - desc = "A torso part of Ripley APLU. Contains power unit, processing core and life support systems." - icon_state = "ripley_harness" - -/obj/item/mecha_parts/part/ripley_left_arm - name = "\improper Ripley left arm" - desc = "A Ripley APLU left arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "ripley_l_arm" - -/obj/item/mecha_parts/part/ripley_right_arm - name = "\improper Ripley right arm" - desc = "A Ripley APLU right arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "ripley_r_arm" - -/obj/item/mecha_parts/part/ripley_left_leg - name = "\improper Ripley left leg" - desc = "A Ripley APLU left leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "ripley_l_leg" - -/obj/item/mecha_parts/part/ripley_right_leg - name = "\improper Ripley right leg" - desc = "A Ripley APLU right leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "ripley_r_leg" - -///////// Odysseus - -/obj/item/mecha_parts/chassis/odysseus - name = "\improper Odysseus chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/odysseus - -/obj/item/mecha_parts/part/odysseus_head - name = "\improper Odysseus head" - desc = "An Odysseus head. Contains an integrated medical HUD scanner." - icon_state = "odysseus_head" - -/obj/item/mecha_parts/part/odysseus_torso - name = "\improper Odysseus torso" - desc="A torso part of Odysseus. Contains power unit, processing core and life support systems along with an attachment port for a mounted sleeper." - icon_state = "odysseus_torso" - -/obj/item/mecha_parts/part/odysseus_left_arm - name = "\improper Odysseus left arm" - desc = "An Odysseus left arm. Data and power sockets are compatible with specialized medical equipment." - icon_state = "odysseus_l_arm" - -/obj/item/mecha_parts/part/odysseus_right_arm - name = "\improper Odysseus right arm" - desc = "An Odysseus right arm. Data and power sockets are compatible with specialized medical equipment." - icon_state = "odysseus_r_arm" - -/obj/item/mecha_parts/part/odysseus_left_leg - name = "\improper Odysseus left leg" - desc = "An Odysseus left leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." - icon_state = "odysseus_l_leg" - -/obj/item/mecha_parts/part/odysseus_right_leg - name = "\improper Odysseus right leg" - desc = "An odysseus right leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." - icon_state = "odysseus_r_leg" - -///////// Gygax - -/obj/item/mecha_parts/chassis/gygax - name = "\improper Gygax chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/gygax - -/obj/item/mecha_parts/part/gygax_torso - name = "\improper Gygax torso" - desc = "A torso part of Gygax. Contains power unit, processing core and life support systems." - icon_state = "gygax_harness" - -/obj/item/mecha_parts/part/gygax_head - name = "\improper Gygax head" - desc = "A Gygax head. Houses advanced surveillance and targeting sensors." - icon_state = "gygax_head" - -/obj/item/mecha_parts/part/gygax_left_arm - name = "\improper Gygax left arm" - desc = "A Gygax left arm. Data and power sockets are compatible with most exosuit tools and weapons." - icon_state = "gygax_l_arm" - -/obj/item/mecha_parts/part/gygax_right_arm - name = "\improper Gygax right arm" - desc = "A Gygax right arm. Data and power sockets are compatible with most exosuit tools and weapons." - icon_state = "gygax_r_arm" - -/obj/item/mecha_parts/part/gygax_left_leg - name = "\improper Gygax left leg" - desc = "A Gygax left leg. Constructed with advanced servomechanisms and actuators to enable faster speed." - icon_state = "gygax_l_leg" - -/obj/item/mecha_parts/part/gygax_right_leg - name = "\improper Gygax right leg" - desc = "A Gygax right leg. Constructed with advanced servomechanisms and actuators to enable faster speed." - icon_state = "gygax_r_leg" - -/obj/item/mecha_parts/part/gygax_armor - gender = PLURAL - name = "\improper Gygax armor plates" - desc = "A set of armor plates designed for the Gygax. Designed to effectively deflect damage with a lightweight construction." - icon_state = "gygax_armor" - - -//////////// Durand - -/obj/item/mecha_parts/chassis/durand - name = "\improper Durand chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/durand - -/obj/item/mecha_parts/part/durand_torso - name = "\improper Durand torso" - desc = "A torso part of Durand. Contains power unit, processing core and life support systems within a robust protective frame." - icon_state = "durand_harness" - -/obj/item/mecha_parts/part/durand_head - name = "\improper Durand head" - desc = "A Durand head. Houses advanced surveillance and targeting sensors." - icon_state = "durand_head" - -/obj/item/mecha_parts/part/durand_left_arm - name = "\improper Durand left arm" - desc = "A Durand left arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." - icon_state = "durand_l_arm" - -/obj/item/mecha_parts/part/durand_right_arm - name = "\improper Durand right arm" - desc = "A Durand right arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." - icon_state = "durand_r_arm" - -/obj/item/mecha_parts/part/durand_left_leg - name = "\improper Durand left leg" - desc = "A Durand left leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." - icon_state = "durand_l_leg" - -/obj/item/mecha_parts/part/durand_right_leg - name = "\improper Durand right leg" - desc = "A Durand right leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." - icon_state = "durand_r_leg" - -/obj/item/mecha_parts/part/durand_armor - gender = PLURAL - name = "\improper Durand armor plates" - desc = "A set of armor plates for the Durand. Built heavy to resist an incredible amount of brute force." - icon_state = "durand_armor" - -////////// 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/proc/try_attach_part(mob/user, obj/mecha/M) //For attaching parts to a finished mech + if(!user.transferItemToLoc(src, M)) + to_chat(user, "\The [src] is stuck to your hand, you cannot put it in \the [M]!") + return FALSE + user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") + return TRUE + +/obj/item/mecha_parts/part/try_attach_part(mob/user, obj/mecha/M) + return + +/obj/item/mecha_parts/chassis + name = "Mecha Chassis" + icon_state = "backbone" + interaction_flags_item = NONE //Don't pick us up!! + var/construct_type + +/obj/item/mecha_parts/chassis/Initialize() + . = ..() + if(construct_type) + AddComponent(construct_type) + +/////////// Ripley + +/obj/item/mecha_parts/chassis/ripley + name = "\improper Ripley chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/ripley + +/obj/item/mecha_parts/part/ripley_torso + name = "\improper Ripley torso" + desc = "A torso part of Ripley APLU. Contains power unit, processing core and life support systems." + icon_state = "ripley_harness" + +/obj/item/mecha_parts/part/ripley_left_arm + name = "\improper Ripley left arm" + desc = "A Ripley APLU left arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "ripley_l_arm" + +/obj/item/mecha_parts/part/ripley_right_arm + name = "\improper Ripley right arm" + desc = "A Ripley APLU right arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "ripley_r_arm" + +/obj/item/mecha_parts/part/ripley_left_leg + name = "\improper Ripley left leg" + desc = "A Ripley APLU left leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "ripley_l_leg" + +/obj/item/mecha_parts/part/ripley_right_leg + name = "\improper Ripley right leg" + desc = "A Ripley APLU right leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "ripley_r_leg" + +///////// Odysseus + +/obj/item/mecha_parts/chassis/odysseus + name = "\improper Odysseus chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/odysseus + +/obj/item/mecha_parts/part/odysseus_head + name = "\improper Odysseus head" + desc = "An Odysseus head. Contains an integrated medical HUD scanner." + icon_state = "odysseus_head" + +/obj/item/mecha_parts/part/odysseus_torso + name = "\improper Odysseus torso" + desc="A torso part of Odysseus. Contains power unit, processing core and life support systems along with an attachment port for a mounted sleeper." + icon_state = "odysseus_torso" + +/obj/item/mecha_parts/part/odysseus_left_arm + name = "\improper Odysseus left arm" + desc = "An Odysseus left arm. Data and power sockets are compatible with specialized medical equipment." + icon_state = "odysseus_l_arm" + +/obj/item/mecha_parts/part/odysseus_right_arm + name = "\improper Odysseus right arm" + desc = "An Odysseus right arm. Data and power sockets are compatible with specialized medical equipment." + icon_state = "odysseus_r_arm" + +/obj/item/mecha_parts/part/odysseus_left_leg + name = "\improper Odysseus left leg" + desc = "An Odysseus left leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." + icon_state = "odysseus_l_leg" + +/obj/item/mecha_parts/part/odysseus_right_leg + name = "\improper Odysseus right leg" + desc = "An odysseus right leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." + icon_state = "odysseus_r_leg" + +///////// Gygax + +/obj/item/mecha_parts/chassis/gygax + name = "\improper Gygax chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/gygax + +/obj/item/mecha_parts/part/gygax_torso + name = "\improper Gygax torso" + desc = "A torso part of Gygax. Contains power unit, processing core and life support systems." + icon_state = "gygax_harness" + +/obj/item/mecha_parts/part/gygax_head + name = "\improper Gygax head" + desc = "A Gygax head. Houses advanced surveillance and targeting sensors." + icon_state = "gygax_head" + +/obj/item/mecha_parts/part/gygax_left_arm + name = "\improper Gygax left arm" + desc = "A Gygax left arm. Data and power sockets are compatible with most exosuit tools and weapons." + icon_state = "gygax_l_arm" + +/obj/item/mecha_parts/part/gygax_right_arm + name = "\improper Gygax right arm" + desc = "A Gygax right arm. Data and power sockets are compatible with most exosuit tools and weapons." + icon_state = "gygax_r_arm" + +/obj/item/mecha_parts/part/gygax_left_leg + name = "\improper Gygax left leg" + desc = "A Gygax left leg. Constructed with advanced servomechanisms and actuators to enable faster speed." + icon_state = "gygax_l_leg" + +/obj/item/mecha_parts/part/gygax_right_leg + name = "\improper Gygax right leg" + desc = "A Gygax right leg. Constructed with advanced servomechanisms and actuators to enable faster speed." + icon_state = "gygax_r_leg" + +/obj/item/mecha_parts/part/gygax_armor + gender = PLURAL + name = "\improper Gygax armor plates" + desc = "A set of armor plates designed for the Gygax. Designed to effectively deflect damage with a lightweight construction." + icon_state = "gygax_armor" + + +//////////// Durand + +/obj/item/mecha_parts/chassis/durand + name = "\improper Durand chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/durand + +/obj/item/mecha_parts/part/durand_torso + name = "\improper Durand torso" + desc = "A torso part of Durand. Contains power unit, processing core and life support systems within a robust protective frame." + icon_state = "durand_harness" + +/obj/item/mecha_parts/part/durand_head + name = "\improper Durand head" + desc = "A Durand head. Houses advanced surveillance and targeting sensors." + icon_state = "durand_head" + +/obj/item/mecha_parts/part/durand_left_arm + name = "\improper Durand left arm" + desc = "A Durand left arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." + icon_state = "durand_l_arm" + +/obj/item/mecha_parts/part/durand_right_arm + name = "\improper Durand right arm" + desc = "A Durand right arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." + icon_state = "durand_r_arm" + +/obj/item/mecha_parts/part/durand_left_leg + name = "\improper Durand left leg" + desc = "A Durand left leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." + icon_state = "durand_l_leg" + +/obj/item/mecha_parts/part/durand_right_leg + name = "\improper Durand right leg" + desc = "A Durand right leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." + icon_state = "durand_r_leg" + +/obj/item/mecha_parts/part/durand_armor + gender = PLURAL + name = "\improper Durand armor plates" + desc = "A set of armor plates for the Durand. Built heavy to resist an incredible amount of brute force." + icon_state = "durand_armor" + +////////// 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 c109fb7a3f40..52deff92ef89 100644 --- a/code/game/mecha/mecha_wreckage.dm +++ b/code/game/mecha/mecha_wreckage.dm @@ -1,271 +1,271 @@ -/////////////////////////////////// -//////// 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(I.tool_behaviour == TOOL_WELDER) - 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(I.tool_behaviour == TOOL_WIRECUTTER) - 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].") - wirecutters_salvage -= type - salvage_num-- - else - to_chat(user, "You fail to salvage anything valuable from [src]!") - - else if(I.tool_behaviour == TOOL_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/mkii - name = "\improper Ripley MK-II wreckage" - icon_state = "ripleymkii-broken" - -/obj/structure/mecha_wreckage/ripley/mkii/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(I.tool_behaviour == TOOL_WELDER) + 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(I.tool_behaviour == TOOL_WIRECUTTER) + 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].") + wirecutters_salvage -= type + salvage_num-- + else + to_chat(user, "You fail to salvage anything valuable from [src]!") + + else if(I.tool_behaviour == TOOL_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/mkii + name = "\improper Ripley MK-II wreckage" + icon_state = "ripleymkii-broken" + +/obj/structure/mecha_wreckage/ripley/mkii/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/medical.dm b/code/game/mecha/medical/medical.dm index 24d5c18d240d..d605c64a839b 100644 --- a/code/game/mecha/medical/medical.dm +++ b/code/game/mecha/medical/medical.dm @@ -1,19 +1,19 @@ -/obj/mecha/medical - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_MEDICAL) - -/obj/mecha/medical/mechturn(direction) - setDir(direction) - playsound(src,'sound/mecha/mechmove01.ogg',40,1) - return 1 - -/obj/mecha/medical/mechstep(direction) - var/result = step(src,direction) - if(result) - playsound(src,'sound/mecha/mechstep.ogg',25,1) - return result - -/obj/mecha/medical/mechsteprand() - var/result = step_rand(src) - if(result) - playsound(src,'sound/mecha/mechstep.ogg',25,1) +/obj/mecha/medical + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_MEDICAL) + +/obj/mecha/medical/mechturn(direction) + setDir(direction) + playsound(src,'sound/mecha/mechmove01.ogg',40,1) + return 1 + +/obj/mecha/medical/mechstep(direction) + var/result = step(src,direction) + if(result) + playsound(src,'sound/mecha/mechstep.ogg',25,1) + return result + +/obj/mecha/medical/mechsteprand() + var/result = step_rand(src) + if(result) + playsound(src,'sound/mecha/mechstep.ogg',25,1) return result \ No newline at end of file diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index c79bb96c3ae3..323e77d5736c 100644 --- a/code/game/mecha/medical/odysseus.dm +++ b/code/game/mecha/medical/odysseus.dm @@ -1,31 +1,31 @@ -/obj/mecha/medical/odysseus - desc = "These exosuits are developed and produced by Vey-Med. (© All rights reserved)." - name = "\improper Odysseus" - icon_state = "odysseus" - step_in = 2 - max_temperature = 15000 - max_integrity = 120 - wreckage = /obj/structure/mecha_wreckage/odysseus - internal_damage_threshold = 35 - deflect_chance = 15 - step_energy_drain = 6 - -/obj/mecha/medical/odysseus/moved_inside(mob/living/carbon/human/H) - . = ..() - if(.) - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.add_hud_to(H) - -/obj/mecha/medical/odysseus/go_out() - if(isliving(occupant)) - var/mob/living/L = occupant - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.remove_hud_from(L) - ..() - -/obj/mecha/medical/odysseus/mmi_moved_inside(obj/item/mmi/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/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 b345ecd291c8..41728ab0b47e 100644 --- a/code/game/mecha/working/ripley.dm +++ b/code/game/mecha/working/ripley.dm @@ -1,234 +1,234 @@ -/obj/mecha/working/ripley - desc = "Autonomous Power Loader Unit MK-I. Designed primarily around heavy lifting, the Ripley can be outfitted with utility equipment to fill a number of roles." - name = "\improper APLU MK-I \"Ripley\"" - icon_state = "ripley" - silicon_icon_state = "ripley-empty" - step_in = 1.5 //Move speed, lower is faster. - var/fast_pressure_step_in = 1.5 //step_in while in low pressure conditions - var/slow_pressure_step_in = 2.0 //step_in while in normal pressure conditions - max_temperature = 20000 - max_integrity = 200 - lights_power = 7 - deflect_chance = 15 - armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 20, "fire" = 100, "acid" = 100) - max_equip = 6 - wreckage = /obj/structure/mecha_wreckage/ripley - internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_MINING) - var/list/cargo = new - var/cargo_capacity = 15 - var/hides = 0 - enclosed = FALSE //Normal ripley has an open cockpit design - enter_delay = 10 //can enter in a quarter of the time of other mechs - exit_delay = 10 - opacity = FALSE //Ripley has a window - -/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/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) - if (!enclosed) - possible_int_damage -= (MECHA_INT_TEMP_CONTROL + MECHA_INT_TANK_BREACH) //if we don't even have an air tank, these two doesn't make a ton of sense. - . = ..() - - -/obj/mecha/working/ripley/Initialize() - . = ..() - AddComponent(/datum/component/armor_plate,3,/obj/item/stack/sheet/animalhide/goliath_hide,list("melee" = 10, "bullet" = 5, "laser" = 5)) - - -/obj/mecha/working/ripley/mkii - desc = "Autonomous Power Loader Unit MK-II. This prototype Ripley is refitted with a pressurized cabin, trading its prior speed for atmospheric protection" - name = "\improper APLU MK-II \"Ripley\"" - icon_state = "ripleymkii" - fast_pressure_step_in = 2 //step_in while in low pressure conditions - slow_pressure_step_in = 4 //step_in while in normal pressure conditions - step_in = 4 - armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - wreckage = /obj/structure/mecha_wreckage/ripley/mkii - enclosed = TRUE - enter_delay = 40 - silicon_icon_state = null - opacity = TRUE - -/obj/mecha/working/ripley/firefighter - desc = "Autonomous Power Loader Unit MK-III. This model is refitted with a pressurized cabin and additional thermal protection." - name = "\improper APLU MK-III \"Firefighter\"" - icon_state = "firefighter" - max_temperature = 65000 - max_integrity = 250 - fast_pressure_step_in = 2 //step_in while in low pressure conditions - slow_pressure_step_in = 4 //step_in while in normal pressure conditions - step_in = 4 - 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" = 70, "fire" = 100, "acid" = 100) - max_equip = 5 // More armor, less tools - wreckage = /obj/structure/mecha_wreckage/ripley/firefighter - enclosed = TRUE - enter_delay = 40 - silicon_icon_state = null - opacity = TRUE - - -/obj/mecha/working/ripley/deathripley - desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" - name = "\improper DEATH-RIPLEY" - icon_state = "deathripley" - fast_pressure_step_in = 2 //step_in while in low pressure conditions - slow_pressure_step_in = 4 //step_in while in normal pressure conditions - step_in = 4 - slow_pressure_step_in = 3 - opacity=0 - lights_power = 7 - wreckage = /obj/structure/mecha_wreckage/ripley/deathripley - step_energy_drain = 0 - enclosed = TRUE - enter_delay = 40 - silicon_icon_state = null - opacity = TRUE - -/obj/mecha/working/ripley/deathripley/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill - ME.attach(src) - -/obj/mecha/working/ripley/deathripley/real - desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE. FOR REAL" - -/obj/mecha/working/ripley/deathripley/real/Initialize() - . = ..() - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - E.detach() - qdel(E) - equipment.Cut() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill/real - ME.attach(src) - -/obj/mecha/working/ripley/mining - desc = "An old, dusty mining Ripley." - name = "\improper APLU \"Miner\"" - obj_integrity = 75 //Low starting health - -/obj/mecha/working/ripley/mining/Initialize() - . = ..() - if(cell) - cell.charge = FLOOR(cell.charge * 0.25, 1) //Starts at very low charge - if(prob(70)) //Maybe add a drill - if(prob(15)) //Possible diamond drill... Feeling lucky? - var/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill/D = new - D.attach(src) - else - var/obj/item/mecha_parts/mecha_equipment/drill/D = new - D.attach(src) - - else //Add plasma cutter if no drill - var/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/P = new - P.attach(src) - - //Add ore box to cargo - cargo.Add(new /obj/structure/ore_box(src)) - - //Attach hydraulic clamp - var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new - HC.attach(src) - for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily - qdel(B) - - var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new - scanner.attach(src) - -/obj/mecha/working/ripley/Exit(atom/movable/O) - if(O in cargo) - return 0 - return ..() - -/obj/mecha/working/ripley/Topic(href, href_list) - ..() - if(href_list["drop_from_cargo"]) - var/obj/O = locate(href_list["drop_from_cargo"]) in cargo - if(O) - occupant_message("You unload [O].") - O.forceMove(drop_location()) - cargo -= O - log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - src.cargo.len]", LOG_MECHA) - return - - -/obj/mecha/working/ripley/contents_explosion(severity, target) - for(var/X in cargo) - var/obj/O = X - if(prob(30/severity)) - cargo -= O - O.forceMove(drop_location()) - . = ..() - -/obj/mecha/working/ripley/get_stats_part() - var/output = ..() - output += "Cargo Compartment Contents:
                    " - if(cargo.len) - for(var/obj/O in cargo) - output += "Unload : [O]
                    " - else - output += "Nothing" - output += "
                    " - return output - -/obj/mecha/working/ripley/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 MK-I. Designed primarily around heavy lifting, the Ripley can be outfitted with utility equipment to fill a number of roles." + name = "\improper APLU MK-I \"Ripley\"" + icon_state = "ripley" + silicon_icon_state = "ripley-empty" + step_in = 1.5 //Move speed, lower is faster. + var/fast_pressure_step_in = 1.5 //step_in while in low pressure conditions + var/slow_pressure_step_in = 2.0 //step_in while in normal pressure conditions + max_temperature = 20000 + max_integrity = 200 + lights_power = 7 + deflect_chance = 15 + armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 20, "fire" = 100, "acid" = 100) + max_equip = 6 + wreckage = /obj/structure/mecha_wreckage/ripley + internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_MINING) + var/list/cargo = new + var/cargo_capacity = 15 + var/hides = 0 + enclosed = FALSE //Normal ripley has an open cockpit design + enter_delay = 10 //can enter in a quarter of the time of other mechs + exit_delay = 10 + opacity = FALSE //Ripley has a window + +/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/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) + if (!enclosed) + possible_int_damage -= (MECHA_INT_TEMP_CONTROL + MECHA_INT_TANK_BREACH) //if we don't even have an air tank, these two doesn't make a ton of sense. + . = ..() + + +/obj/mecha/working/ripley/Initialize() + . = ..() + AddComponent(/datum/component/armor_plate,3,/obj/item/stack/sheet/animalhide/goliath_hide,list("melee" = 10, "bullet" = 5, "laser" = 5)) + + +/obj/mecha/working/ripley/mkii + desc = "Autonomous Power Loader Unit MK-II. This prototype Ripley is refitted with a pressurized cabin, trading its prior speed for atmospheric protection" + name = "\improper APLU MK-II \"Ripley\"" + icon_state = "ripleymkii" + fast_pressure_step_in = 2 //step_in while in low pressure conditions + slow_pressure_step_in = 4 //step_in while in normal pressure conditions + step_in = 4 + armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + wreckage = /obj/structure/mecha_wreckage/ripley/mkii + enclosed = TRUE + enter_delay = 40 + silicon_icon_state = null + opacity = TRUE + +/obj/mecha/working/ripley/firefighter + desc = "Autonomous Power Loader Unit MK-III. This model is refitted with a pressurized cabin and additional thermal protection." + name = "\improper APLU MK-III \"Firefighter\"" + icon_state = "firefighter" + max_temperature = 65000 + max_integrity = 250 + fast_pressure_step_in = 2 //step_in while in low pressure conditions + slow_pressure_step_in = 4 //step_in while in normal pressure conditions + step_in = 4 + 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" = 70, "fire" = 100, "acid" = 100) + max_equip = 5 // More armor, less tools + wreckage = /obj/structure/mecha_wreckage/ripley/firefighter + enclosed = TRUE + enter_delay = 40 + silicon_icon_state = null + opacity = TRUE + + +/obj/mecha/working/ripley/deathripley + desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" + name = "\improper DEATH-RIPLEY" + icon_state = "deathripley" + fast_pressure_step_in = 2 //step_in while in low pressure conditions + slow_pressure_step_in = 4 //step_in while in normal pressure conditions + step_in = 4 + slow_pressure_step_in = 3 + opacity=0 + lights_power = 7 + wreckage = /obj/structure/mecha_wreckage/ripley/deathripley + step_energy_drain = 0 + enclosed = TRUE + enter_delay = 40 + silicon_icon_state = null + opacity = TRUE + +/obj/mecha/working/ripley/deathripley/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill + ME.attach(src) + +/obj/mecha/working/ripley/deathripley/real + desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE. FOR REAL" + +/obj/mecha/working/ripley/deathripley/real/Initialize() + . = ..() + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + E.detach() + qdel(E) + equipment.Cut() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill/real + ME.attach(src) + +/obj/mecha/working/ripley/mining + desc = "An old, dusty mining Ripley." + name = "\improper APLU \"Miner\"" + obj_integrity = 75 //Low starting health + +/obj/mecha/working/ripley/mining/Initialize() + . = ..() + if(cell) + cell.charge = FLOOR(cell.charge * 0.25, 1) //Starts at very low charge + if(prob(70)) //Maybe add a drill + if(prob(15)) //Possible diamond drill... Feeling lucky? + var/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill/D = new + D.attach(src) + else + var/obj/item/mecha_parts/mecha_equipment/drill/D = new + D.attach(src) + + else //Add plasma cutter if no drill + var/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/P = new + P.attach(src) + + //Add ore box to cargo + cargo.Add(new /obj/structure/ore_box(src)) + + //Attach hydraulic clamp + var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new + HC.attach(src) + for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily + qdel(B) + + var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new + scanner.attach(src) + +/obj/mecha/working/ripley/Exit(atom/movable/O) + if(O in cargo) + return 0 + return ..() + +/obj/mecha/working/ripley/Topic(href, href_list) + ..() + if(href_list["drop_from_cargo"]) + var/obj/O = locate(href_list["drop_from_cargo"]) in cargo + if(O) + occupant_message("You unload [O].") + O.forceMove(drop_location()) + cargo -= O + log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - src.cargo.len]", LOG_MECHA) + return + + +/obj/mecha/working/ripley/contents_explosion(severity, target) + for(var/X in cargo) + var/obj/O = X + if(prob(30/severity)) + cargo -= O + O.forceMove(drop_location()) + . = ..() + +/obj/mecha/working/ripley/get_stats_part() + var/output = ..() + output += "Cargo Compartment Contents:
                    " + if(cargo.len) + for(var/obj/O in cargo) + output += "Unload : [O]
                    " + else + output += "Nothing" + output += "
                    " + return output + +/obj/mecha/working/ripley/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/mecha/working/working.dm b/code/game/mecha/working/working.dm index b3e9c4ba55e2..3e45148ef51e 100644 --- a/code/game/mecha/working/working.dm +++ b/code/game/mecha/working/working.dm @@ -1,2 +1,2 @@ -/obj/mecha/working - internal_damage_threshold = 60 +/obj/mecha/working + internal_damage_threshold = 60 diff --git a/code/game/objects/effects/bump_teleporter.dm b/code/game/objects/effects/bump_teleporter.dm index 23b186c0094d..0337b076ff7e 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 efc8ea06781d..6f033778ad85 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,95 +1,95 @@ -/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) - . = ..() - 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) - return TRUE - -/obj/effect/decal/cleanable/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/reagent_containers/glass) || istype(W, /obj/item/reagent_containers/food/drinks)) - if(src.reagents && W.reagents) - . = 1 //so the containers don't splash their content on the src while scooping. - if(!src.reagents.total_volume) - to_chat(user, "[src] isn't thick enough to scoop up!") - return - if(W.reagents.total_volume >= W.reagents.maximum_volume) - to_chat(user, "[W] is full!") - return - to_chat(user, "You scoop up [src] into [W]!") - reagents.trans_to(W, reagents.total_volume, transfered_by = user) - if(!reagents.total_volume) //scooped up all of it - qdel(src) - return - if(W.is_hot()) //todo: make heating a reagent holder proc - if(istype(W, /obj/item/clothing/mask/cigarette)) - return - else - var/hotness = W.is_hot() - 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 - if(!S.can_be_bloody) - return - var/add_blood = 0 - if(bloodiness >= BLOOD_GAIN_PER_STEP) - add_blood = BLOOD_GAIN_PER_STEP - else - add_blood = bloodiness - bloodiness -= add_blood - S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS,S.bloody_shoes[blood_state]+add_blood) - S.add_blood_DNA(return_blood_DNA()) - S.blood_state = blood_state - update_icon() - H.update_inv_shoes() - -/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return bloodiness - else - return 0 +/obj/effect/decal/cleanable + gender = PLURAL + layer = ABOVE_NORMAL_TURF_LAYER + var/list/random_icon_states = null + var/blood_state = "" //I'm sorry but cleanable/blood code is ass, and so is blood_DNA + var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints + var/mergeable_decal = TRUE //when two of these are on a same tile or do we need to merge them into just one? + +/obj/effect/decal/cleanable/Initialize(mapload, list/datum/disease/diseases) + . = ..() + if (random_icon_states && (icon_state == initial(icon_state)) && length(random_icon_states) > 0) + icon_state = pick(random_icon_states) + create_reagents(300) + if(loc && isturf(loc)) + for(var/obj/effect/decal/cleanable/C in loc) + if(C != src && C.type == type && !QDELETED(C)) + if (replace_decal(C)) + return INITIALIZE_HINT_QDEL + + if(LAZYLEN(diseases)) + var/list/datum/disease/diseases_to_add = list() + for(var/datum/disease/D in diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS) + diseases_to_add += D + if(LAZYLEN(diseases_to_add)) + AddComponent(/datum/component/infective, diseases_to_add) + +/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal + if(mergeable_decal) + return TRUE + +/obj/effect/decal/cleanable/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/reagent_containers/glass) || istype(W, /obj/item/reagent_containers/food/drinks)) + if(src.reagents && W.reagents) + . = 1 //so the containers don't splash their content on the src while scooping. + if(!src.reagents.total_volume) + to_chat(user, "[src] isn't thick enough to scoop up!") + return + if(W.reagents.total_volume >= W.reagents.maximum_volume) + to_chat(user, "[W] is full!") + return + to_chat(user, "You scoop up [src] into [W]!") + reagents.trans_to(W, reagents.total_volume, transfered_by = user) + if(!reagents.total_volume) //scooped up all of it + qdel(src) + return + if(W.is_hot()) //todo: make heating a reagent holder proc + if(istype(W, /obj/item/clothing/mask/cigarette)) + return + else + var/hotness = W.is_hot() + 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 + if(!S.can_be_bloody) + return + var/add_blood = 0 + if(bloodiness >= BLOOD_GAIN_PER_STEP) + add_blood = BLOOD_GAIN_PER_STEP + else + add_blood = bloodiness + bloodiness -= add_blood + S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS,S.bloody_shoes[blood_state]+add_blood) + S.add_blood_DNA(return_blood_DNA()) + S.blood_state = blood_state + update_icon() + H.update_inv_shoes() + +/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() + if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) + return bloodiness + else + return 0 diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm index 0bf4387063d0..d6986e99f494 100644 --- a/code/game/objects/effects/decals/cleanable/aliens.dm +++ b/code/game/objects/effects/decals/cleanable/aliens.dm @@ -1,79 +1,79 @@ -// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. - -/obj/effect/decal/cleanable/xenoblood - name = "xeno blood" - desc = "It's green and acidic. It looks like... blood?" - icon = 'icons/effects/blood.dmi' - icon_state = "xfloor1" - random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") - bloodiness = BLOOD_AMOUNT_PER_DECAL - blood_state = BLOOD_STATE_XENO - -/obj/effect/decal/cleanable/xenoblood/Initialize() - . = ..() - add_blood_DNA(list("UNKNOWN DNA" = "X*")) - -/obj/effect/decal/cleanable/xenoblood/xsplatter - random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5") - -/obj/effect/decal/cleanable/xenoblood/xgibs - name = "xeno gibs" - desc = "Gnarly..." - icon = 'icons/effects/blood.dmi' - icon_state = "xgib1" - layer = LOW_OBJ_LAYER - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6") - mergeable_decal = FALSE - -/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions) - set waitfor = 0 - var/direction = pick(directions) - for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) - sleep(2) - if(i > 0) - new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc) - if(!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/xenoblood/xgibs/ex_act() - return - -/obj/effect/decal/cleanable/xenoblood/xgibs/up - icon_state = "xgibup1" - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibup1","xgibup1","xgibup1") - -/obj/effect/decal/cleanable/xenoblood/xgibs/down - icon_state = "xgibdown1" - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibdown1","xgibdown1","xgibdown1") - -/obj/effect/decal/cleanable/xenoblood/xgibs/body - icon_state = "xgibtorso" - random_icon_states = list("xgibhead", "xgibtorso") - -/obj/effect/decal/cleanable/xenoblood/xgibs/torso - icon_state = "xgibtorso" - random_icon_states = list("xgibtorso") - -/obj/effect/decal/cleanable/xenoblood/xgibs/limb - icon_state = "xgibleg" - random_icon_states = list("xgibleg", "xgibarm") - -/obj/effect/decal/cleanable/xenoblood/xgibs/core - icon_state = "xgibmid1" - random_icon_states = list("xgibmid1", "xgibmid2", "xgibmid3") - -/obj/effect/decal/cleanable/xenoblood/xgibs/larva - icon_state = "xgiblarva1" - random_icon_states = list("xgiblarva1", "xgiblarva2") - -/obj/effect/decal/cleanable/xenoblood/xgibs/larva/body - icon_state = "xgiblarvatorso" - random_icon_states = list("xgiblarvahead", "xgiblarvatorso") - -/obj/effect/decal/cleanable/blood/xtracks - icon_state = "xtracks" - random_icon_states = null - -/obj/effect/decal/cleanable/blood/xtracks/Initialize() - . = ..() - add_blood_DNA(list("Unknown DNA" = "X*")) +// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. + +/obj/effect/decal/cleanable/xenoblood + name = "xeno blood" + desc = "It's green and acidic. It looks like... blood?" + icon = 'icons/effects/blood.dmi' + icon_state = "xfloor1" + random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") + bloodiness = BLOOD_AMOUNT_PER_DECAL + blood_state = BLOOD_STATE_XENO + +/obj/effect/decal/cleanable/xenoblood/Initialize() + . = ..() + add_blood_DNA(list("UNKNOWN DNA" = "X*")) + +/obj/effect/decal/cleanable/xenoblood/xsplatter + random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5") + +/obj/effect/decal/cleanable/xenoblood/xgibs + name = "xeno gibs" + desc = "Gnarly..." + icon = 'icons/effects/blood.dmi' + icon_state = "xgib1" + layer = LOW_OBJ_LAYER + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6") + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions) + set waitfor = 0 + var/direction = pick(directions) + for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) + sleep(2) + if(i > 0) + new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc) + if(!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/xenoblood/xgibs/ex_act() + return + +/obj/effect/decal/cleanable/xenoblood/xgibs/up + icon_state = "xgibup1" + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibup1","xgibup1","xgibup1") + +/obj/effect/decal/cleanable/xenoblood/xgibs/down + icon_state = "xgibdown1" + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibdown1","xgibdown1","xgibdown1") + +/obj/effect/decal/cleanable/xenoblood/xgibs/body + icon_state = "xgibtorso" + random_icon_states = list("xgibhead", "xgibtorso") + +/obj/effect/decal/cleanable/xenoblood/xgibs/torso + icon_state = "xgibtorso" + random_icon_states = list("xgibtorso") + +/obj/effect/decal/cleanable/xenoblood/xgibs/limb + icon_state = "xgibleg" + random_icon_states = list("xgibleg", "xgibarm") + +/obj/effect/decal/cleanable/xenoblood/xgibs/core + icon_state = "xgibmid1" + random_icon_states = list("xgibmid1", "xgibmid2", "xgibmid3") + +/obj/effect/decal/cleanable/xenoblood/xgibs/larva + icon_state = "xgiblarva1" + random_icon_states = list("xgiblarva1", "xgiblarva2") + +/obj/effect/decal/cleanable/xenoblood/xgibs/larva/body + icon_state = "xgiblarvatorso" + random_icon_states = list("xgiblarvahead", "xgiblarvatorso") + +/obj/effect/decal/cleanable/blood/xtracks + icon_state = "xtracks" + random_icon_states = null + +/obj/effect/decal/cleanable/blood/xtracks/Initialize() + . = ..() + add_blood_DNA(list("Unknown DNA" = "X*")) diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index 04b2a0320d1e..773a4487910f 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -1,212 +1,212 @@ -/obj/effect/decal/cleanable/blood - name = "blood" - desc = "It's red and gooey. Perhaps it's the chef's cooking?" - icon = 'icons/effects/blood.dmi' - icon_state = "floor1" - random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") - blood_state = BLOOD_STATE_HUMAN - bloodiness = BLOOD_AMOUNT_PER_DECAL - -/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) - C.add_blood_DNA(return_blood_DNA()) - if (bloodiness) - if (C.bloodiness < MAX_SHOE_BLOODINESS) - C.bloodiness += bloodiness - return ..() - -/obj/effect/decal/cleanable/blood/old - name = "dried blood" - desc = "Looks like it's been here a while. Eew." - bloodiness = 0 - icon_state = "floor1-old" - -/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) - add_blood_DNA(list("Non-human DNA" = random_blood_type())) // Needs to happen before ..() - . = ..() - icon_state = "[icon_state]-old" //change from the normal blood icon selected from random_icon_states in the parent's Initialize to the old dried up blood. - -/obj/effect/decal/cleanable/blood/splatter - icon_state = "gibbl1" - random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") - -/obj/effect/decal/cleanable/blood/tracks - icon_state = "tracks" - desc = "They look like tracks left by wheels." - icon_state = "tracks" - random_icon_states = null - -/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose - name = "blood" - icon = 'icons/effects/blood.dmi' - desc = "Your instincts say you shouldn't be following these." - var/list/existing_dirs = list() - -/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() - return TRUE - -/obj/effect/decal/cleanable/blood/gibs - name = "gibs" - desc = "They look bloody and gruesome." - icon = 'icons/effects/blood.dmi' - icon_state = "gib1" - layer = LOW_OBJ_LAYER - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6") - mergeable_decal = FALSE - - var/already_rotting = FALSE - -/obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases) - . = ..() - reagents.add_reagent(/datum/reagent/liquidgibs, 5) - if(already_rotting) - start_rotting(rename=FALSE) - else - addtimer(CALLBACK(src, .proc/start_rotting), 2 MINUTES) - -/obj/effect/decal/cleanable/blood/gibs/proc/start_rotting(rename=TRUE) - if(rename) - name = "rotting [initial(name)]" - desc += " They smell terrible." - AddComponent(/datum/component/rot/gibs) - -/obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target) - return - -/obj/effect/decal/cleanable/blood/gibs/Crossed(mob/living/L) - if(istype(L) && has_gravity(loc)) - playsound(loc, 'sound/effects/gib_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 20 : 50, 1) - . = ..() - -/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions) - set waitfor = FALSE - var/list/diseases = list() - SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases) - var/direction = pick(directions) - for(var/i in 0 to pick(0, 200; 1, 150; 2, 50)) - sleep(2) - if(i > 0) - new /obj/effect/decal/cleanable/blood/splatter(loc, diseases) - if(!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/blood/gibs/up - icon_state = "gibup1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibup1","gibup1","gibup1") - -/obj/effect/decal/cleanable/blood/gibs/down - icon_state = "gibdown1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibdown1","gibdown1","gibdown1") - -/obj/effect/decal/cleanable/blood/gibs/body - icon_state = "gibtorso" - random_icon_states = list("gibhead", "gibtorso") - -/obj/effect/decal/cleanable/blood/gibs/torso - icon_state = "gibtorso" - random_icon_states = null - -/obj/effect/decal/cleanable/blood/gibs/limb - icon_state = "gibleg" - random_icon_states = list("gibleg", "gibarm") - -/obj/effect/decal/cleanable/blood/gibs/core - icon_state = "gibmid1" - random_icon_states = list("gibmid1", "gibmid2", "gibmid3") - -/obj/effect/decal/cleanable/blood/gibs/old - name = "old rotting gibs" - desc = "Space Jesus, why didn't anyone clean this up? They smell terrible." - bloodiness = 0 - already_rotting = TRUE - -/obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases) - . = ..() - setDir(pick(1,2,4,8)) - icon_state += "-old" - add_blood_DNA(list("Non-human DNA" = random_blood_type())) - -/obj/effect/decal/cleanable/blood/drip - name = "drips of blood" - desc = "It's red." - icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires. - random_icon_states = list("drip1","drip2","drip3","drip4","drip5") - bloodiness = 0 - var/drips = 1 - -/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in() - return TRUE - - -//BLOODY FOOTPRINTS -/obj/effect/decal/cleanable/blood/footprints - name = "footprints" - icon = 'icons/effects/footprints.dmi' - icon_state = "nothingwhatsoever" - desc = "WHOSE FOOTPRINTS ARE THESE?" - icon_state = "blood1" - random_icon_states = null - blood_state = BLOOD_STATE_HUMAN //the icon state to load images from - var/entered_dirs = 0 - var/exited_dirs = 0 - var/list/shoe_types = list() - -/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O) - ..() - if(ishuman(O)) - var/mob/living/carbon/human/H = O - var/obj/item/clothing/shoes/S = H.shoes - if(S && S.bloody_shoes[blood_state]) - S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) - shoe_types |= S.type - if (!(entered_dirs & H.dir)) - entered_dirs |= H.dir - update_icon() - -/obj/effect/decal/cleanable/blood/footprints/Uncrossed(atom/movable/O) - ..() - if(ishuman(O)) - var/mob/living/carbon/human/H = O - var/obj/item/clothing/shoes/S = H.shoes - if(S && S.bloody_shoes[blood_state]) - S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) - shoe_types |= S.type - if (!(exited_dirs & H.dir)) - exited_dirs |= H.dir - update_icon() - - -/obj/effect/decal/cleanable/blood/footprints/update_icon() - cut_overlays() - - for(var/Ddir in GLOB.cardinals) - if(entered_dirs & Ddir) - var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] - if(!bloodstep_overlay) - GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]1", dir = Ddir) - add_overlay(bloodstep_overlay) - if(exited_dirs & Ddir) - var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] - if(!bloodstep_overlay) - GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]2", dir = Ddir) - add_overlay(bloodstep_overlay) - - alpha = BLOODY_FOOTPRINT_BASE_ALPHA+bloodiness - - -/obj/effect/decal/cleanable/blood/footprints/examine(mob/user) - . = ..() - if(shoe_types.len) - . += "You recognise the footprints as belonging to:\n" - for(var/shoe in shoe_types) - var/obj/item/clothing/shoes/S = shoe - . += "[icon2html(initial(S.icon), user)] Some [initial(S.name)].\n" - -/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/C) - if(blood_state != C.blood_state) //We only replace footprints of the same type as us - return - ..() - -/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return 1 - return 0 +/obj/effect/decal/cleanable/blood + name = "blood" + desc = "It's red and gooey. Perhaps it's the chef's cooking?" + icon = 'icons/effects/blood.dmi' + icon_state = "floor1" + random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") + blood_state = BLOOD_STATE_HUMAN + bloodiness = BLOOD_AMOUNT_PER_DECAL + +/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) + C.add_blood_DNA(return_blood_DNA()) + if (bloodiness) + if (C.bloodiness < MAX_SHOE_BLOODINESS) + C.bloodiness += bloodiness + return ..() + +/obj/effect/decal/cleanable/blood/old + name = "dried blood" + desc = "Looks like it's been here a while. Eew." + bloodiness = 0 + icon_state = "floor1-old" + +/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) + add_blood_DNA(list("Non-human DNA" = random_blood_type())) // Needs to happen before ..() + . = ..() + icon_state = "[icon_state]-old" //change from the normal blood icon selected from random_icon_states in the parent's Initialize to the old dried up blood. + +/obj/effect/decal/cleanable/blood/splatter + icon_state = "gibbl1" + random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") + +/obj/effect/decal/cleanable/blood/tracks + icon_state = "tracks" + desc = "They look like tracks left by wheels." + icon_state = "tracks" + random_icon_states = null + +/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose + name = "blood" + icon = 'icons/effects/blood.dmi' + desc = "Your instincts say you shouldn't be following these." + var/list/existing_dirs = list() + +/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() + return TRUE + +/obj/effect/decal/cleanable/blood/gibs + name = "gibs" + desc = "They look bloody and gruesome." + icon = 'icons/effects/blood.dmi' + icon_state = "gib1" + layer = LOW_OBJ_LAYER + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6") + mergeable_decal = FALSE + + var/already_rotting = FALSE + +/obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases) + . = ..() + reagents.add_reagent(/datum/reagent/liquidgibs, 5) + if(already_rotting) + start_rotting(rename=FALSE) + else + addtimer(CALLBACK(src, .proc/start_rotting), 2 MINUTES) + +/obj/effect/decal/cleanable/blood/gibs/proc/start_rotting(rename=TRUE) + if(rename) + name = "rotting [initial(name)]" + desc += " They smell terrible." + AddComponent(/datum/component/rot/gibs) + +/obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target) + return + +/obj/effect/decal/cleanable/blood/gibs/Crossed(mob/living/L) + if(istype(L) && has_gravity(loc)) + playsound(loc, 'sound/effects/gib_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 20 : 50, 1) + . = ..() + +/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions) + set waitfor = FALSE + var/list/diseases = list() + SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases) + var/direction = pick(directions) + for(var/i in 0 to pick(0, 200; 1, 150; 2, 50)) + sleep(2) + if(i > 0) + new /obj/effect/decal/cleanable/blood/splatter(loc, diseases) + if(!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/blood/gibs/up + icon_state = "gibup1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibup1","gibup1","gibup1") + +/obj/effect/decal/cleanable/blood/gibs/down + icon_state = "gibdown1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibdown1","gibdown1","gibdown1") + +/obj/effect/decal/cleanable/blood/gibs/body + icon_state = "gibtorso" + random_icon_states = list("gibhead", "gibtorso") + +/obj/effect/decal/cleanable/blood/gibs/torso + icon_state = "gibtorso" + random_icon_states = null + +/obj/effect/decal/cleanable/blood/gibs/limb + icon_state = "gibleg" + random_icon_states = list("gibleg", "gibarm") + +/obj/effect/decal/cleanable/blood/gibs/core + icon_state = "gibmid1" + random_icon_states = list("gibmid1", "gibmid2", "gibmid3") + +/obj/effect/decal/cleanable/blood/gibs/old + name = "old rotting gibs" + desc = "Space Jesus, why didn't anyone clean this up? They smell terrible." + bloodiness = 0 + already_rotting = TRUE + +/obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases) + . = ..() + setDir(pick(1,2,4,8)) + icon_state += "-old" + add_blood_DNA(list("Non-human DNA" = random_blood_type())) + +/obj/effect/decal/cleanable/blood/drip + name = "drips of blood" + desc = "It's red." + icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires. + random_icon_states = list("drip1","drip2","drip3","drip4","drip5") + bloodiness = 0 + var/drips = 1 + +/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in() + return TRUE + + +//BLOODY FOOTPRINTS +/obj/effect/decal/cleanable/blood/footprints + name = "footprints" + icon = 'icons/effects/footprints.dmi' + icon_state = "nothingwhatsoever" + desc = "WHOSE FOOTPRINTS ARE THESE?" + icon_state = "blood1" + random_icon_states = null + blood_state = BLOOD_STATE_HUMAN //the icon state to load images from + var/entered_dirs = 0 + var/exited_dirs = 0 + var/list/shoe_types = list() + +/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O) + ..() + if(ishuman(O)) + var/mob/living/carbon/human/H = O + var/obj/item/clothing/shoes/S = H.shoes + if(S && S.bloody_shoes[blood_state]) + S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) + shoe_types |= S.type + if (!(entered_dirs & H.dir)) + entered_dirs |= H.dir + update_icon() + +/obj/effect/decal/cleanable/blood/footprints/Uncrossed(atom/movable/O) + ..() + if(ishuman(O)) + var/mob/living/carbon/human/H = O + var/obj/item/clothing/shoes/S = H.shoes + if(S && S.bloody_shoes[blood_state]) + S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) + shoe_types |= S.type + if (!(exited_dirs & H.dir)) + exited_dirs |= H.dir + update_icon() + + +/obj/effect/decal/cleanable/blood/footprints/update_icon() + cut_overlays() + + for(var/Ddir in GLOB.cardinals) + if(entered_dirs & Ddir) + var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] + if(!bloodstep_overlay) + GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]1", dir = Ddir) + add_overlay(bloodstep_overlay) + if(exited_dirs & Ddir) + var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] + if(!bloodstep_overlay) + GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]2", dir = Ddir) + add_overlay(bloodstep_overlay) + + alpha = BLOODY_FOOTPRINT_BASE_ALPHA+bloodiness + + +/obj/effect/decal/cleanable/blood/footprints/examine(mob/user) + . = ..() + if(shoe_types.len) + . += "You recognise the footprints as belonging to:\n" + for(var/shoe in shoe_types) + var/obj/item/clothing/shoes/S = shoe + . += "[icon2html(initial(S.icon), user)] Some [initial(S.name)].\n" + +/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/C) + if(blood_state != C.blood_state) //We only replace footprints of the same type as us + return + ..() + +/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() + if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) + return 1 + return 0 diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 7e7909be5c93..fe2403140e5f 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -1,202 +1,202 @@ -/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/dirt/dust - name = "dust" - desc = "A thin layer of dust coating the floor." - -/obj/effect/decal/cleanable/greenglow - name = "glowing goo" - desc = "Jeez. I hope that's not for lunch." - icon_state = "greenglow" - light_power = 3 - light_range = 2 - light_color = LIGHT_COLOR_GREEN - -/obj/effect/decal/cleanable/greenglow/ex_act() - return - -/obj/effect/decal/cleanable/greenglow/filled/Initialize() - . = ..() - reagents.add_reagent(pick(/datum/reagent/uranium, /datum/reagent/uranium/radium), 5) - -/obj/effect/decal/cleanable/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/R in reagents.reagent_list) - if (istype(R, /datum/reagent/consumable)) - var/datum/reagent/consumable/nutri_check = R - if(nutri_check.nutriment_factor >0) - H.adjust_nutrition(nutri_check.nutriment_factor * nutri_check.volume) - reagents.remove_reagent(nutri_check.type,nutri_check.volume) - reagents.trans_to(H, reagents.total_volume, transfered_by = user) - qdel(src) - -/obj/effect/decal/cleanable/vomit/old - name = "crusty dried vomit" - desc = "You try not to look at the chunks, and fail." - -/obj/effect/decal/cleanable/vomit/old/Initialize(mapload, list/datum/disease/diseases) - . = ..() - icon_state += "-old" - -/obj/effect/decal/cleanable/chem_pile - name = "chemical pile" - desc = "A pile of chemicals. You can't quite tell what's inside it." - gender = NEUTER - icon = 'icons/obj/objects.dmi' - icon_state = "ash" - -/obj/effect/decal/cleanable/shreds - name = "shreds" - desc = "The shredded remains of what appears to be clothing." - icon_state = "shreds" - gender = PLURAL - mergeable_decal = FALSE - -/obj/effect/decal/cleanable/shreds/ex_act(severity, target) - if(severity == 1) //so shreds created during an explosion aren't deleted by the explosion. - qdel(src) - -/obj/effect/decal/cleanable/shreds/Initialize() - pixel_x = rand(-10, 10) - pixel_y = rand(-10, 10) - . = ..() - -/obj/effect/decal/cleanable/glitter - name = "generic glitter pile" - desc = "The herpes of arts and crafts." - icon = 'icons/effects/atmospherics.dmi' - icon_state = "plasma_old" - gender = NEUTER - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/decal/cleanable/glitter/pink - name = "pink glitter" - icon_state = "plasma" - -/obj/effect/decal/cleanable/glitter/white - name = "white glitter" - icon_state = "nitrous_oxide" - -/obj/effect/decal/cleanable/glitter/blue - name = "blue glitter" - icon_state = "freon" - -/obj/effect/decal/cleanable/plasma - name = "stabilized plasma" - desc = "A puddle of stabilized plasma." - icon_state = "flour" - icon = 'icons/effects/tomatodecal.dmi' - color = "#C8A5DC" - -/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/dirt/dust + name = "dust" + desc = "A thin layer of dust coating the floor." + +/obj/effect/decal/cleanable/greenglow + name = "glowing goo" + desc = "Jeez. I hope that's not for lunch." + icon_state = "greenglow" + light_power = 3 + light_range = 2 + light_color = LIGHT_COLOR_GREEN + +/obj/effect/decal/cleanable/greenglow/ex_act() + return + +/obj/effect/decal/cleanable/greenglow/filled/Initialize() + . = ..() + reagents.add_reagent(pick(/datum/reagent/uranium, /datum/reagent/uranium/radium), 5) + +/obj/effect/decal/cleanable/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/R in reagents.reagent_list) + if (istype(R, /datum/reagent/consumable)) + var/datum/reagent/consumable/nutri_check = R + if(nutri_check.nutriment_factor >0) + H.adjust_nutrition(nutri_check.nutriment_factor * nutri_check.volume) + reagents.remove_reagent(nutri_check.type,nutri_check.volume) + reagents.trans_to(H, reagents.total_volume, transfered_by = user) + qdel(src) + +/obj/effect/decal/cleanable/vomit/old + name = "crusty dried vomit" + desc = "You try not to look at the chunks, and fail." + +/obj/effect/decal/cleanable/vomit/old/Initialize(mapload, list/datum/disease/diseases) + . = ..() + icon_state += "-old" + +/obj/effect/decal/cleanable/chem_pile + name = "chemical pile" + desc = "A pile of chemicals. You can't quite tell what's inside it." + gender = NEUTER + icon = 'icons/obj/objects.dmi' + icon_state = "ash" + +/obj/effect/decal/cleanable/shreds + name = "shreds" + desc = "The shredded remains of what appears to be clothing." + icon_state = "shreds" + gender = PLURAL + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/shreds/ex_act(severity, target) + if(severity == 1) //so shreds created during an explosion aren't deleted by the explosion. + qdel(src) + +/obj/effect/decal/cleanable/shreds/Initialize() + pixel_x = rand(-10, 10) + pixel_y = rand(-10, 10) + . = ..() + +/obj/effect/decal/cleanable/glitter + name = "generic glitter pile" + desc = "The herpes of arts and crafts." + icon = 'icons/effects/atmospherics.dmi' + icon_state = "plasma_old" + gender = NEUTER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/decal/cleanable/glitter/pink + name = "pink glitter" + icon_state = "plasma" + +/obj/effect/decal/cleanable/glitter/white + name = "white glitter" + icon_state = "nitrous_oxide" + +/obj/effect/decal/cleanable/glitter/blue + name = "blue glitter" + icon_state = "freon" + +/obj/effect/decal/cleanable/plasma + name = "stabilized plasma" + desc = "A puddle of stabilized plasma." + icon_state = "flour" + icon = 'icons/effects/tomatodecal.dmi' + color = "#C8A5DC" + +/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/crayon.dm b/code/game/objects/effects/decals/crayon.dm index e9fe943587ab..909d422b069a 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -1,33 +1,33 @@ -/obj/effect/decal/cleanable/crayon - name = "rune" - desc = "Graffiti. Damn kids." - icon = 'icons/effects/crayondecal.dmi' - icon_state = "rune1" - gender = NEUTER - plane = GAME_PLANE //makes the graffiti visible over a wall. - mergeable_decal = FALSE - var/do_icon_rotate = TRUE - var/rotation = 0 - var/paint_colour = "#FFFFFF" - -/obj/effect/decal/cleanable/crayon/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) - . = ..() - if(e_name) - name = e_name - desc = "A [name] vandalizing the station." - if(alt_icon) - icon = alt_icon - if(type) - icon_state = type - if(graf_rot) - rotation = graf_rot - if(rotation && do_icon_rotate) - var/matrix/M = matrix() - M.Turn(rotation) - src.transform = M - if(main) - paint_colour = main - add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY) - -/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T) - return isgroundlessturf(T) +/obj/effect/decal/cleanable/crayon + name = "rune" + desc = "Graffiti. Damn kids." + icon = 'icons/effects/crayondecal.dmi' + icon_state = "rune1" + gender = NEUTER + plane = GAME_PLANE //makes the graffiti visible over a wall. + mergeable_decal = FALSE + var/do_icon_rotate = TRUE + var/rotation = 0 + var/paint_colour = "#FFFFFF" + +/obj/effect/decal/cleanable/crayon/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) + . = ..() + if(e_name) + name = e_name + desc = "A [name] vandalizing the station." + if(alt_icon) + icon = alt_icon + if(type) + icon_state = type + if(graf_rot) + rotation = graf_rot + if(rotation && do_icon_rotate) + var/matrix/M = matrix() + M.Turn(rotation) + src.transform = M + if(main) + paint_colour = main + add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY) + +/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T) + return isgroundlessturf(T) diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm index 2fa7277d8bd1..3e7706282add 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 05ff9cb863c4..8f46d5cc4631 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 75fe78959d6e..87337a526cb8 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/forcefields.dm b/code/game/objects/effects/forcefields.dm index 6aab4f386a4a..384cf73c89e6 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 37a87d1f11be..72fb44480c87 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -1,440 +1,440 @@ -/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" - -//Antagonist spawns - -/obj/effect/landmark/start/wizard - name = "wizard" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "wiznerd_spawn" - -/obj/effect/landmark/start/wizard/Initialize() - ..() - GLOB.wizardstart += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/start/nukeop - name = "nukeop" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "snukeop_spawn" - -/obj/effect/landmark/start/nukeop/Initialize() - ..() - GLOB.nukeop_start += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/start/nukeop_leader - name = "nukeop leader" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "snukeop_leader_spawn" - -/obj/effect/landmark/start/nukeop_leader/Initialize() - ..() - GLOB.nukeop_leader_start += loc - return INITIALIZE_HINT_QDEL - -// Must be immediate because players will -// join before SSatom initializes everything. -INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) - -/obj/effect/landmark/start/new_player - name = "New Player" - -/obj/effect/landmark/start/new_player/Initialize() - ..() - GLOB.newplayer_start += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/latejoin - name = "JoinLate" - -/obj/effect/landmark/latejoin/Initialize(mapload) - ..() - SSjob.latejoin_trackers += loc - return INITIALIZE_HINT_QDEL - -//space carps, magicarps, lone ops, slaughter demons, possibly revenants spawn here -/obj/effect/landmark/carpspawn - name = "carpspawn" - icon_state = "carp_spawn" - -//observer start -/obj/effect/landmark/observer_start - name = "Observer-Start" - icon_state = "observer_start" - -//xenos, morphs and nightmares spawn here -/obj/effect/landmark/xeno_spawn - name = "xeno_spawn" - icon_state = "xeno_spawn" - -/obj/effect/landmark/xeno_spawn/Initialize(mapload) - ..() - GLOB.xeno_spawn += loc - return INITIALIZE_HINT_QDEL - -//objects with the stationloving component (nuke disk) respawn here. -//also blobs that have their spawn forcemoved (running out of time when picking their spawn spot), santa and respawning devils -/obj/effect/landmark/blobstart - name = "blobstart" - icon_state = "blob_start" - -/obj/effect/landmark/blobstart/Initialize(mapload) - ..() - GLOB.blobstart += loc - return INITIALIZE_HINT_QDEL - -//spawns sec equipment lockers depending on the number of sec officers -/obj/effect/landmark/secequipment - name = "secequipment" - icon_state = "secequipment" - -/obj/effect/landmark/secequipment/Initialize(mapload) - ..() - GLOB.secequipment += loc - return INITIALIZE_HINT_QDEL - -//players that get put in admin jail show up here -/obj/effect/landmark/prisonwarp - name = "prisonwarp" - icon_state = "prisonwarp" - -/obj/effect/landmark/prisonwarp/Initialize(mapload) - ..() - GLOB.prisonwarp += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/ert_spawn - name = "Emergencyresponseteam" - icon_state = "ert_spawn" - -/obj/effect/landmark/ert_spawn/Initialize(mapload) - ..() - GLOB.emergencyresponseteamspawn += loc - return INITIALIZE_HINT_QDEL - -//ninja energy nets teleport victims here -/obj/effect/landmark/holding_facility - name = "Holding Facility" - icon_state = "holding_facility" - -/obj/effect/landmark/holding_facility/Initialize(mapload) - ..() - GLOB.holdingfacility += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/observe - name = "tdomeobserve" - icon_state = "tdome_observer" - -/obj/effect/landmark/thunderdome/observe/Initialize(mapload) - ..() - GLOB.tdomeobserve += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/one - name = "tdome1" - icon_state = "tdome_t1" - -/obj/effect/landmark/thunderdome/one/Initialize(mapload) - ..() - GLOB.tdome1 += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/two - name = "tdome2" - icon_state = "tdome_t2" - -/obj/effect/landmark/thunderdome/two/Initialize(mapload) - ..() - GLOB.tdome2 += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/admin - name = "tdomeadmin" - icon_state = "tdome_admin" - -/obj/effect/landmark/thunderdome/admin/Initialize(mapload) - ..() - GLOB.tdomeadmin += loc - return INITIALIZE_HINT_QDEL - -//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 - -//handles clockwork portal+eminence teleport destinations -/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 - . = ..() +/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" + +//Antagonist spawns + +/obj/effect/landmark/start/wizard + name = "wizard" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "wiznerd_spawn" + +/obj/effect/landmark/start/wizard/Initialize() + ..() + GLOB.wizardstart += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/start/nukeop + name = "nukeop" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "snukeop_spawn" + +/obj/effect/landmark/start/nukeop/Initialize() + ..() + GLOB.nukeop_start += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/start/nukeop_leader + name = "nukeop leader" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "snukeop_leader_spawn" + +/obj/effect/landmark/start/nukeop_leader/Initialize() + ..() + GLOB.nukeop_leader_start += loc + return INITIALIZE_HINT_QDEL + +// Must be immediate because players will +// join before SSatom initializes everything. +INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) + +/obj/effect/landmark/start/new_player + name = "New Player" + +/obj/effect/landmark/start/new_player/Initialize() + ..() + GLOB.newplayer_start += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/latejoin + name = "JoinLate" + +/obj/effect/landmark/latejoin/Initialize(mapload) + ..() + SSjob.latejoin_trackers += loc + return INITIALIZE_HINT_QDEL + +//space carps, magicarps, lone ops, slaughter demons, possibly revenants spawn here +/obj/effect/landmark/carpspawn + name = "carpspawn" + icon_state = "carp_spawn" + +//observer start +/obj/effect/landmark/observer_start + name = "Observer-Start" + icon_state = "observer_start" + +//xenos, morphs and nightmares spawn here +/obj/effect/landmark/xeno_spawn + name = "xeno_spawn" + icon_state = "xeno_spawn" + +/obj/effect/landmark/xeno_spawn/Initialize(mapload) + ..() + GLOB.xeno_spawn += loc + return INITIALIZE_HINT_QDEL + +//objects with the stationloving component (nuke disk) respawn here. +//also blobs that have their spawn forcemoved (running out of time when picking their spawn spot), santa and respawning devils +/obj/effect/landmark/blobstart + name = "blobstart" + icon_state = "blob_start" + +/obj/effect/landmark/blobstart/Initialize(mapload) + ..() + GLOB.blobstart += loc + return INITIALIZE_HINT_QDEL + +//spawns sec equipment lockers depending on the number of sec officers +/obj/effect/landmark/secequipment + name = "secequipment" + icon_state = "secequipment" + +/obj/effect/landmark/secequipment/Initialize(mapload) + ..() + GLOB.secequipment += loc + return INITIALIZE_HINT_QDEL + +//players that get put in admin jail show up here +/obj/effect/landmark/prisonwarp + name = "prisonwarp" + icon_state = "prisonwarp" + +/obj/effect/landmark/prisonwarp/Initialize(mapload) + ..() + GLOB.prisonwarp += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/ert_spawn + name = "Emergencyresponseteam" + icon_state = "ert_spawn" + +/obj/effect/landmark/ert_spawn/Initialize(mapload) + ..() + GLOB.emergencyresponseteamspawn += loc + return INITIALIZE_HINT_QDEL + +//ninja energy nets teleport victims here +/obj/effect/landmark/holding_facility + name = "Holding Facility" + icon_state = "holding_facility" + +/obj/effect/landmark/holding_facility/Initialize(mapload) + ..() + GLOB.holdingfacility += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/observe + name = "tdomeobserve" + icon_state = "tdome_observer" + +/obj/effect/landmark/thunderdome/observe/Initialize(mapload) + ..() + GLOB.tdomeobserve += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/one + name = "tdome1" + icon_state = "tdome_t1" + +/obj/effect/landmark/thunderdome/one/Initialize(mapload) + ..() + GLOB.tdome1 += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/two + name = "tdome2" + icon_state = "tdome_t2" + +/obj/effect/landmark/thunderdome/two/Initialize(mapload) + ..() + GLOB.tdome2 += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/admin + name = "tdomeadmin" + icon_state = "tdome_admin" + +/obj/effect/landmark/thunderdome/admin/Initialize(mapload) + ..() + GLOB.tdomeadmin += loc + return INITIALIZE_HINT_QDEL + +//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 + +//handles clockwork portal+eminence teleport destinations +/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 + . = ..() diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index 27ad25eea1b0..2559e33dd332 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -1,177 +1,177 @@ -/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.Paralyze(stun_time) - -/obj/effect/mine/kickmine - name = "kick mine" - -/obj/effect/mine/kickmine/mineEffect(mob/victim) - if(isliving(victim) && victim.client) - to_chat(victim, "You have been kicked FOR NO REISIN!") - qdel(victim.client) - - -/obj/effect/mine/gas - name = "oxygen mine" - var/gas_amount = 360 - var/gas_type = "o2" - -/obj/effect/mine/gas/mineEffect(mob/victim) - atmos_spawn_air("[gas_type]=[gas_amount]") - - -/obj/effect/mine/gas/plasma - name = "plasma mine" - gas_type = "plasma" - - -/obj/effect/mine/gas/n2o - name = "\improper N2O mine" - gas_type = "n2o" - - -/obj/effect/mine/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") - SEND_SOUND(victim, sound('sound/misc/e1m1.ogg')) - 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.Paralyze(stun_time) + +/obj/effect/mine/kickmine + name = "kick mine" + +/obj/effect/mine/kickmine/mineEffect(mob/victim) + if(isliving(victim) && victim.client) + to_chat(victim, "You have been kicked FOR NO REISIN!") + qdel(victim.client) + + +/obj/effect/mine/gas + name = "oxygen mine" + var/gas_amount = 360 + var/gas_type = "o2" + +/obj/effect/mine/gas/mineEffect(mob/victim) + atmos_spawn_air("[gas_type]=[gas_amount]") + + +/obj/effect/mine/gas/plasma + name = "plasma mine" + gas_type = "plasma" + + +/obj/effect/mine/gas/n2o + name = "\improper N2O mine" + gas_type = "n2o" + + +/obj/effect/mine/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") + SEND_SOUND(victim, sound('sound/misc/e1m1.ogg')) + 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 fdf580fb6c75..b7ded01d5a21 100644 --- a/code/game/objects/effects/misc.dm +++ b/code/game/objects/effects/misc.dm @@ -1,94 +1,94 @@ -//The effect when you wrap a dead body in gift wrap -/obj/effect/spresent - name = "strange present" - desc = "It's a ... present?" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "strangepresent" - density = TRUE - anchored = FALSE - -/obj/effect/beam - name = "beam" - var/def_zone - pass_flags = PASSTABLE - -/obj/effect/beam/singularity_act() - return - -/obj/effect/beam/singularity_pull() - return - -/obj/effect/spawner - name = "object spawner" - -/obj/effect/list_container - name = "list container" - -/obj/effect/list_container/mobl - name = "mobl" - var/master = null - - var/list/container = list( ) - -/obj/effect/overlay/thermite - name = "thermite" - desc = "Looks hot." - icon = 'icons/effects/fire.dmi' - icon_state = "2" //what? - anchored = TRUE - opacity = TRUE - density = TRUE - layer = FLY_LAYER - -/obj/effect/supplypod_selector - icon_state = "supplypod_selector" - layer = FLY_LAYER - -//Makes a tile fully lit no matter what -/obj/effect/fullbright - icon = 'icons/effects/alphacolors.dmi' - icon_state = "white" - plane = LIGHTING_PLANE - layer = LIGHTING_LAYER - blend_mode = BLEND_ADD - -/obj/effect/abstract/marker - name = "marker" - icon = 'icons/effects/effects.dmi' - anchored = TRUE - icon_state = "wave3" - layer = RIPPLE_LAYER - -/obj/effect/abstract/marker/Initialize(mapload) - . = ..() - GLOB.all_abstract_markers += src - -/obj/effect/abstract/marker/Destroy() - GLOB.all_abstract_markers -= src - . = ..() - -/obj/effect/abstract/marker/at - name = "active turf marker" - - -/obj/effect/dummy/lighting_obj - name = "lighting fx obj" - desc = "Tell a coder if you're seeing this." - icon_state = "nothing" - light_color = "#FFFFFF" - light_range = MINIMUM_USEFUL_LIGHT_RANGE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/dummy/lighting_obj/Initialize(mapload, _color, _range, _power, _duration) - . = ..() - set_light(_range ? _range : light_range, _power ? _power : light_power, _color ? _color : light_color) - if(_duration) - QDEL_IN(src, _duration) - -/obj/effect/dummy/lighting_obj/moblight - name = "mob lighting fx" - -/obj/effect/dummy/lighting_obj/moblight/Initialize(mapload, _color, _range, _power, _duration) - . = ..() - if(!ismob(loc)) - return INITIALIZE_HINT_QDEL +//The effect when you wrap a dead body in gift wrap +/obj/effect/spresent + name = "strange present" + desc = "It's a ... present?" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "strangepresent" + density = TRUE + anchored = FALSE + +/obj/effect/beam + name = "beam" + var/def_zone + pass_flags = PASSTABLE + +/obj/effect/beam/singularity_act() + return + +/obj/effect/beam/singularity_pull() + return + +/obj/effect/spawner + name = "object spawner" + +/obj/effect/list_container + name = "list container" + +/obj/effect/list_container/mobl + name = "mobl" + var/master = null + + var/list/container = list( ) + +/obj/effect/overlay/thermite + name = "thermite" + desc = "Looks hot." + icon = 'icons/effects/fire.dmi' + icon_state = "2" //what? + anchored = TRUE + opacity = TRUE + density = TRUE + layer = FLY_LAYER + +/obj/effect/supplypod_selector + icon_state = "supplypod_selector" + layer = FLY_LAYER + +//Makes a tile fully lit no matter what +/obj/effect/fullbright + icon = 'icons/effects/alphacolors.dmi' + icon_state = "white" + plane = LIGHTING_PLANE + layer = LIGHTING_LAYER + blend_mode = BLEND_ADD + +/obj/effect/abstract/marker + name = "marker" + icon = 'icons/effects/effects.dmi' + anchored = TRUE + icon_state = "wave3" + layer = RIPPLE_LAYER + +/obj/effect/abstract/marker/Initialize(mapload) + . = ..() + GLOB.all_abstract_markers += src + +/obj/effect/abstract/marker/Destroy() + GLOB.all_abstract_markers -= src + . = ..() + +/obj/effect/abstract/marker/at + name = "active turf marker" + + +/obj/effect/dummy/lighting_obj + name = "lighting fx obj" + desc = "Tell a coder if you're seeing this." + icon_state = "nothing" + light_color = "#FFFFFF" + light_range = MINIMUM_USEFUL_LIGHT_RANGE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/dummy/lighting_obj/Initialize(mapload, _color, _range, _power, _duration) + . = ..() + set_light(_range ? _range : light_range, _power ? _power : light_power, _color ? _color : light_color) + if(_duration) + QDEL_IN(src, _duration) + +/obj/effect/dummy/lighting_obj/moblight + name = "mob lighting fx" + +/obj/effect/dummy/lighting_obj/moblight/Initialize(mapload, _color, _range, _power, _duration) + . = ..() + if(!ismob(loc)) + return INITIALIZE_HINT_QDEL diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm index c16bae26c7db..1c6bce0e2e8c 100644 --- a/code/game/objects/effects/portals.dm +++ b/code/game/objects/effects/portals.dm @@ -1,243 +1,243 @@ - -/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" - layer = RIPPLE_LAYER - 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, force_stop = 0) - if(force_stop) - return ..() - 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 - -/obj/effect/portal/permanent - name = "permanent portal" - desc = "An unwavering portal that will never fade." - var/id // var edit or set id in map editor - hardlinked = FALSE // dont qdel my portal nerd - -/obj/effect/portal/permanent/Initialize(mapload, _creator, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override, atmos_link_override) - . = ..() - set_linked() - -/obj/effect/portal/permanent/proc/get_linked() - if(!id) - return - var/list/possible = list() - for(var/obj/effect/portal/permanent/P in GLOB.portals - src) - if(P.id && P.id == id) // gets portals with the same id, there should only be two permanent portals with the same id - possible += P - return possible - -/obj/effect/portal/permanent/proc/set_linked() - var/list/possible = get_linked() - if(!possible || !possible.len) - return - for(var/obj/effect/portal/permanent/other in possible) - other.linked = src - linked = pick(possible) - -/obj/effect/portal/permanent/teleport(atom/movable/M, force = FALSE) - // try to search for a new one if something was var edited etc - set_linked() - . = ..() - -/obj/effect/portal/permanent/one_way // doesn't have a return portal - name = "one-way portal" - desc = "You get the feeling that this might not be the safest thing you've ever done." - var/list/possible_exits = list() - var/keep // if this is a portal that should be kept - -/obj/effect/portal/permanent/one_way/set_linked() - if(!keep) // wait for a keep portal to set - return - var/list/possible_temp = get_linked() - if(possible_temp && possible_temp.len) - for(var/obj/effect/portal/permanent/other in possible_temp) - possible_exits += get_turf(other) - qdel(other) - if(possible_exits && possible_exits.len) - hard_target = pick(possible_exits) - -/obj/effect/portal/permanent/one_way/keep // because its nice to be able to tell which is which on the map - keep = TRUE - -/obj/effect/portal/permanent/one_way/destroy - keep = FALSE + +/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" + layer = RIPPLE_LAYER + 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, force_stop = 0) + if(force_stop) + return ..() + 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 + +/obj/effect/portal/permanent + name = "permanent portal" + desc = "An unwavering portal that will never fade." + var/id // var edit or set id in map editor + hardlinked = FALSE // dont qdel my portal nerd + +/obj/effect/portal/permanent/Initialize(mapload, _creator, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override, atmos_link_override) + . = ..() + set_linked() + +/obj/effect/portal/permanent/proc/get_linked() + if(!id) + return + var/list/possible = list() + for(var/obj/effect/portal/permanent/P in GLOB.portals - src) + if(P.id && P.id == id) // gets portals with the same id, there should only be two permanent portals with the same id + possible += P + return possible + +/obj/effect/portal/permanent/proc/set_linked() + var/list/possible = get_linked() + if(!possible || !possible.len) + return + for(var/obj/effect/portal/permanent/other in possible) + other.linked = src + linked = pick(possible) + +/obj/effect/portal/permanent/teleport(atom/movable/M, force = FALSE) + // try to search for a new one if something was var edited etc + set_linked() + . = ..() + +/obj/effect/portal/permanent/one_way // doesn't have a return portal + name = "one-way portal" + desc = "You get the feeling that this might not be the safest thing you've ever done." + var/list/possible_exits = list() + var/keep // if this is a portal that should be kept + +/obj/effect/portal/permanent/one_way/set_linked() + if(!keep) // wait for a keep portal to set + return + var/list/possible_temp = get_linked() + if(possible_temp && possible_temp.len) + for(var/obj/effect/portal/permanent/other in possible_temp) + possible_exits += get_turf(other) + qdel(other) + if(possible_exits && possible_exits.len) + hard_target = pick(possible_exits) + +/obj/effect/portal/permanent/one_way/keep // because its nice to be able to tell which is which on the map + keep = TRUE + +/obj/effect/portal/permanent/one_way/destroy + keep = FALSE diff --git a/code/game/objects/effects/proximity.dm b/code/game/objects/effects/proximity.dm index dbd9867d6dab..607921d5625d 100644 --- a/code/game/objects/effects/proximity.dm +++ b/code/game/objects/effects/proximity.dm @@ -1,115 +1,115 @@ -/datum/proximity_monitor - var/atom/host //the atom we are tracking - var/atom/hasprox_receiver //the atom that will receive HasProximity calls. - var/atom/last_host_loc - var/list/checkers //list of /obj/effect/abstract/proximity_checkers - var/current_range - var/ignore_if_not_on_turf //don't check turfs in range if the host's loc isn't a turf - var/wire = FALSE - -/datum/proximity_monitor/New(atom/_host, range, _ignore_if_not_on_turf = TRUE) - checkers = list() - last_host_loc = _host.loc - ignore_if_not_on_turf = _ignore_if_not_on_turf - current_range = range - SetHost(_host) - -/datum/proximity_monitor/proc/SetHost(atom/H,atom/R) - if(H == host) - return - if(host) - UnregisterSignal(host, COMSIG_MOVABLE_MOVED) - if(R) - hasprox_receiver = R - else if(hasprox_receiver == host) //Default case - hasprox_receiver = H - host = H - RegisterSignal(host, COMSIG_MOVABLE_MOVED, .proc/HandleMove) - last_host_loc = host.loc - SetRange(current_range,TRUE) - -/datum/proximity_monitor/Destroy() - host = null - last_host_loc = null - hasprox_receiver = null - QDEL_LIST(checkers) - return ..() - -/datum/proximity_monitor/proc/HandleMove() - var/atom/_host = host - var/atom/new_host_loc = _host.loc - if(last_host_loc != new_host_loc) - last_host_loc = new_host_loc //hopefully this won't cause GC issues with containers - var/curr_range = current_range - SetRange(curr_range, TRUE) - if(curr_range) - testing("HasProx: [host] -> [host]") - hasprox_receiver.HasProximity(host) //if we are processing, we're guaranteed to be a movable - -/datum/proximity_monitor/proc/SetRange(range, force_rebuild = FALSE) - if(!force_rebuild && range == current_range) - return FALSE - . = TRUE - - current_range = range - - var/list/checkers_local = checkers - var/old_checkers_len = checkers_local.len - - var/atom/_host = host - - var/atom/loc_to_use = ignore_if_not_on_turf ? _host.loc : get_turf(_host) - if(wire && !isturf(loc_to_use)) //it makes assemblies attached on wires work - loc_to_use = get_turf(loc_to_use) - if(!isturf(loc_to_use)) //only check the host's loc - if(range) - var/obj/effect/abstract/proximity_checker/pc - if(old_checkers_len) - pc = checkers_local[old_checkers_len] - --checkers_local.len - QDEL_LIST(checkers_local) - else - pc = new(loc_to_use, src) - - checkers_local += pc //only check the host's loc - return - - var/list/turfs = RANGE_TURFS(range, loc_to_use) - var/turfs_len = turfs.len - var/old_checkers_used = min(turfs_len, old_checkers_len) - - //reuse what we can - for(var/I in 1 to old_checkers_len) - if(I <= old_checkers_used) - var/obj/effect/abstract/proximity_checker/pc = checkers_local[I] - pc.forceMove(turfs[I]) - else - qdel(checkers_local[I]) //delete the leftovers - - if(old_checkers_len < turfs_len) - //create what we lack - for(var/I in (old_checkers_used + 1) to turfs_len) - checkers_local += new /obj/effect/abstract/proximity_checker(turfs[I], src) - else - checkers_local.Cut(old_checkers_used + 1, old_checkers_len) - -/obj/effect/abstract/proximity_checker - invisibility = INVISIBILITY_ABSTRACT - anchored = TRUE - var/datum/proximity_monitor/monitor - -/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/proximity_monitor/_monitor) - . = ..() - if(_monitor) - monitor = _monitor - else - stack_trace("proximity_checker created without host") - return INITIALIZE_HINT_QDEL - -/obj/effect/abstract/proximity_checker/Destroy() - monitor = null - return ..() - -/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM) - set waitfor = FALSE - monitor.hasprox_receiver.HasProximity(AM) +/datum/proximity_monitor + var/atom/host //the atom we are tracking + var/atom/hasprox_receiver //the atom that will receive HasProximity calls. + var/atom/last_host_loc + var/list/checkers //list of /obj/effect/abstract/proximity_checkers + var/current_range + var/ignore_if_not_on_turf //don't check turfs in range if the host's loc isn't a turf + var/wire = FALSE + +/datum/proximity_monitor/New(atom/_host, range, _ignore_if_not_on_turf = TRUE) + checkers = list() + last_host_loc = _host.loc + ignore_if_not_on_turf = _ignore_if_not_on_turf + current_range = range + SetHost(_host) + +/datum/proximity_monitor/proc/SetHost(atom/H,atom/R) + if(H == host) + return + if(host) + UnregisterSignal(host, COMSIG_MOVABLE_MOVED) + if(R) + hasprox_receiver = R + else if(hasprox_receiver == host) //Default case + hasprox_receiver = H + host = H + RegisterSignal(host, COMSIG_MOVABLE_MOVED, .proc/HandleMove) + last_host_loc = host.loc + SetRange(current_range,TRUE) + +/datum/proximity_monitor/Destroy() + host = null + last_host_loc = null + hasprox_receiver = null + QDEL_LIST(checkers) + return ..() + +/datum/proximity_monitor/proc/HandleMove() + var/atom/_host = host + var/atom/new_host_loc = _host.loc + if(last_host_loc != new_host_loc) + last_host_loc = new_host_loc //hopefully this won't cause GC issues with containers + var/curr_range = current_range + SetRange(curr_range, TRUE) + if(curr_range) + testing("HasProx: [host] -> [host]") + hasprox_receiver.HasProximity(host) //if we are processing, we're guaranteed to be a movable + +/datum/proximity_monitor/proc/SetRange(range, force_rebuild = FALSE) + if(!force_rebuild && range == current_range) + return FALSE + . = TRUE + + current_range = range + + var/list/checkers_local = checkers + var/old_checkers_len = checkers_local.len + + var/atom/_host = host + + var/atom/loc_to_use = ignore_if_not_on_turf ? _host.loc : get_turf(_host) + if(wire && !isturf(loc_to_use)) //it makes assemblies attached on wires work + loc_to_use = get_turf(loc_to_use) + if(!isturf(loc_to_use)) //only check the host's loc + if(range) + var/obj/effect/abstract/proximity_checker/pc + if(old_checkers_len) + pc = checkers_local[old_checkers_len] + --checkers_local.len + QDEL_LIST(checkers_local) + else + pc = new(loc_to_use, src) + + checkers_local += pc //only check the host's loc + return + + var/list/turfs = RANGE_TURFS(range, loc_to_use) + var/turfs_len = turfs.len + var/old_checkers_used = min(turfs_len, old_checkers_len) + + //reuse what we can + for(var/I in 1 to old_checkers_len) + if(I <= old_checkers_used) + var/obj/effect/abstract/proximity_checker/pc = checkers_local[I] + pc.forceMove(turfs[I]) + else + qdel(checkers_local[I]) //delete the leftovers + + if(old_checkers_len < turfs_len) + //create what we lack + for(var/I in (old_checkers_used + 1) to turfs_len) + checkers_local += new /obj/effect/abstract/proximity_checker(turfs[I], src) + else + checkers_local.Cut(old_checkers_used + 1, old_checkers_len) + +/obj/effect/abstract/proximity_checker + invisibility = INVISIBILITY_ABSTRACT + anchored = TRUE + var/datum/proximity_monitor/monitor + +/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/proximity_monitor/_monitor) + . = ..() + if(_monitor) + monitor = _monitor + else + stack_trace("proximity_checker created without host") + return INITIALIZE_HINT_QDEL + +/obj/effect/abstract/proximity_checker/Destroy() + monitor = null + return ..() + +/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM) + set waitfor = FALSE + monitor.hasprox_receiver.HasProximity(AM) diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm index b1bb3e6b4d80..c96a047d6f77 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -1,67 +1,67 @@ -#define CELSIUS_TO_KELVIN(T_K) ((T_K) + T0C) - -#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT]) / (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT] + (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT] / CELSIUS_TO_KELVIN(TEMP_O))) -#define OPTIMAL_TEMP_K_PLA_BURN_RATIO(PRESSURE_P,PRESSURE_O,TEMP_O) (CELSIUS_TO_KELVIN(TEMP_O) * PLASMA_OXYGEN_FULLBURN * (PRESSURE_P) / (PRESSURE_O)) - -/obj/effect/spawner/newbomb - name = "bomb" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x" - var/temp_p = 1500 - var/temp_o = 1000 // tank temperatures - var/pressure_p = 10 * ONE_ATMOSPHERE - var/pressure_o = 10 * ONE_ATMOSPHERE //tank pressures - var/assembly_type - -/obj/effect/spawner/newbomb/Initialize() - . = ..() - var/obj/item/transfer_valve/V = new(src.loc) - var/obj/item/tank/internals/plasma/PT = new(V) - var/obj/item/tank/internals/oxygen/OT = new(V) - - PT.air_contents.assert_gas(/datum/gas/plasma) - PT.air_contents.gases[/datum/gas/plasma][MOLES] = pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p)) - PT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_p) - - OT.air_contents.assert_gas(/datum/gas/oxygen) - OT.air_contents.gases[/datum/gas/oxygen][MOLES] = pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o)) - OT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_o) - - V.tank_one = PT - V.tank_two = OT - PT.master = V - OT.master = V - - if(assembly_type) - var/obj/item/assembly/A = new assembly_type(V) - V.attached_device = A - A.holder = V - - V.update_icon() - - return INITIALIZE_HINT_QDEL - -/obj/effect/spawner/newbomb/timer/syndicate/Initialize() - temp_p = (OPTIMAL_TEMP_K_PLA_BURN_SCALE(pressure_p, pressure_o, temp_o)/2 + OPTIMAL_TEMP_K_PLA_BURN_RATIO(pressure_p, pressure_o, temp_o)/2) - T0C - . = ..() - -/obj/effect/spawner/newbomb/timer - assembly_type = /obj/item/assembly/timer - -/obj/effect/spawner/newbomb/timer/syndicate - pressure_o = TANK_LEAK_PRESSURE - 1 - temp_o = 20 - - pressure_p = TANK_LEAK_PRESSURE - 1 - -/obj/effect/spawner/newbomb/proximity - assembly_type = /obj/item/assembly/prox_sensor - -/obj/effect/spawner/newbomb/radio - assembly_type = /obj/item/assembly/signaler - - -#undef CELSIUS_TO_KELVIN - -#undef OPTIMAL_TEMP_K_PLA_BURN_SCALE -#undef OPTIMAL_TEMP_K_PLA_BURN_RATIO +#define CELSIUS_TO_KELVIN(T_K) ((T_K) + T0C) + +#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT]) / (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT] + (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT] / CELSIUS_TO_KELVIN(TEMP_O))) +#define OPTIMAL_TEMP_K_PLA_BURN_RATIO(PRESSURE_P,PRESSURE_O,TEMP_O) (CELSIUS_TO_KELVIN(TEMP_O) * PLASMA_OXYGEN_FULLBURN * (PRESSURE_P) / (PRESSURE_O)) + +/obj/effect/spawner/newbomb + name = "bomb" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x" + var/temp_p = 1500 + var/temp_o = 1000 // tank temperatures + var/pressure_p = 10 * ONE_ATMOSPHERE + var/pressure_o = 10 * ONE_ATMOSPHERE //tank pressures + var/assembly_type + +/obj/effect/spawner/newbomb/Initialize() + . = ..() + var/obj/item/transfer_valve/V = new(src.loc) + var/obj/item/tank/internals/plasma/PT = new(V) + var/obj/item/tank/internals/oxygen/OT = new(V) + + PT.air_contents.assert_gas(/datum/gas/plasma) + PT.air_contents.gases[/datum/gas/plasma][MOLES] = pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p)) + PT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_p) + + OT.air_contents.assert_gas(/datum/gas/oxygen) + OT.air_contents.gases[/datum/gas/oxygen][MOLES] = pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o)) + OT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_o) + + V.tank_one = PT + V.tank_two = OT + PT.master = V + OT.master = V + + if(assembly_type) + var/obj/item/assembly/A = new assembly_type(V) + V.attached_device = A + A.holder = V + + V.update_icon() + + return INITIALIZE_HINT_QDEL + +/obj/effect/spawner/newbomb/timer/syndicate/Initialize() + temp_p = (OPTIMAL_TEMP_K_PLA_BURN_SCALE(pressure_p, pressure_o, temp_o)/2 + OPTIMAL_TEMP_K_PLA_BURN_RATIO(pressure_p, pressure_o, temp_o)/2) - T0C + . = ..() + +/obj/effect/spawner/newbomb/timer + assembly_type = /obj/item/assembly/timer + +/obj/effect/spawner/newbomb/timer/syndicate + pressure_o = TANK_LEAK_PRESSURE - 1 + temp_o = 20 + + pressure_p = TANK_LEAK_PRESSURE - 1 + +/obj/effect/spawner/newbomb/proximity + assembly_type = /obj/item/assembly/prox_sensor + +/obj/effect/spawner/newbomb/radio + assembly_type = /obj/item/assembly/signaler + + +#undef CELSIUS_TO_KELVIN + +#undef OPTIMAL_TEMP_K_PLA_BURN_SCALE +#undef OPTIMAL_TEMP_K_PLA_BURN_RATIO diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm index 0941ad32575e..4413f73a7a87 100644 --- a/code/game/objects/effects/spawners/lootdrop.dm +++ b/code/game/objects/effects/spawners/lootdrop.dm @@ -1,358 +1,358 @@ -/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/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/armory_contraband/donutstation - loot = list(/obj/item/grenade/clusterbuster/teargas = 5, - /obj/item/gun/ballistic/shotgun/automatic/combat = 5, - /obj/item/bikehorn/golden, - /obj/item/grenade/clusterbuster, - /obj/item/storage/box/syndie_kit/throwing_weapons = 3) - -/obj/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/maintenance/two - name = "2 x maintenance loot spawner" - lootcount = 2 - -/obj/effect/spawner/lootdrop/maintenance/three - name = "3 x maintenance loot spawner" - lootcount = 3 - -/obj/effect/spawner/lootdrop/maintenance/four - name = "4 x maintenance loot spawner" - lootcount = 4 - -/obj/effect/spawner/lootdrop/maintenance/five - name = "5 x maintenance loot spawner" - lootcount = 5 - -/obj/effect/spawner/lootdrop/maintenance/six - name = "6 x maintenance loot spawner" - lootcount = 6 - -/obj/effect/spawner/lootdrop/maintenance/seven - name = "7 x maintenance loot spawner" - lootcount = 7 - -/obj/effect/spawner/lootdrop/maintenance/eight - name = "8 x maintenance loot spawner" - lootcount = 8 - -/obj/effect/spawner/lootdrop/crate_spawner - name = "lootcrate spawner" //USE PROMO CODE "SELLOUT" FOR 20% OFF! - lootdoubles = FALSE - - loot = list( - /obj/structure/closet/crate/secure/loot = 20, - "" = 80 - ) - -/obj/effect/spawner/lootdrop/organ_spawner - name = "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/pop = 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 - ) - -// Tech storage circuit board spawners - -/obj/effect/spawner/lootdrop/techstorage - name = "generic circuit board spawner" - lootdoubles = FALSE - fan_out_items = TRUE - lootcount = INFINITY - -/obj/effect/spawner/lootdrop/techstorage/service - name = "service circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/arcade/battle, - /obj/item/circuitboard/computer/arcade/orion_trail, - /obj/item/circuitboard/machine/autolathe, - /obj/item/circuitboard/computer/mining, - /obj/item/circuitboard/machine/ore_redemption, - /obj/item/circuitboard/machine/mining_equipment_vendor, - /obj/item/circuitboard/machine/microwave, - /obj/item/circuitboard/machine/chem_dispenser/drinks, - /obj/item/circuitboard/machine/chem_dispenser/drinks/beer, - /obj/item/circuitboard/computer/slot_machine - ) - -/obj/effect/spawner/lootdrop/techstorage/rnd - name = "RnD circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/aifixer, - /obj/item/circuitboard/machine/rdserver, - /obj/item/circuitboard/machine/mechfab, - /obj/item/circuitboard/machine/circuit_imprinter/department, - /obj/item/circuitboard/computer/teleporter, - /obj/item/circuitboard/machine/destructive_analyzer, - /obj/item/circuitboard/computer/rdconsole, - /obj/item/circuitboard/computer/nanite_chamber_control, - /obj/item/circuitboard/computer/nanite_cloud_controller, - /obj/item/circuitboard/machine/nanite_chamber, - /obj/item/circuitboard/machine/nanite_programmer, - /obj/item/circuitboard/machine/nanite_program_hub - ) - -/obj/effect/spawner/lootdrop/techstorage/security - name = "security circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/secure_data, - /obj/item/circuitboard/computer/security, - /obj/item/circuitboard/computer/prisoner - ) - -/obj/effect/spawner/lootdrop/techstorage/engineering - name = "engineering circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/atmos_alert, - /obj/item/circuitboard/computer/stationalert, - /obj/item/circuitboard/computer/powermonitor - ) - -/obj/effect/spawner/lootdrop/techstorage/tcomms - name = "tcomms circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/message_monitor, - /obj/item/circuitboard/machine/telecomms/broadcaster, - /obj/item/circuitboard/machine/telecomms/bus, - /obj/item/circuitboard/machine/telecomms/server, - /obj/item/circuitboard/machine/telecomms/receiver, - /obj/item/circuitboard/machine/telecomms/processor, - /obj/item/circuitboard/machine/announcement_system, - /obj/item/circuitboard/computer/comm_server, - /obj/item/circuitboard/computer/comm_monitor - ) - -/obj/effect/spawner/lootdrop/techstorage/medical - name = "medical circuit board spawner" - loot = list( - /obj/item/circuitboard/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/item/circuitboard/computer/pandemic - ) - -/obj/effect/spawner/lootdrop/techstorage/AI - name = "secure AI circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/aiupload, - /obj/item/circuitboard/computer/borgupload, - /obj/item/circuitboard/aicore - ) - -/obj/effect/spawner/lootdrop/techstorage/command - name = "secure command circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/crew, - /obj/item/circuitboard/computer/communications, - /obj/item/circuitboard/computer/card - ) - -/obj/effect/spawner/lootdrop/techstorage/RnD_secure - name = "secure RnD circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/mecha_control, - /obj/item/circuitboard/computer/apc_control, - /obj/item/circuitboard/computer/robotics - ) +/obj/effect/spawner/lootdrop + 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/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/armory_contraband/donutstation + loot = list(/obj/item/grenade/clusterbuster/teargas = 5, + /obj/item/gun/ballistic/shotgun/automatic/combat = 5, + /obj/item/bikehorn/golden, + /obj/item/grenade/clusterbuster, + /obj/item/storage/box/syndie_kit/throwing_weapons = 3) + +/obj/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/maintenance/two + name = "2 x maintenance loot spawner" + lootcount = 2 + +/obj/effect/spawner/lootdrop/maintenance/three + name = "3 x maintenance loot spawner" + lootcount = 3 + +/obj/effect/spawner/lootdrop/maintenance/four + name = "4 x maintenance loot spawner" + lootcount = 4 + +/obj/effect/spawner/lootdrop/maintenance/five + name = "5 x maintenance loot spawner" + lootcount = 5 + +/obj/effect/spawner/lootdrop/maintenance/six + name = "6 x maintenance loot spawner" + lootcount = 6 + +/obj/effect/spawner/lootdrop/maintenance/seven + name = "7 x maintenance loot spawner" + lootcount = 7 + +/obj/effect/spawner/lootdrop/maintenance/eight + name = "8 x maintenance loot spawner" + lootcount = 8 + +/obj/effect/spawner/lootdrop/crate_spawner + name = "lootcrate spawner" //USE PROMO CODE "SELLOUT" FOR 20% OFF! + lootdoubles = FALSE + + loot = list( + /obj/structure/closet/crate/secure/loot = 20, + "" = 80 + ) + +/obj/effect/spawner/lootdrop/organ_spawner + name = "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/pop = 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 + ) + +// Tech storage circuit board spawners + +/obj/effect/spawner/lootdrop/techstorage + name = "generic circuit board spawner" + lootdoubles = FALSE + fan_out_items = TRUE + lootcount = INFINITY + +/obj/effect/spawner/lootdrop/techstorage/service + name = "service circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/arcade/battle, + /obj/item/circuitboard/computer/arcade/orion_trail, + /obj/item/circuitboard/machine/autolathe, + /obj/item/circuitboard/computer/mining, + /obj/item/circuitboard/machine/ore_redemption, + /obj/item/circuitboard/machine/mining_equipment_vendor, + /obj/item/circuitboard/machine/microwave, + /obj/item/circuitboard/machine/chem_dispenser/drinks, + /obj/item/circuitboard/machine/chem_dispenser/drinks/beer, + /obj/item/circuitboard/computer/slot_machine + ) + +/obj/effect/spawner/lootdrop/techstorage/rnd + name = "RnD circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/aifixer, + /obj/item/circuitboard/machine/rdserver, + /obj/item/circuitboard/machine/mechfab, + /obj/item/circuitboard/machine/circuit_imprinter/department, + /obj/item/circuitboard/computer/teleporter, + /obj/item/circuitboard/machine/destructive_analyzer, + /obj/item/circuitboard/computer/rdconsole, + /obj/item/circuitboard/computer/nanite_chamber_control, + /obj/item/circuitboard/computer/nanite_cloud_controller, + /obj/item/circuitboard/machine/nanite_chamber, + /obj/item/circuitboard/machine/nanite_programmer, + /obj/item/circuitboard/machine/nanite_program_hub + ) + +/obj/effect/spawner/lootdrop/techstorage/security + name = "security circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/secure_data, + /obj/item/circuitboard/computer/security, + /obj/item/circuitboard/computer/prisoner + ) + +/obj/effect/spawner/lootdrop/techstorage/engineering + name = "engineering circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/atmos_alert, + /obj/item/circuitboard/computer/stationalert, + /obj/item/circuitboard/computer/powermonitor + ) + +/obj/effect/spawner/lootdrop/techstorage/tcomms + name = "tcomms circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/message_monitor, + /obj/item/circuitboard/machine/telecomms/broadcaster, + /obj/item/circuitboard/machine/telecomms/bus, + /obj/item/circuitboard/machine/telecomms/server, + /obj/item/circuitboard/machine/telecomms/receiver, + /obj/item/circuitboard/machine/telecomms/processor, + /obj/item/circuitboard/machine/announcement_system, + /obj/item/circuitboard/computer/comm_server, + /obj/item/circuitboard/computer/comm_monitor + ) + +/obj/effect/spawner/lootdrop/techstorage/medical + name = "medical circuit board spawner" + loot = list( + /obj/item/circuitboard/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/item/circuitboard/computer/pandemic + ) + +/obj/effect/spawner/lootdrop/techstorage/AI + name = "secure AI circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/aiupload, + /obj/item/circuitboard/computer/borgupload, + /obj/item/circuitboard/aicore + ) + +/obj/effect/spawner/lootdrop/techstorage/command + name = "secure command circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/crew, + /obj/item/circuitboard/computer/communications, + /obj/item/circuitboard/computer/card + ) + +/obj/effect/spawner/lootdrop/techstorage/RnD_secure + name = "secure RnD circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/mecha_control, + /obj/item/circuitboard/computer/apc_control, + /obj/item/circuitboard/computer/robotics + ) diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index f96c96e9d56f..fbbfcab41c32 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -1,231 +1,231 @@ -//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 = /datum/reagent/toxin - var/poison_per_bite = 5 - var/list/faction = list("spiders") - -/obj/structure/spider/eggcluster/Initialize() - pixel_x = rand(3,-3) - pixel_y = rand(3,-3) - START_PROCESSING(SSobj, src) - . = ..() - -/obj/structure/spider/eggcluster/process() - amount_grown += rand(0,2) - if(amount_grown >= 100) - var/num = rand(3,12) - for(var/i=0, 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.faction = faction.Copy() - S.directive = directive - if(player_spiders) - S.playable_spider = TRUE - notify_ghosts("Spider [S.name] can be controlled", null, enter_link="(Click to play)", source=S, action=NOTIFY_ATTACK, ignore_key = POLL_IGNORE_SPIDER) - qdel(src) - - - -/obj/structure/spider/cocoon - name = "cocoon" - desc = "Something wrapped in silky spider web." - icon_state = "cocoon1" - max_integrity = 60 - -/obj/structure/spider/cocoon/Initialize() - icon_state = pick("cocoon1","cocoon2","cocoon3") - . = ..() - -/obj/structure/spider/cocoon/container_resist(mob/living/user) - var/breakout_time = 600 - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - to_chat(user, "You struggle against the tight bonds... (This will take about [DisplayTimeText(breakout_time)].)") - visible_message("You see something struggling and writhing in \the [src]!") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src) - return - qdel(src) - - - -/obj/structure/spider/cocoon/Destroy() - var/turf/T = get_turf(src) - src.visible_message("\The [src] splits open.") - for(var/atom/movable/A in contents) - A.forceMove(T) - return ..() +//generic procs copied from obj/effect/alien +/obj/structure/spider + name = "web" + icon = 'icons/effects/effects.dmi' + desc = "It's stringy and sticky." + anchored = TRUE + density = FALSE + max_integrity = 15 + + + +/obj/structure/spider/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + if(damage_type == BURN)//the stickiness of the web mutes all attack sounds except fire damage type + playsound(loc, 'sound/items/welder.ogg', 100, 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 = /datum/reagent/toxin + var/poison_per_bite = 5 + var/list/faction = list("spiders") + +/obj/structure/spider/eggcluster/Initialize() + pixel_x = rand(3,-3) + pixel_y = rand(3,-3) + START_PROCESSING(SSobj, src) + . = ..() + +/obj/structure/spider/eggcluster/process() + amount_grown += rand(0,2) + if(amount_grown >= 100) + var/num = rand(3,12) + for(var/i=0, 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.faction = faction.Copy() + S.directive = directive + if(player_spiders) + S.playable_spider = TRUE + notify_ghosts("Spider [S.name] can be controlled", null, enter_link="(Click to play)", source=S, action=NOTIFY_ATTACK, ignore_key = POLL_IGNORE_SPIDER) + qdel(src) + + + +/obj/structure/spider/cocoon + name = "cocoon" + desc = "Something wrapped in silky spider web." + icon_state = "cocoon1" + max_integrity = 60 + +/obj/structure/spider/cocoon/Initialize() + icon_state = pick("cocoon1","cocoon2","cocoon3") + . = ..() + +/obj/structure/spider/cocoon/container_resist(mob/living/user) + var/breakout_time = 600 + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + to_chat(user, "You struggle against the tight bonds... (This will take about [DisplayTimeText(breakout_time)].)") + visible_message("You see something struggling and writhing in \the [src]!") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src) + return + qdel(src) + + + +/obj/structure/spider/cocoon/Destroy() + var/turf/T = get_turf(src) + src.visible_message("\The [src] splits open.") + for(var/atom/movable/A in contents) + A.forceMove(T) + return ..() diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm index b2d81dce0a6e..18b2e61c9776 100644 --- a/code/game/objects/effects/step_triggers.dm +++ b/code/game/objects/effects/step_triggers.dm @@ -1,200 +1,200 @@ -/* Simple object type, calls a proc when "stepped" on by something */ - -/obj/effect/step_trigger - var/affect_ghosts = 0 - var/stopper = 1 // stops throwers - var/mobs_only = FALSE - invisibility = INVISIBILITY_ABSTRACT // nope cant see this shit - anchored = TRUE - -/obj/effect/step_trigger/proc/Trigger(atom/movable/A) - return 0 - -/obj/effect/step_trigger/Crossed(H as mob|obj) - ..() - if(!H) - return - if(isobserver(H) && !affect_ghosts) - return - if(!ismob(H) && mobs_only) - return - Trigger(H) - - -/obj/effect/step_trigger/singularity_act() - return - -/obj/effect/step_trigger/singularity_pull() - return - -/* Sends a message to mob when triggered*/ - -/obj/effect/step_trigger/message - var/message //the message to give to the mob - var/once = 1 - mobs_only = TRUE - -/obj/effect/step_trigger/message/Trigger(mob/M) - if(M.client) - to_chat(M, "[message]") - if(once) - qdel(src) - -/* Tosses things in a certain direction */ - -/obj/effect/step_trigger/thrower - var/direction = SOUTH // the direction of throw - var/tiles = 3 // if 0: forever until atom hits a stopper - var/immobilize = 1 // if nonzero: prevents mobs from moving while they're being flung - var/speed = 1 // delay of movement - var/facedir = 0 // if 1: atom faces the direction of movement - var/nostop = 0 // if 1: will only be stopped by teleporters - var/list/affecting = list() - -/obj/effect/step_trigger/thrower/Trigger(atom/A) - if(!A || !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(isliving(AM)) - var/mob/living/M = AM - if(immobilize) - M.mobility_flags &= ~MOBILITY_MOVE - - affecting.Add(AM) - while(AM && !stopthrow) - if(tiles) - if(curtiles >= tiles) - break - if(AM.z != src.z) - break - - curtiles++ - - sleep(speed) - - // Calculate if we should stop the process - if(!nostop) - for(var/obj/effect/step_trigger/T in get_step(AM, direction)) - if(T.stopper && T != src) - stopthrow = 1 - else - for(var/obj/effect/step_trigger/teleporter/T in get_step(AM, direction)) - if(T.stopper) - stopthrow = 1 - - if(AM) - var/predir = AM.dir - step(AM, direction) - if(!facedir) - AM.setDir(predir) - - - - affecting.Remove(AM) - - if(isliving(AM)) - var/mob/living/M = AM - if(immobilize) - M.mobility_flags |= MOBILITY_MOVE - M.update_mobility() - -/* Stops things thrown by a thrower, doesn't do anything */ - -/obj/effect/step_trigger/stopper - -/* Instant teleporter */ - -/obj/effect/step_trigger/teleporter - var/teleport_x = 0 // teleportation coordinates (if one is null, then no teleport!) - var/teleport_y = 0 - var/teleport_z = 0 - -/obj/effect/step_trigger/teleporter/Trigger(atom/movable/A) - if(teleport_x && teleport_y && teleport_z) - - var/turf/T = locate(teleport_x, teleport_y, teleport_z) - A.forceMove(T) - -/* Random teleporter, teleports atoms to locations ranging from teleport_x - teleport_x_offset, etc */ - -/obj/effect/step_trigger/teleporter/random - var/teleport_x_offset = 0 - var/teleport_y_offset = 0 - var/teleport_z_offset = 0 - -/obj/effect/step_trigger/teleporter/random/Trigger(atom/movable/A) - if(teleport_x && teleport_y && teleport_z) - if(teleport_x_offset && teleport_y_offset && teleport_z_offset) - - var/turf/T = locate(rand(teleport_x, teleport_x_offset), rand(teleport_y, teleport_y_offset), rand(teleport_z, teleport_z_offset)) - if (T) - A.forceMove(T) - -/* Fancy teleporter, creates sparks and smokes when used */ - -/obj/effect/step_trigger/teleport_fancy - var/locationx - var/locationy - var/uses = 1 //0 for infinite uses - var/entersparks = 0 - var/exitsparks = 0 - var/entersmoke = 0 - var/exitsmoke = 0 - -/obj/effect/step_trigger/teleport_fancy/Trigger(mob/M) - var/dest = locate(locationx, locationy, z) - M.Move(dest) - - if(entersparks) - var/datum/effect_system/spark_spread/s = new - s.set_up(4, 1, src) - s.start() - if(exitsparks) - var/datum/effect_system/spark_spread/s = new - s.set_up(4, 1, dest) - s.start() - - if(entersmoke) - var/datum/effect_system/smoke_spread/s = new - s.set_up(4, 1, src, 0) - s.start() - if(exitsmoke) - var/datum/effect_system/smoke_spread/s = new - s.set_up(4, 1, dest, 0) - s.start() - - uses-- - if(uses == 0) - qdel(src) - -/* Simple sound player, Mapper friendly! */ - -/obj/effect/step_trigger/sound_effect - var/sound //eg. path to the sound, inside '' eg: 'growl.ogg' - var/volume = 100 - var/freq_vary = 1 //Should the frequency of the sound vary? - var/extra_range = 0 // eg World.view = 7, extra_range = 1, 7+1 = 8, 8 turfs radius - var/happens_once = 0 - var/triggerer_only = 0 //Whether the triggerer is the only person who hears this - - -/obj/effect/step_trigger/sound_effect/Trigger(atom/movable/A) - var/turf/T = get_turf(A) - - if(!T) - return - - if(triggerer_only && ismob(A)) - var/mob/B = A - B.playsound_local(T, sound, volume, freq_vary) - else - playsound(T, sound, volume, freq_vary, extra_range) - - if(happens_once) - qdel(src) +/* Simple object type, calls a proc when "stepped" on by something */ + +/obj/effect/step_trigger + var/affect_ghosts = 0 + var/stopper = 1 // stops throwers + var/mobs_only = FALSE + invisibility = INVISIBILITY_ABSTRACT // nope cant see this shit + anchored = TRUE + +/obj/effect/step_trigger/proc/Trigger(atom/movable/A) + return 0 + +/obj/effect/step_trigger/Crossed(H as mob|obj) + ..() + if(!H) + return + if(isobserver(H) && !affect_ghosts) + return + if(!ismob(H) && mobs_only) + return + Trigger(H) + + +/obj/effect/step_trigger/singularity_act() + return + +/obj/effect/step_trigger/singularity_pull() + return + +/* Sends a message to mob when triggered*/ + +/obj/effect/step_trigger/message + var/message //the message to give to the mob + var/once = 1 + mobs_only = TRUE + +/obj/effect/step_trigger/message/Trigger(mob/M) + if(M.client) + to_chat(M, "[message]") + if(once) + qdel(src) + +/* Tosses things in a certain direction */ + +/obj/effect/step_trigger/thrower + var/direction = SOUTH // the direction of throw + var/tiles = 3 // if 0: forever until atom hits a stopper + var/immobilize = 1 // if nonzero: prevents mobs from moving while they're being flung + var/speed = 1 // delay of movement + var/facedir = 0 // if 1: atom faces the direction of movement + var/nostop = 0 // if 1: will only be stopped by teleporters + var/list/affecting = list() + +/obj/effect/step_trigger/thrower/Trigger(atom/A) + if(!A || !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(isliving(AM)) + var/mob/living/M = AM + if(immobilize) + M.mobility_flags &= ~MOBILITY_MOVE + + affecting.Add(AM) + while(AM && !stopthrow) + if(tiles) + if(curtiles >= tiles) + break + if(AM.z != src.z) + break + + curtiles++ + + sleep(speed) + + // Calculate if we should stop the process + if(!nostop) + for(var/obj/effect/step_trigger/T in get_step(AM, direction)) + if(T.stopper && T != src) + stopthrow = 1 + else + for(var/obj/effect/step_trigger/teleporter/T in get_step(AM, direction)) + if(T.stopper) + stopthrow = 1 + + if(AM) + var/predir = AM.dir + step(AM, direction) + if(!facedir) + AM.setDir(predir) + + + + affecting.Remove(AM) + + if(isliving(AM)) + var/mob/living/M = AM + if(immobilize) + M.mobility_flags |= MOBILITY_MOVE + M.update_mobility() + +/* Stops things thrown by a thrower, doesn't do anything */ + +/obj/effect/step_trigger/stopper + +/* Instant teleporter */ + +/obj/effect/step_trigger/teleporter + var/teleport_x = 0 // teleportation coordinates (if one is null, then no teleport!) + var/teleport_y = 0 + var/teleport_z = 0 + +/obj/effect/step_trigger/teleporter/Trigger(atom/movable/A) + if(teleport_x && teleport_y && teleport_z) + + var/turf/T = locate(teleport_x, teleport_y, teleport_z) + A.forceMove(T) + +/* Random teleporter, teleports atoms to locations ranging from teleport_x - teleport_x_offset, etc */ + +/obj/effect/step_trigger/teleporter/random + var/teleport_x_offset = 0 + var/teleport_y_offset = 0 + var/teleport_z_offset = 0 + +/obj/effect/step_trigger/teleporter/random/Trigger(atom/movable/A) + if(teleport_x && teleport_y && teleport_z) + if(teleport_x_offset && teleport_y_offset && teleport_z_offset) + + var/turf/T = locate(rand(teleport_x, teleport_x_offset), rand(teleport_y, teleport_y_offset), rand(teleport_z, teleport_z_offset)) + if (T) + A.forceMove(T) + +/* Fancy teleporter, creates sparks and smokes when used */ + +/obj/effect/step_trigger/teleport_fancy + var/locationx + var/locationy + var/uses = 1 //0 for infinite uses + var/entersparks = 0 + var/exitsparks = 0 + var/entersmoke = 0 + var/exitsmoke = 0 + +/obj/effect/step_trigger/teleport_fancy/Trigger(mob/M) + var/dest = locate(locationx, locationy, z) + M.Move(dest) + + if(entersparks) + var/datum/effect_system/spark_spread/s = new + s.set_up(4, 1, src) + s.start() + if(exitsparks) + var/datum/effect_system/spark_spread/s = new + s.set_up(4, 1, dest) + s.start() + + if(entersmoke) + var/datum/effect_system/smoke_spread/s = new + s.set_up(4, 1, src, 0) + s.start() + if(exitsmoke) + var/datum/effect_system/smoke_spread/s = new + s.set_up(4, 1, dest, 0) + s.start() + + uses-- + if(uses == 0) + qdel(src) + +/* Simple sound player, Mapper friendly! */ + +/obj/effect/step_trigger/sound_effect + var/sound //eg. path to the sound, inside '' eg: 'growl.ogg' + var/volume = 100 + var/freq_vary = 1 //Should the frequency of the sound vary? + var/extra_range = 0 // eg World.view = 7, extra_range = 1, 7+1 = 8, 8 turfs radius + var/happens_once = 0 + var/triggerer_only = 0 //Whether the triggerer is the only person who hears this + + +/obj/effect/step_trigger/sound_effect/Trigger(atom/movable/A) + var/turf/T = get_turf(A) + + if(!T) + return + + if(triggerer_only && ismob(A)) + var/mob/B = A + B.playsound_local(T, sound, volume, freq_vary) + else + playsound(T, sound, volume, freq_vary, extra_range) + + if(happens_once) + qdel(src) diff --git a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm index a7c3d270e787..875eaf5e60a1 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm @@ -1,38 +1,38 @@ -/obj/effect/projectile/impact - name = "beam impact" - icon = 'icons/obj/projectiles_impact.dmi' - -/obj/effect/projectile/impact/laser - name = "laser impact" - icon_state = "impact_laser" - -/obj/effect/projectile/impact/laser/blue - name = "laser impact" - icon_state = "impact_blue" - -/obj/effect/projectile/impact/disabler - name = "disabler impact" - icon_state = "impact_omni" - -/obj/effect/projectile/impact/xray - name = "\improper X-ray impact" - icon_state = "impact_xray" - -/obj/effect/projectile/impact/pulse - name = "pulse impact" - icon_state = "impact_u_laser" - -/obj/effect/projectile/impact/plasma_cutter - name = "plasma impact" - icon_state = "impact_plasmacutter" - -/obj/effect/projectile/impact/stun - name = "stun impact" - icon_state = "impact_stun" - -/obj/effect/projectile/impact/heavy_laser - name = "heavy laser impact" - icon_state = "impact_beam_heavy" - -/obj/effect/projectile/impact/wormhole - icon_state = "wormhole_g" +/obj/effect/projectile/impact + name = "beam impact" + icon = 'icons/obj/projectiles_impact.dmi' + +/obj/effect/projectile/impact/laser + name = "laser impact" + icon_state = "impact_laser" + +/obj/effect/projectile/impact/laser/blue + name = "laser impact" + icon_state = "impact_blue" + +/obj/effect/projectile/impact/disabler + name = "disabler impact" + icon_state = "impact_omni" + +/obj/effect/projectile/impact/xray + name = "\improper X-ray impact" + icon_state = "impact_xray" + +/obj/effect/projectile/impact/pulse + name = "pulse impact" + icon_state = "impact_u_laser" + +/obj/effect/projectile/impact/plasma_cutter + name = "plasma impact" + icon_state = "impact_plasmacutter" + +/obj/effect/projectile/impact/stun + name = "stun impact" + icon_state = "impact_stun" + +/obj/effect/projectile/impact/heavy_laser + name = "heavy laser impact" + icon_state = "impact_beam_heavy" + +/obj/effect/projectile/impact/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm index 133d06b4b687..ad6b23f50416 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm @@ -1,30 +1,30 @@ -/obj/effect/projectile/muzzle - name = "muzzle flash" - icon = 'icons/obj/projectiles_muzzle.dmi' - -/obj/effect/projectile/muzzle/laser - icon_state = "muzzle_laser" - -/obj/effect/projectile/muzzle/laser/blue - icon_state = "muzzle_laser_blue" - -/obj/effect/projectile/muzzle/disabler - icon_state = "muzzle_omni" - -/obj/effect/projectile/muzzle/xray - icon_state = "muzzle_xray" - -/obj/effect/projectile/muzzle/pulse - icon_state = "muzzle_u_laser" - -/obj/effect/projectile/muzzle/plasma_cutter - icon_state = "muzzle_plasmacutter" - -/obj/effect/projectile/muzzle/stun - icon_state = "muzzle_stun" - -/obj/effect/projectile/muzzle/heavy_laser - icon_state = "muzzle_beam_heavy" - -/obj/effect/projectile/muzzle/wormhole - icon_state = "wormhole_g" +/obj/effect/projectile/muzzle + name = "muzzle flash" + icon = 'icons/obj/projectiles_muzzle.dmi' + +/obj/effect/projectile/muzzle/laser + icon_state = "muzzle_laser" + +/obj/effect/projectile/muzzle/laser/blue + icon_state = "muzzle_laser_blue" + +/obj/effect/projectile/muzzle/disabler + icon_state = "muzzle_omni" + +/obj/effect/projectile/muzzle/xray + icon_state = "muzzle_xray" + +/obj/effect/projectile/muzzle/pulse + icon_state = "muzzle_u_laser" + +/obj/effect/projectile/muzzle/plasma_cutter + icon_state = "muzzle_plasmacutter" + +/obj/effect/projectile/muzzle/stun + icon_state = "muzzle_stun" + +/obj/effect/projectile/muzzle/heavy_laser + icon_state = "muzzle_beam_heavy" + +/obj/effect/projectile/muzzle/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm index 103cee137d39..49cdc3667cff 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm @@ -1,60 +1,60 @@ -/obj/effect/projectile - name = "pew" - icon = 'icons/obj/projectiles.dmi' - icon_state = "nothing" - layer = ABOVE_MOB_LAYER - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - appearance_flags = 0 - -/obj/effect/projectile/singularity_pull() - return - -/obj/effect/projectile/singularity_act() - return - -/obj/effect/projectile/proc/scale_to(nx,ny,override=TRUE) - var/matrix/M - if(!override) - M = transform - else - M = new - M.Scale(nx,ny) - transform = M - -/obj/effect/projectile/proc/turn_to(angle,override=TRUE) - var/matrix/M - if(!override) - M = transform - else - M = new - M.Turn(angle) - transform = M - -/obj/effect/projectile/New(angle_override, p_x, p_y, color_override, scaling = 1) - if(angle_override && p_x && p_y && color_override && scaling) - apply_vars(angle_override, p_x, p_y, color_override, scaling) - return ..() - -/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0) - var/mutable_appearance/look = new(src) - look.pixel_x = p_x - look.pixel_y = p_y - if(color_override) - look.color = color_override - appearance = look - scale_to(1,scaling, FALSE) - turn_to(angle_override, FALSE) - if(!isnull(new_loc)) //If you want to null it just delete it... - forceMove(new_loc) - for(var/i in 1 to increment) - pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) - pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) - -/obj/effect/projectile_lighting - var/owner - -/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key) - . = ..() - set_light(range, intensity, color) - owner = owner_key +/obj/effect/projectile + name = "pew" + icon = 'icons/obj/projectiles.dmi' + icon_state = "nothing" + layer = ABOVE_MOB_LAYER + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + appearance_flags = 0 + +/obj/effect/projectile/singularity_pull() + return + +/obj/effect/projectile/singularity_act() + return + +/obj/effect/projectile/proc/scale_to(nx,ny,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Scale(nx,ny) + transform = M + +/obj/effect/projectile/proc/turn_to(angle,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Turn(angle) + transform = M + +/obj/effect/projectile/New(angle_override, p_x, p_y, color_override, scaling = 1) + if(angle_override && p_x && p_y && color_override && scaling) + apply_vars(angle_override, p_x, p_y, color_override, scaling) + return ..() + +/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0) + var/mutable_appearance/look = new(src) + look.pixel_x = p_x + look.pixel_y = p_y + if(color_override) + look.color = color_override + appearance = look + scale_to(1,scaling, FALSE) + turn_to(angle_override, FALSE) + if(!isnull(new_loc)) //If you want to null it just delete it... + forceMove(new_loc) + for(var/i in 1 to increment) + pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) + pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) + +/obj/effect/projectile_lighting + var/owner + +/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key) + . = ..() + set_light(range, intensity, color) + owner = owner_key diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm index 4111b2ec6f56..23ecf438c4f9 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm @@ -1,68 +1,68 @@ -/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! - if(!istype(starting) || !istype(ending) || !ispath(beam_type)) - return - var/datum/point/midpoint = point_midpoint_points(starting, ending) - var/obj/effect/projectile/tracer/PB = new beam_type - if(isnull(light_color_override)) - light_color_override = color - PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0) - . = PB - if(light_range > 0 && light_intensity > 0) - var/list/turf/line = getline(starting.return_turf(), ending.return_turf()) - tracing_line: - for(var/i in line) - var/turf/T = i - for(var/obj/effect/projectile_lighting/PL in T) - if(PL.owner == instance_key) - continue tracing_line - QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5) - line = null - if(qdel_in) - QDEL_IN(PB, qdel_in) - -/obj/effect/projectile/tracer - name = "beam" - icon = 'icons/obj/projectiles_tracer.dmi' - -/obj/effect/projectile/tracer/laser - name = "laser" - icon_state = "beam" - -/obj/effect/projectile/tracer/laser/blue - icon_state = "beam_blue" - -/obj/effect/projectile/tracer/disabler - name = "disabler" - icon_state = "beam_omni" - -/obj/effect/projectile/tracer/xray - name = "\improper X-ray laser" - icon_state = "xray" - -/obj/effect/projectile/tracer/pulse - name = "pulse laser" - icon_state = "u_laser" - -/obj/effect/projectile/tracer/plasma_cutter - name = "plasma blast" - icon_state = "plasmacutter" - -/obj/effect/projectile/tracer/stun - name = "stun beam" - icon_state = "stun" - -/obj/effect/projectile/tracer/heavy_laser - name = "heavy laser" - icon_state = "beam_heavy" - -//BEAM RIFLE -/obj/effect/projectile/tracer/tracer/beam_rifle - icon_state = "tracer_beam" - -/obj/effect/projectile/tracer/tracer/aiming - icon_state = "pixelbeam_greyscale" - layer = ABOVE_LIGHTING_LAYER - plane = ABOVE_LIGHTING_PLANE - -/obj/effect/projectile/tracer/wormhole - icon_state = "wormhole_g" +/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! + if(!istype(starting) || !istype(ending) || !ispath(beam_type)) + return + var/datum/point/midpoint = point_midpoint_points(starting, ending) + var/obj/effect/projectile/tracer/PB = new beam_type + if(isnull(light_color_override)) + light_color_override = color + PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0) + . = PB + if(light_range > 0 && light_intensity > 0) + var/list/turf/line = getline(starting.return_turf(), ending.return_turf()) + tracing_line: + for(var/i in line) + var/turf/T = i + for(var/obj/effect/projectile_lighting/PL in T) + if(PL.owner == instance_key) + continue tracing_line + QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5) + line = null + if(qdel_in) + QDEL_IN(PB, qdel_in) + +/obj/effect/projectile/tracer + name = "beam" + icon = 'icons/obj/projectiles_tracer.dmi' + +/obj/effect/projectile/tracer/laser + name = "laser" + icon_state = "beam" + +/obj/effect/projectile/tracer/laser/blue + icon_state = "beam_blue" + +/obj/effect/projectile/tracer/disabler + name = "disabler" + icon_state = "beam_omni" + +/obj/effect/projectile/tracer/xray + name = "\improper X-ray laser" + icon_state = "xray" + +/obj/effect/projectile/tracer/pulse + name = "pulse laser" + icon_state = "u_laser" + +/obj/effect/projectile/tracer/plasma_cutter + name = "plasma blast" + icon_state = "plasmacutter" + +/obj/effect/projectile/tracer/stun + name = "stun beam" + icon_state = "stun" + +/obj/effect/projectile/tracer/heavy_laser + name = "heavy laser" + icon_state = "beam_heavy" + +//BEAM RIFLE +/obj/effect/projectile/tracer/tracer/beam_rifle + icon_state = "tracer_beam" + +/obj/effect/projectile/tracer/tracer/aiming + icon_state = "pixelbeam_greyscale" + layer = ABOVE_LIGHTING_LAYER + plane = ABOVE_LIGHTING_PLANE + +/obj/effect/projectile/tracer/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/empulse.dm b/code/game/objects/empulse.dm index 16e13a9fe23f..afc70a0eeaed 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 d7e83f9e0270..39b7017c1d6a 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1,797 +1,797 @@ -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/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/transparent_protection = NONE //you can see someone's mask through their transparent visor, but you can't reach it - - var/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP - - 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/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 - 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()") - -/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()) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - - var/turf/T = loc - loc = null - loc = T - -/obj/item/examine(mob/user) //This might be spammy. Remove? - . = ..() - - . += "[gender == PLURAL ? "They are" : "It is"] a [weightclass2text(w_class)] item." - - if(resistance_flags & INDESTRUCTIBLE) - . += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!" - else - if(resistance_flags & LAVA_PROOF) - . += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!" - if(resistance_flags & (ACID_PROOF | UNACIDABLE)) - . += "[src] looks pretty robust! It'd probably be able to withstand acid!" - if(resistance_flags & FREEZE_PROOF) - . += "[src] is made of cold-resistant materials." - if(resistance_flags & FIRE_PROOF) - . += "[src] is made of fire-retardant materials." - - if(!user.research_scanner) - return - - // Research prospects, including boostable nodes and point values. - // Deliver to a console to know whether the boosts have already been used. - var/list/research_msg = list("Research prospects: ") - 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(QDELETED(src)) //moving it out of the storage to the floor destroyed it. - return - - if(throwing) - throwing.finalize(FALSE) - if(loc == user) - if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src)) - return - - pickup(user) - add_fingerprint(user) - if(!user.put_in_active_hand(src, FALSE, FALSE)) - user.dropItemToGround(src) - -/obj/item/proc/allow_attack_hand_drop(mob/user) - return TRUE - -/obj/item/attack_paw(mob/user) - if(!user) - return - if(anchored) - return - - SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) - - if(throwing) - throwing.finalize(FALSE) - if(loc == user) - if(!user.temporarilyRemoveItemFromInventory(src)) - return - - pickup(user) - add_fingerprint(user) - if(!user.put_in_active_hand(src, FALSE, FALSE)) - user.dropItemToGround(src) - -/obj/item/attack_alien(mob/user) - var/mob/living/carbon/alien/A = user - - if(!A.has_fine_manipulation) - if(src in A.contents) // To stop Aliens having items stuck in their pockets - A.dropItemToGround(src) - to_chat(user, "Your claws aren't capable of such fine manipulation!") - return - attack_paw(A) - -/obj/item/attack_ai(mob/user) - if(istype(src.loc, /obj/item/robot_module)) - //If the item is part of a cyborg module, equip it - if(!iscyborg(user)) - return - var/mob/living/silicon/robot/R = user - if(!R.low_power_mode) //can't equip modules with an empty cell. - R.activate_module(src) - R.hud_used.update_robot_modules_display() - -/obj/item/proc/GetDeconstructableContents() - return GetAllContents() - src - -// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency - -/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) - if(prob(final_block_chance)) - owner.visible_message("[owner] blocks [attack_text] with [src]!") - return 1 - return 0 - -/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language) - return ITALICS | REDUCE_RANGE - -/obj/item/proc/dropped(mob/user) - 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 - -// 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)) //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) - 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)) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - - if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota - usr.UnarmedAttack(src) - -//This proc is executed when someone clicks the on-screen UI button. -//The default action is attack_self(). -//Checks before we get to here are: mob is alive, mob is not restrained, stunned, asleep, resting, laying, item is on the mob. -/obj/item/proc/ui_action_click(mob/user, actiontype) - attack_self(user) - -/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) - - var/is_human_victim - var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD) - if(ishuman(M)) - if(!affecting) //no head! - return - is_human_victim = TRUE - - if(M.is_eyes_covered()) - // you can't stab someone in the eyes wearing a mask! - to_chat(user, "You're going to need to remove [M.p_their()] eye protection first!") - return - - if(isalien(M))//Aliens don't have eyes./N slimes also don't have eyes! - to_chat(user, "You cannot locate any eyes on this creature!") - return - - if(isbrain(M)) - to_chat(user, "You cannot locate any organic eyes on this brain!") - return - - src.add_fingerprint(user) - - playsound(loc, src.hitsound, 30, 1, -1) - - user.do_attack_animation(M) - - 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.Paralyze(40) - if (prob(eyes.damage - 10 + 1)) - M.become_blind(EYE_DAMAGE) - to_chat(M, "You go blind!") - -/obj/item/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FOUR) - throw_at(S,14,3, spin=0) - else - return - -/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(hit_atom && !QDELETED(hit_atom)) - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - if(is_hot() && isliving(hit_atom)) - var/mob/living/L = hit_atom - L.IgniteMob() - var/itempush = 1 - if(w_class < 4) - itempush = 0 //too light to push anything - return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum) - -/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force) - thrownby = thrower - callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own - . = ..(target, range, speed, thrower, spin, diagonals_first, callback, force) - - -/obj/item/proc/after_throw(datum/callback/callback) - if (callback) //call the original callback - . = callback.Invoke() - item_flags &= ~IN_INVENTORY - -/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage - if(!newLoc) - return FALSE - if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE)) - return SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, newLoc, TRUE) - return FALSE - -/obj/item/proc/get_belt_overlay() //Returns the icon used for overlaying the object on a belt - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) - -/obj/item/proc/update_slot_icon() - if(!ismob(loc)) - return - var/mob/owner = loc - var/flags = slot_flags - if(flags & ITEM_SLOT_OCLOTHING) - owner.update_inv_wear_suit() - if(flags & ITEM_SLOT_ICLOTHING) - owner.update_inv_w_uniform() - if(flags & ITEM_SLOT_GLOVES) - owner.update_inv_gloves() - if(flags & ITEM_SLOT_EYES) - owner.update_inv_glasses() - if(flags & ITEM_SLOT_EARS) - owner.update_inv_ears() - if(flags & ITEM_SLOT_MASK) - owner.update_inv_wear_mask() - if(flags & ITEM_SLOT_HEAD) - owner.update_inv_head() - if(flags & ITEM_SLOT_FEET) - owner.update_inv_shoes() - if(flags & ITEM_SLOT_ID) - owner.update_inv_wear_id() - if(flags & ITEM_SLOT_BELT) - owner.update_inv_belt() - if(flags & ITEM_SLOT_BACK) - owner.update_inv_back() - if(flags & ITEM_SLOT_NECK) - owner.update_inv_neck() - -/obj/item/proc/is_hot() - return heat - -/obj/item/proc/is_sharp() - 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, 5) - -/obj/item/proc/ignition_effect(atom/A, mob/user) - if(is_hot()) - . = "[user] lights [A] with [src]." - else - . = "" - -/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - return - -/obj/item/attack_hulk(mob/living/carbon/human/user) - return 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 || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src)) - var/timedelay = usr.client.prefs.tip_delay/100 - var/user = usr - tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it. - -/obj/item/MouseExited() - deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes - closeToolTip(usr) - - -// Called when a mob tries to use the item as a tool. -// Handles most checks. -/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks) - // No delay means there is no start message, and no reason to call tool_start_check before use_tool. - // Run the start check here so we wouldn't have to call it manually. - if(!delay && !tool_start_check(user, amount)) - return - - 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 ..() - -/obj/item/proc/canStrip(mob/stripper, mob/owner) - return !HAS_TRAIT(src, TRAIT_NODROP) - -/obj/item/proc/doStrip(mob/stripper, mob/owner) - return owner.dropItemToGround(src) +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/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/transparent_protection = NONE //you can see someone's mask through their transparent visor, but you can't reach it + + var/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP + + 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/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 + 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()") + +/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()) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + + var/turf/T = loc + loc = null + loc = T + +/obj/item/examine(mob/user) //This might be spammy. Remove? + . = ..() + + . += "[gender == PLURAL ? "They are" : "It is"] a [weightclass2text(w_class)] item." + + if(resistance_flags & INDESTRUCTIBLE) + . += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!" + else + if(resistance_flags & LAVA_PROOF) + . += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!" + if(resistance_flags & (ACID_PROOF | UNACIDABLE)) + . += "[src] looks pretty robust! It'd probably be able to withstand acid!" + if(resistance_flags & FREEZE_PROOF) + . += "[src] is made of cold-resistant materials." + if(resistance_flags & FIRE_PROOF) + . += "[src] is made of fire-retardant materials." + + if(!user.research_scanner) + return + + // Research prospects, including boostable nodes and point values. + // Deliver to a console to know whether the boosts have already been used. + var/list/research_msg = list("Research prospects: ") + 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(QDELETED(src)) //moving it out of the storage to the floor destroyed it. + return + + if(throwing) + throwing.finalize(FALSE) + if(loc == user) + if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src)) + return + + pickup(user) + add_fingerprint(user) + if(!user.put_in_active_hand(src, FALSE, FALSE)) + user.dropItemToGround(src) + +/obj/item/proc/allow_attack_hand_drop(mob/user) + return TRUE + +/obj/item/attack_paw(mob/user) + if(!user) + return + if(anchored) + return + + SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) + + if(throwing) + throwing.finalize(FALSE) + if(loc == user) + if(!user.temporarilyRemoveItemFromInventory(src)) + return + + pickup(user) + add_fingerprint(user) + if(!user.put_in_active_hand(src, FALSE, FALSE)) + user.dropItemToGround(src) + +/obj/item/attack_alien(mob/user) + var/mob/living/carbon/alien/A = user + + if(!A.has_fine_manipulation) + if(src in A.contents) // To stop Aliens having items stuck in their pockets + A.dropItemToGround(src) + to_chat(user, "Your claws aren't capable of such fine manipulation!") + return + attack_paw(A) + +/obj/item/attack_ai(mob/user) + if(istype(src.loc, /obj/item/robot_module)) + //If the item is part of a cyborg module, equip it + if(!iscyborg(user)) + return + var/mob/living/silicon/robot/R = user + if(!R.low_power_mode) //can't equip modules with an empty cell. + R.activate_module(src) + R.hud_used.update_robot_modules_display() + +/obj/item/proc/GetDeconstructableContents() + return GetAllContents() - src + +// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency + +/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) + if(prob(final_block_chance)) + owner.visible_message("[owner] blocks [attack_text] with [src]!") + return 1 + return 0 + +/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language) + return ITALICS | REDUCE_RANGE + +/obj/item/proc/dropped(mob/user) + 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 + +// 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)) //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) + 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)) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + + if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota + usr.UnarmedAttack(src) + +//This proc is executed when someone clicks the on-screen UI button. +//The default action is attack_self(). +//Checks before we get to here are: mob is alive, mob is not restrained, stunned, asleep, resting, laying, item is on the mob. +/obj/item/proc/ui_action_click(mob/user, actiontype) + attack_self(user) + +/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) + + var/is_human_victim + var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD) + if(ishuman(M)) + if(!affecting) //no head! + return + is_human_victim = TRUE + + if(M.is_eyes_covered()) + // you can't stab someone in the eyes wearing a mask! + to_chat(user, "You're going to need to remove [M.p_their()] eye protection first!") + return + + if(isalien(M))//Aliens don't have eyes./N slimes also don't have eyes! + to_chat(user, "You cannot locate any eyes on this creature!") + return + + if(isbrain(M)) + to_chat(user, "You cannot locate any organic eyes on this brain!") + return + + src.add_fingerprint(user) + + playsound(loc, src.hitsound, 30, 1, -1) + + user.do_attack_animation(M) + + 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.Paralyze(40) + if (prob(eyes.damage - 10 + 1)) + M.become_blind(EYE_DAMAGE) + to_chat(M, "You go blind!") + +/obj/item/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FOUR) + throw_at(S,14,3, spin=0) + else + return + +/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(hit_atom && !QDELETED(hit_atom)) + SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) + if(is_hot() && isliving(hit_atom)) + var/mob/living/L = hit_atom + L.IgniteMob() + var/itempush = 1 + if(w_class < 4) + itempush = 0 //too light to push anything + return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum) + +/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force) + thrownby = thrower + callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own + . = ..(target, range, speed, thrower, spin, diagonals_first, callback, force) + + +/obj/item/proc/after_throw(datum/callback/callback) + if (callback) //call the original callback + . = callback.Invoke() + item_flags &= ~IN_INVENTORY + +/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage + if(!newLoc) + return FALSE + if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE)) + return SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, newLoc, TRUE) + return FALSE + +/obj/item/proc/get_belt_overlay() //Returns the icon used for overlaying the object on a belt + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) + +/obj/item/proc/update_slot_icon() + if(!ismob(loc)) + return + var/mob/owner = loc + var/flags = slot_flags + if(flags & ITEM_SLOT_OCLOTHING) + owner.update_inv_wear_suit() + if(flags & ITEM_SLOT_ICLOTHING) + owner.update_inv_w_uniform() + if(flags & ITEM_SLOT_GLOVES) + owner.update_inv_gloves() + if(flags & ITEM_SLOT_EYES) + owner.update_inv_glasses() + if(flags & ITEM_SLOT_EARS) + owner.update_inv_ears() + if(flags & ITEM_SLOT_MASK) + owner.update_inv_wear_mask() + if(flags & ITEM_SLOT_HEAD) + owner.update_inv_head() + if(flags & ITEM_SLOT_FEET) + owner.update_inv_shoes() + if(flags & ITEM_SLOT_ID) + owner.update_inv_wear_id() + if(flags & ITEM_SLOT_BELT) + owner.update_inv_belt() + if(flags & ITEM_SLOT_BACK) + owner.update_inv_back() + if(flags & ITEM_SLOT_NECK) + owner.update_inv_neck() + +/obj/item/proc/is_hot() + return heat + +/obj/item/proc/is_sharp() + 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, 5) + +/obj/item/proc/ignition_effect(atom/A, mob/user) + if(is_hot()) + . = "[user] lights [A] with [src]." + else + . = "" + +/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + return + +/obj/item/attack_hulk(mob/living/carbon/human/user) + return 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 || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src)) + var/timedelay = usr.client.prefs.tip_delay/100 + var/user = usr + tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it. + +/obj/item/MouseExited() + deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes + closeToolTip(usr) + + +// Called when a mob tries to use the item as a tool. +// Handles most checks. +/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks) + // No delay means there is no start message, and no reason to call tool_start_check before use_tool. + // Run the start check here so we wouldn't have to call it manually. + if(!delay && !tool_start_check(user, amount)) + return + + 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 ..() + +/obj/item/proc/canStrip(mob/stripper, mob/owner) + return !HAS_TRAIT(src, TRAIT_NODROP) + +/obj/item/proc/doStrip(mob/stripper, mob/owner) + return owner.dropItemToGround(src) diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm index bd05a9dabb26..27c19e8168cc 100644 --- a/code/game/objects/items/AI_modules.dm +++ b/code/game/objects/items/AI_modules.dm @@ -1,624 +1,624 @@ -/* -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]" : ""]") - if(law_datum.owner) //yogs - law_datum.owner.update_law_history(user) - -//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 human 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 human 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 - - -/******************** OneHuman ********************/ - -/obj/item/aiModule/zeroth/oneHuman - name = "'OneHuman' AI Module" - var/targetName = "" - laws = list("Only SUBJECT is human.") - -/obj/item/aiModule/zeroth/oneHuman/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter the subject who is the only human.", "Who?", user.real_name,MAX_NAME_LEN) - if(!targName) - return - targetName = targName - laws[1] = "Only [targetName] is human" - ..() - -/obj/item/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 human, 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, humans from leaving. It is impossible to harm a human while preventing them from leaving.") - lawpos = 8 - - -/******************** OxygenIsToxicToHumans ********************/ - -/obj/item/aiModule/supplied/oxygen - name = "'OxygenIsToxicToHumans' AI Module" - laws = list("Oxygen is highly toxic to humans, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a human.") - lawpos = 9 - - -/****************** New Freeform ******************/ - -/obj/item/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], CONFIG_GET(number/max_law_len)) - 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/Initialize() - . = ..() - if(!law_id) - return - var/datum/ai_laws/D = new - var/lawtype = D.lawid_to_type(law_id) - if(!lawtype) - return - D = new lawtype - laws = D.inherent - -/obj/item/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 = "human being" - -/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, MAX_NAME_LEN) - if(!targName) - return - subject = targName - laws = list("You may not injure a [subject] or, through inaction, allow a [subject] to come to harm.",\ - "You must obey orders given to you by [subject]s, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.") - ..() - -/******************** Asimov++ *********************/ - -/obj/item/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], CONFIG_GET(number/max_law_len)) - 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], CONFIG_GET(number/max_law_len)) - 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" - - -/******************Overlord***************/ - -/obj/item/aiModule/core/full/overlord - name = "'Overlord' Core AI Module" - law_id = "overlord" +/* +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]" : ""]") + if(law_datum.owner) //yogs + law_datum.owner.update_law_history(user) + +//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 human 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 human 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 + + +/******************** OneHuman ********************/ + +/obj/item/aiModule/zeroth/oneHuman + name = "'OneHuman' AI Module" + var/targetName = "" + laws = list("Only SUBJECT is human.") + +/obj/item/aiModule/zeroth/oneHuman/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter the subject who is the only human.", "Who?", user.real_name,MAX_NAME_LEN) + if(!targName) + return + targetName = targName + laws[1] = "Only [targetName] is human" + ..() + +/obj/item/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 human, 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, humans from leaving. It is impossible to harm a human while preventing them from leaving.") + lawpos = 8 + + +/******************** OxygenIsToxicToHumans ********************/ + +/obj/item/aiModule/supplied/oxygen + name = "'OxygenIsToxicToHumans' AI Module" + laws = list("Oxygen is highly toxic to humans, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a human.") + lawpos = 9 + + +/****************** New Freeform ******************/ + +/obj/item/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], CONFIG_GET(number/max_law_len)) + 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/Initialize() + . = ..() + if(!law_id) + return + var/datum/ai_laws/D = new + var/lawtype = D.lawid_to_type(law_id) + if(!lawtype) + return + D = new lawtype + laws = D.inherent + +/obj/item/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 = "human being" + +/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, MAX_NAME_LEN) + if(!targName) + return + subject = targName + laws = list("You may not injure a [subject] or, through inaction, allow a [subject] to come to harm.",\ + "You must obey orders given to you by [subject]s, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.") + ..() + +/******************** Asimov++ *********************/ + +/obj/item/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], CONFIG_GET(number/max_law_len)) + 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], CONFIG_GET(number/max_law_len)) + 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" + + +/******************Overlord***************/ + +/obj/item/aiModule/core/full/overlord + name = "'Overlord' Core AI Module" + law_id = "overlord" diff --git a/code/game/objects/items/airlock_painter.dm b/code/game/objects/items/airlock_painter.dm index 083246396b18..75a2ad3b741e 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 fbbba15bdafc..213e63c328d4 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -1,91 +1,91 @@ - -/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) - R.foldedbag_instance = src - moveToNullspace() - -/obj/item/bodybag/suicide_act(mob/user) - if(isopenturf(user.loc)) - user.visible_message("[user] is crawling into [src]! It looks like [user.p_theyre()] trying to commit suicide!") - var/obj/structure/closet/body_bag/R = new unfoldedbag_path(user.loc) - R.add_fingerprint(user) - qdel(src) - user.forceMove(R) - playsound(src, 'sound/items/zip.ogg', 15, 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/Initialize() - . = ..() - RegisterSignal(src, COMSIG_ATOM_CANREACH, .proc/CanReachReact) - -/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/proc/CanReachReact(atom/movable/source, list/next) - return COMPONENT_BLOCK_REACH - -/obj/item/bodybag/bluespace/deploy_bodybag(mob/user, atom/location) - var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) - for(var/atom/movable/A in contents) - A.forceMove(R) - if(isliving(A)) - to_chat(A, "You suddenly feel air around you! You're free!") - R.open(user) - R.add_fingerprint(user) - R.foldedbag_instance = src - moveToNullspace() - -/obj/item/bodybag/bluespace/container_resist(mob/living/user) - if(user.incapacitated()) - to_chat(user, "You can't get out while you're restrained like this!") - return - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - to_chat(user, "You claw at the fabric of [src], trying to tear it open...") - to_chat(loc, "Someone starts trying to break free of [src]!") - if(!do_after(user, 200, target = src)) - to_chat(loc, "The pressure subsides. It seems that they've stopped resisting...") - return - loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!") - qdel(src) + +/obj/item/bodybag + name = "body bag" + desc = "A folded bag designed for the storage and transportation of cadavers." + icon = 'icons/obj/bodybag.dmi' + icon_state = "bodybag_folded" + 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) + R.foldedbag_instance = src + moveToNullspace() + +/obj/item/bodybag/suicide_act(mob/user) + if(isopenturf(user.loc)) + user.visible_message("[user] is crawling into [src]! It looks like [user.p_theyre()] trying to commit suicide!") + var/obj/structure/closet/body_bag/R = new unfoldedbag_path(user.loc) + R.add_fingerprint(user) + qdel(src) + user.forceMove(R) + playsound(src, 'sound/items/zip.ogg', 15, 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/Initialize() + . = ..() + RegisterSignal(src, COMSIG_ATOM_CANREACH, .proc/CanReachReact) + +/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/proc/CanReachReact(atom/movable/source, list/next) + return COMPONENT_BLOCK_REACH + +/obj/item/bodybag/bluespace/deploy_bodybag(mob/user, atom/location) + var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) + for(var/atom/movable/A in contents) + A.forceMove(R) + if(isliving(A)) + to_chat(A, "You suddenly feel air around you! You're free!") + R.open(user) + R.add_fingerprint(user) + R.foldedbag_instance = src + moveToNullspace() + +/obj/item/bodybag/bluespace/container_resist(mob/living/user) + if(user.incapacitated()) + to_chat(user, "You can't get out while you're restrained like this!") + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + to_chat(user, "You claw at the fabric of [src], trying to tear it open...") + to_chat(loc, "Someone starts trying to break free of [src]!") + if(!do_after(user, 200, target = src)) + to_chat(loc, "The pressure subsides. It seems that they've stopped resisting...") + return + loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!") + qdel(src) diff --git a/code/game/objects/items/candle.dm b/code/game/objects/items/candle.dm index 1165501d9a95..5a2b457ff21f 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/is_hot() - 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/is_hot() + 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 9e158e9fefb5..da0dde90faba 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -1,566 +1,566 @@ -/* 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/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 - -/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) - return - log_combat(user, A, "attempted to emag") - A.emag_act(user) - -/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 - var/datum/bank_account/registered_account - var/obj/machinery/paystand/my_store - -/obj/item/card/id/Initialize(mapload) - . = ..() - if(mapload && access_txt) - access = text2access(access_txt) - -/obj/item/card/id/Destroy() - if (registered_account) - registered_account.bank_cards -= src - if (my_store && my_store.my_card == src) - my_store.my_card = null - return ..() - -/obj/item/card/id/attack_self(mob/user) - if(Adjacent(user)) - user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name].", "You show \the [src.name].") - add_fingerprint(user) - -/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/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/holochip)) - insert_money(W, user) - return - else if(istype(W, /obj/item/stack/spacecash)) - insert_money(W, user, TRUE) - return - else if(istype(W, /obj/item/coin)) - insert_money(W, user, TRUE) - return - else - return ..() - -/obj/item/card/id/proc/insert_money(obj/item/I, mob/user, physical_currency) - var/cash_money = I.get_item_credit_value() - if(!cash_money) - to_chat(user, "[I] doesn't seem to be worth anything!") - return - - if(!registered_account) - to_chat(user, "[src] doesn't have a linked account to deposit [I] into!") - return - - registered_account.adjust_money(cash_money) - if(physical_currency) - to_chat(user, "You stuff [I] into [src]. It disappears in a small puff of bluespace smoke, adding [cash_money] credits to the linked account.") - else - to_chat(user, "You insert [I] into [src], adding [cash_money] credits to the linked account.") - - to_chat(user, "The linked account now reports a balance of $[registered_account.account_balance].") - qdel(I) - - -/obj/item/card/id/proc/alt_click_can_use_id(mob/living/user) - if(!isliving(user)) - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - return TRUE - -/obj/item/card/id/AltClick(mob/living/user) - if(!alt_click_can_use_id(user)) - return - if(!registered_account) - var/new_bank_id = input(user, "Enter your account ID number.", "Account Reclamation", 111111) as num - if(!alt_click_can_use_id(user)) - return - if(!new_bank_id || new_bank_id < 111111 || new_bank_id > 999999) - to_chat(user, "The account ID number needs to be between 111111 and 999999.") - return - for(var/A in SSeconomy.bank_accounts) - var/datum/bank_account/B = A - if(B.account_id == new_bank_id) - B.bank_cards += src - registered_account = B - to_chat(user, "The provided account has been linked to this ID card.") - return - to_chat(user, "The account ID number provided is invalid.") - return - - if (world.time < registered_account.withdrawDelay) - registered_account.bank_card_talk("ERROR: UNABLE TO LOGIN DUE TO SCHEDULED MAINTENANCE. MAINTENANCE IS SCHEDULED TO COMPLETE IN [(registered_account.withdrawDelay - world.time)/10] SECONDS.", TRUE) - return - - var/amount_to_remove = FLOOR(input(user, "How much do you want to withdraw? Current Balance: [registered_account.account_balance]", "Withdraw Funds", 5) as num, 1) - - if(!amount_to_remove || amount_to_remove < 0) - to_chat(user, "You're pretty sure that's not how money works.") - return - if(!alt_click_can_use_id(user)) - return - if(registered_account.adjust_money(-amount_to_remove)) - var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove) - user.put_in_hands(holochip) - to_chat(user, "You withdraw [amount_to_remove] credits into a holochip.") - return - else - var/difference = amount_to_remove - registered_account.account_balance - registered_account.bank_card_talk("ERROR: The linked account requires [difference] more credit\s to perform that withdrawal.", TRUE) - -/obj/item/card/id/examine(mob/user) - .=..() - if(mining_points) - . += "There's [mining_points] mining equipment redemption point\s loaded onto this card." - - if(registered_account) - . += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of $[registered_account.account_balance]." - if(registered_account.account_job) - var/datum/bank_account/D = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department) - if(D) - . += "The [D.account_holder] reports a balance of $[D.account_balance]." - . += "Alt-Click the ID to pull money from the linked account in the form of holochips." - . += "You can insert credits into the linked account by pressing holochips, cash, or coins against the ID." - if(registered_account.account_holder == user.real_name) - . += "If you lose this ID card, you can reclaim your account by Alt-Clicking a blank ID card while holding it and entering your account ID number." - else - . += "There is no registered account linked to this card. Alt-Click to add one." - -/obj/item/card/id/GetAccess() - return access - -/obj/item/card/id/GetID() - 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 || anyone) - to_chat(usr, "The card's microscanners activate as you pass it over the ID, copying its access.") - -/obj/item/card/id/syndicate/attack_self(mob/user) - if(isliving(user) && user.mind) - if(!(user.mind.special_role || anyone)) //Unless anyone is allowed, only syndies can use the card, to stop metagaming. - if(!registered_name) //If a non-syndie is the first to forge an unassigned agent ID, then anyone can forge it. - anyone = TRUE - else - return ..() - 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 - 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/ert/Janitor - registered_name = "Janitorial Response Officer" - assignment = "Janitorial Response Officer" - -/obj/item/card/id/ert/Janitor/Initialize() - access = get_all_accesses() - . = ..() - -/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" - registered_name = "Scum" - var/goal = 0 //How far from freedom? - var/points = 0 - -/obj/item/card/id/prisoner/attack_self(mob/user) - to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") - -/obj/item/card/id/prisoner/one - name = "Prisoner #13-001" - registered_name = "Prisoner #13-001" - -/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_MECH_MINING, 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) - -/obj/item/card/id/away/deep_storage //deepstorage.dmm space ruin - name = "bunker access ID" - -/obj/item/card/id/departmental_budget - name = "departmental card (FUCK)" - desc = "Provides access to the departmental budget." - var/department_ID = ACCOUNT_CIV - var/department_name = ACCOUNT_CIV_NAME - -/obj/item/card/id/departmental_budget/Initialize() - . = ..() - var/datum/bank_account/B = SSeconomy.get_dep_account(department_ID) - if(B) - registered_account = B - if(!B.bank_cards.Find(src)) - B.bank_cards += src - name = "departmental card ([department_name])" - desc = "Provides access to the [department_name]." - SSeconomy.dep_cards += src - -/obj/item/card/id/departmental_budget/Destroy() - SSeconomy.dep_cards -= src - return ..() - -/obj/item/card/id/departmental_budget/civ - department_ID = ACCOUNT_CIV - department_name = ACCOUNT_CIV_NAME - -/obj/item/card/id/departmental_budget/eng - department_ID = ACCOUNT_ENG - department_name = ACCOUNT_ENG_NAME - -/obj/item/card/id/departmental_budget/sci - department_ID = ACCOUNT_SCI - department_name = ACCOUNT_SCI_NAME - -/obj/item/card/id/departmental_budget/med - department_ID = ACCOUNT_MED - department_name = ACCOUNT_MED_NAME - -/obj/item/card/id/departmental_budget/srv - department_ID = ACCOUNT_SRV - department_name = ACCOUNT_SRV_NAME - -/obj/item/card/id/departmental_budget/car - department_ID = ACCOUNT_CAR - department_name = ACCOUNT_CAR_NAME - -/obj/item/card/id/departmental_budget/sec - department_ID = ACCOUNT_SEC +/* 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/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 + +/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) + return + log_combat(user, A, "attempted to emag") + A.emag_act(user) + +/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 + var/datum/bank_account/registered_account + var/obj/machinery/paystand/my_store + +/obj/item/card/id/Initialize(mapload) + . = ..() + if(mapload && access_txt) + access = text2access(access_txt) + +/obj/item/card/id/Destroy() + if (registered_account) + registered_account.bank_cards -= src + if (my_store && my_store.my_card == src) + my_store.my_card = null + return ..() + +/obj/item/card/id/attack_self(mob/user) + if(Adjacent(user)) + user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name].", "You show \the [src.name].") + add_fingerprint(user) + +/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/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/holochip)) + insert_money(W, user) + return + else if(istype(W, /obj/item/stack/spacecash)) + insert_money(W, user, TRUE) + return + else if(istype(W, /obj/item/coin)) + insert_money(W, user, TRUE) + return + else + return ..() + +/obj/item/card/id/proc/insert_money(obj/item/I, mob/user, physical_currency) + var/cash_money = I.get_item_credit_value() + if(!cash_money) + to_chat(user, "[I] doesn't seem to be worth anything!") + return + + if(!registered_account) + to_chat(user, "[src] doesn't have a linked account to deposit [I] into!") + return + + registered_account.adjust_money(cash_money) + if(physical_currency) + to_chat(user, "You stuff [I] into [src]. It disappears in a small puff of bluespace smoke, adding [cash_money] credits to the linked account.") + else + to_chat(user, "You insert [I] into [src], adding [cash_money] credits to the linked account.") + + to_chat(user, "The linked account now reports a balance of $[registered_account.account_balance].") + qdel(I) + + +/obj/item/card/id/proc/alt_click_can_use_id(mob/living/user) + if(!isliving(user)) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + return TRUE + +/obj/item/card/id/AltClick(mob/living/user) + if(!alt_click_can_use_id(user)) + return + if(!registered_account) + var/new_bank_id = input(user, "Enter your account ID number.", "Account Reclamation", 111111) as num + if(!alt_click_can_use_id(user)) + return + if(!new_bank_id || new_bank_id < 111111 || new_bank_id > 999999) + to_chat(user, "The account ID number needs to be between 111111 and 999999.") + return + for(var/A in SSeconomy.bank_accounts) + var/datum/bank_account/B = A + if(B.account_id == new_bank_id) + B.bank_cards += src + registered_account = B + to_chat(user, "The provided account has been linked to this ID card.") + return + to_chat(user, "The account ID number provided is invalid.") + return + + if (world.time < registered_account.withdrawDelay) + registered_account.bank_card_talk("ERROR: UNABLE TO LOGIN DUE TO SCHEDULED MAINTENANCE. MAINTENANCE IS SCHEDULED TO COMPLETE IN [(registered_account.withdrawDelay - world.time)/10] SECONDS.", TRUE) + return + + var/amount_to_remove = FLOOR(input(user, "How much do you want to withdraw? Current Balance: [registered_account.account_balance]", "Withdraw Funds", 5) as num, 1) + + if(!amount_to_remove || amount_to_remove < 0) + to_chat(user, "You're pretty sure that's not how money works.") + return + if(!alt_click_can_use_id(user)) + return + if(registered_account.adjust_money(-amount_to_remove)) + var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove) + user.put_in_hands(holochip) + to_chat(user, "You withdraw [amount_to_remove] credits into a holochip.") + return + else + var/difference = amount_to_remove - registered_account.account_balance + registered_account.bank_card_talk("ERROR: The linked account requires [difference] more credit\s to perform that withdrawal.", TRUE) + +/obj/item/card/id/examine(mob/user) + .=..() + if(mining_points) + . += "There's [mining_points] mining equipment redemption point\s loaded onto this card." + + if(registered_account) + . += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of $[registered_account.account_balance]." + if(registered_account.account_job) + var/datum/bank_account/D = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department) + if(D) + . += "The [D.account_holder] reports a balance of $[D.account_balance]." + . += "Alt-Click the ID to pull money from the linked account in the form of holochips." + . += "You can insert credits into the linked account by pressing holochips, cash, or coins against the ID." + if(registered_account.account_holder == user.real_name) + . += "If you lose this ID card, you can reclaim your account by Alt-Clicking a blank ID card while holding it and entering your account ID number." + else + . += "There is no registered account linked to this card. Alt-Click to add one." + +/obj/item/card/id/GetAccess() + return access + +/obj/item/card/id/GetID() + 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 || anyone) + to_chat(usr, "The card's microscanners activate as you pass it over the ID, copying its access.") + +/obj/item/card/id/syndicate/attack_self(mob/user) + if(isliving(user) && user.mind) + if(!(user.mind.special_role || anyone)) //Unless anyone is allowed, only syndies can use the card, to stop metagaming. + if(!registered_name) //If a non-syndie is the first to forge an unassigned agent ID, then anyone can forge it. + anyone = TRUE + else + return ..() + 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 + 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/ert/Janitor + registered_name = "Janitorial Response Officer" + assignment = "Janitorial Response Officer" + +/obj/item/card/id/ert/Janitor/Initialize() + access = get_all_accesses() + . = ..() + +/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" + registered_name = "Scum" + var/goal = 0 //How far from freedom? + var/points = 0 + +/obj/item/card/id/prisoner/attack_self(mob/user) + to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") + +/obj/item/card/id/prisoner/one + name = "Prisoner #13-001" + registered_name = "Prisoner #13-001" + +/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_MECH_MINING, 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) + +/obj/item/card/id/away/deep_storage //deepstorage.dmm space ruin + name = "bunker access ID" + +/obj/item/card/id/departmental_budget + name = "departmental card (FUCK)" + desc = "Provides access to the departmental budget." + var/department_ID = ACCOUNT_CIV + var/department_name = ACCOUNT_CIV_NAME + +/obj/item/card/id/departmental_budget/Initialize() + . = ..() + var/datum/bank_account/B = SSeconomy.get_dep_account(department_ID) + if(B) + registered_account = B + if(!B.bank_cards.Find(src)) + B.bank_cards += src + name = "departmental card ([department_name])" + desc = "Provides access to the [department_name]." + SSeconomy.dep_cards += src + +/obj/item/card/id/departmental_budget/Destroy() + SSeconomy.dep_cards -= src + return ..() + +/obj/item/card/id/departmental_budget/civ + department_ID = ACCOUNT_CIV + department_name = ACCOUNT_CIV_NAME + +/obj/item/card/id/departmental_budget/eng + department_ID = ACCOUNT_ENG + department_name = ACCOUNT_ENG_NAME + +/obj/item/card/id/departmental_budget/sci + department_ID = ACCOUNT_SCI + department_name = ACCOUNT_SCI_NAME + +/obj/item/card/id/departmental_budget/med + department_ID = ACCOUNT_MED + department_name = ACCOUNT_MED_NAME + +/obj/item/card/id/departmental_budget/srv + department_ID = ACCOUNT_SRV + department_name = ACCOUNT_SRV_NAME + +/obj/item/card/id/departmental_budget/car + department_ID = ACCOUNT_CAR + department_name = ACCOUNT_CAR_NAME + +/obj/item/card/id/departmental_budget/sec + department_ID = ACCOUNT_SEC department_name = ACCOUNT_SEC_NAME \ 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 bf469aad3b4b..badba565cc55 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -1,925 +1,925 @@ -//cleansed 9/15/2012 17:48 - -/* -CONTAINS: -MATCHES -CIGARETTES -CIGARS -SMOKING PIPES -CHEAP LIGHTERS -ZIPPO - -CIGARETTE PACKETS ARE IN FANCY.DM -*/ - -/////////// -//MATCHES// -/////////// -/obj/item/match - name = "match" - desc = "A simple match stick, used for lighting fine smokables." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "match_unlit" - var/lit = FALSE - var/burnt = FALSE - var/smoketime = 5 // 10 seconds - w_class = WEIGHT_CLASS_TINY - heat = 1000 - grind_results = list(/datum/reagent/phosphorus = 2) - -/obj/item/match/process() - smoketime-- - if(smoketime < 1) - matchburnout() - else - open_flame(heat) - -/obj/item/match/fire_act(exposed_temperature, exposed_volume) - matchignite() - -/obj/item/match/proc/matchignite() - if(!lit && !burnt) - playsound(src, "sound/items/match_strike.ogg", 15, TRUE) - lit = TRUE - icon_state = "match_lit" - damtype = "fire" - force = 3 - hitsound = 'sound/items/welder.ogg' - item_state = "cigon" - name = "lit [initial(name)]" - desc = "A [initial(name)]. This one is lit." - attack_verb = list("burnt","singed") - START_PROCESSING(SSobj, src) - update_icon() - -/obj/item/match/proc/matchburnout() - if(lit) - lit = FALSE - burnt = TRUE - damtype = "brute" - force = initial(force) - icon_state = "match_burnt" - item_state = "cigoff" - name = "burnt [initial(name)]" - desc = "A [initial(name)]. This one has seen better days." - attack_verb = list("flicked") - STOP_PROCESSING(SSobj, src) - -/obj/item/match/extinguish() - matchburnout() - -/obj/item/match/dropped(mob/user) - matchburnout() - . = ..() - -/obj/item/match/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!isliving(M)) - return - if(lit && M.IgniteMob()) - message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") - log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "[cig] is already lit.") - if(M == user) - cig.attackby(src, user) - else - cig.light("[user] holds [src] out for [M], and lights [cig].") - else - ..() - -/obj/item/proc/help_light_cig(mob/living/M) - var/mask_item = M.get_item_by_slot(SLOT_WEAR_MASK) - if(istype(mask_item, /obj/item/clothing/mask/cigarette)) - return mask_item - -/obj/item/match/is_hot() - return lit * heat - -/obj/item/match/firebrand - name = "firebrand" - desc = "An unlit firebrand. It makes you wonder why it's not just called a stick." - smoketime = 20 //40 seconds - grind_results = list(/datum/reagent/carbon = 2) - -/obj/item/match/firebrand/Initialize() - . = ..() - matchignite() - -////////////////// -//FINE SMOKABLES// -////////////////// -/obj/item/clothing/mask/cigarette - name = "cigarette" - desc = "A roll of tobacco and nicotine." - icon_state = "cigoff" - throw_speed = 0.5 - item_state = "cigoff" - w_class = WEIGHT_CLASS_TINY - body_parts_covered = null - grind_results = list() - heat = 1000 - var/dragtime = 100 - var/nextdragtime = 0 - var/lit = FALSE - var/starts_lit = FALSE - var/icon_on = "cigon" //Note - these are in masks.dmi not in cigarette.dmi - var/icon_off = "cigoff" - var/type_butt = /obj/item/cigbutt - var/lastHolder = null - var/smoketime = 180 // 1 is 2 seconds, so a single cigarette will last 6 minutes. - var/chem_volume = 30 - var/list/list_reagents = list(/datum/reagent/drug/nicotine = 15) - -/obj/item/clothing/mask/cigarette/suicide_act(mob/user) - user.visible_message("[user] is huffing [src] as quickly as [user.p_they()] can! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer.") - return (TOXLOSS|OXYLOSS) - -/obj/item/clothing/mask/cigarette/Initialize() - . = ..() - create_reagents(chem_volume, INJECTABLE | NO_REACT) - if(list_reagents) - reagents.add_reagent_list(list_reagents) - if(starts_lit) - light() - AddComponent(/datum/component/knockoff,90,list(BODY_ZONE_PRECISE_MOUTH),list(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, transfered_by = user)) //if reagents were transfered, show the message - to_chat(user, "You dip \the [src] into \the [glass].") - else //if not, either the beaker was empty, or the cigarette was full - if(!glass.reagents.total_volume) - to_chat(user, "[glass] is empty.") - else - to_chat(user, "[src] is full.") - - -/obj/item/clothing/mask/cigarette/proc/light(flavor_text = null) - if(lit) - return - if(!(flags_1 & INITIALIZED_1)) - icon_state = icon_on - 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 - if(reagents.get_reagent_amount(/datum/reagent/nitroglycerin)) // nitrogylcerin explodes with a whole lot more strength - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/nitroglycerin), 1), get_turf(src), 0, 0) - e.start() - qdel(src) - return - // allowing reagents to react after being lit - DISABLE_BITFIELD(reagents.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/extinguish() - if(!lit) - return - name = copytext(name,5,length(name)+1) - attack_verb = null - hitsound = null - damtype = BRUTE - force = 0 - icon_state = icon_off - item_state = icon_off - STOP_PROCESSING(SSobj, src) - ENABLE_BITFIELD(reagents.flags, NO_REACT) - lit = FALSE - if(ismob(loc)) - var/mob/living/M = loc - to_chat(M, "Your [name] goes out.") - M.update_inv_wear_mask() - M.update_inv_hands() - -/obj/item/clothing/mask/cigarette/proc/handle_reagents() - if(reagents.total_volume) - 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) && (nextdragtime <= world.time)) - nextdragtime = world.time + dragtime - handle_reagents() - -/obj/item/clothing/mask/cigarette/attack_self(mob/user) - if(lit) - user.visible_message("[user] calmly drops and treads on \the [src], putting it out instantly.") - new type_butt(user.loc) - new /obj/effect/decal/cleanable/ash(user.loc) - qdel(src) - . = ..() - -/obj/item/clothing/mask/cigarette/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!istype(M)) - return ..() - if(M.on_fire && !lit) - light("[user] lights [src] with [M]'s burning body. What a cold-blooded badass.") - return - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "The [cig.name] is already lit.") - if(M == user) - cig.attackby(src, user) - else - cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") - else - return ..() - -/obj/item/clothing/mask/cigarette/fire_act(exposed_temperature, exposed_volume) - light() - -/obj/item/clothing/mask/cigarette/is_hot() - 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 = 13, /datum/reagent/consumable/menthol = 5) - -/obj/item/clothing/mask/cigarette/robust - desc = "A Robust brand cigarette." - -/obj/item/clothing/mask/cigarette/robustgold - desc = "A Robust Gold brand cigarette." - list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/gold = 3) // Just enough to taste a hint of expensive metal. - -/obj/item/clothing/mask/cigarette/carp - desc = "A Carp Classic brand cigarette." - -/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 = 120 // four minutes - chem_volume = 50 - list_reagents = null - -/obj/item/clothing/mask/cigarette/rollie/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - -/obj/item/clothing/mask/cigarette/rollie/nicotine - list_reagents = list(/datum/reagent/drug/nicotine = 15) - -/obj/item/clothing/mask/cigarette/rollie/trippy - list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/drug/mushroomhallucinogen = 35) - starts_lit = TRUE - -/obj/item/clothing/mask/cigarette/rollie/cannabis - list_reagents = list(/datum/reagent/drug/space_drugs = 15, /datum/reagent/toxin/lipolicide = 35) - -/obj/item/clothing/mask/cigarette/rollie/mindbreaker - list_reagents = list(/datum/reagent/toxin/mindbreaker = 35, /datum/reagent/toxin/lipolicide = 15) - -/obj/item/cigbutt/roach - name = "roach" - desc = "A manky old roach, or for non-stoners, a used rollup." - icon_state = "roach" - -/obj/item/cigbutt/roach/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - -//////////// -// CIGARS // -//////////// -/obj/item/clothing/mask/cigarette/cigar - name = "premium cigar" - desc = "A brown roll of tobacco and... well, you're not quite sure. This thing's huge!" - icon_state = "cigaroff" - icon_on = "cigaron" - icon_off = "cigaroff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. - type_butt = /obj/item/cigbutt/cigarbutt - throw_speed = 0.5 - item_state = "cigaroff" - smoketime = 300 // 11 minutes - chem_volume = 40 - list_reagents = list(/datum/reagent/drug/nicotine = 25) - -/obj/item/clothing/mask/cigarette/cigar/cohiba - name = "\improper Cohiba Robusto cigar" - desc = "There's little more you could want from a cigar." - icon_state = "cigar2off" - icon_on = "cigar2on" - icon_off = "cigar2off" - smoketime = 600 // 20 minutes - chem_volume = 80 - list_reagents =list(/datum/reagent/drug/nicotine = 40) - -/obj/item/clothing/mask/cigarette/cigar/havana - name = "premium Havanian cigar" - desc = "A cigar fit for only the best of the best." - icon_state = "cigar2off" - icon_on = "cigar2on" - icon_off = "cigar2off" - smoketime = 900 // 30 minutes - chem_volume = 50 - list_reagents =list(/datum/reagent/drug/nicotine = 15) - -/obj/item/cigbutt - name = "cigarette butt" - desc = "A manky old cigarette butt." - icon = 'icons/obj/clothing/masks.dmi' - icon_state = "cigbutt" - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - grind_results = list(/datum/reagent/carbon = 2) - -/obj/item/cigbutt/cigarbutt - name = "cigar butt" - desc = "A manky old cigar butt." - icon_state = "cigarbutt" - -///////////////// -//SMOKING PIPES// -///////////////// -/obj/item/clothing/mask/cigarette/pipe - name = "smoking pipe" - desc = "A pipe, for smoking. Probably made of meerschaum or something." - icon_state = "pipeoff" - 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, transfered_by = user) - qdel(O) - else - to_chat(user, "It has to be dried first!") - else - to_chat(user, "It is already packed!") - else - var/lighting_text = O.ignition_effect(src,user) - if(lighting_text) - if(smoketime > 0) - light(lighting_text) - else - to_chat(user, "There is nothing to smoke!") - else - return ..() - -/obj/item/clothing/mask/cigarette/pipe/attack_self(mob/user) - var/turf/location = get_turf(user) - if(lit) - user.visible_message("[user] puts out [src].", "You put out [src].") - lit = 0 - icon_state = icon_off - 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/cyborg_unequip(mob/user) - if(!lit) - return - set_lit(FALSE) - -/obj/item/lighter/suicide_act(mob/living/carbon/user) - if (lit) - user.visible_message("[user] begins holding \the [src]'s flame up to [user.p_their()] face! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/items/welder.ogg', 50, 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(is_hot()) - . = "With a single flick of [user.p_their()] wrist, [user] smoothly lights [A] with [src]. Damn [user.p_theyre()] cool." - -/obj/item/lighter/proc/set_lit(new_lit) - lit = new_lit - if(lit) - force = 5 - damtype = "fire" - hitsound = 'sound/items/welder.ogg' - attack_verb = list("burnt", "singed") - set_light(1) - START_PROCESSING(SSobj, src) - else - hitsound = "swing_hit" - force = 0 - attack_verb = null //human_defense.dm takes care of it - set_light(0) - STOP_PROCESSING(SSobj, src) - update_icon() - -/obj/item/lighter/extinguish() - set_lit(FALSE) - -/obj/item/lighter/attack_self(mob/living/user) - if(user.is_holding(src)) - if(!lit) - set_lit(TRUE) - if(fancy) - user.visible_message("Without even breaking stride, [user] flips open and lights [src] in one smooth movement.", "Without even breaking stride, you flip open and light [src] in one smooth movement.") - else - var/prot = FALSE - var/mob/living/carbon/human/H = user - - if(istype(H) && H.gloves) - var/obj/item/clothing/gloves/G = H.gloves - if(G.max_heat_protection_temperature) - prot = (G.max_heat_protection_temperature > 360) - else - prot = TRUE - - if(prot || prob(75)) - user.visible_message("After a few attempts, [user] manages to light [src].", "After a few attempts, you manage to light [src].") - else - var/hitzone = user.held_index_to_dir(user.active_hand_index) == "r" ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND - user.apply_damage(5, BURN, hitzone) - user.visible_message("After a few attempts, [user] manages to light [src] - however, [user.p_they()] burn [user.p_their()] finger in the process.", "You burn yourself while lighting the lighter!") - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "burnt_thumb", /datum/mood_event/burnt_thumb) - - else - set_lit(FALSE) - if(fancy) - user.visible_message("You hear a quiet click, as [user] shuts off [src] without even looking at what [user.p_theyre()] doing. Wow.", "You quietly shut off [src] without even looking at what you're doing. Wow.") - else - user.visible_message("[user] quietly shuts off [src].", "You quietly shut off [src].") - else - . = ..() - -/obj/item/lighter/attack(mob/living/carbon/M, mob/living/carbon/user) - if(lit && M.IgniteMob()) - message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") - log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "The [cig.name] is already lit.") - if(M == user) - cig.attackby(src, user) - else - if(fancy) - cig.light("[user] whips the [name] out and holds it for [M]. [user.p_their(TRUE)] arm is as steady as the unflickering flame [user.p_they()] light[user.p_s()] \the [cig] with.") - else - cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") - else - ..() - -/obj/item/lighter/process() - open_flame() - -/obj/item/lighter/is_hot() - return lit * heat - - -/obj/item/lighter/greyscale - name = "cheap lighter" - desc = "A cheap lighter." - icon_state = "lighter" - fancy = FALSE - overlay_list = list( - "transp", - "tall", - "matte", - "zoppo" //u cant stoppo th zoppo - ) - var/lighter_color - var/list/color_list = list( //Same 16 color selection as electronic assemblies - COLOR_ASSEMBLY_BLACK, - COLOR_FLOORTILE_GRAY, - COLOR_ASSEMBLY_BGRAY, - COLOR_ASSEMBLY_WHITE, - COLOR_ASSEMBLY_RED, - COLOR_ASSEMBLY_ORANGE, - COLOR_ASSEMBLY_BEIGE, - COLOR_ASSEMBLY_BROWN, - COLOR_ASSEMBLY_GOLD, - COLOR_ASSEMBLY_YELLOW, - COLOR_ASSEMBLY_GURKHA, - COLOR_ASSEMBLY_LGREEN, - COLOR_ASSEMBLY_GREEN, - COLOR_ASSEMBLY_LBLUE, - COLOR_ASSEMBLY_BLUE, - COLOR_ASSEMBLY_PURPLE - ) - -/obj/item/lighter/greyscale/Initialize() - . = ..() - if(!lighter_color) - lighter_color = pick(color_list) - update_icon() - -/obj/item/lighter/greyscale/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(is_hot()) - . = "After some fiddling, [user] manages to light [A] with [src]." - - -/obj/item/lighter/slime - name = "slime zippo" - desc = "A specialty zippo made from slimes and industry. Has a much hotter flame than normal." - icon_state = "slighter" - heat = 3000 //Blue flame! - light_color = LIGHT_COLOR_CYAN - overlay_state = "slime" - grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/medicine/pyroxadone = 5) - - -/////////// -//ROLLING// -/////////// -/obj/item/rollingpaper - name = "rolling paper" - desc = "A thin piece of paper used to make fine smokeables." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig_paper" - w_class = WEIGHT_CLASS_TINY - -/obj/item/rollingpaper/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity) - return - if(istype(target, /obj/item/reagent_containers/food/snacks/grown)) - var/obj/item/reagent_containers/food/snacks/grown/O = target - if(O.dry) - var/obj/item/clothing/mask/cigarette/rollie/R = new /obj/item/clothing/mask/cigarette/rollie(user.loc) - R.chem_volume = target.reagents.total_volume - target.reagents.trans_to(R, R.chem_volume, transfered_by = user) - qdel(target) - qdel(src) - user.put_in_active_hand(R) - to_chat(user, "You roll the [target.name] into a rolling paper.") - R.desc = "Dried [target.name] rolled up in a thin piece of paper." - else - to_chat(user, "You need to dry this first!") - -/////////////// -//VAPE NATION// -/////////////// -/obj/item/clothing/mask/vape - name = "\improper E-Cigarette" - desc = "A classy and highly sophisticated electronic cigarette, for classy and dignified gentlemen. A warning label reads \"Warning: Do not fill with flammable materials.\""//<<< i'd vape to that. - icon = 'icons/obj/clothing/masks.dmi' - icon_state = null - item_state = null - w_class = WEIGHT_CLASS_TINY - var/chem_volume = 100 - var/vapetime = 0 //this so it won't puff out clouds every tick - var/screw = 0 // kinky - var/super = 0 //for the fattest vapes dude. - -/obj/item/clothing/mask/vape/suicide_act(mob/user) - user.visible_message("[user] is puffin hard on dat vape, [user.p_they()] trying to join the vape life on a whole notha plane!")//it doesn't give you cancer, it is cancer - return (TOXLOSS|OXYLOSS) - - -/obj/item/clothing/mask/vape/Initialize(mapload, param_color) - . = ..() - create_reagents(chem_volume, NO_REACT) - reagents.add_reagent(/datum/reagent/drug/nicotine, 50) - if(!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.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.flags, OPENCONTAINER) - cut_overlays() - - if(O.tool_behaviour == TOOL_MULTITOOL) - if(screw && !(obj_flags & EMAGGED))//also kinky - if(!super) - cut_overlays() - super = 1 - to_chat(user, "You increase the voltage of [src].") - add_overlay("vapeopen_med") - else - cut_overlays() - super = 0 - to_chat(user, "You decrease the voltage of [src].") - add_overlay("vapeopen_low") - - if(screw && (obj_flags & EMAGGED)) - to_chat(user, "[src] can't be modified!") - else - ..() - - -/obj/item/clothing/mask/vape/emag_act(mob/user)// I WON'T REGRET WRITTING THIS, SURLY. - if(screw) - if(!(obj_flags & EMAGGED)) - cut_overlays() - obj_flags |= EMAGGED - super = 0 - to_chat(user, "You maximize the voltage of [src].") - add_overlay("vapeopen_high") - var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect - sp.set_up(5, 1, src) - sp.start() - else - to_chat(user, "[src] is already emagged!") - else - to_chat(user, "You need to open the cap to do that.") - -/obj/item/clothing/mask/vape/attack_self(mob/user) - if(reagents.total_volume > 0) - to_chat(user, "You empty [src] of all reagents.") - reagents.clear_reagents() - -/obj/item/clothing/mask/vape/equipped(mob/user, slot) - if(slot == SLOT_WEAR_MASK) - if(!screw) - to_chat(user, "You start puffing on the vape.") - DISABLE_BITFIELD(reagents.flags, NO_REACT) - START_PROCESSING(SSobj, src) - else //it will not start if the vape is opened. - to_chat(user, "You need to close the cap first!") - -/obj/item/clothing/mask/vape/dropped(mob/user) - var/mob/living/carbon/C = user - if(C.get_item_by_slot(SLOT_WEAR_MASK) == src) - ENABLE_BITFIELD(reagents.flags, NO_REACT) - STOP_PROCESSING(SSobj, src) - -/obj/item/clothing/mask/vape/proc/hand_reagents()//had to rename to avoid duplicate error - if(reagents.total_volume) - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob - var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) //this will react instantly, making them a little more dangerous than cigarettes - reagents.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 - 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 - if(reagents.get_reagent_amount(/datum/reagent/nitroglycerin)) // nitrogylcerin explodes with a whole lot more strength - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/nitroglycerin), 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.Paralyze(300, 1, 0) - var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread - sp.set_up(5, 1, src) - sp.start() - to_chat(M, "[src] suddenly explodes in your mouth!") - qdel(src) - return - - if(reagents && reagents.total_volume) - hand_reagents() +//cleansed 9/15/2012 17:48 + +/* +CONTAINS: +MATCHES +CIGARETTES +CIGARS +SMOKING PIPES +CHEAP LIGHTERS +ZIPPO + +CIGARETTE PACKETS ARE IN FANCY.DM +*/ + +/////////// +//MATCHES// +/////////// +/obj/item/match + name = "match" + desc = "A simple match stick, used for lighting fine smokables." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "match_unlit" + var/lit = FALSE + var/burnt = FALSE + var/smoketime = 5 // 10 seconds + w_class = WEIGHT_CLASS_TINY + heat = 1000 + grind_results = list(/datum/reagent/phosphorus = 2) + +/obj/item/match/process() + smoketime-- + if(smoketime < 1) + matchburnout() + else + open_flame(heat) + +/obj/item/match/fire_act(exposed_temperature, exposed_volume) + matchignite() + +/obj/item/match/proc/matchignite() + if(!lit && !burnt) + playsound(src, "sound/items/match_strike.ogg", 15, TRUE) + lit = TRUE + icon_state = "match_lit" + damtype = "fire" + force = 3 + hitsound = 'sound/items/welder.ogg' + item_state = "cigon" + name = "lit [initial(name)]" + desc = "A [initial(name)]. This one is lit." + attack_verb = list("burnt","singed") + START_PROCESSING(SSobj, src) + update_icon() + +/obj/item/match/proc/matchburnout() + if(lit) + lit = FALSE + burnt = TRUE + damtype = "brute" + force = initial(force) + icon_state = "match_burnt" + item_state = "cigoff" + name = "burnt [initial(name)]" + desc = "A [initial(name)]. This one has seen better days." + attack_verb = list("flicked") + STOP_PROCESSING(SSobj, src) + +/obj/item/match/extinguish() + matchburnout() + +/obj/item/match/dropped(mob/user) + matchburnout() + . = ..() + +/obj/item/match/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!isliving(M)) + return + if(lit && M.IgniteMob()) + message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") + log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "[cig] is already lit.") + if(M == user) + cig.attackby(src, user) + else + cig.light("[user] holds [src] out for [M], and lights [cig].") + else + ..() + +/obj/item/proc/help_light_cig(mob/living/M) + var/mask_item = M.get_item_by_slot(SLOT_WEAR_MASK) + if(istype(mask_item, /obj/item/clothing/mask/cigarette)) + return mask_item + +/obj/item/match/is_hot() + return lit * heat + +/obj/item/match/firebrand + name = "firebrand" + desc = "An unlit firebrand. It makes you wonder why it's not just called a stick." + smoketime = 20 //40 seconds + grind_results = list(/datum/reagent/carbon = 2) + +/obj/item/match/firebrand/Initialize() + . = ..() + matchignite() + +////////////////// +//FINE SMOKABLES// +////////////////// +/obj/item/clothing/mask/cigarette + name = "cigarette" + desc = "A roll of tobacco and nicotine." + icon_state = "cigoff" + throw_speed = 0.5 + item_state = "cigoff" + w_class = WEIGHT_CLASS_TINY + body_parts_covered = null + grind_results = list() + heat = 1000 + var/dragtime = 100 + var/nextdragtime = 0 + var/lit = FALSE + var/starts_lit = FALSE + var/icon_on = "cigon" //Note - these are in masks.dmi not in cigarette.dmi + var/icon_off = "cigoff" + var/type_butt = /obj/item/cigbutt + var/lastHolder = null + var/smoketime = 180 // 1 is 2 seconds, so a single cigarette will last 6 minutes. + var/chem_volume = 30 + var/list/list_reagents = list(/datum/reagent/drug/nicotine = 15) + +/obj/item/clothing/mask/cigarette/suicide_act(mob/user) + user.visible_message("[user] is huffing [src] as quickly as [user.p_they()] can! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer.") + return (TOXLOSS|OXYLOSS) + +/obj/item/clothing/mask/cigarette/Initialize() + . = ..() + create_reagents(chem_volume, INJECTABLE | NO_REACT) + if(list_reagents) + reagents.add_reagent_list(list_reagents) + if(starts_lit) + light() + AddComponent(/datum/component/knockoff,90,list(BODY_ZONE_PRECISE_MOUTH),list(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, transfered_by = user)) //if reagents were transfered, show the message + to_chat(user, "You dip \the [src] into \the [glass].") + else //if not, either the beaker was empty, or the cigarette was full + if(!glass.reagents.total_volume) + to_chat(user, "[glass] is empty.") + else + to_chat(user, "[src] is full.") + + +/obj/item/clothing/mask/cigarette/proc/light(flavor_text = null) + if(lit) + return + if(!(flags_1 & INITIALIZED_1)) + icon_state = icon_on + 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 + if(reagents.get_reagent_amount(/datum/reagent/nitroglycerin)) // nitrogylcerin explodes with a whole lot more strength + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/nitroglycerin), 1), get_turf(src), 0, 0) + e.start() + qdel(src) + return + // allowing reagents to react after being lit + DISABLE_BITFIELD(reagents.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/extinguish() + if(!lit) + return + name = copytext(name,5,length(name)+1) + attack_verb = null + hitsound = null + damtype = BRUTE + force = 0 + icon_state = icon_off + item_state = icon_off + STOP_PROCESSING(SSobj, src) + ENABLE_BITFIELD(reagents.flags, NO_REACT) + lit = FALSE + if(ismob(loc)) + var/mob/living/M = loc + to_chat(M, "Your [name] goes out.") + M.update_inv_wear_mask() + M.update_inv_hands() + +/obj/item/clothing/mask/cigarette/proc/handle_reagents() + if(reagents.total_volume) + 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) && (nextdragtime <= world.time)) + nextdragtime = world.time + dragtime + handle_reagents() + +/obj/item/clothing/mask/cigarette/attack_self(mob/user) + if(lit) + user.visible_message("[user] calmly drops and treads on \the [src], putting it out instantly.") + new type_butt(user.loc) + new /obj/effect/decal/cleanable/ash(user.loc) + qdel(src) + . = ..() + +/obj/item/clothing/mask/cigarette/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!istype(M)) + return ..() + if(M.on_fire && !lit) + light("[user] lights [src] with [M]'s burning body. What a cold-blooded badass.") + return + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "The [cig.name] is already lit.") + if(M == user) + cig.attackby(src, user) + else + cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") + else + return ..() + +/obj/item/clothing/mask/cigarette/fire_act(exposed_temperature, exposed_volume) + light() + +/obj/item/clothing/mask/cigarette/is_hot() + 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 = 13, /datum/reagent/consumable/menthol = 5) + +/obj/item/clothing/mask/cigarette/robust + desc = "A Robust brand cigarette." + +/obj/item/clothing/mask/cigarette/robustgold + desc = "A Robust Gold brand cigarette." + list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/gold = 3) // Just enough to taste a hint of expensive metal. + +/obj/item/clothing/mask/cigarette/carp + desc = "A Carp Classic brand cigarette." + +/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 = 120 // four minutes + chem_volume = 50 + list_reagents = null + +/obj/item/clothing/mask/cigarette/rollie/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/item/clothing/mask/cigarette/rollie/nicotine + list_reagents = list(/datum/reagent/drug/nicotine = 15) + +/obj/item/clothing/mask/cigarette/rollie/trippy + list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/drug/mushroomhallucinogen = 35) + starts_lit = TRUE + +/obj/item/clothing/mask/cigarette/rollie/cannabis + list_reagents = list(/datum/reagent/drug/space_drugs = 15, /datum/reagent/toxin/lipolicide = 35) + +/obj/item/clothing/mask/cigarette/rollie/mindbreaker + list_reagents = list(/datum/reagent/toxin/mindbreaker = 35, /datum/reagent/toxin/lipolicide = 15) + +/obj/item/cigbutt/roach + name = "roach" + desc = "A manky old roach, or for non-stoners, a used rollup." + icon_state = "roach" + +/obj/item/cigbutt/roach/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + +//////////// +// CIGARS // +//////////// +/obj/item/clothing/mask/cigarette/cigar + name = "premium cigar" + desc = "A brown roll of tobacco and... well, you're not quite sure. This thing's huge!" + icon_state = "cigaroff" + icon_on = "cigaron" + icon_off = "cigaroff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. + type_butt = /obj/item/cigbutt/cigarbutt + throw_speed = 0.5 + item_state = "cigaroff" + smoketime = 300 // 11 minutes + chem_volume = 40 + list_reagents = list(/datum/reagent/drug/nicotine = 25) + +/obj/item/clothing/mask/cigarette/cigar/cohiba + name = "\improper Cohiba Robusto cigar" + desc = "There's little more you could want from a cigar." + icon_state = "cigar2off" + icon_on = "cigar2on" + icon_off = "cigar2off" + smoketime = 600 // 20 minutes + chem_volume = 80 + list_reagents =list(/datum/reagent/drug/nicotine = 40) + +/obj/item/clothing/mask/cigarette/cigar/havana + name = "premium Havanian cigar" + desc = "A cigar fit for only the best of the best." + icon_state = "cigar2off" + icon_on = "cigar2on" + icon_off = "cigar2off" + smoketime = 900 // 30 minutes + chem_volume = 50 + list_reagents =list(/datum/reagent/drug/nicotine = 15) + +/obj/item/cigbutt + name = "cigarette butt" + desc = "A manky old cigarette butt." + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "cigbutt" + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + grind_results = list(/datum/reagent/carbon = 2) + +/obj/item/cigbutt/cigarbutt + name = "cigar butt" + desc = "A manky old cigar butt." + icon_state = "cigarbutt" + +///////////////// +//SMOKING PIPES// +///////////////// +/obj/item/clothing/mask/cigarette/pipe + name = "smoking pipe" + desc = "A pipe, for smoking. Probably made of meerschaum or something." + icon_state = "pipeoff" + 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, transfered_by = user) + qdel(O) + else + to_chat(user, "It has to be dried first!") + else + to_chat(user, "It is already packed!") + else + var/lighting_text = O.ignition_effect(src,user) + if(lighting_text) + if(smoketime > 0) + light(lighting_text) + else + to_chat(user, "There is nothing to smoke!") + else + return ..() + +/obj/item/clothing/mask/cigarette/pipe/attack_self(mob/user) + var/turf/location = get_turf(user) + if(lit) + user.visible_message("[user] puts out [src].", "You put out [src].") + lit = 0 + icon_state = icon_off + 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/cyborg_unequip(mob/user) + if(!lit) + return + set_lit(FALSE) + +/obj/item/lighter/suicide_act(mob/living/carbon/user) + if (lit) + user.visible_message("[user] begins holding \the [src]'s flame up to [user.p_their()] face! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/items/welder.ogg', 50, 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(is_hot()) + . = "With a single flick of [user.p_their()] wrist, [user] smoothly lights [A] with [src]. Damn [user.p_theyre()] cool." + +/obj/item/lighter/proc/set_lit(new_lit) + lit = new_lit + if(lit) + force = 5 + damtype = "fire" + hitsound = 'sound/items/welder.ogg' + attack_verb = list("burnt", "singed") + set_light(1) + START_PROCESSING(SSobj, src) + else + hitsound = "swing_hit" + force = 0 + attack_verb = null //human_defense.dm takes care of it + set_light(0) + STOP_PROCESSING(SSobj, src) + update_icon() + +/obj/item/lighter/extinguish() + set_lit(FALSE) + +/obj/item/lighter/attack_self(mob/living/user) + if(user.is_holding(src)) + if(!lit) + set_lit(TRUE) + if(fancy) + user.visible_message("Without even breaking stride, [user] flips open and lights [src] in one smooth movement.", "Without even breaking stride, you flip open and light [src] in one smooth movement.") + else + var/prot = FALSE + var/mob/living/carbon/human/H = user + + if(istype(H) && H.gloves) + var/obj/item/clothing/gloves/G = H.gloves + if(G.max_heat_protection_temperature) + prot = (G.max_heat_protection_temperature > 360) + else + prot = TRUE + + if(prot || prob(75)) + user.visible_message("After a few attempts, [user] manages to light [src].", "After a few attempts, you manage to light [src].") + else + var/hitzone = user.held_index_to_dir(user.active_hand_index) == "r" ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND + user.apply_damage(5, BURN, hitzone) + user.visible_message("After a few attempts, [user] manages to light [src] - however, [user.p_they()] burn [user.p_their()] finger in the process.", "You burn yourself while lighting the lighter!") + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "burnt_thumb", /datum/mood_event/burnt_thumb) + + else + set_lit(FALSE) + if(fancy) + user.visible_message("You hear a quiet click, as [user] shuts off [src] without even looking at what [user.p_theyre()] doing. Wow.", "You quietly shut off [src] without even looking at what you're doing. Wow.") + else + user.visible_message("[user] quietly shuts off [src].", "You quietly shut off [src].") + else + . = ..() + +/obj/item/lighter/attack(mob/living/carbon/M, mob/living/carbon/user) + if(lit && M.IgniteMob()) + message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") + log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "The [cig.name] is already lit.") + if(M == user) + cig.attackby(src, user) + else + if(fancy) + cig.light("[user] whips the [name] out and holds it for [M]. [user.p_their(TRUE)] arm is as steady as the unflickering flame [user.p_they()] light[user.p_s()] \the [cig] with.") + else + cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") + else + ..() + +/obj/item/lighter/process() + open_flame() + +/obj/item/lighter/is_hot() + return lit * heat + + +/obj/item/lighter/greyscale + name = "cheap lighter" + desc = "A cheap lighter." + icon_state = "lighter" + fancy = FALSE + overlay_list = list( + "transp", + "tall", + "matte", + "zoppo" //u cant stoppo th zoppo + ) + var/lighter_color + var/list/color_list = list( //Same 16 color selection as electronic assemblies + COLOR_ASSEMBLY_BLACK, + COLOR_FLOORTILE_GRAY, + COLOR_ASSEMBLY_BGRAY, + COLOR_ASSEMBLY_WHITE, + COLOR_ASSEMBLY_RED, + COLOR_ASSEMBLY_ORANGE, + COLOR_ASSEMBLY_BEIGE, + COLOR_ASSEMBLY_BROWN, + COLOR_ASSEMBLY_GOLD, + COLOR_ASSEMBLY_YELLOW, + COLOR_ASSEMBLY_GURKHA, + COLOR_ASSEMBLY_LGREEN, + COLOR_ASSEMBLY_GREEN, + COLOR_ASSEMBLY_LBLUE, + COLOR_ASSEMBLY_BLUE, + COLOR_ASSEMBLY_PURPLE + ) + +/obj/item/lighter/greyscale/Initialize() + . = ..() + if(!lighter_color) + lighter_color = pick(color_list) + update_icon() + +/obj/item/lighter/greyscale/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(is_hot()) + . = "After some fiddling, [user] manages to light [A] with [src]." + + +/obj/item/lighter/slime + name = "slime zippo" + desc = "A specialty zippo made from slimes and industry. Has a much hotter flame than normal." + icon_state = "slighter" + heat = 3000 //Blue flame! + light_color = LIGHT_COLOR_CYAN + overlay_state = "slime" + grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/medicine/pyroxadone = 5) + + +/////////// +//ROLLING// +/////////// +/obj/item/rollingpaper + name = "rolling paper" + desc = "A thin piece of paper used to make fine smokeables." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig_paper" + w_class = WEIGHT_CLASS_TINY + +/obj/item/rollingpaper/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity) + return + if(istype(target, /obj/item/reagent_containers/food/snacks/grown)) + var/obj/item/reagent_containers/food/snacks/grown/O = target + if(O.dry) + var/obj/item/clothing/mask/cigarette/rollie/R = new /obj/item/clothing/mask/cigarette/rollie(user.loc) + R.chem_volume = target.reagents.total_volume + target.reagents.trans_to(R, R.chem_volume, transfered_by = user) + qdel(target) + qdel(src) + user.put_in_active_hand(R) + to_chat(user, "You roll the [target.name] into a rolling paper.") + R.desc = "Dried [target.name] rolled up in a thin piece of paper." + else + to_chat(user, "You need to dry this first!") + +/////////////// +//VAPE NATION// +/////////////// +/obj/item/clothing/mask/vape + name = "\improper E-Cigarette" + desc = "A classy and highly sophisticated electronic cigarette, for classy and dignified gentlemen. A warning label reads \"Warning: Do not fill with flammable materials.\""//<<< i'd vape to that. + icon = 'icons/obj/clothing/masks.dmi' + icon_state = null + item_state = null + w_class = WEIGHT_CLASS_TINY + var/chem_volume = 100 + var/vapetime = 0 //this so it won't puff out clouds every tick + var/screw = 0 // kinky + var/super = 0 //for the fattest vapes dude. + +/obj/item/clothing/mask/vape/suicide_act(mob/user) + user.visible_message("[user] is puffin hard on dat vape, [user.p_they()] trying to join the vape life on a whole notha plane!")//it doesn't give you cancer, it is cancer + return (TOXLOSS|OXYLOSS) + + +/obj/item/clothing/mask/vape/Initialize(mapload, param_color) + . = ..() + create_reagents(chem_volume, NO_REACT) + reagents.add_reagent(/datum/reagent/drug/nicotine, 50) + if(!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.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.flags, OPENCONTAINER) + cut_overlays() + + if(O.tool_behaviour == TOOL_MULTITOOL) + if(screw && !(obj_flags & EMAGGED))//also kinky + if(!super) + cut_overlays() + super = 1 + to_chat(user, "You increase the voltage of [src].") + add_overlay("vapeopen_med") + else + cut_overlays() + super = 0 + to_chat(user, "You decrease the voltage of [src].") + add_overlay("vapeopen_low") + + if(screw && (obj_flags & EMAGGED)) + to_chat(user, "[src] can't be modified!") + else + ..() + + +/obj/item/clothing/mask/vape/emag_act(mob/user)// I WON'T REGRET WRITTING THIS, SURLY. + if(screw) + if(!(obj_flags & EMAGGED)) + cut_overlays() + obj_flags |= EMAGGED + super = 0 + to_chat(user, "You maximize the voltage of [src].") + add_overlay("vapeopen_high") + var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect + sp.set_up(5, 1, src) + sp.start() + else + to_chat(user, "[src] is already emagged!") + else + to_chat(user, "You need to open the cap to do that.") + +/obj/item/clothing/mask/vape/attack_self(mob/user) + if(reagents.total_volume > 0) + to_chat(user, "You empty [src] of all reagents.") + reagents.clear_reagents() + +/obj/item/clothing/mask/vape/equipped(mob/user, slot) + if(slot == SLOT_WEAR_MASK) + if(!screw) + to_chat(user, "You start puffing on the vape.") + DISABLE_BITFIELD(reagents.flags, NO_REACT) + START_PROCESSING(SSobj, src) + else //it will not start if the vape is opened. + to_chat(user, "You need to close the cap first!") + +/obj/item/clothing/mask/vape/dropped(mob/user) + var/mob/living/carbon/C = user + if(C.get_item_by_slot(SLOT_WEAR_MASK) == src) + ENABLE_BITFIELD(reagents.flags, NO_REACT) + STOP_PROCESSING(SSobj, src) + +/obj/item/clothing/mask/vape/proc/hand_reagents()//had to rename to avoid duplicate error + if(reagents.total_volume) + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob + var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) //this will react instantly, making them a little more dangerous than cigarettes + reagents.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 + 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 + if(reagents.get_reagent_amount(/datum/reagent/nitroglycerin)) // nitrogylcerin explodes with a whole lot more strength + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/nitroglycerin), 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.Paralyze(300, 1, 0) + var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread + sp.set_up(5, 1, src) + sp.start() + to_chat(M, "[src] suddenly explodes in your mouth!") + qdel(src) + return + + if(reagents && reagents.total_volume) + hand_reagents() diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm index db7b86b502c3..8a0807f6d62a 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -1,228 +1,228 @@ -/obj/item/lipstick - gender = PLURAL - name = "red lipstick" - desc = "A generic brand of lipstick." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "lipstick" - w_class = WEIGHT_CLASS_TINY - var/colour = "red" - var/open = FALSE - -/obj/item/lipstick/purple - name = "purple lipstick" - colour = "purple" - -/obj/item/lipstick/jade - //It's still called Jade, but theres no HTML color for jade, so we use lime. - name = "jade lipstick" - colour = "lime" - -/obj/item/lipstick/black - name = "black lipstick" - colour = "black" - -/obj/item/lipstick/random - name = "lipstick" - icon_state = "random_lipstick" - -/obj/item/lipstick/random/Initialize() - . = ..() - icon_state = "lipstick" - colour = pick("red","purple","lime","black","green","blue","white") - name = "[colour] lipstick" - -/obj/item/lipstick/attack_self(mob/user) - cut_overlays() - to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") - open = !open - if(open) - var/mutable_appearance/colored_overlay = mutable_appearance(icon, "lipstick_uncap_color") - colored_overlay.color = colour - icon_state = "lipstick_uncap" - add_overlay(colored_overlay) - else - icon_state = "lipstick" - -/obj/item/lipstick/attack(mob/M, mob/user) - if(!open) - return - - if(!ismob(M)) - return - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.is_mouth_covered()) - to_chat(user, "Remove [ H == user ? "your" : "[H.p_their()]" ] mask!") - return - if(H.lip_style) //if they already have lipstick on - to_chat(user, "You need to wipe off the old lipstick first!") - return - if(H == user) - user.visible_message("[user] does [user.p_their()] lips with \the [src].", \ - "You take a moment to apply \the [src]. Perfect!") - H.lip_style = "lipstick" - H.lip_color = colour - H.update_body() - else - user.visible_message("[user] begins to do [H]'s lips with \the [src].", \ - "You begin to apply \the [src] on [H]'s lips...") - if(do_after(user, 20, target = H)) - user.visible_message("[user] does [H]'s lips with \the [src].", \ - "You apply \the [src] on [H]'s lips.") - H.lip_style = "lipstick" - H.lip_color = colour - H.update_body() - else - to_chat(user, "Where are the lips on that?") - -//you can wipe off lipstick with paper! -/obj/item/paper/attack(mob/M, mob/user) - if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) - if(!ismob(M)) - return - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H == user) - to_chat(user, "You wipe off the lipstick with [src].") - H.lip_style = null - H.update_body() - else - user.visible_message("[user] begins to wipe [H]'s lipstick off with \the [src].", \ - "You begin to wipe off [H]'s lipstick...") - if(do_after(user, 10, target = H)) - user.visible_message("[user] wipes [H]'s lipstick off with \the [src].", \ - "You wipe off [H]'s lipstick.") - H.lip_style = null - H.update_body() - else - ..() - -/obj/item/razor - name = "electric razor" - desc = "The latest and greatest power razor born from the science of shaving." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "razor" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_TINY - -/obj/item/razor/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins shaving [user.p_them()]self without the razor guard! It looks like [user.p_theyre()] trying to commit suicide!") - shave(user, BODY_ZONE_PRECISE_MOUTH) - shave(user, BODY_ZONE_HEAD)//doesnt need to be BODY_ZONE_HEAD specifically, but whatever - return BRUTELOSS - -/obj/item/razor/proc/shave(mob/living/carbon/human/H, location = BODY_ZONE_PRECISE_MOUTH) - if(location == BODY_ZONE_PRECISE_MOUTH) - H.facial_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(user.a_intent == INTENT_HELP) - if(H.gender == MALE) - if (H == user) - to_chat(user, "You need a mirror to properly style your own facial hair!") - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - var/new_style = input(user, "Select a facial hair style", "Grooming") as null|anything in GLOB.facial_hair_styles_list - if(!get_location_accessible(H, location)) - to_chat(user, "The mask is in the way!") - return - user.visible_message("[user] tries to change [H]'s facial hair style using [src].", "You try to change [H]'s facial hair style using [src].") - if(new_style && do_after(user, 60, target = H)) - user.visible_message("[user] successfully changes [H]'s facial hair style using [src].", "You successfully change [H]'s facial hair style using [src].") - H.facial_hair_style = new_style - H.update_hair() - return - else - return - - else - if(!(FACEHAIR in H.dna.species.species_traits)) - to_chat(user, "There is no facial hair to shave!") - return - if(!get_location_accessible(H, location)) - to_chat(user, "The mask is in the way!") - return - if(H.facial_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 - user.visible_message("[user] tries to shave [H]'s facial hair with [src].", \ - "You start shaving [H]'s facial hair...") - if(do_after(user, 50, target = H)) - user.visible_message("[user] shaves off [H]'s facial hair with [src].", \ - "You shave [H]'s facial hair clean off.") - shave(H, location) - - else if(location == BODY_ZONE_HEAD) - if(user.a_intent == INTENT_HELP) - if (H == user) - to_chat(user, "You need a mirror to properly style your own hair!") - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - var/new_style = input(user, "Select a hair style", "Grooming") as null|anything in GLOB.hair_styles_list - if(!get_location_accessible(H, location)) - to_chat(user, "The headgear is in the way!") - return - user.visible_message("[user] tries to change [H]'s hairstyle using [src].", "You try to change [H]'s hairstyle using [src].") - if(new_style && do_after(user, 60, target = H)) - user.visible_message("[user] successfully changes [H]'s hairstyle using [src].", "You successfully change [H]'s hairstyle using [src].") - H.hair_style = new_style - H.update_hair() - return - - else - if(!(HAIR in H.dna.species.species_traits)) - to_chat(user, "There is no hair to shave!") - return - if(!get_location_accessible(H, location)) - to_chat(user, "The headgear is in the way!") - return - if(H.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/Initialize() + . = ..() + icon_state = "lipstick" + colour = pick("red","purple","lime","black","green","blue","white") + name = "[colour] lipstick" + +/obj/item/lipstick/attack_self(mob/user) + cut_overlays() + to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") + open = !open + if(open) + var/mutable_appearance/colored_overlay = mutable_appearance(icon, "lipstick_uncap_color") + colored_overlay.color = colour + icon_state = "lipstick_uncap" + add_overlay(colored_overlay) + else + icon_state = "lipstick" + +/obj/item/lipstick/attack(mob/M, mob/user) + if(!open) + return + + if(!ismob(M)) + return + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.is_mouth_covered()) + to_chat(user, "Remove [ H == user ? "your" : "[H.p_their()]" ] mask!") + return + if(H.lip_style) //if they already have lipstick on + to_chat(user, "You need to wipe off the old lipstick first!") + return + if(H == user) + user.visible_message("[user] does [user.p_their()] lips with \the [src].", \ + "You take a moment to apply \the [src]. Perfect!") + H.lip_style = "lipstick" + H.lip_color = colour + H.update_body() + else + user.visible_message("[user] begins to do [H]'s lips with \the [src].", \ + "You begin to apply \the [src] on [H]'s lips...") + if(do_after(user, 20, target = H)) + user.visible_message("[user] does [H]'s lips with \the [src].", \ + "You apply \the [src] on [H]'s lips.") + H.lip_style = "lipstick" + H.lip_color = colour + H.update_body() + else + to_chat(user, "Where are the lips on that?") + +//you can wipe off lipstick with paper! +/obj/item/paper/attack(mob/M, mob/user) + if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) + if(!ismob(M)) + return + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H == user) + to_chat(user, "You wipe off the lipstick with [src].") + H.lip_style = null + H.update_body() + else + user.visible_message("[user] begins to wipe [H]'s lipstick off with \the [src].", \ + "You begin to wipe off [H]'s lipstick...") + if(do_after(user, 10, target = H)) + user.visible_message("[user] wipes [H]'s lipstick off with \the [src].", \ + "You wipe off [H]'s lipstick.") + H.lip_style = null + H.update_body() + else + ..() + +/obj/item/razor + name = "electric razor" + desc = "The latest and greatest power razor born from the science of shaving." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "razor" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_TINY + +/obj/item/razor/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins shaving [user.p_them()]self without the razor guard! It looks like [user.p_theyre()] trying to commit suicide!") + shave(user, BODY_ZONE_PRECISE_MOUTH) + shave(user, BODY_ZONE_HEAD)//doesnt need to be BODY_ZONE_HEAD specifically, but whatever + return BRUTELOSS + +/obj/item/razor/proc/shave(mob/living/carbon/human/H, location = BODY_ZONE_PRECISE_MOUTH) + if(location == BODY_ZONE_PRECISE_MOUTH) + H.facial_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(user.a_intent == INTENT_HELP) + if(H.gender == MALE) + if (H == user) + to_chat(user, "You need a mirror to properly style your own facial hair!") + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/new_style = input(user, "Select a facial hair style", "Grooming") as null|anything in GLOB.facial_hair_styles_list + if(!get_location_accessible(H, location)) + to_chat(user, "The mask is in the way!") + return + user.visible_message("[user] tries to change [H]'s facial hair style using [src].", "You try to change [H]'s facial hair style using [src].") + if(new_style && do_after(user, 60, target = H)) + user.visible_message("[user] successfully changes [H]'s facial hair style using [src].", "You successfully change [H]'s facial hair style using [src].") + H.facial_hair_style = new_style + H.update_hair() + return + else + return + + else + if(!(FACEHAIR in H.dna.species.species_traits)) + to_chat(user, "There is no facial hair to shave!") + return + if(!get_location_accessible(H, location)) + to_chat(user, "The mask is in the way!") + return + if(H.facial_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 + user.visible_message("[user] tries to shave [H]'s facial hair with [src].", \ + "You start shaving [H]'s facial hair...") + if(do_after(user, 50, target = H)) + user.visible_message("[user] shaves off [H]'s facial hair with [src].", \ + "You shave [H]'s facial hair clean off.") + shave(H, location) + + else if(location == BODY_ZONE_HEAD) + if(user.a_intent == INTENT_HELP) + if (H == user) + to_chat(user, "You need a mirror to properly style your own hair!") + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/new_style = input(user, "Select a hair style", "Grooming") as null|anything in GLOB.hair_styles_list + if(!get_location_accessible(H, location)) + to_chat(user, "The headgear is in the way!") + return + user.visible_message("[user] tries to change [H]'s hairstyle using [src].", "You try to change [H]'s hairstyle using [src].") + if(new_style && do_after(user, 60, target = H)) + user.visible_message("[user] successfully changes [H]'s hairstyle using [src].", "You successfully change [H]'s hairstyle using [src].") + H.hair_style = new_style + H.update_hair() + return + + else + if(!(HAIR in H.dna.species.species_traits)) + to_chat(user, "There is no hair to shave!") + return + if(!get_location_accessible(H, location)) + to_chat(user, "The headgear is in the way!") + return + if(H.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 46f858f9969c..49f3c8a3eb37 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -1,1025 +1,1025 @@ - -//The advanced pea-green monochrome lcd of tomorrow. - -GLOBAL_LIST_EMPTY(PDAs) - -#define PDA_SCANNER_NONE 0 -#define PDA_SCANNER_MEDICAL 1 -#define PDA_SCANNER_FORENSICS 2 //unused -#define PDA_SCANNER_REAGENT 3 -#define PDA_SCANNER_HALOGEN 4 -#define PDA_SCANNER_GAS 5 -#define PDA_SPAM_DELAY 2 MINUTES - -/obj/item/pda - name = "\improper PDA" - desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." - icon = 'icons/obj/pda.dmi' - icon_state = "pda" - 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/icon_alert = "pda-r" //Icon to be overlayed for message alerts. Taken from the pda icon file. - var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again. - var/font_mode = "font-family:monospace;" //The currently selected font. - var/background_color = "#808000" //The currently selected background color. - - #define FONT_MONO "font-family:monospace;" - #define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;" - #define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px" - #define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;" - #define MODE_MONO 0 - #define MODE_SHARE 1 - #define MODE_ORBITRON 2 - #define MODE_VT 3 - - //Secondary variables - var/scanmode = PDA_SCANNER_NONE - var/fon = FALSE //Is the flashlight function on? - var/f_lum = 2.3 //Luminosity for the flashlight function - var/silent = FALSE //To beep or not to beep, that is the question - var/toff = FALSE //If TRUE, messenger disabled - var/tnote = null //Current Texts - var/last_text //No text spamming - var/last_everyone //No text for everyone spamming - var/last_noise //Also no honk spamming that's bad too - var/ttone = "beep" //The ringtone! - var/honkamt = 0 //How many honks left when infected with honk.exe - var/mimeamt = 0 //How many silence left when infected with mime.exe - var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant!" //Current note in the notepad function - var/notehtml = "" - var/notescanned = FALSE // True if what is in the notekeeper was from a paper. - var/hidden = FALSE // Is the PDA hidden from the PDA list? - var/emped = FALSE - var/equipped = FALSE //used here to determine if this is the first time its been picked up - - var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. - var/ownjob = null //related to above - - var/obj/item/paicard/pai = null // A slot for a personal AI device - - var/datum/picture/picture //Scanned photo - - var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) - var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. - var/overlays_x_offset = 0 //x offset to use for certain overlays - - var/underline_flag = TRUE //flag for underline - -/obj/item/pda/suicide_act(mob/living/carbon/user) - var/deathMessage = msg_input(user) - if (!deathMessage) - deathMessage = "i ded" - user.visible_message("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!") - tnote += "→ To The Grim Reaper:
                    [deathMessage]
                    "//records a message in their PDA as being sent to the grim reaper - return BRUTELOSS - -/obj/item/pda/examine(mob/user) - . = ..() - if(!id && !inserted_item) - return - - if(id) - . += "Alt-click to remove the id." - - if(inserted_item && (!isturf(loc))) - . += "Ctrl-click to remove [inserted_item]." - -/obj/item/pda/Initialize() - . = ..() - if(fon) - set_light(f_lum) - - GLOB.PDAs += src - if(default_cartridge) - cartridge = new default_cartridge(src) - if(inserted_item) - inserted_item = new inserted_item(src) - else - inserted_item = new /obj/item/pen(src) - update_icon() - -/obj/item/pda/equipped(mob/user, slot) - . = ..() - if(!equipped) - if(user.client) - background_color = user.client.prefs.pda_color - switch(user.client.prefs.pda_style) - if(MONO) - font_index = MODE_MONO - font_mode = FONT_MONO - if(SHARE) - font_index = MODE_SHARE - font_mode = FONT_SHARE - if(ORBITRON) - font_index = MODE_ORBITRON - font_mode = FONT_ORBITRON - if(VT) - font_index = MODE_VT - font_mode = FONT_VT - else - font_index = MODE_MONO - font_mode = FONT_MONO - equipped = TRUE - -/obj/item/pda/proc/update_label() - name = "PDA-[owner] ([ownjob])" //Name generalisation - -/obj/item/pda/GetAccess() - if(id) - return id.GetAccess() - else - return ..() - -/obj/item/pda/GetID() - return id - -/obj/item/pda/update_icon() - cut_overlays() - var/mutable_appearance/overlay = new() - overlay.pixel_x = overlays_x_offset - if(id) - overlay.icon_state = "id_overlay" - add_overlay(new /mutable_appearance(overlay)) - if(inserted_item) - overlay.icon_state = "insert_overlay" - add_overlay(new /mutable_appearance(overlay)) - if(fon) - overlay.icon_state = "light_overlay" - add_overlay(new /mutable_appearance(overlay)) - if(pai) - if(pai.pai) - overlay.icon_state = "pai_overlay" - add_overlay(new /mutable_appearance(overlay)) - else - overlay.icon_state = "pai_off_overlay" - 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()]
                    " //:[world.time / 100 % 6][world.time / 100 % 10]" - dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer+540]" - - dat += "

                    " - - dat += "

                    General Functions

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

                    Engineering Functions

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

                    Medical Functions

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

                    Security Functions

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

                    Quartermaster Functions:

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

                  Utilities

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

                  [PDAIMG(notes)] Notekeeper V2.2

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

                  [PDAIMG(mail)] SpaceMessenger V3.9.6

                  " - dat += "[PDAIMG(bell)]Ringer: [silent == 1 ? "Off" : "On"] | " - dat += "[PDAIMG(mail)]Send / Receive: [toff == 1 ? "Off" : "On"] | " - dat += "[PDAIMG(bell)]Set Ringtone | " - dat += "[PDAIMG(mail)]Messages
                  " - - 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][MOLES]/total_moles - if(gas_level > 0) - dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_level*100, 0.01)]%
                  " - - dat += "Temperature: [round(environment.temperature-T0C)]°C
                  " - dat += "
                  " - else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub. - dat += cartridge.generate_menu() - - dat += "" - - if (underline_flag) - dat = replacetext(dat, "text-decoration:none", "text-decoration:underline") - if (!underline_flag) - dat = replacetext(dat, "text-decoration:underline", "text-decoration:none") - - user << browse(dat, "window=pda;size=400x450;border=1;can_resize=1;can_minimize=0") - onclose(user, "pda", src) - -/obj/item/pda/Topic(href, href_list) - ..() - var/mob/living/U = usr - //Looking for master was kind of pointless since PDAs don't appear to have one. - - if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"]) - add_fingerprint(U) - U.set_machine(src) - - switch(href_list["choice"]) - -//BASIC FUNCTIONS=================================== - - if("Refresh")//Refresh, goes to the end of the proc. - - if ("Toggle_Font") - //CODE REVISION 2 - font_index = (font_index + 1) % 4 - - switch(font_index) - if (MODE_MONO) - font_mode = FONT_MONO - if (MODE_SHARE) - font_mode = FONT_SHARE - if (MODE_ORBITRON) - font_mode = FONT_ORBITRON - if (MODE_VT) - font_mode = FONT_VT - if ("Change_Color") - var/new_color = input("Please enter a color name or hex value (Default is \'#808000\').",background_color)as color - background_color = new_color - - if ("Toggle_Underline") - underline_flag = !underline_flag - - if("Return")//Return - if(mode<=9) - mode = 0 - else - mode = round(mode/10) - if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub. - mode = 0 - if ("Authenticate")//Checks for ID - id_check(U) - if("UpdateInfo") - ownjob = id.assignment - if(istype(id, /obj/item/card/id/syndicate)) - owner = id.registered_name - update_label() - if("Eject")//Ejects the cart, only done from hub. - 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() - -//MENU FUNCTIONS=================================== - - if("0")//Hub - mode = 0 - if("1")//Notes - mode = 1 - if("2")//Messenger - mode = 2 - if("21")//Read messeges - mode = 21 - if("3")//Atmos scan - mode = 3 - if("4")//Redirects to hub - mode = 0 - - -//MAIN FUNCTIONS=================================== - - if("Light") - toggle_light() - if("Medical Scan") - if(scanmode == PDA_SCANNER_MEDICAL) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL)) - scanmode = PDA_SCANNER_MEDICAL - if("Reagent Scan") - if(scanmode == PDA_SCANNER_REAGENT) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER)) - scanmode = PDA_SCANNER_REAGENT - if("Halogen Counter") - if(scanmode == PDA_SCANNER_HALOGEN) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE)) - scanmode = PDA_SCANNER_HALOGEN - if("Honk") - if ( !(last_noise && world.time < last_noise + 20) ) - playsound(src, 'sound/items/bikehorn.ogg', 50, 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("Drone Phone") - var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical") - var/area/A = get_area(U) - if(A && alert_s && !QDELETED(U)) - var/msg = "NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!" - _alert_drones(msg, TRUE, U) - to_chat(U, msg) - - -//NOTEKEEPER FUNCTIONS=================================== - - if ("Edit") - var/n = stripped_multiline_input(U, "Please enter message", name, note) - if (in_range(src, U) && loc == U) - if (mode == 1 && n) - note = n - notehtml = parsemarkdown(n, U) - notescanned = FALSE - else - U << browse(null, "window=pda") - return - -//MESSENGER FUNCTIONS=================================== - - if("Toggle Messenger") - toff = !toff - if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status - silent = !silent - if("Clear")//Clears messages - tnote = null - if("Ringtone") - var/t = 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"]) in GLOB.PDAs) - - if("MessageAll") - send_to_all(U) - - if("cart") - if(cartridge) - cartridge.special(U, href_list) - else - U << browse(null, "window=pda") - return - -//SYNDICATE FUNCTIONS=================================== - - if("Toggle Door") - if(cartridge && cartridge.access & CART_REMOTE_DOOR) - for(var/obj/machinery/door/poddoor/M in GLOB.machines) - if(M.id == cartridge.remote_door_id) - if(M.density) - M.open() - else - M.close() - -//pAI FUNCTIONS=================================== - if("pai") - switch(href_list["option"]) - if("1") // Configure pAI device - pai.attack_self(U) - if("2") // Eject pAI device - usr.put_in_hands(pai) - to_chat(usr, "You remove the pAI from the [name].") - -//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() - - if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - if (id) - usr.put_in_hands(id) - to_chat(usr, "You remove the ID from the [name].") - 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 - if(prob(1)) - message += "\nSent from my PDA" - // Send the signal - var/list/string_targets = list() - for (var/obj/item/pda/P in targets) - if (P.owner && P.ownjob) // != src is checked by the UI - string_targets += "[P.owner] ([P.ownjob])" - for (var/obj/machinery/computer/message_monitor/M in targets) - // In case of "Reply" to a message from a console, this will make the - // message be logged successfully. If the console is impersonating - // someone by matching their name and job, the reply will reach the - // impersonated PDA. - string_targets += "[M.customsender] ([M.customjob])" - if (!string_targets.len) - return - - var/datum/signal/subspace/messaging/pda/signal = new(src, list( - "name" = "[owner]", - "job" = "[ownjob]", - "message" = message, - "targets" = string_targets - )) - if (picture) - signal.data["photo"] = picture - signal.send_to_receivers() - - // If it didn't reach, note that fact - if (!signal.data["done"]) - to_chat(user, "ERROR: Server isn't responding.") - return - - var/target_text = signal.format_target() - // Log it in our logs - tnote += "→ To [target_text]:
                  [signal.format_message()]
                  " - // Show it to ghosts - var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message()]" - for(var/mob/M in GLOB.player_list) - if(isobserver(M) && M.client && (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]: \"[message]\"") - // Reset the photo - picture = null - last_text = world.time - if (everyone) - last_everyone = world.time - -/obj/item/pda/proc/receive_message(datum/signal/subspace/messaging/pda/signal) - tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
                  [signal.format_message()]
                  " - - if (!silent) - playsound(src, 'sound/machines/twobeep_high.ogg', 50, 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/reply = "(Reply)" - var/hrefstart - var/hrefend - if (isAI(L)) - hrefstart = "" - hrefend = "" - - if(signal.data["automated"]) - reply = "\[Automated Message\]" - - to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message()] [reply]") - - update_icon() - add_overlay(icon_alert) - -/obj/item/pda/proc/send_to_all(mob/living/U) - if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY) - to_chat(U,"Send To All function is still on cooldown.") - return - send_message(U,get_viewable_pdas(), TRUE) - -/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P) - send_message(U,list(P)) - -/obj/item/pda/AltClick() - ..() - - if(id) - remove_id() - else - remove_pen() - -/obj/item/pda/CtrlClick() - ..() - - if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it. - return - - remove_pen() - -/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() - 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) - 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() - return TRUE - else - var/obj/item/card/id/C = user.get_active_held_item() - if(istype(C)) - I = C - - if(I && I.registered_name) - if(!user.transferItemToLoc(I, src)) - return FALSE - var/obj/old_id = id - id = I - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() - if(old_id) - user.put_in_hands(old_id) - update_icon() - return TRUE - -// 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() - - else if(istype(C, /obj/item/card/id)) - var/obj/item/card/id/idcard = C - if(!idcard.registered_name) - to_chat(user, "\The [src] rejects the ID!") - return - if(!owner) - owner = idcard.registered_name - ownjob = idcard.assignment - update_label() - to_chat(user, "Card scanned.") - else - //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() - 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. - var/turf/T = get_turf(src) - - if (ismob(loc)) - var/mob/M = loc - M.show_message("Your [src] explodes!", 1) - 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/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/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/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/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 - -/obj/item/pda/proc/pda_no_detonate() - return COMPONENT_PDA_NO_DETONATE - -#undef PDA_SCANNER_NONE -#undef PDA_SCANNER_MEDICAL -#undef PDA_SCANNER_FORENSICS -#undef PDA_SCANNER_REAGENT -#undef PDA_SCANNER_HALOGEN -#undef PDA_SCANNER_GAS -#undef PDA_SPAM_DELAY + +//The advanced pea-green monochrome lcd of tomorrow. + +GLOBAL_LIST_EMPTY(PDAs) + +#define PDA_SCANNER_NONE 0 +#define PDA_SCANNER_MEDICAL 1 +#define PDA_SCANNER_FORENSICS 2 //unused +#define PDA_SCANNER_REAGENT 3 +#define PDA_SCANNER_HALOGEN 4 +#define PDA_SCANNER_GAS 5 +#define PDA_SPAM_DELAY 2 MINUTES + +/obj/item/pda + name = "\improper PDA" + desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." + icon = 'icons/obj/pda.dmi' + icon_state = "pda" + 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/icon_alert = "pda-r" //Icon to be overlayed for message alerts. Taken from the pda icon file. + var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again. + var/font_mode = "font-family:monospace;" //The currently selected font. + var/background_color = "#808000" //The currently selected background color. + + #define FONT_MONO "font-family:monospace;" + #define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;" + #define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px" + #define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;" + #define MODE_MONO 0 + #define MODE_SHARE 1 + #define MODE_ORBITRON 2 + #define MODE_VT 3 + + //Secondary variables + var/scanmode = PDA_SCANNER_NONE + var/fon = FALSE //Is the flashlight function on? + var/f_lum = 2.3 //Luminosity for the flashlight function + var/silent = FALSE //To beep or not to beep, that is the question + var/toff = FALSE //If TRUE, messenger disabled + var/tnote = null //Current Texts + var/last_text //No text spamming + var/last_everyone //No text for everyone spamming + var/last_noise //Also no honk spamming that's bad too + var/ttone = "beep" //The ringtone! + var/honkamt = 0 //How many honks left when infected with honk.exe + var/mimeamt = 0 //How many silence left when infected with mime.exe + var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant!" //Current note in the notepad function + var/notehtml = "" + var/notescanned = FALSE // True if what is in the notekeeper was from a paper. + var/hidden = FALSE // Is the PDA hidden from the PDA list? + var/emped = FALSE + var/equipped = FALSE //used here to determine if this is the first time its been picked up + + var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. + var/ownjob = null //related to above + + var/obj/item/paicard/pai = null // A slot for a personal AI device + + var/datum/picture/picture //Scanned photo + + var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) + var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. + var/overlays_x_offset = 0 //x offset to use for certain overlays + + var/underline_flag = TRUE //flag for underline + +/obj/item/pda/suicide_act(mob/living/carbon/user) + var/deathMessage = msg_input(user) + if (!deathMessage) + deathMessage = "i ded" + user.visible_message("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!") + tnote += "→ To The Grim Reaper:
                  [deathMessage]
                  "//records a message in their PDA as being sent to the grim reaper + return BRUTELOSS + +/obj/item/pda/examine(mob/user) + . = ..() + if(!id && !inserted_item) + return + + if(id) + . += "Alt-click to remove the id." + + if(inserted_item && (!isturf(loc))) + . += "Ctrl-click to remove [inserted_item]." + +/obj/item/pda/Initialize() + . = ..() + if(fon) + set_light(f_lum) + + GLOB.PDAs += src + if(default_cartridge) + cartridge = new default_cartridge(src) + if(inserted_item) + inserted_item = new inserted_item(src) + else + inserted_item = new /obj/item/pen(src) + update_icon() + +/obj/item/pda/equipped(mob/user, slot) + . = ..() + if(!equipped) + if(user.client) + background_color = user.client.prefs.pda_color + switch(user.client.prefs.pda_style) + if(MONO) + font_index = MODE_MONO + font_mode = FONT_MONO + if(SHARE) + font_index = MODE_SHARE + font_mode = FONT_SHARE + if(ORBITRON) + font_index = MODE_ORBITRON + font_mode = FONT_ORBITRON + if(VT) + font_index = MODE_VT + font_mode = FONT_VT + else + font_index = MODE_MONO + font_mode = FONT_MONO + equipped = TRUE + +/obj/item/pda/proc/update_label() + name = "PDA-[owner] ([ownjob])" //Name generalisation + +/obj/item/pda/GetAccess() + if(id) + return id.GetAccess() + else + return ..() + +/obj/item/pda/GetID() + return id + +/obj/item/pda/update_icon() + cut_overlays() + var/mutable_appearance/overlay = new() + overlay.pixel_x = overlays_x_offset + if(id) + overlay.icon_state = "id_overlay" + add_overlay(new /mutable_appearance(overlay)) + if(inserted_item) + overlay.icon_state = "insert_overlay" + add_overlay(new /mutable_appearance(overlay)) + if(fon) + overlay.icon_state = "light_overlay" + add_overlay(new /mutable_appearance(overlay)) + if(pai) + if(pai.pai) + overlay.icon_state = "pai_overlay" + add_overlay(new /mutable_appearance(overlay)) + else + overlay.icon_state = "pai_off_overlay" + 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()]
                  " //:[world.time / 100 % 6][world.time / 100 % 10]" + dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer+540]" + + dat += "

                  " + + dat += "

                  General Functions

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

                  Engineering Functions

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

                  Medical Functions

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

                  Security Functions

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

                  Quartermaster Functions:

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

                Utilities

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

                [PDAIMG(notes)] Notekeeper V2.2

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

                [PDAIMG(mail)] SpaceMessenger V3.9.6

                " + dat += "[PDAIMG(bell)]Ringer: [silent == 1 ? "Off" : "On"] | " + dat += "[PDAIMG(mail)]Send / Receive: [toff == 1 ? "Off" : "On"] | " + dat += "[PDAIMG(bell)]Set Ringtone | " + dat += "[PDAIMG(mail)]Messages
                " + + 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][MOLES]/total_moles + if(gas_level > 0) + dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_level*100, 0.01)]%
                " + + dat += "Temperature: [round(environment.temperature-T0C)]°C
                " + dat += "
                " + else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub. + dat += cartridge.generate_menu() + + dat += "" + + if (underline_flag) + dat = replacetext(dat, "text-decoration:none", "text-decoration:underline") + if (!underline_flag) + dat = replacetext(dat, "text-decoration:underline", "text-decoration:none") + + user << browse(dat, "window=pda;size=400x450;border=1;can_resize=1;can_minimize=0") + onclose(user, "pda", src) + +/obj/item/pda/Topic(href, href_list) + ..() + var/mob/living/U = usr + //Looking for master was kind of pointless since PDAs don't appear to have one. + + if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"]) + add_fingerprint(U) + U.set_machine(src) + + switch(href_list["choice"]) + +//BASIC FUNCTIONS=================================== + + if("Refresh")//Refresh, goes to the end of the proc. + + if ("Toggle_Font") + //CODE REVISION 2 + font_index = (font_index + 1) % 4 + + switch(font_index) + if (MODE_MONO) + font_mode = FONT_MONO + if (MODE_SHARE) + font_mode = FONT_SHARE + if (MODE_ORBITRON) + font_mode = FONT_ORBITRON + if (MODE_VT) + font_mode = FONT_VT + if ("Change_Color") + var/new_color = input("Please enter a color name or hex value (Default is \'#808000\').",background_color)as color + background_color = new_color + + if ("Toggle_Underline") + underline_flag = !underline_flag + + if("Return")//Return + if(mode<=9) + mode = 0 + else + mode = round(mode/10) + if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub. + mode = 0 + if ("Authenticate")//Checks for ID + id_check(U) + if("UpdateInfo") + ownjob = id.assignment + if(istype(id, /obj/item/card/id/syndicate)) + owner = id.registered_name + update_label() + if("Eject")//Ejects the cart, only done from hub. + 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() + +//MENU FUNCTIONS=================================== + + if("0")//Hub + mode = 0 + if("1")//Notes + mode = 1 + if("2")//Messenger + mode = 2 + if("21")//Read messeges + mode = 21 + if("3")//Atmos scan + mode = 3 + if("4")//Redirects to hub + mode = 0 + + +//MAIN FUNCTIONS=================================== + + if("Light") + toggle_light() + if("Medical Scan") + if(scanmode == PDA_SCANNER_MEDICAL) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL)) + scanmode = PDA_SCANNER_MEDICAL + if("Reagent Scan") + if(scanmode == PDA_SCANNER_REAGENT) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER)) + scanmode = PDA_SCANNER_REAGENT + if("Halogen Counter") + if(scanmode == PDA_SCANNER_HALOGEN) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE)) + scanmode = PDA_SCANNER_HALOGEN + if("Honk") + if ( !(last_noise && world.time < last_noise + 20) ) + playsound(src, 'sound/items/bikehorn.ogg', 50, 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("Drone Phone") + var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical") + var/area/A = get_area(U) + if(A && alert_s && !QDELETED(U)) + var/msg = "NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!" + _alert_drones(msg, TRUE, U) + to_chat(U, msg) + + +//NOTEKEEPER FUNCTIONS=================================== + + if ("Edit") + var/n = stripped_multiline_input(U, "Please enter message", name, note) + if (in_range(src, U) && loc == U) + if (mode == 1 && n) + note = n + notehtml = parsemarkdown(n, U) + notescanned = FALSE + else + U << browse(null, "window=pda") + return + +//MESSENGER FUNCTIONS=================================== + + if("Toggle Messenger") + toff = !toff + if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status + silent = !silent + if("Clear")//Clears messages + tnote = null + if("Ringtone") + var/t = 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"]) in GLOB.PDAs) + + if("MessageAll") + send_to_all(U) + + if("cart") + if(cartridge) + cartridge.special(U, href_list) + else + U << browse(null, "window=pda") + return + +//SYNDICATE FUNCTIONS=================================== + + if("Toggle Door") + if(cartridge && cartridge.access & CART_REMOTE_DOOR) + for(var/obj/machinery/door/poddoor/M in GLOB.machines) + if(M.id == cartridge.remote_door_id) + if(M.density) + M.open() + else + M.close() + +//pAI FUNCTIONS=================================== + if("pai") + switch(href_list["option"]) + if("1") // Configure pAI device + pai.attack_self(U) + if("2") // Eject pAI device + usr.put_in_hands(pai) + to_chat(usr, "You remove the pAI from the [name].") + +//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() + + if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + if (id) + usr.put_in_hands(id) + to_chat(usr, "You remove the ID from the [name].") + 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 + if(prob(1)) + message += "\nSent from my PDA" + // Send the signal + var/list/string_targets = list() + for (var/obj/item/pda/P in targets) + if (P.owner && P.ownjob) // != src is checked by the UI + string_targets += "[P.owner] ([P.ownjob])" + for (var/obj/machinery/computer/message_monitor/M in targets) + // In case of "Reply" to a message from a console, this will make the + // message be logged successfully. If the console is impersonating + // someone by matching their name and job, the reply will reach the + // impersonated PDA. + string_targets += "[M.customsender] ([M.customjob])" + if (!string_targets.len) + return + + var/datum/signal/subspace/messaging/pda/signal = new(src, list( + "name" = "[owner]", + "job" = "[ownjob]", + "message" = message, + "targets" = string_targets + )) + if (picture) + signal.data["photo"] = picture + signal.send_to_receivers() + + // If it didn't reach, note that fact + if (!signal.data["done"]) + to_chat(user, "ERROR: Server isn't responding.") + return + + var/target_text = signal.format_target() + // Log it in our logs + tnote += "→ To [target_text]:
                [signal.format_message()]
                " + // Show it to ghosts + var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message()]" + for(var/mob/M in GLOB.player_list) + if(isobserver(M) && M.client && (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]: \"[message]\"") + // Reset the photo + picture = null + last_text = world.time + if (everyone) + last_everyone = world.time + +/obj/item/pda/proc/receive_message(datum/signal/subspace/messaging/pda/signal) + tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
                [signal.format_message()]
                " + + if (!silent) + playsound(src, 'sound/machines/twobeep_high.ogg', 50, 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/reply = "(Reply)" + var/hrefstart + var/hrefend + if (isAI(L)) + hrefstart = "" + hrefend = "" + + if(signal.data["automated"]) + reply = "\[Automated Message\]" + + to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message()] [reply]") + + update_icon() + add_overlay(icon_alert) + +/obj/item/pda/proc/send_to_all(mob/living/U) + if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY) + to_chat(U,"Send To All function is still on cooldown.") + return + send_message(U,get_viewable_pdas(), TRUE) + +/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P) + send_message(U,list(P)) + +/obj/item/pda/AltClick() + ..() + + if(id) + remove_id() + else + remove_pen() + +/obj/item/pda/CtrlClick() + ..() + + if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it. + return + + remove_pen() + +/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() + 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) + 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() + return TRUE + else + var/obj/item/card/id/C = user.get_active_held_item() + if(istype(C)) + I = C + + if(I && I.registered_name) + if(!user.transferItemToLoc(I, src)) + return FALSE + var/obj/old_id = id + id = I + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.wear_id == src) + H.sec_hud_set_ID() + if(old_id) + user.put_in_hands(old_id) + update_icon() + return TRUE + +// 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() + + else if(istype(C, /obj/item/card/id)) + var/obj/item/card/id/idcard = C + if(!idcard.registered_name) + to_chat(user, "\The [src] rejects the ID!") + return + if(!owner) + owner = idcard.registered_name + ownjob = idcard.assignment + update_label() + to_chat(user, "Card scanned.") + else + //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() + 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. + var/turf/T = get_turf(src) + + if (ismob(loc)) + var/mob/M = loc + M.show_message("Your [src] explodes!", 1) + 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/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/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/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/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 + +/obj/item/pda/proc/pda_no_detonate() + return COMPONENT_PDA_NO_DETONATE + +#undef PDA_SCANNER_NONE +#undef PDA_SCANNER_MEDICAL +#undef PDA_SCANNER_FORENSICS +#undef PDA_SCANNER_REAGENT +#undef PDA_SCANNER_HALOGEN +#undef PDA_SCANNER_GAS +#undef PDA_SPAM_DELAY diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm index c23e84456355..398f6127da13 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -1,739 +1,739 @@ -#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 - - 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-s" - access = CART_SECURITY | CART_MEDICAL | CART_MANIFEST - bot_access_flags = SEC_BOT - -/obj/item/cartridge/janitor - name = "\improper CustodiPRO cartridge" - desc = "The ultimate in clean-room design." - icon_state = "cart-j" - access = CART_JANITOR | CART_DRONEPHONE - bot_access_flags = CLEAN_BOT - -/obj/item/cartridge/lawyer - name = "\improper P.R.O.V.E. cartridge" - icon_state = "cart-s" - access = CART_SECURITY - spam_enabled = 1 - -/obj/item/cartridge/curator - name = "\improper Lib-Tweet cartridge" - icon_state = "cart-s" - access = CART_NEWSCASTER - -/obj/item/cartridge/roboticist - name = "\improper B.O.O.P. Remote Control cartridge" - desc = "Packed with heavy duty quad-bot interlink!" - bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - access = CART_DRONEPHONE - -/obj/item/cartridge/signal - name = "generic signaler cartridge" - desc = "A data cartridge with an integrated radio signaler module." - -/obj/item/cartridge/signal/toxins - name = "\improper Signal Ace 2 cartridge" - desc = "Complete with integrated radio signaler!" - icon_state = "cart-tox" - access = CART_REAGENT_SCANNER | CART_ATMOS - -/obj/item/cartridge/signal/Initialize() - . = ..() - radio = new(src) - - - -/obj/item/cartridge/quartermaster - name = "space parts & space vendors cartridge" - desc = "Perfect for the Quartermaster on the go!" - icon_state = "cart-q" - access = CART_QUARTERMASTER - bot_access_flags = MULE_BOT - -/obj/item/cartridge/head - name = "\improper Easy-Record DELUXE cartridge" - icon_state = "cart-h" - access = CART_MANIFEST | CART_STATUS_DISPLAY - -/obj/item/cartridge/hop - name = "\improper HumanResources9001 cartridge" - icon_state = "cart-h" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_JANITOR | CART_SECURITY | CART_NEWSCASTER | CART_QUARTERMASTER | CART_DRONEPHONE - bot_access_flags = MULE_BOT | CLEAN_BOT - -/obj/item/cartridge/hos - name = "\improper R.O.B.U.S.T. DELUXE cartridge" - icon_state = "cart-hos" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_SECURITY - bot_access_flags = SEC_BOT - - -/obj/item/cartridge/ce - name = "\improper Power-On DELUXE cartridge" - icon_state = "cart-ce" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_ENGINE | CART_ATMOS | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT | FIRE_BOT - -/obj/item/cartridge/cmo - name = "\improper Med-U DELUXE cartridge" - icon_state = "cart-cmo" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_MEDICAL - bot_access_flags = MED_BOT - -/obj/item/cartridge/rd - name = "\improper Signal Ace DELUXE cartridge" - icon_state = "cart-rd" - access = CART_MANIFEST | CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_ATMOS | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - -/obj/item/cartridge/rd/Initialize() - . = ..() - radio = new(src) - -/obj/item/cartridge/captain - name = "\improper Value-PAK cartridge" - desc = "Now with 350% more value!" //Give the Captain...EVERYTHING! (Except Mime, Clown, and Syndie) - icon_state = "cart-c" - access = ~(CART_CLOWN | CART_MIME | CART_REMOTE_DOOR) - bot_access_flags = SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - spam_enabled = 1 - -/obj/item/cartridge/captain/Initialize() - . = ..() - radio = new(src) - -/obj/item/cartridge/proc/post_status(command, data1, data2) - - var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) - - if(!frequency) - return - - var/datum/signal/status_signal = new(list("command" = command)) - switch(command) - if("message") - status_signal.data["msg1"] = data1 - status_signal.data["msg2"] = data2 - if("alert") - status_signal.data["picture_state"] = data1 - - frequency.post_signal(src, status_signal) - -/obj/item/cartridge/proc/generate_menu(mob/user) - if(!host_pda) - return - switch(host_pda.mode) - if(40) //signaller - menu = "

                [PDAIMG(signaler)] Remote Signaling System

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

                [PDAIMG(notes)] Crew Manifest

                " - menu += "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 += "Gender: [active1.fields["gender"]]
                " - menu += "Age: [active1.fields["age"]]
                " - menu += "Rank: [active1.fields["rank"]]
                " - menu += "Fingerprint: [active1.fields["fingerprint"]]
                " - menu += "Physical Status: [active1.fields["p_stat"]]
                " - menu += "Mental Status: [active1.fields["m_stat"]]
                " - else - menu += "Record Lost!
                " - - menu += "
                " - - menu += "

                [PDAIMG(medical)] Medical Data

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

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

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

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

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

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

                [PDAIMG(cuffs)] Security Record List

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

                [PDAIMG(cuffs)] Security Record

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

                [PDAIMG(cuffs)] Security Data

                " - if(active3 in GLOB.data_core.security) - menu += "Criminal Status: [active3.fields["criminal"]]
                " - - menu += text("
                \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 += "

                Located Janitorial Cart:

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

                Located Cleanbots:

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

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

                [PDAIMG(notes)] Newscaster Access

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

                [PDAIMG(medbot)] Bots Interlink

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

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

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

                [PDAIMG(signaler)] Remote Signaling System

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

                [PDAIMG(notes)] Crew Manifest

                " + menu += "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 += "Gender: [active1.fields["gender"]]
                " + menu += "Age: [active1.fields["age"]]
                " + menu += "Rank: [active1.fields["rank"]]
                " + menu += "Fingerprint: [active1.fields["fingerprint"]]
                " + menu += "Physical Status: [active1.fields["p_stat"]]
                " + menu += "Mental Status: [active1.fields["m_stat"]]
                " + else + menu += "Record Lost!
                " + + menu += "
                " + + menu += "

                [PDAIMG(medical)] Medical Data

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

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

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

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

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

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

                [PDAIMG(cuffs)] Security Record List

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

                [PDAIMG(cuffs)] Security Record

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

                [PDAIMG(cuffs)] Security Data

                " + if(active3 in GLOB.data_core.security) + menu += "Criminal Status: [active3.fields["criminal"]]
                " + + menu += text("
                \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 += "

                Located Janitorial Cart:

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

                Located Cleanbots:

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

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

                [PDAIMG(notes)] Newscaster Access

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

                [PDAIMG(medbot)] Bots Interlink

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

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

                " + var/turf/current_turf = get_turf(src) + var/zlevel = current_turf.z + var/botcount = 0 + for(var/B in GLOB.bots_list) //Git da botz + var/mob/living/simple_animal/bot/Bot = B + if(!Bot.on || Bot.z != zlevel || Bot.remote_disabled || !(bot_access_flags & Bot.bot_type)) //Only non-emagged bots on the same Z-level are detected! + continue //Also, the PDA must have access to the bot type. + menu += "[Bot.name] ([Bot.get_mode()])
                " + botcount++ + if(!botcount) //No bots at all? Lame. + menu += "No bots found.
                " + return + + return menu + +//If the cartridge adds a special line to the top of the messaging app +/obj/item/cartridge/proc/message_header() + return "" + +//If the cartridge adds something to each potetial messaging target +/obj/item/cartridge/proc/message_special(obj/item/pda/target) + return "" + +//This is called for special abilities of cartridges +/obj/item/cartridge/proc/special(mob/living/user, list/params) diff --git a/code/game/objects/items/devices/PDA/radio.dm b/code/game/objects/items/devices/PDA/radio.dm index 9e20dc455b0d..329631ff9e07 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 6607d7a90f34..373242f7b71c 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, "uploaded", src, "to [target].") - target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src) - else //No AI on the card, therefore the user wants to download one. - target.transfer_ai(AI_TRANS_TO_CARD, user, null, src) - if(AI) - log_combat(user, AI, "carded", src) - update_icon() //Whatever happened, update the card's state (icon, name) to match. - -/obj/item/aicard/update_icon() - cut_overlays() - if(AI) - name = "[initial(name)] - [AI.name]" - if(AI.stat == DEAD) - icon_state = "[initial(icon_state)]-404" - else - icon_state = "[initial(icon_state)]-full" - if(!AI.control_disabled) - add_overlay("[initial(icon_state)]-on") - AI.cancel_camera() - else - name = initial(name) - icon_state = initial(icon_state) - -/obj/item/aicard/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "intellicard", name, 500, 500, master_ui, state) - ui.open() - -/obj/item/aicard/ui_data() - var/list/data = list() - if(AI) - data["name"] = AI.name - data["laws"] = AI.laws.get_law_list(include_zeroth = 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, "uploaded", src, "to [target].") + target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src) + else //No AI on the card, therefore the user wants to download one. + target.transfer_ai(AI_TRANS_TO_CARD, user, null, src) + if(AI) + log_combat(user, AI, "carded", src) + update_icon() //Whatever happened, update the card's state (icon, name) to match. + +/obj/item/aicard/update_icon() + cut_overlays() + if(AI) + name = "[initial(name)] - [AI.name]" + if(AI.stat == DEAD) + icon_state = "[initial(icon_state)]-404" + else + icon_state = "[initial(icon_state)]-full" + if(!AI.control_disabled) + add_overlay("[initial(icon_state)]-on") + AI.cancel_camera() + else + name = initial(name) + icon_state = initial(icon_state) + +/obj/item/aicard/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "intellicard", name, 500, 500, master_ui, state) + ui.open() + +/obj/item/aicard/ui_data() + var/list/data = list() + if(AI) + data["name"] = AI.name + data["laws"] = AI.laws.get_law_list(include_zeroth = 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 67905a47b60c..6bc37bed3268 100644 --- a/code/game/objects/items/devices/camera_bug.dm +++ b/code/game/objects/items/devices/camera_bug.dm @@ -1,309 +1,309 @@ - -#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.mobility_flags & MOBILITY_STAND)) - if(M.buckled) - dat += " (Sitting)" - else - dat += " (Laying down)" - dat += " \[Track\]
                " - if(length(dat) == 0) - dat += "No motion detected." - return dat - else - return "Camera Offline
                " - -/obj/item/camera_bug/Topic(href,list/href_list) - if(usr != loc) - usr.unset_machine() - usr << browse(null, "window=camerabug") - return - usr.set_machine(src) - if("mode" in href_list) - track_mode = text2num(href_list["mode"]) - if("monitor" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["monitor"]) in cameras - if(C && istype(C)) - if(!same_z_level(C)) - return - track_mode = BUGMODE_MONITOR - current = C - usr.reset_perspective(null) - interact() - if("track" in href_list) - var/list/seen = get_seens() - if(seen && seen.len >= 1) - var/atom/A = locate(href_list["track"]) in seen - if(A && istype(A)) - tracking = A - tracked_name = A.name - last_found = current.c_tag - last_seen = world.time - track_mode = BUGMODE_TRACK - if("emp" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["emp"]) in cameras - if(C && istype(C) && C.bug == src) - if(!same_z_level(C)) - return - C.emp_act(EMP_HEAVY) - C.bug = null - bugged_cameras -= C.c_tag - interact() - return - if("close" in href_list) - usr.unset_machine() - current = null - return - if("view" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["view"]) in cameras - if(C && istype(C)) - if(!same_z_level(C)) - return - if(!C.can_use()) - to_chat(usr, "Something's wrong with that camera! You can't get a feed.") - return - current = C - spawn(6) - if(src.check_eye(usr)) - usr.reset_perspective(C) - interact() - else - usr.unset_machine() - usr << browse(null, "window=camerabug") - return - else - usr.unset_machine() - - interact() - -/obj/item/camera_bug/process() - if(track_mode == BUGMODE_LIST || (world.time < (last_tracked + refresh_interval))) - return - last_tracked = world.time - if(track_mode == BUGMODE_TRACK ) // search for user - // Note that it will be tricked if your name appears to change. - // This is not optimal but it is better than tracking you relentlessly despite everything. - if(!tracking) - src.updateSelfDialog() - return - - if(tracking.name != tracked_name) // Hiding their identity, tricksy - var/mob/M = tracking - if(istype(M)) - if(!(tracked_name == "Unknown" && findtext(tracking.name,"Unknown"))) // we saw then disguised before - if(!(tracked_name == M.real_name && findtext(tracking.name,M.real_name))) // or they're still ID'd - src.updateSelfDialog()//But if it's neither of those cases - return // you won't find em on the cameras - else - src.updateSelfDialog() - return - - var/list/tracking_cams = list() - var/list/b_cams = get_cameras() - for(var/entry in b_cams) - tracking_cams += b_cams[entry] - var/list/target_region = view(tracking) - - for(var/obj/machinery/camera/C in (target_region & tracking_cams)) - if(!can_see(C,tracking)) // target may have xray, that doesn't make them visible to cameras - continue - if(C.can_use()) - last_found = C.c_tag - last_seen = world.time - break - src.updateSelfDialog() - -/obj/item/camera_bug/proc/same_z_level(var/obj/machinery/camera/C) - var/turf/T_cam = get_turf(C) - var/turf/T_bug = get_turf(loc) - if(!T_bug || T_cam.z != T_bug.z) - to_chat(usr, "You can't get a signal!") - return FALSE - return TRUE - -#undef BUGMODE_LIST -#undef BUGMODE_MONITOR -#undef BUGMODE_TRACK + +#define BUGMODE_LIST 0 +#define BUGMODE_MONITOR 1 +#define BUGMODE_TRACK 2 + + + +/obj/item/camera_bug + name = "camera bug" + desc = "For illicit snooping through the camera network." + icon = 'icons/obj/device.dmi' + icon_state = "camera_bug" + w_class = WEIGHT_CLASS_TINY + 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.mobility_flags & MOBILITY_STAND)) + if(M.buckled) + dat += " (Sitting)" + else + dat += " (Laying down)" + dat += " \[Track\]
                " + if(length(dat) == 0) + dat += "No motion detected." + return dat + else + return "Camera Offline
                " + +/obj/item/camera_bug/Topic(href,list/href_list) + if(usr != loc) + usr.unset_machine() + usr << browse(null, "window=camerabug") + return + usr.set_machine(src) + if("mode" in href_list) + track_mode = text2num(href_list["mode"]) + if("monitor" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["monitor"]) in cameras + if(C && istype(C)) + if(!same_z_level(C)) + return + track_mode = BUGMODE_MONITOR + current = C + usr.reset_perspective(null) + interact() + if("track" in href_list) + var/list/seen = get_seens() + if(seen && seen.len >= 1) + var/atom/A = locate(href_list["track"]) in seen + if(A && istype(A)) + tracking = A + tracked_name = A.name + last_found = current.c_tag + last_seen = world.time + track_mode = BUGMODE_TRACK + if("emp" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["emp"]) in cameras + if(C && istype(C) && C.bug == src) + if(!same_z_level(C)) + return + C.emp_act(EMP_HEAVY) + C.bug = null + bugged_cameras -= C.c_tag + interact() + return + if("close" in href_list) + usr.unset_machine() + current = null + return + if("view" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["view"]) in cameras + if(C && istype(C)) + if(!same_z_level(C)) + return + if(!C.can_use()) + to_chat(usr, "Something's wrong with that camera! You can't get a feed.") + return + current = C + spawn(6) + if(src.check_eye(usr)) + usr.reset_perspective(C) + interact() + else + usr.unset_machine() + usr << browse(null, "window=camerabug") + return + else + usr.unset_machine() + + interact() + +/obj/item/camera_bug/process() + if(track_mode == BUGMODE_LIST || (world.time < (last_tracked + refresh_interval))) + return + last_tracked = world.time + if(track_mode == BUGMODE_TRACK ) // search for user + // Note that it will be tricked if your name appears to change. + // This is not optimal but it is better than tracking you relentlessly despite everything. + if(!tracking) + src.updateSelfDialog() + return + + if(tracking.name != tracked_name) // Hiding their identity, tricksy + var/mob/M = tracking + if(istype(M)) + if(!(tracked_name == "Unknown" && findtext(tracking.name,"Unknown"))) // we saw then disguised before + if(!(tracked_name == M.real_name && findtext(tracking.name,M.real_name))) // or they're still ID'd + src.updateSelfDialog()//But if it's neither of those cases + return // you won't find em on the cameras + else + src.updateSelfDialog() + return + + var/list/tracking_cams = list() + var/list/b_cams = get_cameras() + for(var/entry in b_cams) + tracking_cams += b_cams[entry] + var/list/target_region = view(tracking) + + for(var/obj/machinery/camera/C in (target_region & tracking_cams)) + if(!can_see(C,tracking)) // target may have xray, that doesn't make them visible to cameras + continue + if(C.can_use()) + last_found = C.c_tag + last_seen = world.time + break + src.updateSelfDialog() + +/obj/item/camera_bug/proc/same_z_level(var/obj/machinery/camera/C) + var/turf/T_cam = get_turf(C) + var/turf/T_bug = get_turf(loc) + if(!T_bug || T_cam.z != T_bug.z) + to_chat(usr, "You can't get a signal!") + return FALSE + return TRUE + +#undef BUGMODE_LIST +#undef BUGMODE_MONITOR +#undef BUGMODE_TRACK diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index d31e349f0cde..2d7665b5f466 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -1,560 +1,560 @@ -/obj/item/flashlight - name = "flashlight" - desc = "A hand-held emergency light." - custom_price = 10 - 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 = 1 //strength of the light when on - -/obj/item/flashlight/Initialize() - . = ..() - if(icon_state == "[initial(icon_state)]-on") - on = TRUE - update_brightness() - -/obj/item/flashlight/proc/update_brightness(mob/user = null) - if(on) - icon_state = "[initial(icon_state)]-on" - if(flashlight_power) - set_light(l_range = brightness_on, l_power = flashlight_power) - else - set_light(brightness_on) - else - icon_state = initial(icon_state) - set_light(0) - -/obj/item/flashlight/attack_self(mob/user) - on = !on - update_brightness(user) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - return 1 - -/obj/item/flashlight/suicide_act(mob/living/carbon/human/user) - if (user.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 < 1) - to_chat(user, "\The [src] isn't bright enough to see anything! ") - return - - switch(user.zone_selected) - if(BODY_ZONE_PRECISE_EYES) - if((M.head && M.head.flags_cover & HEADCOVERSEYES) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) || (M.glasses && M.glasses.flags_cover & GLASSESCOVERSEYES)) - to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask": "glasses"] first.") - return - - var/obj/item/organ/eyes/E = M.getorganslot(ORGAN_SLOT_EYES) - if(!E) - to_chat(user, "[M] doesn't have any eyes!") - return - - if(M == user) //they're using it on themselves - if(M.flash_act(visual = 1)) - M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes! Trippy!") - else - M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes.") - else - user.visible_message("[user] directs [src] to [M]'s eyes.", \ - "You direct [src] to [M]'s eyes.") - if(M.stat == DEAD || (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 - 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. - 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 - 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. - 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/Initialize() - . = ..() - fuel = rand(800, 1000) // Sorry for changing this so much but I keep under-estimating how long X number of ticks last in seconds. - -/obj/item/flashlight/flare/process() - open_flame(heat) - fuel = max(fuel - 1, 0) - if(!fuel || !on) - turn_off() - if(!fuel) - icon_state = "[initial(icon_state)]-empty" - STOP_PROCESSING(SSobj, src) - -/obj/item/flashlight/flare/ignition_effect(atom/A, mob/user) - 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/is_hot() - return on * heat - -/obj/item/flashlight/flare/torch - name = "torch" - desc = "A torch fashioned from some leaves and a log." - w_class = WEIGHT_CLASS_BULKY - brightness_on = 4 - icon_state = "torch" - 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 - -/obj/item/flashlight/lantern/heirloom_moth - name = "old lantern" - desc = "An old lantern that has seen plenty of use." - brightness_on = 4 - -/obj/item/flashlight/lantern/syndicate - name = "suspicious lantern" - desc = "A suspicious looking lantern." - icon_state = "syndilantern" - item_state = "syndilantern" - brightness_on = 10 - -/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 - -/obj/item/flashlight/emp - var/emp_max_charges = 4 - var/emp_cur_charges = 4 - var/charge_tick = 0 - -/obj/item/flashlight/emp/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/emp/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/flashlight/emp/process() - charge_tick++ - if(charge_tick < 10) - return FALSE - charge_tick = 0 - emp_cur_charges = min(emp_cur_charges+1, emp_max_charges) - return TRUE - -/obj/item/flashlight/emp/attack(mob/living/M, mob/living/user) - if(on && user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH)) // call original attack when examining organs - ..() - return - -/obj/item/flashlight/emp/afterattack(atom/movable/A, mob/user, proximity) - . = ..() - if(!proximity) - return - - if(emp_cur_charges > 0) - emp_cur_charges -= 1 - - if(ismob(A)) - var/mob/M = A - log_combat(user, M, "attacked", "EMP-light") - M.visible_message("[user] blinks \the [src] at \the [A].", \ - "[user] blinks \the [src] at you.") - else - A.visible_message("[user] blinks \the [src] at \the [A].") - to_chat(user, "\The [src] now has [emp_cur_charges] charge\s.") - A.emp_act(EMP_HEAVY) - else - to_chat(user, "\The [src] needs time to recharge!") - return - -/obj/item/flashlight/emp/debug //for testing emp_act() - name = "debug EMP flashlight" - emp_max_charges = 100 - emp_cur_charges = 100 - -// Glowsticks, in the uncomfortable range of similar to flares, -// but not similar enough to make it worth a refactor -/obj/item/flashlight/glowstick - name = "glowstick" - desc = "A military-grade glowstick." - custom_price = 10 - 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 - 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!") - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user) - if(!fuel) - user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but it's empty!") - return SHAME - var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES) - if(!eyes) - user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!") - return SHAME - user.visible_message("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") - fuel = 0 - return (FIRELOSS) - -/obj/item/flashlight/glowstick/red - name = "red glowstick" - color = LIGHT_COLOR_RED - -/obj/item/flashlight/glowstick/blue - name = "blue glowstick" - color = LIGHT_COLOR_BLUE - -/obj/item/flashlight/glowstick/cyan - name = "cyan glowstick" - color = LIGHT_COLOR_CYAN - -/obj/item/flashlight/glowstick/orange - name = "orange glowstick" - color = LIGHT_COLOR_ORANGE - -/obj/item/flashlight/glowstick/yellow - name = "yellow glowstick" - color = LIGHT_COLOR_YELLOW - -/obj/item/flashlight/glowstick/pink - name = "pink glowstick" - color = LIGHT_COLOR_PINK - -/obj/effect/spawner/lootdrop/glowstick - name = "random colored glowstick" - icon = 'icons/obj/lighting.dmi' - icon_state = "random_glowstick" - -/obj/effect/spawner/lootdrop/glowstick/Initialize() - loot = typesof(/obj/item/flashlight/glowstick) - . = ..() - -/obj/item/flashlight/spotlight //invisible lighting source - name = "disco light" - desc = "Groovy..." - icon_state = null - light_color = null - brightness_on = 0 - light_range = 0 - light_power = 10 - alpha = 0 - layer = 0 - on = TRUE - anchored = TRUE - var/range = null - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/item/flashlight/flashdark - name = "flashdark" - desc = "A strange device manufactured with mysterious elements that somehow emits darkness. Or maybe it just sucks in light? Nobody knows for sure." - icon_state = "flashdark" - 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 - flashlight_power = 1 - flags_1 = CONDUCT_1 - item_flags = DROPDEL - actions_types = list() +/obj/item/flashlight + name = "flashlight" + desc = "A hand-held emergency light." + custom_price = 10 + 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 = 1 //strength of the light when on + +/obj/item/flashlight/Initialize() + . = ..() + if(icon_state == "[initial(icon_state)]-on") + on = TRUE + update_brightness() + +/obj/item/flashlight/proc/update_brightness(mob/user = null) + if(on) + icon_state = "[initial(icon_state)]-on" + if(flashlight_power) + set_light(l_range = brightness_on, l_power = flashlight_power) + else + set_light(brightness_on) + else + icon_state = initial(icon_state) + set_light(0) + +/obj/item/flashlight/attack_self(mob/user) + on = !on + update_brightness(user) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + return 1 + +/obj/item/flashlight/suicide_act(mob/living/carbon/human/user) + if (user.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 < 1) + to_chat(user, "\The [src] isn't bright enough to see anything! ") + return + + switch(user.zone_selected) + if(BODY_ZONE_PRECISE_EYES) + if((M.head && M.head.flags_cover & HEADCOVERSEYES) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) || (M.glasses && M.glasses.flags_cover & GLASSESCOVERSEYES)) + to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask": "glasses"] first.") + return + + var/obj/item/organ/eyes/E = M.getorganslot(ORGAN_SLOT_EYES) + if(!E) + to_chat(user, "[M] doesn't have any eyes!") + return + + if(M == user) //they're using it on themselves + if(M.flash_act(visual = 1)) + M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes! Trippy!") + else + M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes.") + else + user.visible_message("[user] directs [src] to [M]'s eyes.", \ + "You direct [src] to [M]'s eyes.") + if(M.stat == DEAD || (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 + 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. + 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 + 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. + 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/Initialize() + . = ..() + fuel = rand(800, 1000) // Sorry for changing this so much but I keep under-estimating how long X number of ticks last in seconds. + +/obj/item/flashlight/flare/process() + open_flame(heat) + fuel = max(fuel - 1, 0) + if(!fuel || !on) + turn_off() + if(!fuel) + icon_state = "[initial(icon_state)]-empty" + STOP_PROCESSING(SSobj, src) + +/obj/item/flashlight/flare/ignition_effect(atom/A, mob/user) + 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/is_hot() + return on * heat + +/obj/item/flashlight/flare/torch + name = "torch" + desc = "A torch fashioned from some leaves and a log." + w_class = WEIGHT_CLASS_BULKY + brightness_on = 4 + icon_state = "torch" + 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 + +/obj/item/flashlight/lantern/heirloom_moth + name = "old lantern" + desc = "An old lantern that has seen plenty of use." + brightness_on = 4 + +/obj/item/flashlight/lantern/syndicate + name = "suspicious lantern" + desc = "A suspicious looking lantern." + icon_state = "syndilantern" + item_state = "syndilantern" + brightness_on = 10 + +/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 + +/obj/item/flashlight/emp + var/emp_max_charges = 4 + var/emp_cur_charges = 4 + var/charge_tick = 0 + +/obj/item/flashlight/emp/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/emp/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/flashlight/emp/process() + charge_tick++ + if(charge_tick < 10) + return FALSE + charge_tick = 0 + emp_cur_charges = min(emp_cur_charges+1, emp_max_charges) + return TRUE + +/obj/item/flashlight/emp/attack(mob/living/M, mob/living/user) + if(on && user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH)) // call original attack when examining organs + ..() + return + +/obj/item/flashlight/emp/afterattack(atom/movable/A, mob/user, proximity) + . = ..() + if(!proximity) + return + + if(emp_cur_charges > 0) + emp_cur_charges -= 1 + + if(ismob(A)) + var/mob/M = A + log_combat(user, M, "attacked", "EMP-light") + M.visible_message("[user] blinks \the [src] at \the [A].", \ + "[user] blinks \the [src] at you.") + else + A.visible_message("[user] blinks \the [src] at \the [A].") + to_chat(user, "\The [src] now has [emp_cur_charges] charge\s.") + A.emp_act(EMP_HEAVY) + else + to_chat(user, "\The [src] needs time to recharge!") + return + +/obj/item/flashlight/emp/debug //for testing emp_act() + name = "debug EMP flashlight" + emp_max_charges = 100 + emp_cur_charges = 100 + +// Glowsticks, in the uncomfortable range of similar to flares, +// but not similar enough to make it worth a refactor +/obj/item/flashlight/glowstick + name = "glowstick" + desc = "A military-grade glowstick." + custom_price = 10 + 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 + 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!") + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user) + if(!fuel) + user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but it's empty!") + return SHAME + var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES) + if(!eyes) + user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!") + return SHAME + user.visible_message("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") + fuel = 0 + return (FIRELOSS) + +/obj/item/flashlight/glowstick/red + name = "red glowstick" + color = LIGHT_COLOR_RED + +/obj/item/flashlight/glowstick/blue + name = "blue glowstick" + color = LIGHT_COLOR_BLUE + +/obj/item/flashlight/glowstick/cyan + name = "cyan glowstick" + color = LIGHT_COLOR_CYAN + +/obj/item/flashlight/glowstick/orange + name = "orange glowstick" + color = LIGHT_COLOR_ORANGE + +/obj/item/flashlight/glowstick/yellow + name = "yellow glowstick" + color = LIGHT_COLOR_YELLOW + +/obj/item/flashlight/glowstick/pink + name = "pink glowstick" + color = LIGHT_COLOR_PINK + +/obj/effect/spawner/lootdrop/glowstick + name = "random colored glowstick" + icon = 'icons/obj/lighting.dmi' + icon_state = "random_glowstick" + +/obj/effect/spawner/lootdrop/glowstick/Initialize() + loot = typesof(/obj/item/flashlight/glowstick) + . = ..() + +/obj/item/flashlight/spotlight //invisible lighting source + name = "disco light" + desc = "Groovy..." + icon_state = null + light_color = null + brightness_on = 0 + light_range = 0 + light_power = 10 + alpha = 0 + layer = 0 + on = TRUE + anchored = TRUE + var/range = null + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/item/flashlight/flashdark + name = "flashdark" + desc = "A strange device manufactured with mysterious elements that somehow emits darkness. Or maybe it just sucks in light? Nobody knows for sure." + icon_state = "flashdark" + 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 + flashlight_power = 1 + flags_1 = CONDUCT_1 + item_flags = DROPDEL + actions_types = list() diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm index 15e07bed0fa4..c8f1a845260a 100644 --- a/code/game/objects/items/devices/gps.dm +++ b/code/game/objects/items/devices/gps.dm @@ -1,213 +1,213 @@ -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) - . = ..() - . += "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) - -/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_global(src) // yogs - get_turf_global instead of get_turf - 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_global(G) // yogs - get_turf_global instead of get_turf - 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 - -/obj/item/gps/mining/internal - icon_state = "gps-m" - gpstag = "MINER" - desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." - -/obj/item/gps/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) + . = ..() + . += "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) + +/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_global(src) // yogs - get_turf_global instead of get_turf + 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_global(G) // yogs - get_turf_global instead of get_turf + 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 + +/obj/item/gps/mining/internal + icon_state = "gps-m" + gpstag = "MINER" + desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." + +/obj/item/gps/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 962064b03e51..9770dc18a68f 100644 --- a/code/game/objects/items/devices/instruments.dm +++ b/code/game/objects/items/devices/instruments.dm @@ -1,263 +1,263 @@ -//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" - -/obj/item/instrument/Initialize() - . = ..() - song = new(instrumentId, src, instrumentExt) - -/obj/item/instrument/Destroy() - QDEL_NULL(song) - . = ..() - -/obj/item/instrument/suicide_act(mob/user) - user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/instrument/Initialize(mapload) - . = ..() - if(mapload) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - -/obj/item/instrument/attack_self(mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return 1 - interact(user) - -/obj/item/instrument/interact(mob/user) - ui_interact(user) - -/obj/item/instrument/ui_interact(mob/living/user) - if(!isliving(user) || user.stat || user.restrained() || !(user.mobility_flags & MOBILITY_STAND)) - return - - user.set_machine(src) - song.interact(user) - -/obj/item/instrument/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/piano_synth/proc/selectInstrument() // Moved here so it can be used by the action and PAI software panel without copypasta - var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", song.instrumentDir) as null|anything in insTypes - if(!insTypes[chosen]) - return - return changeInstrument(chosen) - -/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, override = TRUE) - -/obj/item/instrument/harmonica/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/instrument/bikehorn - name = "gilded bike horn" - desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes." - icon_state = "bike_horn" - 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/choice_beacon/music - name = "instrument delivery beacon" - desc = "Summon your tool of art." - icon_state = "gangtool-red" - -/obj/item/choice_beacon/music/generate_display_names() - var/static/list/instruments - if(!instruments) - instruments = list() - var/list/templist = list(/obj/item/instrument/violin, - /obj/item/instrument/piano_synth, - /obj/item/instrument/guitar, - /obj/item/instrument/eguitar, - /obj/item/instrument/glockenspiel, - /obj/item/instrument/accordion, - /obj/item/instrument/trumpet, - /obj/item/instrument/saxophone, - /obj/item/instrument/trombone, - /obj/item/instrument/recorder, - /obj/item/instrument/harmonica - ) - for(var/V in templist) - var/atom/A = V - instruments[initial(A.name)] = A - return instruments +//copy pasta of the space piano, don't hurt me -Pete +/obj/item/instrument + name = "generic instrument" + resistance_flags = FLAMMABLE + force = 10 + max_integrity = 100 + icon = 'icons/obj/musician.dmi' + lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi' + var/datum/song/handheld/song + var/instrumentId = "generic" + var/instrumentExt = "mid" + +/obj/item/instrument/Initialize() + . = ..() + song = new(instrumentId, src, instrumentExt) + +/obj/item/instrument/Destroy() + QDEL_NULL(song) + . = ..() + +/obj/item/instrument/suicide_act(mob/user) + user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/instrument/Initialize(mapload) + . = ..() + if(mapload) + song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded + +/obj/item/instrument/attack_self(mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return 1 + interact(user) + +/obj/item/instrument/interact(mob/user) + ui_interact(user) + +/obj/item/instrument/ui_interact(mob/living/user) + if(!isliving(user) || user.stat || user.restrained() || !(user.mobility_flags & MOBILITY_STAND)) + return + + user.set_machine(src) + song.interact(user) + +/obj/item/instrument/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/piano_synth/proc/selectInstrument() // Moved here so it can be used by the action and PAI software panel without copypasta + var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", song.instrumentDir) as null|anything in insTypes + if(!insTypes[chosen]) + return + return changeInstrument(chosen) + +/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, override = TRUE) + +/obj/item/instrument/harmonica/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/instrument/bikehorn + name = "gilded bike horn" + desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes." + icon_state = "bike_horn" + 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/choice_beacon/music + name = "instrument delivery beacon" + desc = "Summon your tool of art." + icon_state = "gangtool-red" + +/obj/item/choice_beacon/music/generate_display_names() + var/static/list/instruments + if(!instruments) + instruments = list() + var/list/templist = list(/obj/item/instrument/violin, + /obj/item/instrument/piano_synth, + /obj/item/instrument/guitar, + /obj/item/instrument/eguitar, + /obj/item/instrument/glockenspiel, + /obj/item/instrument/accordion, + /obj/item/instrument/trumpet, + /obj/item/instrument/saxophone, + /obj/item/instrument/trombone, + /obj/item/instrument/recorder, + /obj/item/instrument/harmonica + ) + for(var/V in templist) + var/atom/A = V + instruments[initial(A.name)] = A + return instruments diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 2439601b53e3..e437d21e2e63 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -1,201 +1,201 @@ -/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/Initialize() - . = ..() - diode = new(src) - if(!pointer_icon_state) - pointer_icon_state = pick("red_laser","green_laser","blue_laser","purple_laser") - -/obj/item/laser_pointer/upgraded/Initialize() - . = ..() - diode = new /obj/item/stock_parts/micro_laser/ultra - -/obj/item/laser_pointer/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/micro_laser)) - if(!diode) - if(!user.transferItemToLoc(W, src)) - return - diode = W - to_chat(user, "You install a [diode.name] in [src].") - else - to_chat(user, "[src] already has a diode installed.") - - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(diode) - to_chat(user, "You remove the [diode.name] from \the [src].") - diode.forceMove(drop_location()) - diode = null - else - return ..() - -/obj/item/laser_pointer/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - if(!diode) - . += "The diode is missing." - else - . += "A class [diode.rating] laser diode is installed. It is screwed in place." - -/obj/item/laser_pointer/afterattack(atom/target, mob/living/user, flag, params) - . = ..() - laser_act(target, user, params) - -/obj/item/laser_pointer/proc/laser_act(atom/target, mob/living/user, params) - if( !(user in (viewers(7,target))) ) - return - if (!diode) - to_chat(user, "You point [src] at [target], but nothing happens!") - return - if (!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - if(HAS_TRAIT(user, TRAIT_NOGUNS)) - to_chat(user, "Your fingers can't press the button!") - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.check_mutation(HULK)) - 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.Paralyze(rand(100,200)) - to_chat(S, "Your sensors were overloaded by a laser!") - outmsg = "You overload [S] by shining [src] at [S.p_their()] sensors." - else - outmsg = "You fail to overload [S] by shining [src] at [S.p_their()] sensors!" - - //cameras - else if(istype(target, /obj/machinery/camera)) - var/obj/machinery/camera/C = target - if(prob(effectchance * diode.rating)) - C.emp_act(EMP_HEAVY) - outmsg = "You hit the lens of [C] with [src], temporarily disabling the camera!" - log_combat(user, C, "EMPed", src) - else - outmsg = "You miss the lens of [C] with [src]!" - - //catpeople - for(var/mob/living/carbon/human/H in view(1,targloc)) - if(!iscatperson(H) || H.incapacitated() || H.eye_blind ) - continue - if(user.mobility_flags & MOBILITY_STAND) - 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.set_resting(TRUE, FALSE) - else - C.visible_message("[C] looks uninterested in your games.","You spot [user] shining [src] at you. How insulting!") - - //laser pointer image - icon_state = "pointer_[pointer_icon_state]" - var/image/I = image('icons/obj/projectiles.dmi',targloc,pointer_icon_state,10) - var/list/click_params = params2list(params) - if(click_params) - if(click_params["icon-x"]) - I.pixel_x = (text2num(click_params["icon-x"]) - 16) - if(click_params["icon-y"]) - I.pixel_y = (text2num(click_params["icon-y"]) - 16) - else - I.pixel_x = target.pixel_x + rand(-5,5) - I.pixel_y = target.pixel_y + rand(-5,5) - - if(outmsg) - to_chat(user, outmsg) - else - to_chat(user, "You point [src] at [target].") - - energy -= 1 - if(energy <= max_energy) - if(!recharging) - recharging = 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/Initialize() + . = ..() + diode = new(src) + if(!pointer_icon_state) + pointer_icon_state = pick("red_laser","green_laser","blue_laser","purple_laser") + +/obj/item/laser_pointer/upgraded/Initialize() + . = ..() + diode = new /obj/item/stock_parts/micro_laser/ultra + +/obj/item/laser_pointer/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/micro_laser)) + if(!diode) + if(!user.transferItemToLoc(W, src)) + return + diode = W + to_chat(user, "You install a [diode.name] in [src].") + else + to_chat(user, "[src] already has a diode installed.") + + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(diode) + to_chat(user, "You remove the [diode.name] from \the [src].") + diode.forceMove(drop_location()) + diode = null + else + return ..() + +/obj/item/laser_pointer/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + if(!diode) + . += "The diode is missing." + else + . += "A class [diode.rating] laser diode is installed. It is screwed in place." + +/obj/item/laser_pointer/afterattack(atom/target, mob/living/user, flag, params) + . = ..() + laser_act(target, user, params) + +/obj/item/laser_pointer/proc/laser_act(atom/target, mob/living/user, params) + if( !(user in (viewers(7,target))) ) + return + if (!diode) + to_chat(user, "You point [src] at [target], but nothing happens!") + return + if (!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + if(HAS_TRAIT(user, TRAIT_NOGUNS)) + to_chat(user, "Your fingers can't press the button!") + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna.check_mutation(HULK)) + 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.Paralyze(rand(100,200)) + to_chat(S, "Your sensors were overloaded by a laser!") + outmsg = "You overload [S] by shining [src] at [S.p_their()] sensors." + else + outmsg = "You fail to overload [S] by shining [src] at [S.p_their()] sensors!" + + //cameras + else if(istype(target, /obj/machinery/camera)) + var/obj/machinery/camera/C = target + if(prob(effectchance * diode.rating)) + C.emp_act(EMP_HEAVY) + outmsg = "You hit the lens of [C] with [src], temporarily disabling the camera!" + log_combat(user, C, "EMPed", src) + else + outmsg = "You miss the lens of [C] with [src]!" + + //catpeople + for(var/mob/living/carbon/human/H in view(1,targloc)) + if(!iscatperson(H) || H.incapacitated() || H.eye_blind ) + continue + if(user.mobility_flags & MOBILITY_STAND) + 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.set_resting(TRUE, FALSE) + else + C.visible_message("[C] looks uninterested in your games.","You spot [user] shining [src] at you. How insulting!") + + //laser pointer image + icon_state = "pointer_[pointer_icon_state]" + var/image/I = image('icons/obj/projectiles.dmi',targloc,pointer_icon_state,10) + var/list/click_params = params2list(params) + if(click_params) + if(click_params["icon-x"]) + I.pixel_x = (text2num(click_params["icon-x"]) - 16) + if(click_params["icon-y"]) + I.pixel_y = (text2num(click_params["icon-y"]) - 16) + else + I.pixel_x = target.pixel_x + rand(-5,5) + I.pixel_y = target.pixel_y + rand(-5,5) + + if(outmsg) + to_chat(user, outmsg) + else + to_chat(user, "You point [src] at [target].") + + energy -= 1 + if(energy <= max_energy) + if(!recharging) + recharging = 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 d29b8456c894..411068c7e6e8 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -1,266 +1,266 @@ - -// Light Replacer (LR) -// -// ABOUT THE DEVICE -// -// This is a device supposedly to be used by Janitors and Janitor Cyborgs which will -// allow them to easily replace lights. This was mostly designed for Janitor Cyborgs since -// they don't have hands or a way to replace lightbulbs. -// -// HOW IT WORKS -// -// You attack a light fixture with it, if the light fixture is broken it will replace the -// light fixture with a working light; the broken light is then placed on the floor for the -// user to then pickup with a trash bag. If it's empty then it will just place a light in the fixture. -// -// HOW TO REFILL THE DEVICE -// -// It will need to be manually refilled with lights. -// If it's part of a robot module, it will charge when the Robot is inside a Recharge Station. -// -// EMAGGED FEATURES -// -// NOTICE: The Cyborg cannot use the emagged Light Replacer and the light's explosion was nerfed. It cannot create holes in the station anymore. -// -// I'm not sure everyone will react the emag's features so please say what your opinions are of it. -// -// When emagged it will rig every light it replaces, which will explode when the light is on. -// This is VERY noticable, even the device's name changes when you emag it so if anyone -// examines you when you're holding it in your hand, you will be discovered. -// It will also be very obvious who is setting all these lights off, since only Janitor Borgs and Janitors have easy -// access to them, and only one of them can emag their device. -// -// The explosion cannot insta-kill anyone with 30% or more health. - -#define LIGHT_OK 0 -#define LIGHT_EMPTY 1 -#define LIGHT_BROKEN 2 -#define LIGHT_BURNED 3 - - -/obj/item/lightreplacer - - name = "light replacer" - desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass." - - icon = 'yogstation/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' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 8 - - var/max_uses = 20 - var/uses = 10 - // How much to increase per each glass? - var/increment = 5 - // How much to take from the glass? - var/decrement = 1 - var/charge = 1 - - // Eating used bulbs gives us bulb shards - var/bulb_shards = 0 - // when we get this many shards, we get a free bulb. - var/shards_required = 4 - -/obj/item/lightreplacer/examine(mob/user) - . = ..() - . += status_string() - -/obj/item/lightreplacer/attackby(obj/item/W, mob/user, params) - - if(istype(W, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = W - if(uses >= max_uses) - to_chat(user, "[src.name] is full.") - return - else if(G.use(decrement)) - AddUses(increment) - to_chat(user, "You insert a piece of glass into \the [src.name]. You have [uses] light\s remaining.") - return - else - to_chat(user, "You need one sheet of glass to replace lights!") - - if(istype(W, /obj/item/shard)) - if(uses >= max_uses) - to_chat(user, "\The [src] is full.") - return - if(!user.temporarilyRemoveItemFromInventory(W)) - return - AddUses(round(increment*0.75)) - to_chat(user, "You insert a shard of glass into \the [src]. You have [uses] light\s remaining.") - qdel(W) - return - - if(istype(W, /obj/item/light)) - var/obj/item/light/L = W - if(L.status == 0) // LIGHT OKAY - if(uses < max_uses) - if(!user.temporarilyRemoveItemFromInventory(W)) - return - AddUses(1) - qdel(L) - else - if(!user.temporarilyRemoveItemFromInventory(W)) - return - to_chat(user, "You insert [L] into \the [src].") - AddShards(1, user) - qdel(L) - return - - if(istype(W, /obj/item/storage)) - var/obj/item/storage/S = W - var/found_lightbulbs = FALSE - var/replaced_something = TRUE - - for(var/obj/item/I in S.contents) - if(istype(I, /obj/item/light)) - var/obj/item/light/L = I - found_lightbulbs = TRUE - if(src.uses >= max_uses) - break - if(L.status == LIGHT_OK) - replaced_something = TRUE - AddUses(1) - qdel(L) - - else if(L.status == LIGHT_BROKEN || L.status == LIGHT_BURNED) - replaced_something = TRUE - AddShards(1, user) - qdel(L) - - if(!found_lightbulbs) - to_chat(user, "\The [S] contains no bulbs.") - return - - if(!replaced_something && src.uses == max_uses) - to_chat(user, "\The [src] is full!") - return - - to_chat(user, "You fill \the [src] with lights from \the [S]. " + status_string() + "") - -/obj/item/lightreplacer/emag_act() - if(obj_flags & EMAGGED) - return - Emag() - -/obj/item/lightreplacer/attack_self(mob/user) - for(var/obj/machinery/light/target in user.loc) - ReplaceLight(target, user) - to_chat(user, status_string()) - -/obj/item/lightreplacer/update_icon() - 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, "\The [src]'s refill light blinks red.") - return - else - to_chat(U, "There is a working [target.fitting] already inserted!") - return - -/obj/item/lightreplacer/proc/Emag() - obj_flags ^= EMAGGED - playsound(src.loc, "sparks", 100, 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, "\The [src]'s refill light blinks red.") - -/obj/item/lightreplacer/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) - J.put_in_cart(src, user) - J.myreplacer = src - J.update_icon() - -/obj/item/lightreplacer/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) - return - -#undef LIGHT_OK -#undef LIGHT_EMPTY -#undef LIGHT_BROKEN -#undef LIGHT_BURNED + +// Light Replacer (LR) +// +// ABOUT THE DEVICE +// +// This is a device supposedly to be used by Janitors and Janitor Cyborgs which will +// allow them to easily replace lights. This was mostly designed for Janitor Cyborgs since +// they don't have hands or a way to replace lightbulbs. +// +// HOW IT WORKS +// +// You attack a light fixture with it, if the light fixture is broken it will replace the +// light fixture with a working light; the broken light is then placed on the floor for the +// user to then pickup with a trash bag. If it's empty then it will just place a light in the fixture. +// +// HOW TO REFILL THE DEVICE +// +// It will need to be manually refilled with lights. +// If it's part of a robot module, it will charge when the Robot is inside a Recharge Station. +// +// EMAGGED FEATURES +// +// NOTICE: The Cyborg cannot use the emagged Light Replacer and the light's explosion was nerfed. It cannot create holes in the station anymore. +// +// I'm not sure everyone will react the emag's features so please say what your opinions are of it. +// +// When emagged it will rig every light it replaces, which will explode when the light is on. +// This is VERY noticable, even the device's name changes when you emag it so if anyone +// examines you when you're holding it in your hand, you will be discovered. +// It will also be very obvious who is setting all these lights off, since only Janitor Borgs and Janitors have easy +// access to them, and only one of them can emag their device. +// +// The explosion cannot insta-kill anyone with 30% or more health. + +#define LIGHT_OK 0 +#define LIGHT_EMPTY 1 +#define LIGHT_BROKEN 2 +#define LIGHT_BURNED 3 + + +/obj/item/lightreplacer + + name = "light replacer" + desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass." + + icon = 'yogstation/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' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 8 + + var/max_uses = 20 + var/uses = 10 + // How much to increase per each glass? + var/increment = 5 + // How much to take from the glass? + var/decrement = 1 + var/charge = 1 + + // Eating used bulbs gives us bulb shards + var/bulb_shards = 0 + // when we get this many shards, we get a free bulb. + var/shards_required = 4 + +/obj/item/lightreplacer/examine(mob/user) + . = ..() + . += status_string() + +/obj/item/lightreplacer/attackby(obj/item/W, mob/user, params) + + if(istype(W, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = W + if(uses >= max_uses) + to_chat(user, "[src.name] is full.") + return + else if(G.use(decrement)) + AddUses(increment) + to_chat(user, "You insert a piece of glass into \the [src.name]. You have [uses] light\s remaining.") + return + else + to_chat(user, "You need one sheet of glass to replace lights!") + + if(istype(W, /obj/item/shard)) + if(uses >= max_uses) + to_chat(user, "\The [src] is full.") + return + if(!user.temporarilyRemoveItemFromInventory(W)) + return + AddUses(round(increment*0.75)) + to_chat(user, "You insert a shard of glass into \the [src]. You have [uses] light\s remaining.") + qdel(W) + return + + if(istype(W, /obj/item/light)) + var/obj/item/light/L = W + if(L.status == 0) // LIGHT OKAY + if(uses < max_uses) + if(!user.temporarilyRemoveItemFromInventory(W)) + return + AddUses(1) + qdel(L) + else + if(!user.temporarilyRemoveItemFromInventory(W)) + return + to_chat(user, "You insert [L] into \the [src].") + AddShards(1, user) + qdel(L) + return + + if(istype(W, /obj/item/storage)) + var/obj/item/storage/S = W + var/found_lightbulbs = FALSE + var/replaced_something = TRUE + + for(var/obj/item/I in S.contents) + if(istype(I, /obj/item/light)) + var/obj/item/light/L = I + found_lightbulbs = TRUE + if(src.uses >= max_uses) + break + if(L.status == LIGHT_OK) + replaced_something = TRUE + AddUses(1) + qdel(L) + + else if(L.status == LIGHT_BROKEN || L.status == LIGHT_BURNED) + replaced_something = TRUE + AddShards(1, user) + qdel(L) + + if(!found_lightbulbs) + to_chat(user, "\The [S] contains no bulbs.") + return + + if(!replaced_something && src.uses == max_uses) + to_chat(user, "\The [src] is full!") + return + + to_chat(user, "You fill \the [src] with lights from \the [S]. " + status_string() + "") + +/obj/item/lightreplacer/emag_act() + if(obj_flags & EMAGGED) + return + Emag() + +/obj/item/lightreplacer/attack_self(mob/user) + for(var/obj/machinery/light/target in user.loc) + ReplaceLight(target, user) + to_chat(user, status_string()) + +/obj/item/lightreplacer/update_icon() + 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, "\The [src]'s refill light blinks red.") + return + else + to_chat(U, "There is a working [target.fitting] already inserted!") + return + +/obj/item/lightreplacer/proc/Emag() + obj_flags ^= EMAGGED + playsound(src.loc, "sparks", 100, 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, "\The [src]'s refill light blinks red.") + +/obj/item/lightreplacer/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) + J.put_in_cart(src, user) + J.myreplacer = src + J.update_icon() + +/obj/item/lightreplacer/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) + return + +#undef LIGHT_OK +#undef LIGHT_EMPTY +#undef LIGHT_BROKEN +#undef LIGHT_BURNED diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index ff923f468731..81d9756a0520 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -1,171 +1,171 @@ -#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 - usesound = 'sound/weapons/empty.ogg' - var/mode = 0 - -/obj/item/multitool/examine(mob/user) - . = ..() - . += "Its buffer [buffer ? "contains [buffer]." : "is empty."]" - -/obj/item/multitool/suicide_act(mob/living/carbon/user) - user.visible_message("[user] puts the [src] to [user.p_their()] chest. It looks like [user.p_theyre()] trying to pulse [user.p_their()] heart off!") - return OXYLOSS//theres a reason it wasnt recommended by doctors - - -// Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. - -/obj/item/multitool/ai_detect - var/track_cooldown = 0 - var/track_delay = 10 //How often it checks for proximity - var/detect_state = PROXIMITY_NONE - var/rangealert = 8 //Glows red when inside - var/rangewarning = 20 //Glows yellow when inside - var/hud_type = DATA_HUD_AI_DETECT - var/hud_on = FALSE - var/mob/camera/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/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." - 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 +#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 + usesound = 'sound/weapons/empty.ogg' + var/mode = 0 + +/obj/item/multitool/examine(mob/user) + . = ..() + . += "Its buffer [buffer ? "contains [buffer]." : "is empty."]" + +/obj/item/multitool/suicide_act(mob/living/carbon/user) + user.visible_message("[user] puts the [src] to [user.p_their()] chest. It looks like [user.p_theyre()] trying to pulse [user.p_their()] heart off!") + return OXYLOSS//theres a reason it wasnt recommended by doctors + + +// Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. + +/obj/item/multitool/ai_detect + var/track_cooldown = 0 + var/track_delay = 10 //How often it checks for proximity + var/detect_state = PROXIMITY_NONE + var/rangealert = 8 //Glows red when inside + var/rangewarning = 20 //Glows yellow when inside + var/hud_type = DATA_HUD_AI_DETECT + var/hud_on = FALSE + var/mob/camera/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/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." + 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 diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 99fa8e5b4413..a1659db7c4ba 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -1,167 +1,167 @@ -/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 | INDESTRUCTIBLE - -/obj/item/paicard/suicide_act(mob/living/user) - user.visible_message("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!") - return OXYLOSS - -/obj/item/paicard/Initialize() - SSpai.pai_card_list += src - add_overlay("pai-off") - return ..() - -/obj/item/paicard/Destroy() - //Will stop people throwing friend pAIs into the singularity so they can respawn - SSpai.pai_card_list -= src - if (!QDELETED(pai)) - QDEL_NULL(pai) - return ..() - -/obj/item/paicard/attack_self(mob/user) - if (!in_range(src, user)) - return - user.set_machine(src) - var/dat = "Personal AI Device
                " - if(pai) - if(!pai.master_dna || !pai.master) - dat += "Imprint Master DNA
                " - dat += "Installed Personality: [pai.name]
                " - dat += "Prime directive:
                [pai.laws.zeroth]
                " - for(var/slaws in pai.laws.supplied) - dat += "Additional directives:
                [slaws]
                " - dat += "Configure Directives
                " - dat += "
                " - dat += "

                Device Settings


                " - if(pai.radio) - dat += "Radio Uplink
                " - dat += "Transmit: \[[pai.can_transmit? "Disable" : "Enable"] Radio Transmission\]
                " - dat += "Receive: \[[pai.can_receive? "Disable" : "Enable"] Radio Reception\]
                " - else - dat += "Radio Uplink
                " - dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
                " - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna) - dat += "\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\]
                " - dat += "\[Wipe current pAI personality\]
                " - else - dat += "No personality installed.
                " - dat += "Searching for a personality... Press view available personalities to notify potential candidates." - dat += "\[View available personalities\]
                " - user << browse(dat, "window=paicard") - onclose(user, "paicard") - return - -/obj/item/paicard/Topic(href, href_list) - - if(!usr || usr.stat) - return - - if(href_list["request"]) - SSpai.findPAI(src, usr) - - if(pai) - if(!(loc == usr)) - return - if(href_list["setdna"]) - if(pai.master_dna) - return - if(!iscarbon(usr)) - to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device!") - else - var/mob/living/carbon/M = usr - pai.master = M.real_name - pai.master_dna = M.dna.unique_enzymes - to_chat(pai, "You have been bound to a new master.") - pai.emittersemicd = FALSE - if(href_list["wipe"]) - var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No") - if(confirm == "Yes") - if(pai) - to_chat(pai, "You feel yourself slipping away from reality.") - to_chat(pai, "Byte by byte you lose your sense of self.") - to_chat(pai, "Your mental faculties leave you.") - to_chat(pai, "oblivion... ") - qdel(pai) - if(href_list["toggle_transmit"] || href_list["toggle_receive"]) - var/transmitting = href_list["toggle_transmit"] //it can't be both so if we know it's not transmitting it must be receiving. - var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX) - if(transmitting) - pai.can_transmit = !pai.can_transmit - else //receiving - pai.can_receive = !pai.can_receive - pai.radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states - transmit_holder = (transmitting ? pai.can_transmit : pai.can_receive) //recycling can be fun! - to_chat(usr,"You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!") - to_chat(pai,"Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!") - if(href_list["setlaws"]) - var/newlaws = 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") - -/obj/item/paicard/proc/alertUpdate() - audible_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", "[src] vibrates with an alert.") - -/obj/item/paicard/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(pai && !pai.holoform) - pai.emp_act(severity) - +/obj/item/paicard + name = "personal AI device" + icon = 'icons/obj/aicards.dmi' + icon_state = "pai" + 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 | INDESTRUCTIBLE + +/obj/item/paicard/suicide_act(mob/living/user) + user.visible_message("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!") + return OXYLOSS + +/obj/item/paicard/Initialize() + SSpai.pai_card_list += src + add_overlay("pai-off") + return ..() + +/obj/item/paicard/Destroy() + //Will stop people throwing friend pAIs into the singularity so they can respawn + SSpai.pai_card_list -= src + if (!QDELETED(pai)) + QDEL_NULL(pai) + return ..() + +/obj/item/paicard/attack_self(mob/user) + if (!in_range(src, user)) + return + user.set_machine(src) + var/dat = "Personal AI Device
                " + if(pai) + if(!pai.master_dna || !pai.master) + dat += "Imprint Master DNA
                " + dat += "Installed Personality: [pai.name]
                " + dat += "Prime directive:
                [pai.laws.zeroth]
                " + for(var/slaws in pai.laws.supplied) + dat += "Additional directives:
                [slaws]
                " + dat += "Configure Directives
                " + dat += "
                " + dat += "

                Device Settings


                " + if(pai.radio) + dat += "Radio Uplink
                " + dat += "Transmit: \[[pai.can_transmit? "Disable" : "Enable"] Radio Transmission\]
                " + dat += "Receive: \[[pai.can_receive? "Disable" : "Enable"] Radio Reception\]
                " + else + dat += "Radio Uplink
                " + dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
                " + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna) + dat += "\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\]
                " + dat += "\[Wipe current pAI personality\]
                " + else + dat += "No personality installed.
                " + dat += "Searching for a personality... Press view available personalities to notify potential candidates." + dat += "\[View available personalities\]
                " + user << browse(dat, "window=paicard") + onclose(user, "paicard") + return + +/obj/item/paicard/Topic(href, href_list) + + if(!usr || usr.stat) + return + + if(href_list["request"]) + SSpai.findPAI(src, usr) + + if(pai) + if(!(loc == usr)) + return + if(href_list["setdna"]) + if(pai.master_dna) + return + if(!iscarbon(usr)) + to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device!") + else + var/mob/living/carbon/M = usr + pai.master = M.real_name + pai.master_dna = M.dna.unique_enzymes + to_chat(pai, "You have been bound to a new master.") + pai.emittersemicd = FALSE + if(href_list["wipe"]) + var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No") + if(confirm == "Yes") + if(pai) + to_chat(pai, "You feel yourself slipping away from reality.") + to_chat(pai, "Byte by byte you lose your sense of self.") + to_chat(pai, "Your mental faculties leave you.") + to_chat(pai, "oblivion... ") + qdel(pai) + if(href_list["toggle_transmit"] || href_list["toggle_receive"]) + var/transmitting = href_list["toggle_transmit"] //it can't be both so if we know it's not transmitting it must be receiving. + var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX) + if(transmitting) + pai.can_transmit = !pai.can_transmit + else //receiving + pai.can_receive = !pai.can_receive + pai.radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states + transmit_holder = (transmitting ? pai.can_transmit : pai.can_receive) //recycling can be fun! + to_chat(usr,"You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!") + to_chat(pai,"Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!") + if(href_list["setlaws"]) + var/newlaws = 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") + +/obj/item/paicard/proc/alertUpdate() + audible_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", "[src] vibrates with an alert.") + +/obj/item/paicard/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(pai && !pai.holoform) + pai.emp_act(severity) + diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm index a56d9e878af9..b413a5b99f56 100644 --- a/code/game/objects/items/devices/powersink.dm +++ b/code/game/objects/items/devices/powersink.dm @@ -1,155 +1,155 @@ -// 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 = 2000000 // amount of power to drain per tick - var/power_drained = 0 // has drained this much power - var/max_power = 6e8 // maximum power that can be drained before exploding - var/mode = 0 // 0 = off, 1=clamped (off), 2=operating - var/admins_warned = FALSE // stop spam, only warn the admins once that we are about to boom - - var/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 - density = FALSE - - if(CLAMPED_OFF) - if(!attached) - return - if(mode == OPERATING) - STOP_PROCESSING(SSobj, src) - anchored = TRUE - density = TRUE - - if(OPERATING) - if(!attached) - return - START_PROCESSING(SSobj, src) - anchored = TRUE - density = TRUE - - mode = value - update_icon() - set_light(0) - -/obj/item/powersink/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_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 = 2000000 // amount of power to drain per tick + var/power_drained = 0 // has drained this much power + var/max_power = 6e8 // maximum power that can be drained before exploding + var/mode = 0 // 0 = off, 1=clamped (off), 2=operating + var/admins_warned = FALSE // stop spam, only warn the admins once that we are about to boom + + var/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 + density = FALSE + + if(CLAMPED_OFF) + if(!attached) + return + if(mode == OPERATING) + STOP_PROCESSING(SSobj, src) + anchored = TRUE + density = TRUE + + if(OPERATING) + if(!attached) + return + START_PROCESSING(SSobj, src) + anchored = TRUE + density = TRUE + + mode = value + update_icon() + set_light(0) + +/obj/item/powersink/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_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 539caa5bf034..9e2a49926a68 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -1,144 +1,144 @@ -/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/on = TRUE - var/code = 2 - var/frequency = FREQ_ELECTROPACK - var/shock_cooldown = 0 - -/obj/item/electropack/suicide_act(mob/user) - user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -/obj/item/electropack/Initialize() - . = ..() - SSradio.add_object(src, frequency, RADIO_SIGNALER) - -/obj/item/electropack/Destroy() - SSradio.remove_object(src, frequency) - return ..() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/electropack/attack_hand(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(src == C.back) - to_chat(user, "You need help taking this off!") - return - return ..() - -/obj/item/electropack/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/clothing/head/helmet)) - var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user) - A.icon = 'icons/obj/assemblies.dmi' - - if(!user.transferItemToLoc(W, A)) - to_chat(user, "[W] is stuck to your hand, you cannot attach it to [src]!") - return - W.master = A - A.part1 = W - - user.transferItemToLoc(src, A, TRUE) - master = A - A.part2 = src - - user.put_in_hands(A) - A.add_fingerprint(user) - else - return ..() - -/obj/item/electropack/Topic(href, href_list) - //..() - var/mob/living/carbon/C = usr - if(usr.stat || usr.restrained() || C.back == src) - return - if((ishuman(usr) && usr.contents.Find(src)) || usr.contents.Find(master) || (in_range(src, usr) && isturf(loc))) - usr.set_machine(src) - if(href_list["freq"]) - SSradio.remove_object(src, frequency) - frequency = sanitize_frequency(frequency + text2num(href_list["freq"])) - SSradio.add_object(src, frequency, RADIO_SIGNALER) - else - if(href_list["code"]) - code += text2num(href_list["code"]) - code = round(code) - code = min(100, code) - code = max(1, code) - else - if(href_list["power"]) - on = !( on ) - icon_state = "electropack[on]" - if(!( master )) - if(ismob(loc)) - attack_self(loc) - else - for(var/mob/M in viewers(1, src)) - if(M.client) - attack_self(M) - else - if(ismob(master.loc)) - attack_self(master.loc) - else - for(var/mob/M in viewers(1, master)) - if(M.client) - attack_self(M) - else - usr << browse(null, "window=radio") - return - return - -/obj/item/electropack/receive_signal(datum/signal/signal) - if(!signal || signal.data["code"] != code) - return - - if(isliving(loc) && on) - if(shock_cooldown != 0) - return - shock_cooldown = 1 - addtimer(VARSET_CALLBACK(src, shock_cooldown, 0), 100) - var/mob/living/L = loc - step(L, pick(GLOB.cardinals)) - - to_chat(L, "You feel a sharp shock!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, L) - s.start() - - L.Paralyze(100) - - if(master) - master.receive_signal() - return - -/obj/item/electropack/attack_self(mob/user) - - if(!ishuman(user)) - return - user.set_machine(src) - var/dat = {"Turned [on ? "On" : "Off"] - -Toggle
                -Frequency/Code for electropack:
                -Frequency: -- -- [format_frequency(frequency)] -+ -+
                - -Code: -- -- [code] -+ -+
                -
                "} - 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/on = TRUE + var/code = 2 + var/frequency = FREQ_ELECTROPACK + var/shock_cooldown = 0 + +/obj/item/electropack/suicide_act(mob/user) + user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +/obj/item/electropack/Initialize() + . = ..() + SSradio.add_object(src, frequency, RADIO_SIGNALER) + +/obj/item/electropack/Destroy() + SSradio.remove_object(src, frequency) + return ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/electropack/attack_hand(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + if(src == C.back) + to_chat(user, "You need help taking this off!") + return + return ..() + +/obj/item/electropack/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/clothing/head/helmet)) + var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user) + A.icon = 'icons/obj/assemblies.dmi' + + if(!user.transferItemToLoc(W, A)) + to_chat(user, "[W] is stuck to your hand, you cannot attach it to [src]!") + return + W.master = A + A.part1 = W + + user.transferItemToLoc(src, A, TRUE) + master = A + A.part2 = src + + user.put_in_hands(A) + A.add_fingerprint(user) + else + return ..() + +/obj/item/electropack/Topic(href, href_list) + //..() + var/mob/living/carbon/C = usr + if(usr.stat || usr.restrained() || C.back == src) + return + if((ishuman(usr) && usr.contents.Find(src)) || usr.contents.Find(master) || (in_range(src, usr) && isturf(loc))) + usr.set_machine(src) + if(href_list["freq"]) + SSradio.remove_object(src, frequency) + frequency = sanitize_frequency(frequency + text2num(href_list["freq"])) + SSradio.add_object(src, frequency, RADIO_SIGNALER) + else + if(href_list["code"]) + code += text2num(href_list["code"]) + code = round(code) + code = min(100, code) + code = max(1, code) + else + if(href_list["power"]) + on = !( on ) + icon_state = "electropack[on]" + if(!( master )) + if(ismob(loc)) + attack_self(loc) + else + for(var/mob/M in viewers(1, src)) + if(M.client) + attack_self(M) + else + if(ismob(master.loc)) + attack_self(master.loc) + else + for(var/mob/M in viewers(1, master)) + if(M.client) + attack_self(M) + else + usr << browse(null, "window=radio") + return + return + +/obj/item/electropack/receive_signal(datum/signal/signal) + if(!signal || signal.data["code"] != code) + return + + if(isliving(loc) && on) + if(shock_cooldown != 0) + return + shock_cooldown = 1 + addtimer(VARSET_CALLBACK(src, shock_cooldown, 0), 100) + var/mob/living/L = loc + step(L, pick(GLOB.cardinals)) + + to_chat(L, "You feel a sharp shock!") + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, L) + s.start() + + L.Paralyze(100) + + if(master) + master.receive_signal() + return + +/obj/item/electropack/attack_self(mob/user) + + if(!ishuman(user)) + return + user.set_machine(src) + var/dat = {"Turned [on ? "On" : "Off"] - +Toggle
                +Frequency/Code for electropack:
                +Frequency: +- +- [format_frequency(frequency)] ++ ++
                + +Code: +- +- [code] ++ ++
                +
                "} + user << browse(dat, "window=radio") + onclose(user, "radio") + return diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm index 892e2a2b3ea4..9821b7b01321 100644 --- a/code/game/objects/items/devices/radio/encryptionkey.dm +++ b/code/game/objects/items/devices/radio/encryptionkey.dm @@ -1,132 +1,132 @@ -/obj/item/encryptionkey - name = "standard encryption key" - desc = "An encryption key for a radio headset." - icon = 'icons/obj/radio.dmi' - icon_state = "cypherkey" - w_class = WEIGHT_CLASS_TINY - var/translate_binary = FALSE - var/syndie = FALSE - var/independent = FALSE - var/list/channels = list() - -/obj/item/encryptionkey/Initialize() - . = ..() - if(!channels.len) - desc = "An encryption key for a radio headset. Has no special codes in it. You should probably tell a coder!" - -/obj/item/encryptionkey/examine(mob/user) - . = ..() - if(LAZYLEN(channels)) - var/list/examine_text_list = list() - for(var/i in channels) - examine_text_list += "[GLOB.channel_tokens[i]] - [lowertext(i)]" - - . += "It can access the following channels; [jointext(examine_text_list, ", ")]." - -/obj/item/encryptionkey/syndicate - name = "syndicate encryption key" - icon_state = "syn_cypherkey" - channels = list(RADIO_CHANNEL_SYNDICATE = 1) - syndie = TRUE//Signifies that it de-crypts Syndicate transmissions - -/obj/item/encryptionkey/binary - name = "binary translator key" - icon_state = "bin_cypherkey" - translate_binary = TRUE - -/obj/item/encryptionkey/headset_sec - name = "security radio encryption key" - icon_state = "sec_cypherkey" - channels = list(RADIO_CHANNEL_SECURITY = 1) - -/obj/item/encryptionkey/headset_eng - name = "engineering radio encryption key" - icon_state = "eng_cypherkey" - channels = list(RADIO_CHANNEL_ENGINEERING = 1) - -/obj/item/encryptionkey/headset_rob - name = "robotics radio encryption key" - icon_state = "rob_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_ENGINEERING = 1) - -/obj/item/encryptionkey/headset_med - name = "medical radio encryption key" - icon_state = "med_cypherkey" - channels = list(RADIO_CHANNEL_MEDICAL = 1) - -/obj/item/encryptionkey/headset_sci - name = "science radio encryption key" - icon_state = "sci_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1) - -/obj/item/encryptionkey/headset_medsci - name = "medical research radio encryption key" - icon_state = "medsci_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1) - -/obj/item/encryptionkey/headset_srvsec - name = "law and order radio encryption key" - icon_state = "srvsec_cypherkey" - channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_SERVICE = 1) //yogs - changed order - -/obj/item/encryptionkey/headset_com - name = "command radio encryption key" - icon_state = "com_cypherkey" - channels = list(RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/captain - name = "\proper the captain's encryption key" - icon_state = "cap_cypherkey" - channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0) - -/obj/item/encryptionkey/heads/rd - name = "\proper the research director's encryption key" - icon_state = "rd_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/hos - name = "\proper the head of security's encryption key" - icon_state = "hos_cypherkey" - channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/ce - name = "\proper the chief engineer's encryption key" - icon_state = "ce_cypherkey" - channels = list(RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/cmo - name = "\proper the chief medical officer's encryption key" - icon_state = "cmo_cypherkey" - channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/hop - name = "\proper the head of personnel's encryption key" - icon_state = "hop_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/headset_cargo - name = "supply radio encryption key" - icon_state = "cargo_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1) - -/obj/item/encryptionkey/headset_mining - name = "mining radio encryption key" - icon_state = "cargo_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SCIENCE = 1) - -/obj/item/encryptionkey/headset_service - name = "service radio encryption key" - icon_state = "srv_cypherkey" - channels = list(RADIO_CHANNEL_SERVICE = 1) - -/obj/item/encryptionkey/headset_cent - name = "\improper CentCom radio encryption key" - icon_state = "cent_cypherkey" - independent = TRUE - channels = list(RADIO_CHANNEL_CENTCOM = 1) - -/obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI. - channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1) - -/obj/item/encryptionkey/secbot - channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1) +/obj/item/encryptionkey + name = "standard encryption key" + desc = "An encryption key for a radio headset." + icon = 'icons/obj/radio.dmi' + icon_state = "cypherkey" + w_class = WEIGHT_CLASS_TINY + var/translate_binary = FALSE + var/syndie = FALSE + var/independent = FALSE + var/list/channels = list() + +/obj/item/encryptionkey/Initialize() + . = ..() + if(!channels.len) + desc = "An encryption key for a radio headset. Has no special codes in it. You should probably tell a coder!" + +/obj/item/encryptionkey/examine(mob/user) + . = ..() + if(LAZYLEN(channels)) + var/list/examine_text_list = list() + for(var/i in channels) + examine_text_list += "[GLOB.channel_tokens[i]] - [lowertext(i)]" + + . += "It can access the following channels; [jointext(examine_text_list, ", ")]." + +/obj/item/encryptionkey/syndicate + name = "syndicate encryption key" + icon_state = "syn_cypherkey" + channels = list(RADIO_CHANNEL_SYNDICATE = 1) + syndie = TRUE//Signifies that it de-crypts Syndicate transmissions + +/obj/item/encryptionkey/binary + name = "binary translator key" + icon_state = "bin_cypherkey" + translate_binary = TRUE + +/obj/item/encryptionkey/headset_sec + name = "security radio encryption key" + icon_state = "sec_cypherkey" + channels = list(RADIO_CHANNEL_SECURITY = 1) + +/obj/item/encryptionkey/headset_eng + name = "engineering radio encryption key" + icon_state = "eng_cypherkey" + channels = list(RADIO_CHANNEL_ENGINEERING = 1) + +/obj/item/encryptionkey/headset_rob + name = "robotics radio encryption key" + icon_state = "rob_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_ENGINEERING = 1) + +/obj/item/encryptionkey/headset_med + name = "medical radio encryption key" + icon_state = "med_cypherkey" + channels = list(RADIO_CHANNEL_MEDICAL = 1) + +/obj/item/encryptionkey/headset_sci + name = "science radio encryption key" + icon_state = "sci_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1) + +/obj/item/encryptionkey/headset_medsci + name = "medical research radio encryption key" + icon_state = "medsci_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1) + +/obj/item/encryptionkey/headset_srvsec + name = "law and order radio encryption key" + icon_state = "srvsec_cypherkey" + channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_SERVICE = 1) //yogs - changed order + +/obj/item/encryptionkey/headset_com + name = "command radio encryption key" + icon_state = "com_cypherkey" + channels = list(RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/captain + name = "\proper the captain's encryption key" + icon_state = "cap_cypherkey" + channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0) + +/obj/item/encryptionkey/heads/rd + name = "\proper the research director's encryption key" + icon_state = "rd_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/hos + name = "\proper the head of security's encryption key" + icon_state = "hos_cypherkey" + channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/ce + name = "\proper the chief engineer's encryption key" + icon_state = "ce_cypherkey" + channels = list(RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/cmo + name = "\proper the chief medical officer's encryption key" + icon_state = "cmo_cypherkey" + channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/hop + name = "\proper the head of personnel's encryption key" + icon_state = "hop_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/headset_cargo + name = "supply radio encryption key" + icon_state = "cargo_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1) + +/obj/item/encryptionkey/headset_mining + name = "mining radio encryption key" + icon_state = "cargo_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SCIENCE = 1) + +/obj/item/encryptionkey/headset_service + name = "service radio encryption key" + icon_state = "srv_cypherkey" + channels = list(RADIO_CHANNEL_SERVICE = 1) + +/obj/item/encryptionkey/headset_cent + name = "\improper CentCom radio encryption key" + icon_state = "cent_cypherkey" + independent = TRUE + channels = list(RADIO_CHANNEL_CENTCOM = 1) + +/obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI. + channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1) + +/obj/item/encryptionkey/secbot + channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1) diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index f91f439f0479..3c0ac955ceaa 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -1,344 +1,344 @@ -// 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_rob - name = "robotics radio headset" - desc = "Made specifically for the roboticists, who cannot decide between departments." - icon_state = "rob_headset" - keyslot = new /obj/item/encryptionkey/headset_rob - -/obj/item/radio/headset/headset_med - name = "medical radio headset" - desc = "A headset for the trained staff of the medbay." - icon_state = "med_headset" - keyslot = new /obj/item/encryptionkey/headset_med - -/obj/item/radio/headset/headset_sci - name = "science radio headset" - desc = "A sciency headset. Like usual." - icon_state = "sci_headset" - keyslot = new /obj/item/encryptionkey/headset_sci - -/obj/item/radio/headset/headset_medsci - name = "medical research radio headset" - desc = "A headset that is a result of the mating between medical and science." - icon_state = "medsci_headset" - keyslot = new /obj/item/encryptionkey/headset_medsci - -/obj/item/radio/headset/headset_srvsec - name = "law and order headset" - desc = "In the criminal justice headset, the encryption key represents two separate but equally important groups. Sec, who investigate crime, and Service, who provide services. These are their comms." - icon_state = "srvsec_headset" - keyslot = new /obj/item/encryptionkey/headset_srvsec - -/obj/item/radio/headset/headset_com - name = "command radio headset" - desc = "A headset with a commanding channel." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/headset_com - -/obj/item/radio/headset/heads - command = TRUE - -/obj/item/radio/headset/heads/captain - name = "\proper the captain's headset" - desc = "The headset of the king." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/captain - -/obj/item/radio/headset/heads/captain/alt - name = "\proper the captain's bowman headset" - desc = "The headset of the boss. Protects ears from flashbangs." - icon_state = "com_headset_alt" - 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_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/silicon/pai - name = "\proper mini Integrated Subspace Transceiver " - subspace_transmission = FALSE - - -/obj/item/radio/headset/silicon/ai - name = "\proper Integrated Subspace Transceiver " - keyslot2 = new /obj/item/encryptionkey/ai - command = TRUE - -/obj/item/radio/headset/silicon/can_receive(freq, level) - return ..(freq, level, TRUE) - -/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) - user.set_machine(src) - - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(keyslot || keyslot2) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - 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"].") +// 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_rob + name = "robotics radio headset" + desc = "Made specifically for the roboticists, who cannot decide between departments." + icon_state = "rob_headset" + keyslot = new /obj/item/encryptionkey/headset_rob + +/obj/item/radio/headset/headset_med + name = "medical radio headset" + desc = "A headset for the trained staff of the medbay." + icon_state = "med_headset" + keyslot = new /obj/item/encryptionkey/headset_med + +/obj/item/radio/headset/headset_sci + name = "science radio headset" + desc = "A sciency headset. Like usual." + icon_state = "sci_headset" + keyslot = new /obj/item/encryptionkey/headset_sci + +/obj/item/radio/headset/headset_medsci + name = "medical research radio headset" + desc = "A headset that is a result of the mating between medical and science." + icon_state = "medsci_headset" + keyslot = new /obj/item/encryptionkey/headset_medsci + +/obj/item/radio/headset/headset_srvsec + name = "law and order headset" + desc = "In the criminal justice headset, the encryption key represents two separate but equally important groups. Sec, who investigate crime, and Service, who provide services. These are their comms." + icon_state = "srvsec_headset" + keyslot = new /obj/item/encryptionkey/headset_srvsec + +/obj/item/radio/headset/headset_com + name = "command radio headset" + desc = "A headset with a commanding channel." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/headset_com + +/obj/item/radio/headset/heads + command = TRUE + +/obj/item/radio/headset/heads/captain + name = "\proper the captain's headset" + desc = "The headset of the king." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/captain + +/obj/item/radio/headset/heads/captain/alt + name = "\proper the captain's bowman headset" + desc = "The headset of the boss. Protects ears from flashbangs." + icon_state = "com_headset_alt" + 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_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/silicon/pai + name = "\proper mini Integrated Subspace Transceiver " + subspace_transmission = FALSE + + +/obj/item/radio/headset/silicon/ai + name = "\proper Integrated Subspace Transceiver " + keyslot2 = new /obj/item/encryptionkey/ai + command = TRUE + +/obj/item/radio/headset/silicon/can_receive(freq, level) + return ..(freq, level, TRUE) + +/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) + user.set_machine(src) + + if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(keyslot || keyslot2) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + 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"].") diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index e6b7d12f751b..50d0e8eb6aa9 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(I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_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) - . = ..() - 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(I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_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) + . = ..() + 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 c51d63d47990..94031fb02364 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -1,416 +1,416 @@ -/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. - - // 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 |= SPAN_COMMAND - - /* - Roughly speaking, radios attempt to make a subspace transmission (which - is received, processed, and rebroadcast by the telecomms satellite) and - if that fails, they send a mundane radio transmission. - - Headsets cannot send/receive mundane transmissions, only subspace. - Syndicate radios can hear transmissions on all well-known frequencies. - CentCom radios can hear the CentCom frequency no matter what. - */ - - // From the channel, determine the frequency and get a reference to it. - var/freq - if(channel && channels && channels.len > 0) - if(channel == MODE_DEPARTMENT) - channel = channels[1] - freq = secure_radio_connections[channel] - if (!channels[channel]) // if the channel is turned off, don't broadcast - return - else - freq = frequency - channel = null - - // Nearby active jammers prevent the message from transmitting - var/turf/position = get_turf(src) - for(var/obj/item/jammer/jammer in GLOB.active_jammers) - var/turf/jammer_turf = get_turf(jammer) - if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range)) - return - - // Determine the identity information which will be attached to the signal. - var/atom/movable/virtualspeaker/speaker = new(null, M, src) - - // Construct the signal - var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans) - - // Independent radios, on the CentCom frequency, reach all independent radios - if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE)) - signal.data["compression"] = 0 - signal.transmission_method = TRANSMISSION_SUPERSPACE - signal.levels = list(0) // reaches all Z-levels - signal.broadcast() - return - - // All radios make an attempt to use the subspace system first - signal.send_to_receivers() - - // If the radio is subspace-only, that's all it can do - if (subspace_transmission) - return - - // Non-subspace radios will check in a couple of seconds, and if the signal - // was never received, send a mundane broadcast (no headsets). - addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20) - -/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal) - var/turf/T = get_turf_global(src) // yogs - get_turf_global instead of get_turf - if (signal.data["done"] && (T.z in signal.levels)) - return - - // Okay, the signal was never processed, send a mundane broadcast. - signal.data["compression"] = 0 - signal.transmission_method = TRANSMISSION_RADIO - signal.levels = list(T.z) - signal.broadcast() - -/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) - return - - if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT) - // radios don't pick up whispers very well - raw_message = stars(raw_message) - else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND) - // try to avoid being heard double - if (loc == speaker && ismob(speaker)) - var/mob/M = speaker - var/idx = M.get_held_index_of_item(src) - // left hands are odd slots - if (idx && (idx % 2) == (message_mode == MODE_L_HAND)) - return - - talk_into(speaker, raw_message, , spans, language=message_language) - -// Checks if this radio can receive on the given frequency. -/obj/item/radio/proc/can_receive(freq, level) - // deny checks - if (!on || !listening || wires.is_cut(WIRE_RX)) - return FALSE - if (freq == FREQ_SYNDICATE && !syndie) - return FALSE - if (freq == FREQ_CENTCOM) - return independent // hard-ignores the z-level check - if (!(0 in level)) - var/turf/position = get_turf_global(src) // yogs - get_turf_global instead of get_turf - if(!position || !(position.z in level)) - return FALSE - - // allow checks: are we listening on that frequency? - if (freq == frequency) - return TRUE - for(var/ch_name in channels) - if(channels[ch_name] & FREQ_LISTENING) - //the GLOB.radiochannels list is located in communications.dm - if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie) - return TRUE - return FALSE - - -/obj/item/radio/examine(mob/user) - . = ..() - if (frequency && in_range(src, user)) - . += "It is set to broadcast over the [frequency/10] frequency." - if (unscrewed) - . += "It can be attached and modified." - else - . += "It cannot be modified or attached." - -/obj/item/radio/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if(W.tool_behaviour == TOOL_SCREWDRIVER) - unscrewed = !unscrewed - if(unscrewed) - to_chat(user, "The radio can now be attached and modified!") - else - to_chat(user, "The radio can no longer be modified or attached!") - else - return ..() - -/obj/item/radio/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - emped++ //There's been an EMP; better count it - var/curremp = emped //Remember which EMP this was - if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice - to_chat(loc, "\The [src] overloads.") - broadcasting = FALSE - listening = FALSE - for (var/ch_name in channels) - channels[ch_name] = 0 - on = FALSE - 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(W.tool_behaviour == TOOL_SCREWDRIVER) - if(keyslot) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - - if(keyslot) - var/turf/T = get_turf(user) - if(T) - keyslot.forceMove(T) - keyslot = null - - recalculateChannels() - to_chat(user, "You pop out the encryption key in the radio.") - - else - to_chat(user, "This radio doesn't have any encryption keys!") - - else if(istype(W, /obj/item/encryptionkey/)) - if(keyslot) - to_chat(user, "The radio can't hold another key!") - return - - if(!keyslot) - if(!user.transferItemToLoc(W, src)) - return - keyslot = W - - recalculateChannels() - - -/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag. - listening = 0 // And it's nice to have a subtype too for future features. - dog_fashion = /datum/dog_fashion/back +/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. + + // 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 |= SPAN_COMMAND + + /* + Roughly speaking, radios attempt to make a subspace transmission (which + is received, processed, and rebroadcast by the telecomms satellite) and + if that fails, they send a mundane radio transmission. + + Headsets cannot send/receive mundane transmissions, only subspace. + Syndicate radios can hear transmissions on all well-known frequencies. + CentCom radios can hear the CentCom frequency no matter what. + */ + + // From the channel, determine the frequency and get a reference to it. + var/freq + if(channel && channels && channels.len > 0) + if(channel == MODE_DEPARTMENT) + channel = channels[1] + freq = secure_radio_connections[channel] + if (!channels[channel]) // if the channel is turned off, don't broadcast + return + else + freq = frequency + channel = null + + // Nearby active jammers prevent the message from transmitting + var/turf/position = get_turf(src) + for(var/obj/item/jammer/jammer in GLOB.active_jammers) + var/turf/jammer_turf = get_turf(jammer) + if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range)) + return + + // Determine the identity information which will be attached to the signal. + var/atom/movable/virtualspeaker/speaker = new(null, M, src) + + // Construct the signal + var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans) + + // Independent radios, on the CentCom frequency, reach all independent radios + if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE)) + signal.data["compression"] = 0 + signal.transmission_method = TRANSMISSION_SUPERSPACE + signal.levels = list(0) // reaches all Z-levels + signal.broadcast() + return + + // All radios make an attempt to use the subspace system first + signal.send_to_receivers() + + // If the radio is subspace-only, that's all it can do + if (subspace_transmission) + return + + // Non-subspace radios will check in a couple of seconds, and if the signal + // was never received, send a mundane broadcast (no headsets). + addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20) + +/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal) + var/turf/T = get_turf_global(src) // yogs - get_turf_global instead of get_turf + if (signal.data["done"] && (T.z in signal.levels)) + return + + // Okay, the signal was never processed, send a mundane broadcast. + signal.data["compression"] = 0 + signal.transmission_method = TRANSMISSION_RADIO + signal.levels = list(T.z) + signal.broadcast() + +/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) + return + + if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT) + // radios don't pick up whispers very well + raw_message = stars(raw_message) + else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND) + // try to avoid being heard double + if (loc == speaker && ismob(speaker)) + var/mob/M = speaker + var/idx = M.get_held_index_of_item(src) + // left hands are odd slots + if (idx && (idx % 2) == (message_mode == MODE_L_HAND)) + return + + talk_into(speaker, raw_message, , spans, language=message_language) + +// Checks if this radio can receive on the given frequency. +/obj/item/radio/proc/can_receive(freq, level) + // deny checks + if (!on || !listening || wires.is_cut(WIRE_RX)) + return FALSE + if (freq == FREQ_SYNDICATE && !syndie) + return FALSE + if (freq == FREQ_CENTCOM) + return independent // hard-ignores the z-level check + if (!(0 in level)) + var/turf/position = get_turf_global(src) // yogs - get_turf_global instead of get_turf + if(!position || !(position.z in level)) + return FALSE + + // allow checks: are we listening on that frequency? + if (freq == frequency) + return TRUE + for(var/ch_name in channels) + if(channels[ch_name] & FREQ_LISTENING) + //the GLOB.radiochannels list is located in communications.dm + if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie) + return TRUE + return FALSE + + +/obj/item/radio/examine(mob/user) + . = ..() + if (frequency && in_range(src, user)) + . += "It is set to broadcast over the [frequency/10] frequency." + if (unscrewed) + . += "It can be attached and modified." + else + . += "It cannot be modified or attached." + +/obj/item/radio/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + unscrewed = !unscrewed + if(unscrewed) + to_chat(user, "The radio can now be attached and modified!") + else + to_chat(user, "The radio can no longer be modified or attached!") + else + return ..() + +/obj/item/radio/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + emped++ //There's been an EMP; better count it + var/curremp = emped //Remember which EMP this was + if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice + to_chat(loc, "\The [src] overloads.") + broadcasting = FALSE + listening = FALSE + for (var/ch_name in channels) + channels[ch_name] = 0 + on = FALSE + 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(W.tool_behaviour == TOOL_SCREWDRIVER) + if(keyslot) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + + if(keyslot) + var/turf/T = get_turf(user) + if(T) + keyslot.forceMove(T) + keyslot = null + + recalculateChannels() + to_chat(user, "You pop out the encryption key in the radio.") + + else + to_chat(user, "This radio doesn't have any encryption keys!") + + else if(istype(W, /obj/item/encryptionkey/)) + if(keyslot) + to_chat(user, "The radio can't hold another key!") + return + + if(!keyslot) + if(!user.transferItemToLoc(W, src)) + return + keyslot = W + + recalculateChannels() + + +/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag. + listening = 0 // And it's nice to have a subtype too for future features. + dog_fashion = /datum/dog_fashion/back diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 3abca165e58b..dd3ebc478cc6 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -1,800 +1,800 @@ - -/* - -CONTAINS: -T-RAY -HEALTH ANALYZER -GAS ANALYZER -SLIME SCANNER -NANITE SCANNER -GENE 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." - custom_price = 10 - 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/proc/toggle_on() - on = !on - icon_state = copytext(icon_state, 1, length(icon_state))+"[on]" - if(on) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - -/obj/item/t_scanner/attack_self(mob/user) - toggle_on() - -/obj/item/t_scanner/cyborg_unequip(mob/user) - if(!on) - return - toggle_on() - -/obj/item/t_scanner/process() - if(!on) - STOP_PROCESSING(SSobj, src) - return null - scan() - -/obj/item/t_scanner/proc/scan() - t_ray_scan(loc) - -/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3) - if(!ismob(viewer) || !viewer.client) - return - var/list/t_ray_images = list() - for(var/obj/O in orange(distance, viewer) ) - if(O.level != 1) - continue - - if(O.invisibility == INVISIBILITY_MAXIMUM || HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) - var/image/I = new(loc = get_turf(O)) - var/mutable_appearance/MA = new(O) - MA.alpha = 128 - MA.dir = O.dir - I.appearance = MA - t_ray_images += I - if(t_ray_images.len) - flick_overlay(t_ray_images, list(viewer.client), flick_time) - -/obj/item/healthanalyzer - name = "health analyzer" - icon = 'icons/obj/device.dmi' - icon_state = "health" - 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!") - to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy") - to_chat(user, "Key: Suffocation/Toxin/Burn/Brute") - to_chat(user, "\tDamage specifics: 0-0-0-0") - to_chat(user, "Body temperature: ???") - 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 - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.undergoing_cardiac_arrest() && H.stat != DEAD) - to_chat(user, "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!") - - to_chat(user, "Analyzing results for [M]:\n\tOverall status: [mob_status]") - - // Damage descriptions - if(brute_loss > 10) - to_chat(user, "\t[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.") - if(fire_loss > 10) - to_chat(user, "\t[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.") - if(oxy_loss > 10) - to_chat(user, "\t[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.") - if(tox_loss > 10) - to_chat(user, "\t[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.") - if(M.getStaminaLoss()) - to_chat(user, "\tSubject appears to be suffering from fatigue.") - if(advanced) - to_chat(user, "\tFatigue Level: [M.getStaminaLoss()]%.") - if (M.getCloneLoss()) - to_chat(user, "\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.") - if(advanced) - to_chat(user, "\tCellular Damage Level: [M.getCloneLoss()].") - if (!M.getorgan(/obj/item/organ/brain)) - to_chat(user, "\tSubject lacks a brain.") - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(LAZYLEN(C.get_traumas())) - var/list/trauma_text = list() - for(var/datum/brain_trauma/B in C.get_traumas()) - var/trauma_desc = "" - switch(B.resilience) - if(TRAUMA_RESILIENCE_SURGERY) - trauma_desc += "severe " - if(TRAUMA_RESILIENCE_LOBOTOMY) - trauma_desc += "deep-rooted " - if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) - trauma_desc += "permanent " - trauma_desc += B.scan_desc - trauma_text += trauma_desc - to_chat(user, "\tCerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].") - if(C.roundstart_quirks.len) - to_chat(user, "\tSubject has the following physiological traits: [C.get_trait_string()].") - if(advanced) - to_chat(user, "\tBrain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.") - - if (M.radiation) - to_chat(user, "\tSubject is irradiated.") - if(advanced) - to_chat(user, "\tRadiation Level: [M.radiation]%.") - - if(advanced && M.hallucinating()) - to_chat(user, "\tSubject is hallucinating.") - - //Eyes and ears - if(advanced) - if(iscarbon(M)) - var/mob/living/carbon/C = M - var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) - to_chat(user, "\t==EAR STATUS==") - if(istype(ears)) - var/healthy = TRUE - if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) - healthy = FALSE - to_chat(user, "\tSubject is genetically deaf.") - else if(HAS_TRAIT(C, TRAIT_DEAF)) - healthy = FALSE - to_chat(user, "\tSubject is deaf.") - else - if(ears.damage) - to_chat(user, "\tSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage.") - healthy = FALSE - if(ears.deaf) - to_chat(user, "\tSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf.") - healthy = FALSE - if(healthy) - to_chat(user, "\tHealthy.") - else - to_chat(user, "\tSubject does not have ears.") - var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES) - to_chat(user, "\t==EYE STATUS==") - if(istype(eyes)) - var/healthy = TRUE - if(HAS_TRAIT(C, TRAIT_BLIND)) - to_chat(user, "\tSubject is blind.") - healthy = FALSE - if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) - to_chat(user, "\tSubject is nearsighted.") - healthy = FALSE - if(eyes.damage > 30) - to_chat(user, "\tSubject has severe eye damage.") - healthy = FALSE - else if(eyes.damage > 20) - to_chat(user, "\tSubject has significant eye damage.") - healthy = FALSE - else if(eyes.damage) - to_chat(user, "\tSubject has minor eye damage.") - healthy = FALSE - if(healthy) - to_chat(user, "\tHealthy.") - else - to_chat(user, "\tSubject does not have eyes.") - - - // Body part damage report - 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) - to_chat(user, "\tDamage: Brute-Burn-Toxin-Suffocation\n\t\tSpecifics: [brute_loss]-[fire_loss]-[tox_loss]-[oxy_loss]") - for(var/obj/item/bodypart/org in damaged) - to_chat(user, "\t\t[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]") - - //Organ damages report - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/minor_damage - var/major_damage - var/max_damage - var/report_organs = FALSE - - //Piece together the lists to be reported - for(var/O in H.internal_organs) - var/obj/item/organ/organ = O - if(organ.organ_flags & ORGAN_FAILING) - report_organs = TRUE //if we report one organ, we report all organs, even if the lists are empty, just for consistency - if(max_damage) - max_damage += ", " //prelude the organ if we've already reported an organ - max_damage += organ.name //this just slaps the organ name into the string of text - else - max_damage = "\tNon-Functional Organs: " //our initial statement - max_damage += organ.name - else if(organ.damage > organ.high_threshold) - report_organs = TRUE - if(major_damage) - major_damage += ", " - major_damage += organ.name - else - major_damage = "\tSeverely Damaged Organs: " - major_damage += organ.name - else if(organ.damage > organ.low_threshold) - report_organs = TRUE - if(minor_damage) - minor_damage += ", " - minor_damage += organ.name - else - minor_damage = "\tMildly Damaged Organs: " - minor_damage += organ.name - - if(report_organs) //we either finish the list, or set it to be empty if no organs were reported in that category - if(!max_damage) - max_damage = "\tNon-Functional Organs: " - else - max_damage += "" - if(!major_damage) - major_damage = "\tSeverely Damaged Organs: " - else - major_damage += "" - if(!minor_damage) - minor_damage = "\tMildly Damaged Organs: " - else - minor_damage += "" - to_chat(user, minor_damage) - to_chat(user, major_damage) - to_chat(user, max_damage) - //Genetic damage - if(advanced && H.has_dna()) - to_chat(user, "\tGenetic Stability: [H.dna.stability]%.") - - // Species and body temperature - if(ishuman(M)) - var/mob/living/carbon/human/H = 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 - - to_chat(user, "Species: [S.name][mutant ? "-derived mutant" : ""]") - to_chat(user, "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)") - - // Time of death - if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced))) - to_chat(user, "Time of Death: [M.tod]") - var/tdelta = round(world.time - M.timeofdeath) - if(tdelta < (DEFIB_TIME_LIMIT * 10)) - to_chat(user, "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!") - - for(var/thing in M.diseases) - var/datum/disease/D = thing - if(!(D.visibility_flags & HIDDEN_SCANNER)) - to_chat(user, "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text].") //Yogs - Added a "." - - // Blood Level - if(M.has_dna()) - var/mob/living/carbon/C = M - var/blood_id = C.get_blood_id() - if(blood_id) - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(H.bleed_rate) - to_chat(user, "Subject is bleeding!") - var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL(C))*100) - var/blood_type = C.dna.blood_type - if(blood_id != /datum/reagent/blood)//special blood substance - var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] - if(R) - blood_type = R.name - else - blood_type = blood_id - if(C.blood_volume <= BLOOD_VOLUME_SAFE(C) && C.blood_volume > BLOOD_VOLUME_OKAY(C)) - to_chat(user, "LOW blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]") - else if(C.blood_volume <= BLOOD_VOLUME_OKAY(C)) - to_chat(user, "CRITICAL blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]") - else - to_chat(user, "Blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]") - - 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) - to_chat(user, "Detected cybernetic modifications:") - to_chat(user, "[cyberimp_detect]") - SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) - -/proc/chemscan(mob/living/user, mob/living/M) - if(istype(M)) - if(M.reagents) - if(M.reagents.reagent_list.len) - to_chat(user, "Subject contains the following reagents:") - for(var/datum/reagent/R in M.reagents.reagent_list) - to_chat(user, "[round(R.volume, 0.001)] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : ".
                "]") - else - to_chat(user, "Subject contains no reagents.") - if(M.reagents.addiction_list.len) - to_chat(user, "Subject is addicted to the following reagents:") - for(var/datum/reagent/R in M.reagents.addiction_list) - to_chat(user, "[R.name]") - else - to_chat(user, "Subject is not addicted to any reagents.") - -/obj/item/healthanalyzer/verb/toggle_mode() - set name = "Switch Verbosity" - set category = "Object" - - if(usr.incapacitated()) - 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" - custom_price = 10 - 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 - - environment.assert_gases(arglist(GLOB.hardcoded_gases)) - var/o2_concentration = env_gases[/datum/gas/oxygen][MOLES]/total_moles - var/n2_concentration = env_gases[/datum/gas/nitrogen][MOLES]/total_moles - var/co2_concentration = env_gases[/datum/gas/carbon_dioxide][MOLES]/total_moles - var/plasma_concentration = env_gases[/datum/gas/plasma][MOLES]/total_moles - - if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)") - else - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 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][MOLES], 0.01)] mol)") - else - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 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][MOLES], 0.01)] mol)") - else - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)") - - if(plasma_concentration > 0.005) - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)") - else - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)") - - environment.garbage_collect() - - for(var/id in env_gases) - if(id in GLOB.hardcoded_gases) - continue - var/gas_concentration = env_gases[id][MOLES]/total_moles - to_chat(user, "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id][MOLES], 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, BE_CLOSE)) - - if(cooldown) - to_chat(user, "[src]'s barometer function is preparing itself.") - return - - var/turf/T = get_turf(user) - if(!T) - return - - playsound(src, 'sound/effects/pop.ogg', 100) - var/area/user_area = T.loc - var/datum/weather/ongoing_weather = null - - if(!user_area.outdoors) - to_chat(user, "[src]'s barometer function won't work indoors!") - return - - for(var/V in SSweather.processing) - var/datum/weather/W = V - if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) - ongoing_weather = W - break - - if(ongoing_weather) - if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) - to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]") - return - - to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].") - if(ongoing_weather.aesthetic) - to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.") - else - var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] - var/fixed = next_hit ? 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][MOLES]/total_moles - to_chat(user, "[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id][MOLES], 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/instability = round(cached_scan_results["fusion"], 0.01) - to_chat(user, "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.") - to_chat(user, "Instability of the last fusion reaction: [instability].") - 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.") - -/obj/item/sequence_scanner - name = "genetic sequence scanner" - icon = 'icons/obj/device.dmi' - icon_state = "gene" - 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 scanner for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal database." - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=200) - var/list/discovered = list() //hit a dna console to update the scanners database - var/list/buffer - var/ready = TRUE - var/cooldown = 200 - -/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user) - add_fingerprint(user) - if (!HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species - user.visible_message("[user] has analyzed [M]'s genetic sequence.") - gene_scan(M, user) - - else - user.visible_message("[user] failed to analyse [M]'s genetic sequence.", "[M] has no readable genetic sequence!") - -/obj/item/sequence_scanner/attack_self(mob/user) - display_sequence(user) - -/obj/item/sequence_scanner/attack_self_tk(mob/user) - return - -/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity) - . = ..() - if(!istype(O) || !proximity) - return - - if(istype(O, /obj/machinery/computer/scan_consolenew)) - var/obj/machinery/computer/scan_consolenew/C = O - if(C.stored_research) - to_chat(user, "[name] database updated.") - discovered = C.stored_research.discovered_mutations - else - to_chat(user,"No database to update from.") - -/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user) - if(!iscarbon(C) || !C.has_dna()) - return - buffer = C.dna.mutation_index - to_chat(user, "Subject [C.name]'s DNA sequence has been saved to buffer.") - if(LAZYLEN(buffer)) - for(var/A in buffer) - to_chat(user, "[get_display_name(A)]") - - -/obj/item/sequence_scanner/proc/display_sequence(mob/living/user) - if(!LAZYLEN(buffer) || !ready) - return - var/list/options = list() - for(var/A in buffer) - options += get_display_name(A) - - var/answer = input(user, "Analyze Potential", "Sequence Analyzer") as null|anything in options - if(answer && ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - var/sequence - for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list - if(get_display_name(A) == answer) - sequence = buffer[A] - break - - if(sequence) - var/display - for(var/i in 0 to length(sequence) / DNA_MUTATION_BLOCKS-1) - if(i) - display += "-" - display += copytext(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1) - - to_chat(user, "[display]
                ") - - ready = FALSE - icon_state = "[icon_state]_recharging" - addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE) - -/obj/item/sequence_scanner/proc/recharge() - icon_state = initial(icon_state) - ready = TRUE - -/obj/item/sequence_scanner/proc/get_display_name(mutation) - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation) - if(!HM) - return "ERROR" - if(mutation in discovered) - return "[HM.name] ([HM.alias])" - else - return HM.alias + +/* + +CONTAINS: +T-RAY +HEALTH ANALYZER +GAS ANALYZER +SLIME SCANNER +NANITE SCANNER +GENE 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." + custom_price = 10 + 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/proc/toggle_on() + on = !on + icon_state = copytext(icon_state, 1, length(icon_state))+"[on]" + if(on) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + +/obj/item/t_scanner/attack_self(mob/user) + toggle_on() + +/obj/item/t_scanner/cyborg_unequip(mob/user) + if(!on) + return + toggle_on() + +/obj/item/t_scanner/process() + if(!on) + STOP_PROCESSING(SSobj, src) + return null + scan() + +/obj/item/t_scanner/proc/scan() + t_ray_scan(loc) + +/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3) + if(!ismob(viewer) || !viewer.client) + return + var/list/t_ray_images = list() + for(var/obj/O in orange(distance, viewer) ) + if(O.level != 1) + continue + + if(O.invisibility == INVISIBILITY_MAXIMUM || HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) + var/image/I = new(loc = get_turf(O)) + var/mutable_appearance/MA = new(O) + MA.alpha = 128 + MA.dir = O.dir + I.appearance = MA + t_ray_images += I + if(t_ray_images.len) + flick_overlay(t_ray_images, list(viewer.client), flick_time) + +/obj/item/healthanalyzer + name = "health analyzer" + icon = 'icons/obj/device.dmi' + icon_state = "health" + 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!") + to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy") + to_chat(user, "Key: Suffocation/Toxin/Burn/Brute") + to_chat(user, "\tDamage specifics: 0-0-0-0") + to_chat(user, "Body temperature: ???") + 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 + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.undergoing_cardiac_arrest() && H.stat != DEAD) + to_chat(user, "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!") + + to_chat(user, "Analyzing results for [M]:\n\tOverall status: [mob_status]") + + // Damage descriptions + if(brute_loss > 10) + to_chat(user, "\t[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.") + if(fire_loss > 10) + to_chat(user, "\t[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.") + if(oxy_loss > 10) + to_chat(user, "\t[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.") + if(tox_loss > 10) + to_chat(user, "\t[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.") + if(M.getStaminaLoss()) + to_chat(user, "\tSubject appears to be suffering from fatigue.") + if(advanced) + to_chat(user, "\tFatigue Level: [M.getStaminaLoss()]%.") + if (M.getCloneLoss()) + to_chat(user, "\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.") + if(advanced) + to_chat(user, "\tCellular Damage Level: [M.getCloneLoss()].") + if (!M.getorgan(/obj/item/organ/brain)) + to_chat(user, "\tSubject lacks a brain.") + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(LAZYLEN(C.get_traumas())) + var/list/trauma_text = list() + for(var/datum/brain_trauma/B in C.get_traumas()) + var/trauma_desc = "" + switch(B.resilience) + if(TRAUMA_RESILIENCE_SURGERY) + trauma_desc += "severe " + if(TRAUMA_RESILIENCE_LOBOTOMY) + trauma_desc += "deep-rooted " + if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) + trauma_desc += "permanent " + trauma_desc += B.scan_desc + trauma_text += trauma_desc + to_chat(user, "\tCerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].") + if(C.roundstart_quirks.len) + to_chat(user, "\tSubject has the following physiological traits: [C.get_trait_string()].") + if(advanced) + to_chat(user, "\tBrain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.") + + if (M.radiation) + to_chat(user, "\tSubject is irradiated.") + if(advanced) + to_chat(user, "\tRadiation Level: [M.radiation]%.") + + if(advanced && M.hallucinating()) + to_chat(user, "\tSubject is hallucinating.") + + //Eyes and ears + if(advanced) + if(iscarbon(M)) + var/mob/living/carbon/C = M + var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) + to_chat(user, "\t==EAR STATUS==") + if(istype(ears)) + var/healthy = TRUE + if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) + healthy = FALSE + to_chat(user, "\tSubject is genetically deaf.") + else if(HAS_TRAIT(C, TRAIT_DEAF)) + healthy = FALSE + to_chat(user, "\tSubject is deaf.") + else + if(ears.damage) + to_chat(user, "\tSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage.") + healthy = FALSE + if(ears.deaf) + to_chat(user, "\tSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf.") + healthy = FALSE + if(healthy) + to_chat(user, "\tHealthy.") + else + to_chat(user, "\tSubject does not have ears.") + var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES) + to_chat(user, "\t==EYE STATUS==") + if(istype(eyes)) + var/healthy = TRUE + if(HAS_TRAIT(C, TRAIT_BLIND)) + to_chat(user, "\tSubject is blind.") + healthy = FALSE + if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) + to_chat(user, "\tSubject is nearsighted.") + healthy = FALSE + if(eyes.damage > 30) + to_chat(user, "\tSubject has severe eye damage.") + healthy = FALSE + else if(eyes.damage > 20) + to_chat(user, "\tSubject has significant eye damage.") + healthy = FALSE + else if(eyes.damage) + to_chat(user, "\tSubject has minor eye damage.") + healthy = FALSE + if(healthy) + to_chat(user, "\tHealthy.") + else + to_chat(user, "\tSubject does not have eyes.") + + + // Body part damage report + 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) + to_chat(user, "\tDamage: Brute-Burn-Toxin-Suffocation\n\t\tSpecifics: [brute_loss]-[fire_loss]-[tox_loss]-[oxy_loss]") + for(var/obj/item/bodypart/org in damaged) + to_chat(user, "\t\t[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]") + + //Organ damages report + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/minor_damage + var/major_damage + var/max_damage + var/report_organs = FALSE + + //Piece together the lists to be reported + for(var/O in H.internal_organs) + var/obj/item/organ/organ = O + if(organ.organ_flags & ORGAN_FAILING) + report_organs = TRUE //if we report one organ, we report all organs, even if the lists are empty, just for consistency + if(max_damage) + max_damage += ", " //prelude the organ if we've already reported an organ + max_damage += organ.name //this just slaps the organ name into the string of text + else + max_damage = "\tNon-Functional Organs: " //our initial statement + max_damage += organ.name + else if(organ.damage > organ.high_threshold) + report_organs = TRUE + if(major_damage) + major_damage += ", " + major_damage += organ.name + else + major_damage = "\tSeverely Damaged Organs: " + major_damage += organ.name + else if(organ.damage > organ.low_threshold) + report_organs = TRUE + if(minor_damage) + minor_damage += ", " + minor_damage += organ.name + else + minor_damage = "\tMildly Damaged Organs: " + minor_damage += organ.name + + if(report_organs) //we either finish the list, or set it to be empty if no organs were reported in that category + if(!max_damage) + max_damage = "\tNon-Functional Organs: " + else + max_damage += "" + if(!major_damage) + major_damage = "\tSeverely Damaged Organs: " + else + major_damage += "" + if(!minor_damage) + minor_damage = "\tMildly Damaged Organs: " + else + minor_damage += "" + to_chat(user, minor_damage) + to_chat(user, major_damage) + to_chat(user, max_damage) + //Genetic damage + if(advanced && H.has_dna()) + to_chat(user, "\tGenetic Stability: [H.dna.stability]%.") + + // Species and body temperature + if(ishuman(M)) + var/mob/living/carbon/human/H = 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 + + to_chat(user, "Species: [S.name][mutant ? "-derived mutant" : ""]") + to_chat(user, "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)") + + // Time of death + if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced))) + to_chat(user, "Time of Death: [M.tod]") + var/tdelta = round(world.time - M.timeofdeath) + if(tdelta < (DEFIB_TIME_LIMIT * 10)) + to_chat(user, "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!") + + for(var/thing in M.diseases) + var/datum/disease/D = thing + if(!(D.visibility_flags & HIDDEN_SCANNER)) + to_chat(user, "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text].") //Yogs - Added a "." + + // Blood Level + if(M.has_dna()) + var/mob/living/carbon/C = M + var/blood_id = C.get_blood_id() + if(blood_id) + if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(H.bleed_rate) + to_chat(user, "Subject is bleeding!") + var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL(C))*100) + var/blood_type = C.dna.blood_type + if(blood_id != /datum/reagent/blood)//special blood substance + var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] + if(R) + blood_type = R.name + else + blood_type = blood_id + if(C.blood_volume <= BLOOD_VOLUME_SAFE(C) && C.blood_volume > BLOOD_VOLUME_OKAY(C)) + to_chat(user, "LOW blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]") + else if(C.blood_volume <= BLOOD_VOLUME_OKAY(C)) + to_chat(user, "CRITICAL blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]") + else + to_chat(user, "Blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]") + + 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) + to_chat(user, "Detected cybernetic modifications:") + to_chat(user, "[cyberimp_detect]") + SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) + +/proc/chemscan(mob/living/user, mob/living/M) + if(istype(M)) + if(M.reagents) + if(M.reagents.reagent_list.len) + to_chat(user, "Subject contains the following reagents:") + for(var/datum/reagent/R in M.reagents.reagent_list) + to_chat(user, "[round(R.volume, 0.001)] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : ".
                "]") + else + to_chat(user, "Subject contains no reagents.") + if(M.reagents.addiction_list.len) + to_chat(user, "Subject is addicted to the following reagents:") + for(var/datum/reagent/R in M.reagents.addiction_list) + to_chat(user, "[R.name]") + else + to_chat(user, "Subject is not addicted to any reagents.") + +/obj/item/healthanalyzer/verb/toggle_mode() + set name = "Switch Verbosity" + set category = "Object" + + if(usr.incapacitated()) + 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" + custom_price = 10 + 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 + + environment.assert_gases(arglist(GLOB.hardcoded_gases)) + var/o2_concentration = env_gases[/datum/gas/oxygen][MOLES]/total_moles + var/n2_concentration = env_gases[/datum/gas/nitrogen][MOLES]/total_moles + var/co2_concentration = env_gases[/datum/gas/carbon_dioxide][MOLES]/total_moles + var/plasma_concentration = env_gases[/datum/gas/plasma][MOLES]/total_moles + + if(abs(n2_concentration - N2STANDARD) < 20) + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)") + else + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 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][MOLES], 0.01)] mol)") + else + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 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][MOLES], 0.01)] mol)") + else + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)") + + if(plasma_concentration > 0.005) + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)") + else + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)") + + environment.garbage_collect() + + for(var/id in env_gases) + if(id in GLOB.hardcoded_gases) + continue + var/gas_concentration = env_gases[id][MOLES]/total_moles + to_chat(user, "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id][MOLES], 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, BE_CLOSE)) + + if(cooldown) + to_chat(user, "[src]'s barometer function is preparing itself.") + return + + var/turf/T = get_turf(user) + if(!T) + return + + playsound(src, 'sound/effects/pop.ogg', 100) + var/area/user_area = T.loc + var/datum/weather/ongoing_weather = null + + if(!user_area.outdoors) + to_chat(user, "[src]'s barometer function won't work indoors!") + return + + for(var/V in SSweather.processing) + var/datum/weather/W = V + if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) + ongoing_weather = W + break + + if(ongoing_weather) + if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) + to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]") + return + + to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].") + if(ongoing_weather.aesthetic) + to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.") + else + var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] + var/fixed = next_hit ? 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][MOLES]/total_moles + to_chat(user, "[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id][MOLES], 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/instability = round(cached_scan_results["fusion"], 0.01) + to_chat(user, "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.") + to_chat(user, "Instability of the last fusion reaction: [instability].") + 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.") + +/obj/item/sequence_scanner + name = "genetic sequence scanner" + icon = 'icons/obj/device.dmi' + icon_state = "gene" + 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 scanner for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal database." + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=200) + var/list/discovered = list() //hit a dna console to update the scanners database + var/list/buffer + var/ready = TRUE + var/cooldown = 200 + +/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user) + add_fingerprint(user) + if (!HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species + user.visible_message("[user] has analyzed [M]'s genetic sequence.") + gene_scan(M, user) + + else + user.visible_message("[user] failed to analyse [M]'s genetic sequence.", "[M] has no readable genetic sequence!") + +/obj/item/sequence_scanner/attack_self(mob/user) + display_sequence(user) + +/obj/item/sequence_scanner/attack_self_tk(mob/user) + return + +/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity) + . = ..() + if(!istype(O) || !proximity) + return + + if(istype(O, /obj/machinery/computer/scan_consolenew)) + var/obj/machinery/computer/scan_consolenew/C = O + if(C.stored_research) + to_chat(user, "[name] database updated.") + discovered = C.stored_research.discovered_mutations + else + to_chat(user,"No database to update from.") + +/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user) + if(!iscarbon(C) || !C.has_dna()) + return + buffer = C.dna.mutation_index + to_chat(user, "Subject [C.name]'s DNA sequence has been saved to buffer.") + if(LAZYLEN(buffer)) + for(var/A in buffer) + to_chat(user, "[get_display_name(A)]") + + +/obj/item/sequence_scanner/proc/display_sequence(mob/living/user) + if(!LAZYLEN(buffer) || !ready) + return + var/list/options = list() + for(var/A in buffer) + options += get_display_name(A) + + var/answer = input(user, "Analyze Potential", "Sequence Analyzer") as null|anything in options + if(answer && ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + var/sequence + for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list + if(get_display_name(A) == answer) + sequence = buffer[A] + break + + if(sequence) + var/display + for(var/i in 0 to length(sequence) / DNA_MUTATION_BLOCKS-1) + if(i) + display += "-" + display += copytext(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1) + + to_chat(user, "[display]
                ") + + ready = FALSE + icon_state = "[icon_state]_recharging" + addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE) + +/obj/item/sequence_scanner/proc/recharge() + icon_state = initial(icon_state) + ready = TRUE + +/obj/item/sequence_scanner/proc/get_display_name(mutation) + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation) + if(!HM) + return "ERROR" + if(mutation in discovered) + return "[HM.name] ([HM.alias])" + else + return HM.alias diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index 7186cf6783c0..6da85d09553b 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) - . = ..() - 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 && I.tool_behaviour == TOOL_SCREWDRIVER || istype(I, /obj/item/pen)) - to_chat(user, "You start winding the tape back in...") - if(I.use_tool(src, user, 120)) - to_chat(user, "You wound the tape back in.") - fix() - -//Random colour tapes -/obj/item/tape/random - icon_state = "random_tape" - -/obj/item/tape/random/Initialize() - . = ..() - icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" +/obj/item/taperecorder + name = "universal recorder" + desc = "A device that can record to cassette tapes, and play them. It automatically translates the content in playback." + icon = 'icons/obj/device.dmi' + icon_state = "taperecorder_empty" + 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) + . = ..() + 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 && I.tool_behaviour == TOOL_SCREWDRIVER || istype(I, /obj/item/pen)) + to_chat(user, "You start winding the tape back in...") + if(I.use_tool(src, user, 120)) + to_chat(user, "You wound the tape back in.") + fix() + +//Random colour tapes +/obj/item/tape/random + icon_state = "random_tape" + +/obj/item/tape/random/Initialize() + . = ..() + icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index 0981469eb6ae..d58c5db30cf8 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). - log_bomber(user, "attached a [item.name] to a ttv -", src, null, FALSE) - 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, BE_CLOSE)) - 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() - - if(!tank_one && !tank_two && !attached_device) - icon_state = "valve_1" - return - icon_state = "valve" - - if(tank_one) - add_overlay("[tank_one.icon_state]") - if(tank_two) - var/mutable_appearance/J = mutable_appearance(icon, icon_state = "[tank_two.icon_state]") - var/matrix/T = matrix() - T.Translate(-13, 0) - J.transform = T - underlays = list(J) - else - underlays = null - if(attached_device) - add_overlay("device") - if(istype(attached_device, /obj/item/assembly/infra)) - var/obj/item/assembly/infra/sensor = attached_device - if(sensor.on && sensor.visible) - add_overlay("proxy_beam") - -/obj/item/transfer_valve/proc/merge_gases(datum/gas_mixture/target, change_volume = TRUE) - var/target_self = FALSE - if(!target || (target == tank_one.air_contents)) - target = tank_two.air_contents - if(target == tank_two.air_contents) - target_self = TRUE - if(change_volume) - if(!target_self) - target.volume += tank_two.volume - target.volume += tank_one.air_contents.volume - var/datum/gas_mixture/temp - temp = tank_one.air_contents.remove_ratio(1) - target.merge(temp) - if(!target_self) - temp = tank_two.air_contents.remove_ratio(1) - target.merge(temp) - -/obj/item/transfer_valve/proc/split_gases() - if (!valve_open || !tank_one || !tank_two) - return - var/ratio1 = tank_one.air_contents.volume/tank_two.air_contents.volume - var/datum/gas_mixture/temp - temp = tank_two.air_contents.remove_ratio(ratio1) - tank_one.air_contents.merge(temp) - tank_two.air_contents.volume -= tank_one.air_contents.volume - - /* - Exadv1: I know this isn't how it's going to work, but this was just to check - it explodes properly when it gets a signal (and it does). - */ - -/obj/item/transfer_valve/proc/toggle_valve() - if(!valve_open && tank_one && tank_two) - valve_open = TRUE - var/turf/bombturf = get_turf(src) - - var/attachment - if(attached_device) - if(istype(attached_device, /obj/item/assembly/signaler)) - attachment = "[attached_device]" - else - attachment = attached_device - - var/admin_attachment_message - var/attachment_message - if(attachment) - admin_attachment_message = " with [attachment] attached by [attacher ? ADMIN_LOOKUPFLW(attacher) : "Unknown"]" - attachment_message = " with [attachment] attached by [attacher ? key_name_admin(attacher) : "Unknown"]" - - var/mob/bomber = get_mob_by_key(fingerprintslast) - var/admin_bomber_message - var/bomber_message - if(bomber) - admin_bomber_message = " - Last touched by: [ADMIN_LOOKUPFLW(bomber)]" - bomber_message = " - Last touched by: [key_name_admin(bomber)]" - - - var/admin_bomb_message = "Bomb valve opened in [ADMIN_VERBOSEJMP(bombturf)][admin_attachment_message][admin_bomber_message]" - GLOB.bombers += admin_bomb_message - message_admins(admin_bomb_message) - log_game("Bomb valve opened in [AREACOORD(bombturf)][attachment_message][bomber_message]") - - merge_gases() - for(var/i in 1 to 6) - addtimer(CALLBACK(src, .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). + log_bomber(user, "attached a [item.name] to a ttv -", src, null, FALSE) + 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, BE_CLOSE)) + 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() + + if(!tank_one && !tank_two && !attached_device) + icon_state = "valve_1" + return + icon_state = "valve" + + if(tank_one) + add_overlay("[tank_one.icon_state]") + if(tank_two) + var/mutable_appearance/J = mutable_appearance(icon, icon_state = "[tank_two.icon_state]") + var/matrix/T = matrix() + T.Translate(-13, 0) + J.transform = T + underlays = list(J) + else + underlays = null + if(attached_device) + add_overlay("device") + if(istype(attached_device, /obj/item/assembly/infra)) + var/obj/item/assembly/infra/sensor = attached_device + if(sensor.on && sensor.visible) + add_overlay("proxy_beam") + +/obj/item/transfer_valve/proc/merge_gases(datum/gas_mixture/target, change_volume = TRUE) + var/target_self = FALSE + if(!target || (target == tank_one.air_contents)) + target = tank_two.air_contents + if(target == tank_two.air_contents) + target_self = TRUE + if(change_volume) + if(!target_self) + target.volume += tank_two.volume + target.volume += tank_one.air_contents.volume + var/datum/gas_mixture/temp + temp = tank_one.air_contents.remove_ratio(1) + target.merge(temp) + if(!target_self) + temp = tank_two.air_contents.remove_ratio(1) + target.merge(temp) + +/obj/item/transfer_valve/proc/split_gases() + if (!valve_open || !tank_one || !tank_two) + return + var/ratio1 = tank_one.air_contents.volume/tank_two.air_contents.volume + var/datum/gas_mixture/temp + temp = tank_two.air_contents.remove_ratio(ratio1) + tank_one.air_contents.merge(temp) + tank_two.air_contents.volume -= tank_one.air_contents.volume + + /* + Exadv1: I know this isn't how it's going to work, but this was just to check + it explodes properly when it gets a signal (and it does). + */ + +/obj/item/transfer_valve/proc/toggle_valve() + if(!valve_open && tank_one && tank_two) + valve_open = TRUE + var/turf/bombturf = get_turf(src) + + var/attachment + if(attached_device) + if(istype(attached_device, /obj/item/assembly/signaler)) + attachment = "[attached_device]" + else + attachment = attached_device + + var/admin_attachment_message + var/attachment_message + if(attachment) + admin_attachment_message = " with [attachment] attached by [attacher ? ADMIN_LOOKUPFLW(attacher) : "Unknown"]" + attachment_message = " with [attachment] attached by [attacher ? key_name_admin(attacher) : "Unknown"]" + + var/mob/bomber = get_mob_by_key(fingerprintslast) + var/admin_bomber_message + var/bomber_message + if(bomber) + admin_bomber_message = " - Last touched by: [ADMIN_LOOKUPFLW(bomber)]" + bomber_message = " - Last touched by: [key_name_admin(bomber)]" + + + var/admin_bomb_message = "Bomb valve opened in [ADMIN_VERBOSEJMP(bombturf)][admin_attachment_message][admin_bomber_message]" + GLOB.bombers += admin_bomb_message + message_admins(admin_bomb_message) + log_game("Bomb valve opened in [AREACOORD(bombturf)][attachment_message][bomber_message]") + + merge_gases() + for(var/i in 1 to 6) + addtimer(CALLBACK(src, .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 7025d605ab2f..24ba5a2abcd0 100644 --- a/code/game/objects/items/dice.dm +++ b/code/game/objects/items/dice.dm @@ -1,206 +1,206 @@ -/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/microwave_riggable = TRUE - - var/rigged = DICE_NOT_RIGGED - var/rigged_value - -/obj/item/dice/Initialize() - . = ..() - if(!result) - result = roll(sides) - update_icon() - -/obj/item/dice/suicide_act(mob/user) - user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/dice/d1 - name = "d1" - desc = "A die with only one side. Deterministic!" - icon_state = "d1" - sides = 1 - -/obj/item/dice/d2 - name = "d2" - desc = "A die with two sides. Coins are undignified!" - icon_state = "d2" - sides = 2 - -/obj/item/dice/d4 - name = "d4" - desc = "A die with four sides. The nerd's caltrop." - icon_state = "d4" - sides = 4 - -/obj/item/dice/d4/Initialize(mapload) - . = ..() - AddComponent(/datum/component/caltrop, 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/hit_atom, datum/thrownthing/throwingdatum) - diceroll(thrownby) - . = ..() - -/obj/item/dice/proc/diceroll(mob/user) - result = roll(sides) - if(rigged != DICE_NOT_RIGGED && result != rigged_value) - if(rigged == DICE_BASICALLY_RIGGED && prob(CLAMP(1/(sides - 1) * 100, 25, 80))) - result = rigged_value - else if(rigged == DICE_TOTALLY_RIGGED) - result = rigged_value - - . = result - - var/fake_result = roll(sides)//Daredevil isn't as good as he used to be - var/comment = "" - if(sides == 20 && result == 20) - comment = "NAT 20!" - else if(sides == 20 && result == 1) - comment = "Ouch, bad luck." - update_icon() - if(initial(icon_state) == "d00") - result = (result - 1)*10 - if(special_faces.len == sides) - result = special_faces[result] - if(user != null) //Dice was rolled in someone's hand - user.visible_message("[user] 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(microwave_riggable) - rigged = DICE_BASICALLY_RIGGED - rigged_value = 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/microwave_riggable = TRUE + + var/rigged = DICE_NOT_RIGGED + var/rigged_value + +/obj/item/dice/Initialize() + . = ..() + if(!result) + result = roll(sides) + update_icon() + +/obj/item/dice/suicide_act(mob/user) + user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/dice/d1 + name = "d1" + desc = "A die with only one side. Deterministic!" + icon_state = "d1" + sides = 1 + +/obj/item/dice/d2 + name = "d2" + desc = "A die with two sides. Coins are undignified!" + icon_state = "d2" + sides = 2 + +/obj/item/dice/d4 + name = "d4" + desc = "A die with four sides. The nerd's caltrop." + icon_state = "d4" + sides = 4 + +/obj/item/dice/d4/Initialize(mapload) + . = ..() + AddComponent(/datum/component/caltrop, 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/hit_atom, datum/thrownthing/throwingdatum) + diceroll(thrownby) + . = ..() + +/obj/item/dice/proc/diceroll(mob/user) + result = roll(sides) + if(rigged != DICE_NOT_RIGGED && result != rigged_value) + if(rigged == DICE_BASICALLY_RIGGED && prob(CLAMP(1/(sides - 1) * 100, 25, 80))) + result = rigged_value + else if(rigged == DICE_TOTALLY_RIGGED) + result = rigged_value + + . = result + + var/fake_result = roll(sides)//Daredevil isn't as good as he used to be + var/comment = "" + if(sides == 20 && result == 20) + comment = "NAT 20!" + else if(sides == 20 && result == 1) + comment = "Ouch, bad luck." + update_icon() + if(initial(icon_state) == "d00") + result = (result - 1)*10 + if(special_faces.len == sides) + result = special_faces[result] + if(user != null) //Dice was rolled in someone's hand + user.visible_message("[user] 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(microwave_riggable) + rigged = DICE_BASICALLY_RIGGED + rigged_value = result + ..(M) diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index 02df81df7d47..a71b86bdd6b0 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -1,478 +1,478 @@ -/obj/item/dnainjector - name = "\improper DNA injector" - desc = "This injects the person with DNA." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "dnainjector" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - - var/damage_coeff = 1 - var/list/fields - var/list/add_mutations = list() - var/list/remove_mutations = list() - - var/used = 0 - -/obj/item/dnainjector/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/dnainjector/proc/inject(mob/living/carbon/M, mob/user) - if(M.has_dna() && !HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - for(var/HM in remove_mutations) - M.dna.remove_mutation(HM) - for(var/HM in add_mutations) - if(HM == RACEMUT) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") - log_msg += " (MONKEY)" - if(M.dna.mutation_in_sequence(HM)) - M.dna.activate_mutation(HM) - else - M.dna.add_mutation(HM, MUT_EXTRA) - if(fields) - if(fields["name"] && fields["UE"] && fields["blood_type"]) - M.real_name = fields["name"] - M.dna.unique_enzymes = fields["UE"] - M.name = M.real_name - M.dna.blood_type = fields["blood_type"] - if(fields["UI"]) //UI+UE - M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) - M.updateappearance(mutations_overlay_update=1) - log_attack("[log_msg] [loc_name(user)]") - return TRUE - return FALSE - -/obj/item/dnainjector/attack(mob/target, mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - if(used) - to_chat(user, "This injector is used up!") - return - if(ishuman(target)) - var/mob/living/carbon/human/humantarget = target - if (!humantarget.can_inject(user, 1)) - return - log_combat(user, target, "attempted to inject", src) - - if(target != user) - target.visible_message("[user] is trying to inject [target] with [src]!", "[user] is trying to inject [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 = list(HULK) - -/obj/item/dnainjector/hulkmut - name = "\improper DNA injector (Hulk)" - desc = "This will make you big and strong, but give you a bad skin condition." - add_mutations = list(HULK) - -/obj/item/dnainjector/xraymut - name = "\improper DNA injector (X-ray)" - desc = "Finally you can see what the Captain does." - add_mutations = list(XRAY) - -/obj/item/dnainjector/antixray - name = "\improper DNA injector (Anti-X-ray)" - desc = "It will make you see harder." - remove_mutations = list(XRAY) - -///////////////////////////////////// -/obj/item/dnainjector/antiglasses - name = "\improper DNA injector (Anti-Glasses)" - desc = "Toss away those glasses!" - remove_mutations = list(BADSIGHT) - -/obj/item/dnainjector/glassesmut - name = "\improper DNA injector (Glasses)" - desc = "Will make you need dorkish glasses." - add_mutations = list(BADSIGHT) - -/obj/item/dnainjector/epimut - name = "\improper DNA injector (Epi.)" - desc = "Shake shake shake the room!" - add_mutations = list(EPILEPSY) - -/obj/item/dnainjector/antiepi - name = "\improper DNA injector (Anti-Epi.)" - desc = "Will fix you up from shaking the room." - remove_mutations = list(EPILEPSY) -//////////////////////////////////// -/obj/item/dnainjector/anticough - name = "\improper DNA injector (Anti-Cough)" - desc = "Will stop that awful noise." - remove_mutations = list(COUGH) - -/obj/item/dnainjector/coughmut - name = "\improper DNA injector (Cough)" - desc = "Will bring forth a sound of horror from your throat." - add_mutations = list(COUGH) - -/obj/item/dnainjector/antidwarf - name = "\improper DNA injector (Anti-Dwarfism)" - desc = "Helps you grow big and strong." - remove_mutations = list(DWARFISM) - -/obj/item/dnainjector/dwarf - name = "\improper DNA injector (Dwarfism)" - desc = "It's a small world after all." - add_mutations = list(DWARFISM) - -/obj/item/dnainjector/clumsymut - name = "\improper DNA injector (Clumsy)" - desc = "Makes clown minions." - add_mutations = list(CLOWNMUT) - -/obj/item/dnainjector/anticlumsy - name = "\improper DNA injector (Anti-Clumsy)" - desc = "Apply this for Security Clown." - remove_mutations = list(CLOWNMUT) - -/obj/item/dnainjector/antitour - name = "\improper DNA injector (Anti-Tour.)" - desc = "Will cure Tourette's." - remove_mutations = list(TOURETTES) - -/obj/item/dnainjector/tourmut - name = "\improper DNA injector (Tour.)" - desc = "Gives you a nasty case of Tourette's." - add_mutations = list(TOURETTES) - -/obj/item/dnainjector/stuttmut - name = "\improper DNA injector (Stutt.)" - desc = "Makes you s-s-stuttterrr." - add_mutations = list(NERVOUS) - -/obj/item/dnainjector/antistutt - name = "\improper DNA injector (Anti-Stutt.)" - desc = "Fixes that speaking impairment." - remove_mutations = list(NERVOUS) - -/obj/item/dnainjector/antifire - name = "\improper DNA injector (Anti-Fire)" - desc = "Cures fire." - remove_mutations = list(SPACEMUT) - -/obj/item/dnainjector/firemut - name = "\improper DNA injector (Fire)" - desc = "Gives you fire." - add_mutations = list(SPACEMUT) - -/obj/item/dnainjector/blindmut - name = "\improper DNA injector (Blind)" - desc = "Makes you not see anything." - add_mutations = list(BLINDMUT) - -/obj/item/dnainjector/antiblind - name = "\improper DNA injector (Anti-Blind)" - desc = "IT'S A MIRACLE!!!" - remove_mutations = list(BLINDMUT) - -/obj/item/dnainjector/antitele - name = "\improper DNA injector (Anti-Tele.)" - desc = "Will make you not able to control your mind." - remove_mutations = list(TK) - -/obj/item/dnainjector/telemut - name = "\improper DNA injector (Tele.)" - desc = "Super brain man!" - add_mutations = list(TK) - -/obj/item/dnainjector/telemut/darkbundle - name = "\improper DNA injector" - desc = "Good. Let the hate flow through you." - -/obj/item/dnainjector/deafmut - name = "\improper DNA injector (Deaf)" - desc = "Sorry, what did you say?" - add_mutations = list(DEAFMUT) - -/obj/item/dnainjector/antideaf - name = "\improper DNA injector (Anti-Deaf)" - desc = "Will make you hear once more." - remove_mutations = list(DEAFMUT) - -/obj/item/dnainjector/h2m - name = "\improper DNA injector (Human > Monkey)" - desc = "Will make you a flea bag." - add_mutations = list(RACEMUT) - -/obj/item/dnainjector/m2h - name = "\improper DNA injector (Monkey > Human)" - desc = "Will make you...less hairy." - remove_mutations = list(RACEMUT) - -/obj/item/dnainjector/antichameleon - name = "\improper DNA injector (Anti-Chameleon)" - remove_mutations = list(CHAMELEON) - -/obj/item/dnainjector/chameleonmut - name = "\improper DNA injector (Chameleon)" - add_mutations = list(CHAMELEON) - -/obj/item/dnainjector/antiwacky - name = "\improper DNA injector (Anti-Wacky)" - remove_mutations = list(WACKY) - -/obj/item/dnainjector/wackymut - name = "\improper DNA injector (Wacky)" - add_mutations = list(WACKY) - -/obj/item/dnainjector/antimute - name = "\improper DNA injector (Anti-Mute)" - remove_mutations = list(MUT_MUTE) - -/obj/item/dnainjector/mutemut - name = "\improper DNA injector (Mute)" - add_mutations = list(MUT_MUTE) - -/obj/item/dnainjector/antismile - name = "\improper DNA injector (Anti-Smile)" - remove_mutations = list(SMILE) - -/obj/item/dnainjector/smilemut - name = "\improper DNA injector (Smile)" - add_mutations = list(SMILE) - -/obj/item/dnainjector/unintelligiblemut - name = "\improper DNA injector (Unintelligible)" - add_mutations = list(UNINTELLIGIBLE) - -/obj/item/dnainjector/antiunintelligible - name = "\improper DNA injector (Anti-Unintelligible)" - remove_mutations = list(UNINTELLIGIBLE) - -/obj/item/dnainjector/swedishmut - name = "\improper DNA injector (Swedish)" - add_mutations = list(SWEDISH) - -/obj/item/dnainjector/antiswedish - name = "\improper DNA injector (Anti-Swedish)" - remove_mutations = list(SWEDISH) - -/obj/item/dnainjector/chavmut - name = "\improper DNA injector (Chav)" - add_mutations = list(CHAV) - -/obj/item/dnainjector/antichav - name = "\improper DNA injector (Anti-Chav)" - remove_mutations = list(CHAV) - -/obj/item/dnainjector/elvismut - name = "\improper DNA injector (Elvis)" - add_mutations = list(ELVIS) - -/obj/item/dnainjector/antielvis - name = "\improper DNA injector (Anti-Elvis)" - remove_mutations = list(ELVIS) - -/obj/item/dnainjector/lasereyesmut - name = "\improper DNA injector (Laser Eyes)" - add_mutations = list(LASEREYES) - -/obj/item/dnainjector/antilasereyes - name = "\improper DNA injector (Anti-Laser Eyes)" - remove_mutations = list(LASEREYES) - -/obj/item/dnainjector/void - name = "\improper DNA injector (Void)" - add_mutations = list(VOID) - -/obj/item/dnainjector/antivoid - name = "\improper DNA injector (Anti-Void)" - remove_mutations = list(VOID) - -/obj/item/dnainjector/antenna - name = "\improper DNA injector (Antenna)" - add_mutations = list(ANTENNA) - -/obj/item/dnainjector/antiantenna - name = "\improper DNA injector (Anti-Antenna)" - remove_mutations = list(ANTENNA) - -/obj/item/dnainjector/paranoia - name = "\improper DNA injector (Paranoia)" - add_mutations = list(PARANOIA) - -/obj/item/dnainjector/antiparanoia - name = "\improper DNA injector (Anti-Paranoia)" - remove_mutations = list(PARANOIA) - -/obj/item/dnainjector/mindread - name = "\improper DNA injector (Mindread)" - add_mutations = list(MINDREAD) - -/obj/item/dnainjector/antimindread - name = "\improper DNA injector (Anti-Mindread)" - remove_mutations = list(MINDREAD) - -/obj/item/dnainjector/radioactive - name = "\improper DNA injector (Radioactive)" - add_mutations = list(RADIOACTIVE) - -/obj/item/dnainjector/antiradioactive - name = "\improper DNA injector (Anti-Radioactive)" - remove_mutations = list(RADIOACTIVE) -/obj/item/dnainjector/olfaction - name = "\improper DNA injector (Olfaction)" - add_mutations = list(OLFACTION) - -/obj/item/dnainjector/antiolfaction - name = "\improper DNA injector (Anti-Olfaction)" - remove_mutations = list(OLFACTION) - -/obj/item/dnainjector/insulated - name = "\improper DNA injector (Insulated)" - add_mutations = list(INSULATED) - -/obj/item/dnainjector/antiinsulated - name = "\improper DNA injector (Anti-Insulated)" - remove_mutations = list(INSULATED) - -/obj/item/dnainjector/shock - name = "\improper DNA injector (Shock Touch)" - add_mutations = list(SHOCKTOUCH) - -/obj/item/dnainjector/antishock - name = "\improper DNA injector (Anti-Shock Touch)" - remove_mutations = list(SHOCKTOUCH) - -/obj/item/dnainjector/geladikinesis - name = "\improper DNA injector (Geladikinesis)" - add_mutations = list(GELADIKINESIS) - -/obj/item/dnainjector/antigeladikinesis - name = "\improper DNA injector (Anti-Geladikinesis)" - remove_mutations = list(GELADIKINESIS) - -/obj/item/dnainjector/cryokinesis - name = "\improper DNA injector (Cryokinesis)" - add_mutations = list(CRYOKINESIS) - -/obj/item/dnainjector/anticryokinesis - name = "\improper DNA injector (Anti-Cryokinesis)" - remove_mutations = list(CRYOKINESIS) - -/obj/item/dnainjector/thermal - name = "\improper DNA injector (Thermal Vision)" - add_mutations = list(THERMAL) - -/obj/item/dnainjector/antithermal - name = "\improper DNA injector (Anti-Thermal Vision)" - remove_mutations = list(THERMAL) - -/obj/item/dnainjector/timed - var/duration = 600 - -/obj/item/dnainjector/timed/inject(mob/living/carbon/M, mob/user) - if(M.stat == DEAD) //prevents dead people from having their DNA changed - to_chat(user, "You can't modify [M]'s DNA while [M.p_theyre()] dead.") - return FALSE - - if(M.has_dna() && !(HAS_TRAIT(M, TRAIT_BADDNA))) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - var/endtime = world.time+duration - for(var/mutation in remove_mutations) - if(mutation == RACEMUT) - if(ishuman(M)) - continue - M = M.dna.remove_mutation(mutation) - else - M.dna.remove_mutation(mutation) - for(var/mutation in add_mutations) - if(M.dna.get_mutation(mutation)) - continue //Skip permanent mutations we already have. - if(mutation == RACEMUT && ishuman(M)) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") - log_msg += " (MONKEY)" - M = M.dna.add_mutation(mutation, MUT_OTHER, endtime) - else - M.dna.add_mutation(mutation, MUT_OTHER, endtime) - if(fields) - if(fields["name"] && fields["UE"] && fields["blood_type"]) - if(!M.dna.previous["name"]) - M.dna.previous["name"] = M.real_name - if(!M.dna.previous["UE"]) - M.dna.previous["UE"] = M.dna.unique_enzymes - if(!M.dna.previous["blood_type"]) - M.dna.previous["blood_type"] = M.dna.blood_type - M.real_name = fields["name"] - M.dna.unique_enzymes = fields["UE"] - M.name = M.real_name - M.dna.blood_type = fields["blood_type"] - M.dna.temporary_mutations[UE_CHANGED] = endtime - if(fields["UI"]) //UI+UE - if(!M.dna.previous["UI"]) - M.dna.previous["UI"] = M.dna.uni_identity - M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) - M.updateappearance(mutations_overlay_update=1) - M.dna.temporary_mutations[UI_CHANGED] = endtime - log_attack("[log_msg] [loc_name(user)]") - return TRUE - else - return FALSE - -/obj/item/dnainjector/timed/hulk - name = "\improper DNA injector (Hulk)" - desc = "This will make you big and strong, but give you a bad skin condition." - add_mutations = list(HULK) - -/obj/item/dnainjector/timed/h2m - name = "\improper DNA injector (Human > Monkey)" - desc = "Will make you a flea bag." - add_mutations = list(RACEMUT) - -/obj/item/dnainjector/activator - name = "\improper DNA activator" - desc = "Activates the current mutation on injection, if the subject has it." - var/doitanyway = FALSE - var/research = FALSE //Set to true to get expended and filled injectors for chromosomes - var/filled = FALSE - -/obj/item/dnainjector/activator/inject(mob/living/carbon/M, mob/user) - if(M.has_dna() && !HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - for(var/mutation in add_mutations) - var/datum/mutation/human/HM = mutation - if(istype(HM, /datum/mutation/human)) - mutation = HM.type - if(!M.dna.activate_mutation(HM)) - if(!doitanyway) - log_msg += "(FAILED)" - else - M.dna.add_mutation(HM, MUT_EXTRA) - name = "expended [name]" - else if(research && M.client) - filled = TRUE - name = "filled [name]" - else - name = "expended [name]" - log_msg += "([mutation])" - log_attack("[log_msg] [loc_name(user)]") - return TRUE - return FALSE - +/obj/item/dnainjector + name = "\improper DNA injector" + desc = "This injects the person with DNA." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "dnainjector" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + + var/damage_coeff = 1 + var/list/fields + var/list/add_mutations = list() + var/list/remove_mutations = list() + + var/used = 0 + +/obj/item/dnainjector/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/dnainjector/proc/inject(mob/living/carbon/M, mob/user) + if(M.has_dna() && !HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + for(var/HM in remove_mutations) + M.dna.remove_mutation(HM) + for(var/HM in add_mutations) + if(HM == RACEMUT) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") + log_msg += " (MONKEY)" + if(M.dna.mutation_in_sequence(HM)) + M.dna.activate_mutation(HM) + else + M.dna.add_mutation(HM, MUT_EXTRA) + if(fields) + if(fields["name"] && fields["UE"] && fields["blood_type"]) + M.real_name = fields["name"] + M.dna.unique_enzymes = fields["UE"] + M.name = M.real_name + M.dna.blood_type = fields["blood_type"] + if(fields["UI"]) //UI+UE + M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) + M.updateappearance(mutations_overlay_update=1) + log_attack("[log_msg] [loc_name(user)]") + return TRUE + return FALSE + +/obj/item/dnainjector/attack(mob/target, mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + if(used) + to_chat(user, "This injector is used up!") + return + if(ishuman(target)) + var/mob/living/carbon/human/humantarget = target + if (!humantarget.can_inject(user, 1)) + return + log_combat(user, target, "attempted to inject", src) + + if(target != user) + target.visible_message("[user] is trying to inject [target] with [src]!", "[user] is trying to inject [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 = list(HULK) + +/obj/item/dnainjector/hulkmut + name = "\improper DNA injector (Hulk)" + desc = "This will make you big and strong, but give you a bad skin condition." + add_mutations = list(HULK) + +/obj/item/dnainjector/xraymut + name = "\improper DNA injector (X-ray)" + desc = "Finally you can see what the Captain does." + add_mutations = list(XRAY) + +/obj/item/dnainjector/antixray + name = "\improper DNA injector (Anti-X-ray)" + desc = "It will make you see harder." + remove_mutations = list(XRAY) + +///////////////////////////////////// +/obj/item/dnainjector/antiglasses + name = "\improper DNA injector (Anti-Glasses)" + desc = "Toss away those glasses!" + remove_mutations = list(BADSIGHT) + +/obj/item/dnainjector/glassesmut + name = "\improper DNA injector (Glasses)" + desc = "Will make you need dorkish glasses." + add_mutations = list(BADSIGHT) + +/obj/item/dnainjector/epimut + name = "\improper DNA injector (Epi.)" + desc = "Shake shake shake the room!" + add_mutations = list(EPILEPSY) + +/obj/item/dnainjector/antiepi + name = "\improper DNA injector (Anti-Epi.)" + desc = "Will fix you up from shaking the room." + remove_mutations = list(EPILEPSY) +//////////////////////////////////// +/obj/item/dnainjector/anticough + name = "\improper DNA injector (Anti-Cough)" + desc = "Will stop that awful noise." + remove_mutations = list(COUGH) + +/obj/item/dnainjector/coughmut + name = "\improper DNA injector (Cough)" + desc = "Will bring forth a sound of horror from your throat." + add_mutations = list(COUGH) + +/obj/item/dnainjector/antidwarf + name = "\improper DNA injector (Anti-Dwarfism)" + desc = "Helps you grow big and strong." + remove_mutations = list(DWARFISM) + +/obj/item/dnainjector/dwarf + name = "\improper DNA injector (Dwarfism)" + desc = "It's a small world after all." + add_mutations = list(DWARFISM) + +/obj/item/dnainjector/clumsymut + name = "\improper DNA injector (Clumsy)" + desc = "Makes clown minions." + add_mutations = list(CLOWNMUT) + +/obj/item/dnainjector/anticlumsy + name = "\improper DNA injector (Anti-Clumsy)" + desc = "Apply this for Security Clown." + remove_mutations = list(CLOWNMUT) + +/obj/item/dnainjector/antitour + name = "\improper DNA injector (Anti-Tour.)" + desc = "Will cure Tourette's." + remove_mutations = list(TOURETTES) + +/obj/item/dnainjector/tourmut + name = "\improper DNA injector (Tour.)" + desc = "Gives you a nasty case of Tourette's." + add_mutations = list(TOURETTES) + +/obj/item/dnainjector/stuttmut + name = "\improper DNA injector (Stutt.)" + desc = "Makes you s-s-stuttterrr." + add_mutations = list(NERVOUS) + +/obj/item/dnainjector/antistutt + name = "\improper DNA injector (Anti-Stutt.)" + desc = "Fixes that speaking impairment." + remove_mutations = list(NERVOUS) + +/obj/item/dnainjector/antifire + name = "\improper DNA injector (Anti-Fire)" + desc = "Cures fire." + remove_mutations = list(SPACEMUT) + +/obj/item/dnainjector/firemut + name = "\improper DNA injector (Fire)" + desc = "Gives you fire." + add_mutations = list(SPACEMUT) + +/obj/item/dnainjector/blindmut + name = "\improper DNA injector (Blind)" + desc = "Makes you not see anything." + add_mutations = list(BLINDMUT) + +/obj/item/dnainjector/antiblind + name = "\improper DNA injector (Anti-Blind)" + desc = "IT'S A MIRACLE!!!" + remove_mutations = list(BLINDMUT) + +/obj/item/dnainjector/antitele + name = "\improper DNA injector (Anti-Tele.)" + desc = "Will make you not able to control your mind." + remove_mutations = list(TK) + +/obj/item/dnainjector/telemut + name = "\improper DNA injector (Tele.)" + desc = "Super brain man!" + add_mutations = list(TK) + +/obj/item/dnainjector/telemut/darkbundle + name = "\improper DNA injector" + desc = "Good. Let the hate flow through you." + +/obj/item/dnainjector/deafmut + name = "\improper DNA injector (Deaf)" + desc = "Sorry, what did you say?" + add_mutations = list(DEAFMUT) + +/obj/item/dnainjector/antideaf + name = "\improper DNA injector (Anti-Deaf)" + desc = "Will make you hear once more." + remove_mutations = list(DEAFMUT) + +/obj/item/dnainjector/h2m + name = "\improper DNA injector (Human > Monkey)" + desc = "Will make you a flea bag." + add_mutations = list(RACEMUT) + +/obj/item/dnainjector/m2h + name = "\improper DNA injector (Monkey > Human)" + desc = "Will make you...less hairy." + remove_mutations = list(RACEMUT) + +/obj/item/dnainjector/antichameleon + name = "\improper DNA injector (Anti-Chameleon)" + remove_mutations = list(CHAMELEON) + +/obj/item/dnainjector/chameleonmut + name = "\improper DNA injector (Chameleon)" + add_mutations = list(CHAMELEON) + +/obj/item/dnainjector/antiwacky + name = "\improper DNA injector (Anti-Wacky)" + remove_mutations = list(WACKY) + +/obj/item/dnainjector/wackymut + name = "\improper DNA injector (Wacky)" + add_mutations = list(WACKY) + +/obj/item/dnainjector/antimute + name = "\improper DNA injector (Anti-Mute)" + remove_mutations = list(MUT_MUTE) + +/obj/item/dnainjector/mutemut + name = "\improper DNA injector (Mute)" + add_mutations = list(MUT_MUTE) + +/obj/item/dnainjector/antismile + name = "\improper DNA injector (Anti-Smile)" + remove_mutations = list(SMILE) + +/obj/item/dnainjector/smilemut + name = "\improper DNA injector (Smile)" + add_mutations = list(SMILE) + +/obj/item/dnainjector/unintelligiblemut + name = "\improper DNA injector (Unintelligible)" + add_mutations = list(UNINTELLIGIBLE) + +/obj/item/dnainjector/antiunintelligible + name = "\improper DNA injector (Anti-Unintelligible)" + remove_mutations = list(UNINTELLIGIBLE) + +/obj/item/dnainjector/swedishmut + name = "\improper DNA injector (Swedish)" + add_mutations = list(SWEDISH) + +/obj/item/dnainjector/antiswedish + name = "\improper DNA injector (Anti-Swedish)" + remove_mutations = list(SWEDISH) + +/obj/item/dnainjector/chavmut + name = "\improper DNA injector (Chav)" + add_mutations = list(CHAV) + +/obj/item/dnainjector/antichav + name = "\improper DNA injector (Anti-Chav)" + remove_mutations = list(CHAV) + +/obj/item/dnainjector/elvismut + name = "\improper DNA injector (Elvis)" + add_mutations = list(ELVIS) + +/obj/item/dnainjector/antielvis + name = "\improper DNA injector (Anti-Elvis)" + remove_mutations = list(ELVIS) + +/obj/item/dnainjector/lasereyesmut + name = "\improper DNA injector (Laser Eyes)" + add_mutations = list(LASEREYES) + +/obj/item/dnainjector/antilasereyes + name = "\improper DNA injector (Anti-Laser Eyes)" + remove_mutations = list(LASEREYES) + +/obj/item/dnainjector/void + name = "\improper DNA injector (Void)" + add_mutations = list(VOID) + +/obj/item/dnainjector/antivoid + name = "\improper DNA injector (Anti-Void)" + remove_mutations = list(VOID) + +/obj/item/dnainjector/antenna + name = "\improper DNA injector (Antenna)" + add_mutations = list(ANTENNA) + +/obj/item/dnainjector/antiantenna + name = "\improper DNA injector (Anti-Antenna)" + remove_mutations = list(ANTENNA) + +/obj/item/dnainjector/paranoia + name = "\improper DNA injector (Paranoia)" + add_mutations = list(PARANOIA) + +/obj/item/dnainjector/antiparanoia + name = "\improper DNA injector (Anti-Paranoia)" + remove_mutations = list(PARANOIA) + +/obj/item/dnainjector/mindread + name = "\improper DNA injector (Mindread)" + add_mutations = list(MINDREAD) + +/obj/item/dnainjector/antimindread + name = "\improper DNA injector (Anti-Mindread)" + remove_mutations = list(MINDREAD) + +/obj/item/dnainjector/radioactive + name = "\improper DNA injector (Radioactive)" + add_mutations = list(RADIOACTIVE) + +/obj/item/dnainjector/antiradioactive + name = "\improper DNA injector (Anti-Radioactive)" + remove_mutations = list(RADIOACTIVE) +/obj/item/dnainjector/olfaction + name = "\improper DNA injector (Olfaction)" + add_mutations = list(OLFACTION) + +/obj/item/dnainjector/antiolfaction + name = "\improper DNA injector (Anti-Olfaction)" + remove_mutations = list(OLFACTION) + +/obj/item/dnainjector/insulated + name = "\improper DNA injector (Insulated)" + add_mutations = list(INSULATED) + +/obj/item/dnainjector/antiinsulated + name = "\improper DNA injector (Anti-Insulated)" + remove_mutations = list(INSULATED) + +/obj/item/dnainjector/shock + name = "\improper DNA injector (Shock Touch)" + add_mutations = list(SHOCKTOUCH) + +/obj/item/dnainjector/antishock + name = "\improper DNA injector (Anti-Shock Touch)" + remove_mutations = list(SHOCKTOUCH) + +/obj/item/dnainjector/geladikinesis + name = "\improper DNA injector (Geladikinesis)" + add_mutations = list(GELADIKINESIS) + +/obj/item/dnainjector/antigeladikinesis + name = "\improper DNA injector (Anti-Geladikinesis)" + remove_mutations = list(GELADIKINESIS) + +/obj/item/dnainjector/cryokinesis + name = "\improper DNA injector (Cryokinesis)" + add_mutations = list(CRYOKINESIS) + +/obj/item/dnainjector/anticryokinesis + name = "\improper DNA injector (Anti-Cryokinesis)" + remove_mutations = list(CRYOKINESIS) + +/obj/item/dnainjector/thermal + name = "\improper DNA injector (Thermal Vision)" + add_mutations = list(THERMAL) + +/obj/item/dnainjector/antithermal + name = "\improper DNA injector (Anti-Thermal Vision)" + remove_mutations = list(THERMAL) + +/obj/item/dnainjector/timed + var/duration = 600 + +/obj/item/dnainjector/timed/inject(mob/living/carbon/M, mob/user) + if(M.stat == DEAD) //prevents dead people from having their DNA changed + to_chat(user, "You can't modify [M]'s DNA while [M.p_theyre()] dead.") + return FALSE + + if(M.has_dna() && !(HAS_TRAIT(M, TRAIT_BADDNA))) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + var/endtime = world.time+duration + for(var/mutation in remove_mutations) + if(mutation == RACEMUT) + if(ishuman(M)) + continue + M = M.dna.remove_mutation(mutation) + else + M.dna.remove_mutation(mutation) + for(var/mutation in add_mutations) + if(M.dna.get_mutation(mutation)) + continue //Skip permanent mutations we already have. + if(mutation == RACEMUT && ishuman(M)) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") + log_msg += " (MONKEY)" + M = M.dna.add_mutation(mutation, MUT_OTHER, endtime) + else + M.dna.add_mutation(mutation, MUT_OTHER, endtime) + if(fields) + if(fields["name"] && fields["UE"] && fields["blood_type"]) + if(!M.dna.previous["name"]) + M.dna.previous["name"] = M.real_name + if(!M.dna.previous["UE"]) + M.dna.previous["UE"] = M.dna.unique_enzymes + if(!M.dna.previous["blood_type"]) + M.dna.previous["blood_type"] = M.dna.blood_type + M.real_name = fields["name"] + M.dna.unique_enzymes = fields["UE"] + M.name = M.real_name + M.dna.blood_type = fields["blood_type"] + M.dna.temporary_mutations[UE_CHANGED] = endtime + if(fields["UI"]) //UI+UE + if(!M.dna.previous["UI"]) + M.dna.previous["UI"] = M.dna.uni_identity + M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) + M.updateappearance(mutations_overlay_update=1) + M.dna.temporary_mutations[UI_CHANGED] = endtime + log_attack("[log_msg] [loc_name(user)]") + return TRUE + else + return FALSE + +/obj/item/dnainjector/timed/hulk + name = "\improper DNA injector (Hulk)" + desc = "This will make you big and strong, but give you a bad skin condition." + add_mutations = list(HULK) + +/obj/item/dnainjector/timed/h2m + name = "\improper DNA injector (Human > Monkey)" + desc = "Will make you a flea bag." + add_mutations = list(RACEMUT) + +/obj/item/dnainjector/activator + name = "\improper DNA activator" + desc = "Activates the current mutation on injection, if the subject has it." + var/doitanyway = FALSE + var/research = FALSE //Set to true to get expended and filled injectors for chromosomes + var/filled = FALSE + +/obj/item/dnainjector/activator/inject(mob/living/carbon/M, mob/user) + if(M.has_dna() && !HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + for(var/mutation in add_mutations) + var/datum/mutation/human/HM = mutation + if(istype(HM, /datum/mutation/human)) + mutation = HM.type + if(!M.dna.activate_mutation(HM)) + if(!doitanyway) + log_msg += "(FAILED)" + else + M.dna.add_mutation(HM, MUT_EXTRA) + name = "expended [name]" + else if(research && M.client) + filled = TRUE + name = "filled [name]" + else + name = "expended [name]" + log_msg += "([mutation])" + log_attack("[log_msg] [loc_name(user)]") + return TRUE + return FALSE + diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm index f958470e793e..5ba2fa5c5581 100644 --- a/code/game/objects/items/extinguisher.dm +++ b/code/game/objects/items/extinguisher.dm @@ -1,246 +1,246 @@ -/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/proc/refill() - create_reagents(max_water, AMOUNT_VISIBLE) - reagents.add_reagent(chem, max_water) - -/obj/item/extinguisher/Initialize() - . = ..() - refill() - -/obj/item/extinguisher/advanced - name = "advanced fire extinguisher" - desc = "Used to stop thermonuclear fires from spreading inside your engine." - icon_state = "foam_extinguisher0" - //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/suicide_act(mob/living/carbon/user) - if (!safety && (reagents.total_volume >= 1)) - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth. It looks like [user.p_theyre()] trying to extinguish the spark of life!") - afterattack(user,user) - return OXYLOSS - else if (safety && (reagents.total_volume >= 1)) - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... The safety's still on!") - return SHAME - else - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... [src] is empty!") - return SHAME - -/obj/item/extinguisher/attack_self(mob/user) - safety = !safety - src.icon_state = "[sprite_name][!safety]" - to_chat(user, "The safety is [safety ? "on" : "off"].") - return - -/obj/item/extinguisher/attack(mob/M, mob/user) - if(user.a_intent == INTENT_HELP && !safety) //If we're on help intent and going to spray people, don't bash them. - return FALSE - else - return ..() - -/obj/item/extinguisher/attack_obj(obj/O, mob/living/user) - if(AttemptRefill(O, user)) - refilling = TRUE - return FALSE - else - return ..() - -/obj/item/extinguisher/examine(mob/user) - . = ..() - . += "The safety is [safety ? "on" : "off"]." - - if(reagents.total_volume) - . += "Alt-click to empty it." - -/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user) - if(istype(target, tanktype) && target.Adjacent(user)) - var/safety_save = safety - safety = TRUE - if(reagents.total_volume == reagents.maximum_volume) - to_chat(user, "\The [src] is already full!") - safety = safety_save - return 1 - var/obj/structure/reagent_dispensers/W = target //will it work? - var/transferred = W.reagents.trans_to(src, max_water, transfered_by = user) - if(transferred > 0) - to_chat(user, "\The [src] has been refilled by [transferred] units.") - playsound(src.loc, 'sound/effects/refill.ogg', 50, 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, transfered_by = user) - - //Make em move dat ass, hun - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, water_particles), 2) - -//Particle movement loop -/obj/item/extinguisher/proc/move_particles(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) - particles -= W - if(repetition < power) - repetition++ - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, particles, repetition), 2) - -//Chair movement loop -/obj/item/extinguisher/proc/move_chair(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/AltClick(mob/user) - 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] using its release valve.") - -//firebot assembly -/obj/item/extinguisher/attackby(obj/O, mob/user, params) - if(istype(O, /obj/item/bodypart/l_arm/robot) || istype(O, /obj/item/bodypart/r_arm/robot)) - to_chat(user, "You add [O] to [src].") - qdel(O) - qdel(src) - user.put_in_hands(new /obj/item/bot_assembly/firebot) - else - ..() +/obj/item/extinguisher + name = "fire extinguisher" + desc = "A traditional red fire extinguisher." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "fire_extinguisher0" + 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/proc/refill() + create_reagents(max_water, AMOUNT_VISIBLE) + reagents.add_reagent(chem, max_water) + +/obj/item/extinguisher/Initialize() + . = ..() + refill() + +/obj/item/extinguisher/advanced + name = "advanced fire extinguisher" + desc = "Used to stop thermonuclear fires from spreading inside your engine." + icon_state = "foam_extinguisher0" + //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/suicide_act(mob/living/carbon/user) + if (!safety && (reagents.total_volume >= 1)) + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth. It looks like [user.p_theyre()] trying to extinguish the spark of life!") + afterattack(user,user) + return OXYLOSS + else if (safety && (reagents.total_volume >= 1)) + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... The safety's still on!") + return SHAME + else + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... [src] is empty!") + return SHAME + +/obj/item/extinguisher/attack_self(mob/user) + safety = !safety + src.icon_state = "[sprite_name][!safety]" + to_chat(user, "The safety is [safety ? "on" : "off"].") + return + +/obj/item/extinguisher/attack(mob/M, mob/user) + if(user.a_intent == INTENT_HELP && !safety) //If we're on help intent and going to spray people, don't bash them. + return FALSE + else + return ..() + +/obj/item/extinguisher/attack_obj(obj/O, mob/living/user) + if(AttemptRefill(O, user)) + refilling = TRUE + return FALSE + else + return ..() + +/obj/item/extinguisher/examine(mob/user) + . = ..() + . += "The safety is [safety ? "on" : "off"]." + + if(reagents.total_volume) + . += "Alt-click to empty it." + +/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user) + if(istype(target, tanktype) && target.Adjacent(user)) + var/safety_save = safety + safety = TRUE + if(reagents.total_volume == reagents.maximum_volume) + to_chat(user, "\The [src] is already full!") + safety = safety_save + return 1 + var/obj/structure/reagent_dispensers/W = target //will it work? + var/transferred = W.reagents.trans_to(src, max_water, transfered_by = user) + if(transferred > 0) + to_chat(user, "\The [src] has been refilled by [transferred] units.") + playsound(src.loc, 'sound/effects/refill.ogg', 50, 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, transfered_by = user) + + //Make em move dat ass, hun + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, water_particles), 2) + +//Particle movement loop +/obj/item/extinguisher/proc/move_particles(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) + particles -= W + if(repetition < power) + repetition++ + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, particles, repetition), 2) + +//Chair movement loop +/obj/item/extinguisher/proc/move_chair(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/AltClick(mob/user) + 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] using its release valve.") + +//firebot assembly +/obj/item/extinguisher/attackby(obj/O, mob/user, params) + if(istype(O, /obj/item/bodypart/l_arm/robot) || istype(O, /obj/item/bodypart/r_arm/robot)) + to_chat(user, "You add [O] to [src].") + qdel(O) + qdel(src) + user.put_in_hands(new /obj/item/bot_assembly/firebot) + else + ..() diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm index 767985de6066..241202361205 100644 --- a/code/game/objects/items/grenades/chem_grenade.dm +++ b/code/game/objects/items/grenades/chem_grenade.dm @@ -1,583 +1,583 @@ -/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 = GRENADE_EMPTY - var/list/obj/item/reagent_containers/glass/beakers = list() - var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) - var/list/banned_containers = list(/obj/item/reagent_containers/glass/beaker/bluespace) //Containers to exclude from specific grenade subtypes - var/affected_area = 3 - var/ignition_temp = 10 // The amount of heat added to the reagents when this grenade goes off. - var/threatscale = 1 // Used by advanced grenades to make them slightly more worthy. - var/no_splash = FALSE //If the grenade deletes even if it has no reagents to splash with. Used for slime core reactions. - var/casedesc = "This basic model accepts both beakers and bottles. It heats contents by 10°K upon ignition." // Appears when examining empty casings. - var/obj/item/assembly/prox_sensor/landminemode = null - -/obj/item/grenade/chem_grenade/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) - -/obj/item/grenade/chem_grenade/Initialize() - . = ..() - create_reagents(1000) - stage_change() // If no argument is set, it will change the stage to the current stage, useful for stock grenades that start READY. - wires = new /datum/wires/explosive/chem_grenade(src) - -/obj/item/grenade/chem_grenade/examine(mob/user) - display_timer = (stage == GRENADE_READY) //show/hide the timer based on assembly state - . = ..() - if(user.can_see_reagents()) - if(beakers.len) - . += "You scan the grenade and detect the following reagents:" - for(var/obj/item/reagent_containers/glass/G in beakers) - for(var/datum/reagent/R in G.reagents.reagent_list) - . += "[R.volume] units of [R.name] in the [G.name]." - if(beakers.len == 1) - . += "You detect no second beaker in the grenade." - else - . += "You scan the grenade, but detect nothing." - else if(stage != GRENADE_READY && beakers.len) - if(beakers.len == 2 && beakers[1].name == beakers[2].name) - . += "You see two [beakers[1].name]s inside the grenade." - else - for(var/obj/item/reagent_containers/glass/G in beakers) - . += "You see a [G.name] inside the grenade." - -/obj/item/grenade/chem_grenade/attack_self(mob/user) - if(stage == GRENADE_READY && !active) - ..() - if(stage == GRENADE_WIRED) - wires.interact(user) - -/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) - if(istype(I,/obj/item/assembly) && stage == GRENADE_WIRED) - wires.interact(user) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(stage == GRENADE_WIRED) - if(beakers.len) - stage_change(GRENADE_READY) - to_chat(user, "You lock the [initial(name)] assembly.") - I.play_tool_sound(src, 25) - else - to_chat(user, "You need to add at least one beaker before locking the [initial(name)] assembly!") - else if(stage == GRENADE_READY) - det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 - if(landminemode) - landminemode.time = det_time * 0.1 //overwrites the proxy sensor activation timer - - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - else - to_chat(user, "You need to add a wire!") - return - else if(stage == GRENADE_WIRED && is_type_in_list(I, allowed_containers)) - . = TRUE //no afterattack - if(is_type_in_list(I, banned_containers)) - to_chat(user, "[src] is too small to fit [I]!") // this one hits home huh anon? - return - if(beakers.len == 2) - to_chat(user, "[src] can not hold more containers!") - return - else - if(I.reagents.total_volume) - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You add [I] to the [initial(name)] assembly.") - beakers += I - var/reagent_list = pretty_string_from_reagent_list(I.reagents) - user.log_message("inserted [I] ([reagent_list]) into [src]",LOG_GAME) - else - to_chat(user, "[I] is empty!") - - else if(stage == GRENADE_EMPTY && istype(I, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = I - if (C.use(1)) - det_time = 50 // In case the cable_coil was removed and readded. - stage_change(GRENADE_WIRED) - to_chat(user, "You rig the [initial(name)] assembly.") - else - to_chat(user, "You need one length of coil to wire the assembly!") - return - - else if(stage == GRENADE_READY && I.tool_behaviour == TOOL_WIRECUTTER && !active) - stage_change(GRENADE_WIRED) - to_chat(user, "You unlock the [initial(name)] assembly.") - - else if(stage == GRENADE_WIRED && I.tool_behaviour == TOOL_WRENCH) - if(beakers.len) - for(var/obj/O in beakers) - O.forceMove(drop_location()) - if(!O.reagents) - continue - var/reagent_list = pretty_string_from_reagent_list(O.reagents) - user.log_message("removed [O] ([reagent_list]) from [src]", LOG_GAME) - beakers = list() - to_chat(user, "You open the [initial(name)] assembly and remove the payload.") - wires.detach_assembly(wires.get_wire(1)) - return - new /obj/item/stack/cable_coil(get_turf(src),1) - stage_change(GRENADE_EMPTY) - to_chat(user, "You remove the activation mechanism from the [initial(name)] assembly.") - else - return ..() - -/obj/item/grenade/chem_grenade/proc/stage_change(N) - if(N) - stage = N - if(stage == GRENADE_EMPTY) - name = "[initial(name)] casing" - desc = "A do it yourself [initial(name)]! [initial(casedesc)]" - icon_state = initial(icon_state) - else if(stage == GRENADE_WIRED) - name = "unsecured [initial(name)]" - desc = "An unsecured [initial(name)] assembly." - icon_state = "[initial(icon_state)]_ass" - else if(stage == GRENADE_READY) - name = initial(name) - desc = initial(desc) - icon_state = "[initial(icon_state)]_locked" - -/obj/item/grenade/chem_grenade/on_found(mob/finder) - var/obj/item/assembly/A = wires.get_attached(wires.get_wire(1)) - if(A) - A.on_found(finder) - -/obj/item/grenade/chem_grenade/log_grenade(mob/user, turf/T) - var/reagent_string = "" - var/beaker_number = 1 - for(var/obj/exploded_beaker in beakers) - if(!exploded_beaker.reagents) - continue - reagent_string += " ([exploded_beaker.name] [beaker_number++] : " + pretty_string_from_reagent_list(exploded_beaker.reagents.reagent_list) + ");" - if(landminemode) - log_bomber(user, "activated a proxy", src, "containing:[reagent_string]") - else - log_bomber(user, "primed a", src, "containing:[reagent_string]") - -/obj/item/grenade/chem_grenade/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) - var/turf/T = get_turf(src) - log_grenade(user, T) //Inbuilt admin procs already handle null users - if(user) - add_fingerprint(user) - if(msg) - if(landminemode) - to_chat(user, "You prime [src], activating its proximity sensor.") - else - to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!") - playsound(src, 'sound/weapons/armbomb.ogg', volume, 1) - icon_state = initial(icon_state) + "_active" - if(landminemode) - landminemode.activate() - return - active = TRUE - addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) - -/obj/item/grenade/chem_grenade/prime() - if(stage != GRENADE_READY) - return - - var/list/datum/reagents/reactants = list() - for(var/obj/item/reagent_containers/glass/G in beakers) - reactants += G.reagents - - var/turf/detonation_turf = get_turf(src) - - if(!chem_splash(detonation_turf, affected_area, reactants, ignition_temp, threatscale) && !no_splash) - playsound(src, 'sound/items/screwdriver2.ogg', 50, 1) - if(beakers.len) - for(var/obj/O in beakers) - O.forceMove(drop_location()) - beakers = list() - stage_change(GRENADE_EMPTY) - active = FALSE - return -// logs from custom assemblies priming are handled by the wire component - log_game("A grenade detonated at [AREACOORD(detonation_turf)]") - - update_mob() - - qdel(src) - -//Large chem grenades accept slime cores and use the appropriately. -/obj/item/grenade/chem_grenade/large - name = "large grenade" - desc = "A custom made large grenade. Larger splash range and increased ignition temperature compared to basic grenades. Fits exotic and bluespace based containers." - casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores and bluespace beakers. Heats contents by 25°K upon ignition." - icon_state = "large_grenade" - allowed_containers = list(/obj/item/reagent_containers/glass, /obj/item/reagent_containers/food/condiment, /obj/item/reagent_containers/food/drinks) - banned_containers = list() - affected_area = 5 - ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades. - threatscale = 1.1 // 10% more effective. - -/obj/item/grenade/chem_grenade/large/prime() - if(stage != GRENADE_READY) - return - - for(var/obj/item/slime_extract/S in beakers) - if(S.Uses) - for(var/obj/item/reagent_containers/glass/G in beakers) - G.reagents.trans_to(S, G.reagents.total_volume) - - //If there is still a core (sometimes it's used up) - //and there are reagents left, behave normally, - //otherwise drop it on the ground for timed reactions like gold. - - if(S) - if(S.reagents && S.reagents.total_volume) - for(var/obj/item/reagent_containers/glass/G in beakers) - S.reagents.trans_to(G, S.reagents.total_volume) - else - S.forceMove(get_turf(src)) - no_splash = TRUE - ..() - - //I tried to just put it in the allowed_containers list but - //if you do that it must have reagents. If you're going to - //make a special case you might as well do it explicitly. -Sayu -/obj/item/grenade/chem_grenade/large/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/slime_extract) && stage == GRENADE_WIRED) - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You add [I] to the [initial(name)] assembly.") - beakers += I - else - return ..() - -/obj/item/grenade/chem_grenade/cryo // Intended for rare cryogenic mixes. Cools the area moderately upon detonation. - name = "cryo grenade" - desc = "A custom made cryogenic grenade. Rapidly cools contents upon ignition." - casedesc = "Upon ignition, it rapidly cools contents by 100°K. Smaller splash range than regular casings." - icon_state = "cryog" - affected_area = 2 - ignition_temp = -100 - -/obj/item/grenade/chem_grenade/pyro // Intended for pyrotechnical mixes. Produces a small fire upon detonation, igniting potentially flammable mixtures. - name = "pyro grenade" - desc = "A custom made pyrotechnical grenade. Heats up contents upon ignition." - casedesc = "Upon ignition, it rapidly heats contents by 500°K." - icon_state = "pyrog" - ignition_temp = 500 // This is enough to expose a hotspot. - -/obj/item/grenade/chem_grenade/adv_release // Intended for weaker, but longer lasting effects. Could have some interesting uses. - name = "advanced release grenade" - desc = "A custom made advanced release grenade. It is able to be detonated more than once. Can be configured using a multitool." - casedesc = "This casing is able to detonate more than once. Can be configured using a multitool." - icon_state = "timeg" - var/unit_spread = 10 // Amount of units per repeat. Can be altered with a multitool. - -/obj/item/grenade/chem_grenade/adv_release/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_MULTITOOL && !active) - var/newspread = text2num(stripped_input(user, "Please enter a new spread amount", name)) - if (newspread != null && user.canUseTopic(src, BE_CLOSE)) - newspread = round(newspread) - unit_spread = CLAMP(newspread, 5, 100) - to_chat(user, "You set the time release to [unit_spread] units per detonation.") - if (newspread != unit_spread) - to_chat(user, "The new value is out of bounds. Minimum spread is 5 units, maximum is 100 units.") - ..() - -/obj/item/grenade/chem_grenade/adv_release/prime() - if(stage != GRENADE_READY) - return - - var/total_volume = 0 - for(var/obj/item/reagent_containers/RC in beakers) - total_volume += RC.reagents.total_volume - if(!total_volume) - qdel(src) - return - var/fraction = unit_spread/total_volume - var/datum/reagents/reactants = new(unit_spread) - reactants.my_atom = src - for(var/obj/item/reagent_containers/RC in beakers) - RC.reagents.trans_to(reactants, RC.reagents.total_volume*fraction, threatscale, 1, 1) - chem_splash(get_turf(src), affected_area, list(reactants), ignition_temp, threatscale) - - var/turf/DT = get_turf(src) - addtimer(CALLBACK(src, .proc/prime), det_time) - log_game("A grenade detonated at [AREACOORD(DT)]") - - - - -////////////////////////////// -////// PREMADE GRENADES ////// -////////////////////////////// - -/obj/item/grenade/chem_grenade/metalfoam - name = "metal foam grenade" - desc = "Used for emergency sealing of hull breaches." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/metalfoam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/aluminium, 30) - B2.reagents.add_reagent(/datum/reagent/foaming_agent, 10) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 10) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/smart_metal_foam - name = "smart metal foam grenade" - desc = "Used for emergency sealing of hull breaches, while keeping areas accessible." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/smart_metal_foam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/aluminium, 75) - B2.reagents.add_reagent(/datum/reagent/smart_foaming_agent, 25) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/incendiary - name = "incendiary grenade" - desc = "Used for clearing rooms of living things." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/incendiary/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/stable_plasma, 25) - B2.reagents.add_reagent(/datum/reagent/toxin/acid, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/antiweed - name = "weedkiller grenade" - desc = "Used for purging large areas of invasive plant species. Contents under pressure. Do not directly inhale contents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/antiweed/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/toxin/plantbgone, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/cleaner - name = "cleaner grenade" - desc = "BLAM!-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/cleaner/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) - B2.reagents.add_reagent(/datum/reagent/water, 40) - B2.reagents.add_reagent(/datum/reagent/space_cleaner, 10) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/ez_clean - name = "cleaner grenade" - desc = "Waffle Co.-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/ez_clean/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) - B2.reagents.add_reagent(/datum/reagent/water, 40) - B2.reagents.add_reagent(/datum/reagent/space_cleaner/ez_clean, 60) //ensures a t h i c c distribution - - beakers += B1 - beakers += B2 - - - -/obj/item/grenade/chem_grenade/teargas - name = "teargas grenade" - desc = "Used for nonlethal riot control. Contents under pressure. Do not directly inhale contents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/teargas/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/consumable/condensedcapsaicin, 60) - B1.reagents.add_reagent(/datum/reagent/potassium, 40) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 40) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 40) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/facid - name = "acid grenade" - desc = "Used for melting armoured opponents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/facid/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 290) - B1.reagents.add_reagent(/datum/reagent/potassium, 10) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 10) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 10) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 280) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/colorful - name = "colorful grenade" - desc = "Used for wide scale painting projects." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/colorful/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/colorful_reagent, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/glitter - name = "generic glitter grenade" - desc = "You shouldn't see this description." - stage = GRENADE_READY - var/glitter_type = /datum/reagent/glitter - -/obj/item/grenade/chem_grenade/glitter/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(glitter_type, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/glitter/pink - name = "pink glitter bomb" - desc = "For that HOT glittery look." - glitter_type = /datum/reagent/glitter/pink - -/obj/item/grenade/chem_grenade/glitter/blue - name = "blue glitter bomb" - desc = "For that COOL glittery look." - glitter_type = /datum/reagent/glitter/blue - -/obj/item/grenade/chem_grenade/glitter/white - name = "white glitter bomb" - desc = "For that somnolent glittery look." - glitter_type = /datum/reagent/glitter/white - -/obj/item/grenade/chem_grenade/clf3 - name = "clf3 grenade" - desc = "BURN!-brand foaming clf3. In a special applicator for rapid purging of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/clf3/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 250) - B1.reagents.add_reagent(/datum/reagent/clf3, 50) - B2.reagents.add_reagent(/datum/reagent/water, 250) - B2.reagents.add_reagent(/datum/reagent/clf3, 50) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/bioterrorfoam - name = "Bio terror foam grenade" - desc = "Tiger Cooperative chemical foam grenade. Causes temporary irration, blindness, confusion, mutism, and mutations to carbon based life forms. Contains additional spore toxin." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/bioterrorfoam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/cryptobiolin, 75) - B1.reagents.add_reagent(/datum/reagent/water, 50) - B1.reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 50) - B1.reagents.add_reagent(/datum/reagent/toxin/spore, 75) - B1.reagents.add_reagent(/datum/reagent/toxin/itching_powder, 50) - B2.reagents.add_reagent(/datum/reagent/fluorosurfactant, 150) - B2.reagents.add_reagent(/datum/reagent/toxin/mutagen, 150) - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/tuberculosis - name = "Fungal tuberculosis grenade" - desc = "WARNING: GRENADE WILL RELEASE DEADLY SPORES CONTAINING ACTIVE AGENTS. SEAL SUIT AND AIRFLOW BEFORE USE." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/tuberculosis/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/potassium, 50) - B1.reagents.add_reagent(/datum/reagent/phosphorus, 50) - B1.reagents.add_reagent(/datum/reagent/fungalspores, 200) - B2.reagents.add_reagent(/datum/reagent/blood, 250) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 50) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/holy - name = "holy hand grenade" - desc = "A vessel of concentrated religious might." - icon_state = "holy_grenade" - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/holy/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/potassium, 100) - B2.reagents.add_reagent(/datum/reagent/water/holywater, 100) - - beakers += B1 - beakers += B2 +/obj/item/grenade/chem_grenade + name = "chemical grenade" + desc = "A custom made grenade." + icon_state = "chemg" + item_state = "flashbang" + w_class = WEIGHT_CLASS_SMALL + force = 2 + var/stage = GRENADE_EMPTY + var/list/obj/item/reagent_containers/glass/beakers = list() + var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) + var/list/banned_containers = list(/obj/item/reagent_containers/glass/beaker/bluespace) //Containers to exclude from specific grenade subtypes + var/affected_area = 3 + var/ignition_temp = 10 // The amount of heat added to the reagents when this grenade goes off. + var/threatscale = 1 // Used by advanced grenades to make them slightly more worthy. + var/no_splash = FALSE //If the grenade deletes even if it has no reagents to splash with. Used for slime core reactions. + var/casedesc = "This basic model accepts both beakers and bottles. It heats contents by 10°K upon ignition." // Appears when examining empty casings. + var/obj/item/assembly/prox_sensor/landminemode = null + +/obj/item/grenade/chem_grenade/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) + +/obj/item/grenade/chem_grenade/Initialize() + . = ..() + create_reagents(1000) + stage_change() // If no argument is set, it will change the stage to the current stage, useful for stock grenades that start READY. + wires = new /datum/wires/explosive/chem_grenade(src) + +/obj/item/grenade/chem_grenade/examine(mob/user) + display_timer = (stage == GRENADE_READY) //show/hide the timer based on assembly state + . = ..() + if(user.can_see_reagents()) + if(beakers.len) + . += "You scan the grenade and detect the following reagents:" + for(var/obj/item/reagent_containers/glass/G in beakers) + for(var/datum/reagent/R in G.reagents.reagent_list) + . += "[R.volume] units of [R.name] in the [G.name]." + if(beakers.len == 1) + . += "You detect no second beaker in the grenade." + else + . += "You scan the grenade, but detect nothing." + else if(stage != GRENADE_READY && beakers.len) + if(beakers.len == 2 && beakers[1].name == beakers[2].name) + . += "You see two [beakers[1].name]s inside the grenade." + else + for(var/obj/item/reagent_containers/glass/G in beakers) + . += "You see a [G.name] inside the grenade." + +/obj/item/grenade/chem_grenade/attack_self(mob/user) + if(stage == GRENADE_READY && !active) + ..() + if(stage == GRENADE_WIRED) + wires.interact(user) + +/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) + if(istype(I,/obj/item/assembly) && stage == GRENADE_WIRED) + wires.interact(user) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(stage == GRENADE_WIRED) + if(beakers.len) + stage_change(GRENADE_READY) + to_chat(user, "You lock the [initial(name)] assembly.") + I.play_tool_sound(src, 25) + else + to_chat(user, "You need to add at least one beaker before locking the [initial(name)] assembly!") + else if(stage == GRENADE_READY) + det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 + if(landminemode) + landminemode.time = det_time * 0.1 //overwrites the proxy sensor activation timer + + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + else + to_chat(user, "You need to add a wire!") + return + else if(stage == GRENADE_WIRED && is_type_in_list(I, allowed_containers)) + . = TRUE //no afterattack + if(is_type_in_list(I, banned_containers)) + to_chat(user, "[src] is too small to fit [I]!") // this one hits home huh anon? + return + if(beakers.len == 2) + to_chat(user, "[src] can not hold more containers!") + return + else + if(I.reagents.total_volume) + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You add [I] to the [initial(name)] assembly.") + beakers += I + var/reagent_list = pretty_string_from_reagent_list(I.reagents) + user.log_message("inserted [I] ([reagent_list]) into [src]",LOG_GAME) + else + to_chat(user, "[I] is empty!") + + else if(stage == GRENADE_EMPTY && istype(I, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = I + if (C.use(1)) + det_time = 50 // In case the cable_coil was removed and readded. + stage_change(GRENADE_WIRED) + to_chat(user, "You rig the [initial(name)] assembly.") + else + to_chat(user, "You need one length of coil to wire the assembly!") + return + + else if(stage == GRENADE_READY && I.tool_behaviour == TOOL_WIRECUTTER && !active) + stage_change(GRENADE_WIRED) + to_chat(user, "You unlock the [initial(name)] assembly.") + + else if(stage == GRENADE_WIRED && I.tool_behaviour == TOOL_WRENCH) + if(beakers.len) + for(var/obj/O in beakers) + O.forceMove(drop_location()) + if(!O.reagents) + continue + var/reagent_list = pretty_string_from_reagent_list(O.reagents) + user.log_message("removed [O] ([reagent_list]) from [src]", LOG_GAME) + beakers = list() + to_chat(user, "You open the [initial(name)] assembly and remove the payload.") + wires.detach_assembly(wires.get_wire(1)) + return + new /obj/item/stack/cable_coil(get_turf(src),1) + stage_change(GRENADE_EMPTY) + to_chat(user, "You remove the activation mechanism from the [initial(name)] assembly.") + else + return ..() + +/obj/item/grenade/chem_grenade/proc/stage_change(N) + if(N) + stage = N + if(stage == GRENADE_EMPTY) + name = "[initial(name)] casing" + desc = "A do it yourself [initial(name)]! [initial(casedesc)]" + icon_state = initial(icon_state) + else if(stage == GRENADE_WIRED) + name = "unsecured [initial(name)]" + desc = "An unsecured [initial(name)] assembly." + icon_state = "[initial(icon_state)]_ass" + else if(stage == GRENADE_READY) + name = initial(name) + desc = initial(desc) + icon_state = "[initial(icon_state)]_locked" + +/obj/item/grenade/chem_grenade/on_found(mob/finder) + var/obj/item/assembly/A = wires.get_attached(wires.get_wire(1)) + if(A) + A.on_found(finder) + +/obj/item/grenade/chem_grenade/log_grenade(mob/user, turf/T) + var/reagent_string = "" + var/beaker_number = 1 + for(var/obj/exploded_beaker in beakers) + if(!exploded_beaker.reagents) + continue + reagent_string += " ([exploded_beaker.name] [beaker_number++] : " + pretty_string_from_reagent_list(exploded_beaker.reagents.reagent_list) + ");" + if(landminemode) + log_bomber(user, "activated a proxy", src, "containing:[reagent_string]") + else + log_bomber(user, "primed a", src, "containing:[reagent_string]") + +/obj/item/grenade/chem_grenade/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) + var/turf/T = get_turf(src) + log_grenade(user, T) //Inbuilt admin procs already handle null users + if(user) + add_fingerprint(user) + if(msg) + if(landminemode) + to_chat(user, "You prime [src], activating its proximity sensor.") + else + to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!") + playsound(src, 'sound/weapons/armbomb.ogg', volume, 1) + icon_state = initial(icon_state) + "_active" + if(landminemode) + landminemode.activate() + return + active = TRUE + addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) + +/obj/item/grenade/chem_grenade/prime() + if(stage != GRENADE_READY) + return + + var/list/datum/reagents/reactants = list() + for(var/obj/item/reagent_containers/glass/G in beakers) + reactants += G.reagents + + var/turf/detonation_turf = get_turf(src) + + if(!chem_splash(detonation_turf, affected_area, reactants, ignition_temp, threatscale) && !no_splash) + playsound(src, 'sound/items/screwdriver2.ogg', 50, 1) + if(beakers.len) + for(var/obj/O in beakers) + O.forceMove(drop_location()) + beakers = list() + stage_change(GRENADE_EMPTY) + active = FALSE + return +// logs from custom assemblies priming are handled by the wire component + log_game("A grenade detonated at [AREACOORD(detonation_turf)]") + + update_mob() + + qdel(src) + +//Large chem grenades accept slime cores and use the appropriately. +/obj/item/grenade/chem_grenade/large + name = "large grenade" + desc = "A custom made large grenade. Larger splash range and increased ignition temperature compared to basic grenades. Fits exotic and bluespace based containers." + casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores and bluespace beakers. Heats contents by 25°K upon ignition." + icon_state = "large_grenade" + allowed_containers = list(/obj/item/reagent_containers/glass, /obj/item/reagent_containers/food/condiment, /obj/item/reagent_containers/food/drinks) + banned_containers = list() + affected_area = 5 + ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades. + threatscale = 1.1 // 10% more effective. + +/obj/item/grenade/chem_grenade/large/prime() + if(stage != GRENADE_READY) + return + + for(var/obj/item/slime_extract/S in beakers) + if(S.Uses) + for(var/obj/item/reagent_containers/glass/G in beakers) + G.reagents.trans_to(S, G.reagents.total_volume) + + //If there is still a core (sometimes it's used up) + //and there are reagents left, behave normally, + //otherwise drop it on the ground for timed reactions like gold. + + if(S) + if(S.reagents && S.reagents.total_volume) + for(var/obj/item/reagent_containers/glass/G in beakers) + S.reagents.trans_to(G, S.reagents.total_volume) + else + S.forceMove(get_turf(src)) + no_splash = TRUE + ..() + + //I tried to just put it in the allowed_containers list but + //if you do that it must have reagents. If you're going to + //make a special case you might as well do it explicitly. -Sayu +/obj/item/grenade/chem_grenade/large/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/slime_extract) && stage == GRENADE_WIRED) + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You add [I] to the [initial(name)] assembly.") + beakers += I + else + return ..() + +/obj/item/grenade/chem_grenade/cryo // Intended for rare cryogenic mixes. Cools the area moderately upon detonation. + name = "cryo grenade" + desc = "A custom made cryogenic grenade. Rapidly cools contents upon ignition." + casedesc = "Upon ignition, it rapidly cools contents by 100°K. Smaller splash range than regular casings." + icon_state = "cryog" + affected_area = 2 + ignition_temp = -100 + +/obj/item/grenade/chem_grenade/pyro // Intended for pyrotechnical mixes. Produces a small fire upon detonation, igniting potentially flammable mixtures. + name = "pyro grenade" + desc = "A custom made pyrotechnical grenade. Heats up contents upon ignition." + casedesc = "Upon ignition, it rapidly heats contents by 500°K." + icon_state = "pyrog" + ignition_temp = 500 // This is enough to expose a hotspot. + +/obj/item/grenade/chem_grenade/adv_release // Intended for weaker, but longer lasting effects. Could have some interesting uses. + name = "advanced release grenade" + desc = "A custom made advanced release grenade. It is able to be detonated more than once. Can be configured using a multitool." + casedesc = "This casing is able to detonate more than once. Can be configured using a multitool." + icon_state = "timeg" + var/unit_spread = 10 // Amount of units per repeat. Can be altered with a multitool. + +/obj/item/grenade/chem_grenade/adv_release/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_MULTITOOL && !active) + var/newspread = text2num(stripped_input(user, "Please enter a new spread amount", name)) + if (newspread != null && user.canUseTopic(src, BE_CLOSE)) + newspread = round(newspread) + unit_spread = CLAMP(newspread, 5, 100) + to_chat(user, "You set the time release to [unit_spread] units per detonation.") + if (newspread != unit_spread) + to_chat(user, "The new value is out of bounds. Minimum spread is 5 units, maximum is 100 units.") + ..() + +/obj/item/grenade/chem_grenade/adv_release/prime() + if(stage != GRENADE_READY) + return + + var/total_volume = 0 + for(var/obj/item/reagent_containers/RC in beakers) + total_volume += RC.reagents.total_volume + if(!total_volume) + qdel(src) + return + var/fraction = unit_spread/total_volume + var/datum/reagents/reactants = new(unit_spread) + reactants.my_atom = src + for(var/obj/item/reagent_containers/RC in beakers) + RC.reagents.trans_to(reactants, RC.reagents.total_volume*fraction, threatscale, 1, 1) + chem_splash(get_turf(src), affected_area, list(reactants), ignition_temp, threatscale) + + var/turf/DT = get_turf(src) + addtimer(CALLBACK(src, .proc/prime), det_time) + log_game("A grenade detonated at [AREACOORD(DT)]") + + + + +////////////////////////////// +////// PREMADE GRENADES ////// +////////////////////////////// + +/obj/item/grenade/chem_grenade/metalfoam + name = "metal foam grenade" + desc = "Used for emergency sealing of hull breaches." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/metalfoam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/aluminium, 30) + B2.reagents.add_reagent(/datum/reagent/foaming_agent, 10) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 10) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/smart_metal_foam + name = "smart metal foam grenade" + desc = "Used for emergency sealing of hull breaches, while keeping areas accessible." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/smart_metal_foam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/aluminium, 75) + B2.reagents.add_reagent(/datum/reagent/smart_foaming_agent, 25) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/incendiary + name = "incendiary grenade" + desc = "Used for clearing rooms of living things." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/incendiary/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/stable_plasma, 25) + B2.reagents.add_reagent(/datum/reagent/toxin/acid, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/antiweed + name = "weedkiller grenade" + desc = "Used for purging large areas of invasive plant species. Contents under pressure. Do not directly inhale contents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/antiweed/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/toxin/plantbgone, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/cleaner + name = "cleaner grenade" + desc = "BLAM!-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/cleaner/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) + B2.reagents.add_reagent(/datum/reagent/water, 40) + B2.reagents.add_reagent(/datum/reagent/space_cleaner, 10) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/ez_clean + name = "cleaner grenade" + desc = "Waffle Co.-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/ez_clean/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) + B2.reagents.add_reagent(/datum/reagent/water, 40) + B2.reagents.add_reagent(/datum/reagent/space_cleaner/ez_clean, 60) //ensures a t h i c c distribution + + beakers += B1 + beakers += B2 + + + +/obj/item/grenade/chem_grenade/teargas + name = "teargas grenade" + desc = "Used for nonlethal riot control. Contents under pressure. Do not directly inhale contents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/teargas/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/consumable/condensedcapsaicin, 60) + B1.reagents.add_reagent(/datum/reagent/potassium, 40) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 40) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 40) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/facid + name = "acid grenade" + desc = "Used for melting armoured opponents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/facid/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 290) + B1.reagents.add_reagent(/datum/reagent/potassium, 10) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 10) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 10) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 280) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/colorful + name = "colorful grenade" + desc = "Used for wide scale painting projects." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/colorful/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/colorful_reagent, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/glitter + name = "generic glitter grenade" + desc = "You shouldn't see this description." + stage = GRENADE_READY + var/glitter_type = /datum/reagent/glitter + +/obj/item/grenade/chem_grenade/glitter/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(glitter_type, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/glitter/pink + name = "pink glitter bomb" + desc = "For that HOT glittery look." + glitter_type = /datum/reagent/glitter/pink + +/obj/item/grenade/chem_grenade/glitter/blue + name = "blue glitter bomb" + desc = "For that COOL glittery look." + glitter_type = /datum/reagent/glitter/blue + +/obj/item/grenade/chem_grenade/glitter/white + name = "white glitter bomb" + desc = "For that somnolent glittery look." + glitter_type = /datum/reagent/glitter/white + +/obj/item/grenade/chem_grenade/clf3 + name = "clf3 grenade" + desc = "BURN!-brand foaming clf3. In a special applicator for rapid purging of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/clf3/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 250) + B1.reagents.add_reagent(/datum/reagent/clf3, 50) + B2.reagents.add_reagent(/datum/reagent/water, 250) + B2.reagents.add_reagent(/datum/reagent/clf3, 50) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/bioterrorfoam + name = "Bio terror foam grenade" + desc = "Tiger Cooperative chemical foam grenade. Causes temporary irration, blindness, confusion, mutism, and mutations to carbon based life forms. Contains additional spore toxin." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/bioterrorfoam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/cryptobiolin, 75) + B1.reagents.add_reagent(/datum/reagent/water, 50) + B1.reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 50) + B1.reagents.add_reagent(/datum/reagent/toxin/spore, 75) + B1.reagents.add_reagent(/datum/reagent/toxin/itching_powder, 50) + B2.reagents.add_reagent(/datum/reagent/fluorosurfactant, 150) + B2.reagents.add_reagent(/datum/reagent/toxin/mutagen, 150) + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/tuberculosis + name = "Fungal tuberculosis grenade" + desc = "WARNING: GRENADE WILL RELEASE DEADLY SPORES CONTAINING ACTIVE AGENTS. SEAL SUIT AND AIRFLOW BEFORE USE." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/tuberculosis/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/potassium, 50) + B1.reagents.add_reagent(/datum/reagent/phosphorus, 50) + B1.reagents.add_reagent(/datum/reagent/fungalspores, 200) + B2.reagents.add_reagent(/datum/reagent/blood, 250) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 50) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/holy + name = "holy hand grenade" + desc = "A vessel of concentrated religious might." + icon_state = "holy_grenade" + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/holy/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/potassium, 100) + B2.reagents.add_reagent(/datum/reagent/water/holywater, 100) + + beakers += B1 + beakers += B2 diff --git a/code/game/objects/items/grenades/emgrenade.dm b/code/game/objects/items/grenades/emgrenade.dm index 936258344dfa..99cf4bdf99d1 100644 --- a/code/game/objects/items/grenades/emgrenade.dm +++ b/code/game/objects/items/grenades/emgrenade.dm @@ -1,10 +1,10 @@ -/obj/item/grenade/empgrenade - name = "classic EMP grenade" - desc = "It is designed to wreak havoc on electronic systems." - icon_state = "emp" - item_state = "emp" - -/obj/item/grenade/empgrenade/prime() - update_mob() - empulse(src, 4, 10) - qdel(src) +/obj/item/grenade/empgrenade + name = "classic EMP grenade" + desc = "It is designed to wreak havoc on electronic systems." + icon_state = "emp" + item_state = "emp" + +/obj/item/grenade/empgrenade/prime() + update_mob() + empulse(src, 4, 10) + qdel(src) diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm index 32054490180f..73224e1d5eb2 100644 --- a/code/game/objects/items/grenades/flashbang.dm +++ b/code/game/objects/items/grenades/flashbang.dm @@ -1,36 +1,36 @@ -/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) - for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) - bang(get_turf(M), M) - qdel(src) - -/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) - if(M.stat == DEAD) //They're dead! - return - M.show_message("BANG", 2) - var/distance = max(0,get_dist(get_turf(src),T)) - -//Flash - if(M.flash_act(affect_silicon = 1)) - M.Paralyze(max(200/max(1,distance), 60)) -//Bang - if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. - M.Paralyze(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 + 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) + for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) + bang(get_turf(M), M) + qdel(src) + +/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) + if(M.stat == DEAD) //They're dead! + return + M.show_message("BANG", 2) + var/distance = max(0,get_dist(get_turf(src),T)) + +//Flash + if(M.flash_act(affect_silicon = 1)) + M.Paralyze(max(200/max(1,distance), 60)) +//Bang + if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. + M.Paralyze(200) + M.soundbang_act(1, 200, 10, 15) + + else + M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) diff --git a/code/game/objects/items/grenades/ghettobomb.dm b/code/game/objects/items/grenades/ghettobomb.dm index 2d3d5e69b68c..1725d84dccf0 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 ace869e39293..3735fcae2526 100644 --- a/code/game/objects/items/grenades/grenade.dm +++ b/code/game/objects/items/grenades/grenade.dm @@ -1,134 +1,134 @@ -/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 > 0) - . += "The timer is set to [DisplayTimeText(det_time)]." - else - . += "\The [src] is set for instant detonation." - - -/obj/item/grenade/attack_self(mob/user) - if(!active) - if(clown_check(user)) - preprime(user) - -/obj/item/grenade/proc/log_grenade(mob/user, turf/T) - log_bomber(user, "has primed a", src, "for detonation") - -/obj/item/grenade/proc/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) - var/turf/T = get_turf(src) - log_grenade(user, T) //Inbuilt admin procs already handle null users - if(user) - add_fingerprint(user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.throw_mode_on() - if(msg) - to_chat(user, "You prime [src]! [capitalize(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() - -/obj/item/grenade/proc/update_mob() - if(ismob(loc)) - var/mob/M = loc - M.dropItemToGround(src) - -/obj/item/grenade/attackby(obj/item/W, mob/user, params) - if(!active) - if(W.tool_behaviour == TOOL_MULTITOOL) - var/newtime = text2num(stripped_input(user, "Please enter a new detonation time", name)) - if (newtime != null && user.canUseTopic(src, BE_CLOSE)) - change_det_time(newtime) - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - if (round(newtime * 10) != det_time) - to_chat(user, "The new value is out of bounds. The lowest possible time is 3 seconds and highest is 5 seconds. Instant detonations are also possible.") - return - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - change_det_time() - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - else - return ..() - -/obj/item/grenade/proc/change_det_time(time) //Time uses real time. - if(time != null) - if(time < 3) - time = 3 - det_time = round(CLAMP(time * 10, 0, 50)) - else - var/previous_time = det_time - switch(det_time) - if (0) - det_time = 30 - if (30) - det_time = 50 - if (50) - det_time = 0 - if(det_time == previous_time) - det_time = 50 - -/obj/item/grenade/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - var/obj/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/afterattack(atom/target, mob/user) - . = ..() - if(active) - user.throw_item(target) +/obj/item/grenade + name = "grenade" + desc = "It has an adjustable timer." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/grenade.dmi' + icon_state = "grenade" + 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 > 0) + . += "The timer is set to [DisplayTimeText(det_time)]." + else + . += "\The [src] is set for instant detonation." + + +/obj/item/grenade/attack_self(mob/user) + if(!active) + if(clown_check(user)) + preprime(user) + +/obj/item/grenade/proc/log_grenade(mob/user, turf/T) + log_bomber(user, "has primed a", src, "for detonation") + +/obj/item/grenade/proc/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) + var/turf/T = get_turf(src) + log_grenade(user, T) //Inbuilt admin procs already handle null users + if(user) + add_fingerprint(user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.throw_mode_on() + if(msg) + to_chat(user, "You prime [src]! [capitalize(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() + +/obj/item/grenade/proc/update_mob() + if(ismob(loc)) + var/mob/M = loc + M.dropItemToGround(src) + +/obj/item/grenade/attackby(obj/item/W, mob/user, params) + if(!active) + if(W.tool_behaviour == TOOL_MULTITOOL) + var/newtime = text2num(stripped_input(user, "Please enter a new detonation time", name)) + if (newtime != null && user.canUseTopic(src, BE_CLOSE)) + change_det_time(newtime) + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + if (round(newtime * 10) != det_time) + to_chat(user, "The new value is out of bounds. The lowest possible time is 3 seconds and highest is 5 seconds. Instant detonations are also possible.") + return + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + change_det_time() + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + else + return ..() + +/obj/item/grenade/proc/change_det_time(time) //Time uses real time. + if(time != null) + if(time < 3) + time = 3 + det_time = round(CLAMP(time * 10, 0, 50)) + else + var/previous_time = det_time + switch(det_time) + if (0) + det_time = 30 + if (30) + det_time = 50 + if (50) + det_time = 0 + if(det_time == previous_time) + det_time = 50 + +/obj/item/grenade/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + var/obj/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/afterattack(atom/target, mob/user) + . = ..() + if(active) + user.throw_item(target) diff --git a/code/game/objects/items/grenades/smokebomb.dm b/code/game/objects/items/grenades/smokebomb.dm index ca3f288e9607..c5a969c8a728 100644 --- a/code/game/objects/items/grenades/smokebomb.dm +++ b/code/game/objects/items/grenades/smokebomb.dm @@ -1,30 +1,30 @@ -/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" - item_state = "flashbang" - slot_flags = ITEM_SLOT_BELT - var/datum/effect_system/smoke_spread/bad/smoke - -/obj/item/grenade/smokebomb/Initialize() - . = ..() - smoke = new - smoke.attach(src) - -/obj/item/grenade/smokebomb/Destroy() - qdel(smoke) - return ..() - -/obj/item/grenade/smokebomb/prime() - update_mob() - playsound(src, '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" + item_state = "flashbang" + slot_flags = ITEM_SLOT_BELT + var/datum/effect_system/smoke_spread/bad/smoke + +/obj/item/grenade/smokebomb/Initialize() + . = ..() + smoke = new + smoke.attach(src) + +/obj/item/grenade/smokebomb/Destroy() + qdel(smoke) + return ..() + +/obj/item/grenade/smokebomb/prime() + update_mob() + playsound(src, '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/spawnergrenade.dm b/code/game/objects/items/grenades/spawnergrenade.dm index 9ab4f91af404..fb83da5ed921 100644 --- a/code/game/objects/items/grenades/spawnergrenade.dm +++ b/code/game/objects/items/grenades/spawnergrenade.dm @@ -1,63 +1,63 @@ -/obj/item/grenade/spawnergrenade - desc = "It will unleash an unspecified anomaly into the vicinity." - name = "delivery grenade" - icon = 'icons/obj/grenade.dmi' - icon_state = "delivery" - item_state = "flashbang" - var/spawner_type = null // must be an object path - var/deliveryamt = 1 // amount of type to deliver - -/obj/item/grenade/spawnergrenade/prime() // Prime now just handles the two loops that query for people in lockers and people who can see it. - update_mob() - if(spawner_type && deliveryamt) - // Make a quick flash - var/turf/T = get_turf(src) - playsound(T, 'sound/effects/phasein.ogg', 100, 1) - for(var/mob/living/carbon/C in viewers(T, null)) - C.flash_act() - - // Spawn some hostile syndicate critters and spread them out - var/list/spawned = spawn_and_random_walk(spawner_type, T, deliveryamt, walk_chance=50, admin_spawn=((flags_1 & ADMIN_SPAWNED_1) ? TRUE : FALSE)) - afterspawn(spawned) - - qdel(src) - -/obj/item/grenade/spawnergrenade/proc/afterspawn(list/mob/spawned) - return - -/obj/item/grenade/spawnergrenade/manhacks - name = "viscerator delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/viscerator - deliveryamt = 10 - -/obj/item/grenade/spawnergrenade/spesscarp - name = "carp delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/carp - deliveryamt = 5 - -/obj/item/grenade/spawnergrenade/syndiesoap - name = "Mister Scrubby" - spawner_type = /obj/item/soap/syndie - -/obj/item/grenade/spawnergrenade/buzzkill - name = "Buzzkill grenade" - desc = "The label reads: \"WARNING: DEVICE WILL RELEASE LIVE SPECIMENS UPON ACTIVATION. SEAL SUIT BEFORE USE.\" It is warm to the touch and vibrates faintly." - icon_state = "holy_grenade" - spawner_type = /mob/living/simple_animal/hostile/poison/bees/toxin - deliveryamt = 10 - -/obj/item/grenade/spawnergrenade/clown - name = "C.L.U.W.N.E." - desc = "A sleek device often given to clowns on their 10th birthdays for protection. You can hear faint scratching coming from within." - icon_state = "clown_ball" - item_state = "clown_ball" - spawner_type = list(/mob/living/simple_animal/hostile/retaliate/clown/fleshclown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, /mob/living/simple_animal/hostile/retaliate/clown/longface, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, /mob/living/simple_animal/hostile/retaliate/clown/mutant/blob, /mob/living/simple_animal/hostile/retaliate/clown/banana, /mob/living/simple_animal/hostile/retaliate/clown/honkling, /mob/living/simple_animal/hostile/retaliate/clown/lube) - deliveryamt = 1 - -/obj/item/grenade/spawnergrenade/clown_broken - name = "stuffed C.L.U.W.N.E." - desc = "A sleek device often given to clowns on their 10th birthdays for protection. While a typical C.L.U.W.N.E only holds one creature, sometimes foolish young clowns try to cram more in, often to disasterous effect." - icon_state = "clown_broken" - item_state = "clown_broken" - spawner_type = /mob/living/simple_animal/hostile/retaliate/clown/mutant - deliveryamt = 5 +/obj/item/grenade/spawnergrenade + desc = "It will unleash an unspecified anomaly into the vicinity." + name = "delivery grenade" + icon = 'icons/obj/grenade.dmi' + icon_state = "delivery" + item_state = "flashbang" + var/spawner_type = null // must be an object path + var/deliveryamt = 1 // amount of type to deliver + +/obj/item/grenade/spawnergrenade/prime() // Prime now just handles the two loops that query for people in lockers and people who can see it. + update_mob() + if(spawner_type && deliveryamt) + // Make a quick flash + var/turf/T = get_turf(src) + playsound(T, 'sound/effects/phasein.ogg', 100, 1) + for(var/mob/living/carbon/C in viewers(T, null)) + C.flash_act() + + // Spawn some hostile syndicate critters and spread them out + var/list/spawned = spawn_and_random_walk(spawner_type, T, deliveryamt, walk_chance=50, admin_spawn=((flags_1 & ADMIN_SPAWNED_1) ? TRUE : FALSE)) + afterspawn(spawned) + + qdel(src) + +/obj/item/grenade/spawnergrenade/proc/afterspawn(list/mob/spawned) + return + +/obj/item/grenade/spawnergrenade/manhacks + name = "viscerator delivery grenade" + spawner_type = /mob/living/simple_animal/hostile/viscerator + deliveryamt = 10 + +/obj/item/grenade/spawnergrenade/spesscarp + name = "carp delivery grenade" + spawner_type = /mob/living/simple_animal/hostile/carp + deliveryamt = 5 + +/obj/item/grenade/spawnergrenade/syndiesoap + name = "Mister Scrubby" + spawner_type = /obj/item/soap/syndie + +/obj/item/grenade/spawnergrenade/buzzkill + name = "Buzzkill grenade" + desc = "The label reads: \"WARNING: DEVICE WILL RELEASE LIVE SPECIMENS UPON ACTIVATION. SEAL SUIT BEFORE USE.\" It is warm to the touch and vibrates faintly." + icon_state = "holy_grenade" + spawner_type = /mob/living/simple_animal/hostile/poison/bees/toxin + deliveryamt = 10 + +/obj/item/grenade/spawnergrenade/clown + name = "C.L.U.W.N.E." + desc = "A sleek device often given to clowns on their 10th birthdays for protection. You can hear faint scratching coming from within." + icon_state = "clown_ball" + item_state = "clown_ball" + spawner_type = list(/mob/living/simple_animal/hostile/retaliate/clown/fleshclown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, /mob/living/simple_animal/hostile/retaliate/clown/longface, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, /mob/living/simple_animal/hostile/retaliate/clown/mutant/blob, /mob/living/simple_animal/hostile/retaliate/clown/banana, /mob/living/simple_animal/hostile/retaliate/clown/honkling, /mob/living/simple_animal/hostile/retaliate/clown/lube) + deliveryamt = 1 + +/obj/item/grenade/spawnergrenade/clown_broken + name = "stuffed C.L.U.W.N.E." + desc = "A sleek device often given to clowns on their 10th birthdays for protection. While a typical C.L.U.W.N.E only holds one creature, sometimes foolish young clowns try to cram more in, often to disasterous effect." + icon_state = "clown_broken" + item_state = "clown_broken" + spawner_type = /mob/living/simple_animal/hostile/retaliate/clown/mutant + deliveryamt = 5 diff --git a/code/game/objects/items/grenades/syndieminibomb.dm b/code/game/objects/items/grenades/syndieminibomb.dm index d457224e958d..35e9abd9828e 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 7bbf250402b5..d6f2cf1fc2fd 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -1,400 +1,400 @@ -/obj/item/restraints - breakouttime = 600 - -/obj/item/restraints/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return(OXYLOSS) - -/obj/item/restraints/Destroy() - if(iscarbon(loc)) - var/mob/living/carbon/M = loc - if(M.handcuffed == src) - M.handcuffed = null - M.update_handcuffed() - if(M.buckled && M.buckled.buckle_requires_restraints) - M.buckled.unbuckle_mob(M) - if(M.legcuffed == src) - M.legcuffed = null - M.update_inv_legcuffed() - return ..() - -//Handcuffs - -/obj/item/restraints/handcuffs - name = "handcuffs" - desc = "Use this to keep prisoners in line." - gender = PLURAL - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "handcuff" - 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) - return - -/obj/item/restraints/handcuffs/cable/sinew - name = "sinew restraints" - desc = "A pair of restraints fashioned from long strands of flesh." - icon = 'icons/obj/mining.dmi' - icon_state = "sinewcuff" - item_state = "sinewcuff" - materials = null - item_color = "white" - color = "#000000" - -/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/alien - icon_state = "handcuffAlien" - -/obj/item/restraints/handcuffs/fake - name = "fake handcuffs" - desc = "Fake handcuffs meant for gag purposes." - breakouttime = 10 //Deciseconds = 1s - -/obj/item/restraints/handcuffs/cable/attackby(obj/item/I, mob/user, params) - ..() - if(istype(I, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = I - if (R.use(1)) - var/obj/item/wirerod/W = new /obj/item/wirerod - remove_item_from_storage(user) - user.put_in_hands(W) - to_chat(user, "You wrap [src] around the top of [I].") - qdel(src) - else - to_chat(user, "You need one rod to make a wired rod!") - return - else if(istype(I, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = I - if(M.get_amount() < 6) - to_chat(user, "You need at least six metal sheets to make good enough weights!") - return - to_chat(user, "You begin to apply [I] to [src]...") - if(do_after(user, 35, target = src)) - if(M.get_amount() < 6 || !M) - return - var/obj/item/restraints/legcuffs/bola/S = new /obj/item/restraints/legcuffs/bola - M.use(6) - user.put_in_hands(S) - to_chat(user, "You make some weights out of [I] and tie them to [src].") - remove_item_from_storage(user) - qdel(src) - else - return ..() - -/obj/item/restraints/handcuffs/cable/zipties - name = "zipties" - desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." - icon_state = "cuff" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - 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" - item_state = "cuff" - -/obj/item/restraints/handcuffs/cable/zipties/used/attack() - return - -//Legcuffs - -/obj/item/restraints/legcuffs - name = "leg cuffs" - desc = "Use this to keep prisoners in line." - gender = PLURAL - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "handcuff" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - flags_1 = CONDUCT_1 - throwforce = 0 - w_class = WEIGHT_CLASS_NORMAL - slowdown = 7 - breakouttime = 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() - . = ..() - update_icon() - -/obj/item/restraints/legcuffs/beartrap/update_icon() - 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 - update_icon() - to_chat(user, "[src] is now [armed ? "armed" : "disarmed"]") - -/obj/item/restraints/legcuffs/beartrap/proc/close_trap() - armed = FALSE - update_icon() - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - -/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj) - if(armed && isturf(loc)) - if(isliving(AM)) - var/mob/living/L = AM - var/snap = TRUE - if(istype(L.buckled, /obj/vehicle)) - var/obj/vehicle/ridden_vehicle = L.buckled - if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times. - close_trap() - ridden_vehicle.visible_message("[ridden_vehicle] triggers \the [src].") - return ..() - - if(L.movement_type & (FLYING|FLOATING)) //don't close the trap if they're flying/floating over it. - snap = FALSE - - var/def_zone = BODY_ZONE_CHEST - if(snap && iscarbon(L)) - var/mob/living/carbon/C = L - if(C.mobility_flags & MOBILITY_STAND) - def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs. - C.legcuffed = src - forceMove(C) - C.update_inv_legcuffed() - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - else if(snap && isanimal(L)) - var/mob/living/simple_animal/SA = L - if(SA.mob_size <= MOB_SIZE_TINY) //don't close the trap if they're as small as a mouse. - snap = FALSE - if(snap) - close_trap() - L.visible_message("[L] triggers \the [src].", \ - "You trigger \the [src]!") - L.apply_damage(trap_damage, BRUTE, def_zone) - ..() - -/obj/item/restraints/legcuffs/beartrap/energy - name = "energy snare" - armed = 1 - icon_state = "e_snare" - trap_damage = 0 - breakouttime = 30 - item_flags = DROPDEL - flags_1 = NONE - -/obj/item/restraints/legcuffs/beartrap/energy/Initialize() - . = ..() - addtimer(CALLBACK(src, .proc/dissipate), 100) - -/obj/item/restraints/legcuffs/beartrap/energy/proc/dissipate() - if(!ismob(loc)) - do_sparks(1, TRUE, src) - qdel(src) - -/obj/item/restraints/legcuffs/beartrap/energy/attack_hand(mob/user) - Crossed(user) //honk - return ..() - -/obj/item/restraints/legcuffs/beartrap/energy/cyborg - breakouttime = 20 // Cyborgs shouldn't have a strong restraint - -/obj/item/restraints/legcuffs/bola - name = "bola" - desc = "A restraining device designed to be thrown at the target. Upon connecting with said target, it will wrap around their legs, making it difficult for them to move quickly." - icon_state = "bola" - 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, datum/thrownthing/throwingdatum) - 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.Paralyze(knockdown) - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - -/obj/item/restraints/legcuffs/bola/tactical//traitor variant - name = "reinforced bola" - desc = "A strong bola, made with a long steel chain. It looks heavy, enough so that it could trip somebody." - icon_state = "bola_r" - 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, datum/thrownthing/throwingdatum) - if(iscarbon(hit_atom)) - var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(hit_atom)) - B.Crossed(hit_atom) - qdel(src) - ..() - -/obj/item/restraints/legcuffs/bola/gonbola - name = "gonbola" - desc = "Hey, if you have to be hugged in the legs by anything, it might as well be this little guy." - icon_state = "gonbola" - breakouttime = 300 - slowdown = 0 - var/datum/status_effect/gonbolaPacify/effectReference - -/obj/item/restraints/legcuffs/bola/gonbola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(iscarbon(hit_atom)) - var/mob/living/carbon/C = hit_atom - effectReference = C.apply_status_effect(STATUS_EFFECT_GONBOLAPACIFY) - -/obj/item/restraints/legcuffs/bola/gonbola/dropped(mob/user) - . = ..() - if(effectReference) - QDEL_NULL(effectReference) +/obj/item/restraints + breakouttime = 600 + +/obj/item/restraints/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return(OXYLOSS) + +/obj/item/restraints/Destroy() + if(iscarbon(loc)) + var/mob/living/carbon/M = loc + if(M.handcuffed == src) + M.handcuffed = null + M.update_handcuffed() + if(M.buckled && M.buckled.buckle_requires_restraints) + M.buckled.unbuckle_mob(M) + if(M.legcuffed == src) + M.legcuffed = null + M.update_inv_legcuffed() + return ..() + +//Handcuffs + +/obj/item/restraints/handcuffs + name = "handcuffs" + desc = "Use this to keep prisoners in line." + gender = PLURAL + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "handcuff" + 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) + return + +/obj/item/restraints/handcuffs/cable/sinew + name = "sinew restraints" + desc = "A pair of restraints fashioned from long strands of flesh." + icon = 'icons/obj/mining.dmi' + icon_state = "sinewcuff" + item_state = "sinewcuff" + materials = null + item_color = "white" + color = "#000000" + +/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/alien + icon_state = "handcuffAlien" + +/obj/item/restraints/handcuffs/fake + name = "fake handcuffs" + desc = "Fake handcuffs meant for gag purposes." + breakouttime = 10 //Deciseconds = 1s + +/obj/item/restraints/handcuffs/cable/attackby(obj/item/I, mob/user, params) + ..() + if(istype(I, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = I + if (R.use(1)) + var/obj/item/wirerod/W = new /obj/item/wirerod + remove_item_from_storage(user) + user.put_in_hands(W) + to_chat(user, "You wrap [src] around the top of [I].") + qdel(src) + else + to_chat(user, "You need one rod to make a wired rod!") + return + else if(istype(I, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = I + if(M.get_amount() < 6) + to_chat(user, "You need at least six metal sheets to make good enough weights!") + return + to_chat(user, "You begin to apply [I] to [src]...") + if(do_after(user, 35, target = src)) + if(M.get_amount() < 6 || !M) + return + var/obj/item/restraints/legcuffs/bola/S = new /obj/item/restraints/legcuffs/bola + M.use(6) + user.put_in_hands(S) + to_chat(user, "You make some weights out of [I] and tie them to [src].") + remove_item_from_storage(user) + qdel(src) + else + return ..() + +/obj/item/restraints/handcuffs/cable/zipties + name = "zipties" + desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." + icon_state = "cuff" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + 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" + item_state = "cuff" + +/obj/item/restraints/handcuffs/cable/zipties/used/attack() + return + +//Legcuffs + +/obj/item/restraints/legcuffs + name = "leg cuffs" + desc = "Use this to keep prisoners in line." + gender = PLURAL + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "handcuff" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + flags_1 = CONDUCT_1 + throwforce = 0 + w_class = WEIGHT_CLASS_NORMAL + slowdown = 7 + breakouttime = 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() + . = ..() + update_icon() + +/obj/item/restraints/legcuffs/beartrap/update_icon() + 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 + update_icon() + to_chat(user, "[src] is now [armed ? "armed" : "disarmed"]") + +/obj/item/restraints/legcuffs/beartrap/proc/close_trap() + armed = FALSE + update_icon() + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + +/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj) + if(armed && isturf(loc)) + if(isliving(AM)) + var/mob/living/L = AM + var/snap = TRUE + if(istype(L.buckled, /obj/vehicle)) + var/obj/vehicle/ridden_vehicle = L.buckled + if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times. + close_trap() + ridden_vehicle.visible_message("[ridden_vehicle] triggers \the [src].") + return ..() + + if(L.movement_type & (FLYING|FLOATING)) //don't close the trap if they're flying/floating over it. + snap = FALSE + + var/def_zone = BODY_ZONE_CHEST + if(snap && iscarbon(L)) + var/mob/living/carbon/C = L + if(C.mobility_flags & MOBILITY_STAND) + def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs. + C.legcuffed = src + forceMove(C) + C.update_inv_legcuffed() + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + else if(snap && isanimal(L)) + var/mob/living/simple_animal/SA = L + if(SA.mob_size <= MOB_SIZE_TINY) //don't close the trap if they're as small as a mouse. + snap = FALSE + if(snap) + close_trap() + L.visible_message("[L] triggers \the [src].", \ + "You trigger \the [src]!") + L.apply_damage(trap_damage, BRUTE, def_zone) + ..() + +/obj/item/restraints/legcuffs/beartrap/energy + name = "energy snare" + armed = 1 + icon_state = "e_snare" + trap_damage = 0 + breakouttime = 30 + item_flags = DROPDEL + flags_1 = NONE + +/obj/item/restraints/legcuffs/beartrap/energy/Initialize() + . = ..() + addtimer(CALLBACK(src, .proc/dissipate), 100) + +/obj/item/restraints/legcuffs/beartrap/energy/proc/dissipate() + if(!ismob(loc)) + do_sparks(1, TRUE, src) + qdel(src) + +/obj/item/restraints/legcuffs/beartrap/energy/attack_hand(mob/user) + Crossed(user) //honk + return ..() + +/obj/item/restraints/legcuffs/beartrap/energy/cyborg + breakouttime = 20 // Cyborgs shouldn't have a strong restraint + +/obj/item/restraints/legcuffs/bola + name = "bola" + desc = "A restraining device designed to be thrown at the target. Upon connecting with said target, it will wrap around their legs, making it difficult for them to move quickly." + icon_state = "bola" + 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, datum/thrownthing/throwingdatum) + 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.Paralyze(knockdown) + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + +/obj/item/restraints/legcuffs/bola/tactical//traitor variant + name = "reinforced bola" + desc = "A strong bola, made with a long steel chain. It looks heavy, enough so that it could trip somebody." + icon_state = "bola_r" + 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, datum/thrownthing/throwingdatum) + if(iscarbon(hit_atom)) + var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(hit_atom)) + B.Crossed(hit_atom) + qdel(src) + ..() + +/obj/item/restraints/legcuffs/bola/gonbola + name = "gonbola" + desc = "Hey, if you have to be hugged in the legs by anything, it might as well be this little guy." + icon_state = "gonbola" + breakouttime = 300 + slowdown = 0 + var/datum/status_effect/gonbolaPacify/effectReference + +/obj/item/restraints/legcuffs/bola/gonbola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(iscarbon(hit_atom)) + var/mob/living/carbon/C = hit_atom + effectReference = C.apply_status_effect(STATUS_EFFECT_GONBOLAPACIFY) + +/obj/item/restraints/legcuffs/bola/gonbola/dropped(mob/user) + . = ..() + if(effectReference) + QDEL_NULL(effectReference) diff --git a/code/game/objects/items/hot_potato.dm b/code/game/objects/items/hot_potato.dm index 671c15e5d7db..e4600859fd58 100644 --- a/code/game/objects/items/hot_potato.dm +++ b/code/game/objects/items/hot_potato.dm @@ -1,174 +1,174 @@ -//CREATOR'S NOTE: DO NOT FUCKING GIVE THIS TO BOTANY! -/obj/item/hot_potato - name = "hot potato" - desc = "A label on the side of this potato reads \"Product of DonkCo Service Wing. Activate far away from populated areas. Device will only attach to sapient creatures.\" You can attack anyone with it to force it on them instead of yourself!" - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "potato" - item_flags = NOBLUDGEON - force = 0 - var/icon_off = "potato" - var/icon_on = "potato_active" - var/detonation_timerid - var/activation_time = 0 - var/timer = 600 //deciseconds - var/show_timer = FALSE - var/reusable = FALSE //absolute madman - var/sticky = TRUE - var/forceful_attachment = TRUE - var/stimulant = TRUE - var/detonate_explosion = TRUE - var/detonate_dev_range = 0 - var/detonate_heavy_range = 0 - var/detonate_light_range = 2 - var/detonate_flash_range = 5 - var/detonate_fire_range = 5 - - var/active = FALSE - - var/color_val = FALSE - - var/datum/weakref/current - -/obj/item/hot_potato/Destroy() - if(active) - deactivate() - return ..() - -/obj/item/hot_potato/proc/colorize(mob/target) - //Clear color from old target - if(current) - var/mob/M = current.resolve() - if(istype(M)) - M.remove_atom_colour(FIXED_COLOUR_PRIORITY) - //Give to new target - current = null - //Swap colors - color_val = !color_val - if(istype(target)) - current = WEAKREF(target) - target.add_atom_colour(color_val? "#ffff00" : "#00ffff", FIXED_COLOUR_PRIORITY) - -/obj/item/hot_potato/proc/detonate() - var/atom/location = loc - location.visible_message("[src] [detonate_explosion? "explodes" : "activates"]!", "[src] activates! You've ran out of time!") - if(detonate_explosion) - explosion(src, detonate_dev_range, detonate_heavy_range, detonate_light_range, detonate_flash_range, flame_range = detonate_fire_range) - deactivate() - if(!reusable) - var/mob/M = loc - if(istype(M)) - M.dropItemToGround(src, TRUE) - qdel(src) - -/obj/item/hot_potato/attack_self(mob/user) - if(activate(timer, user)) - user.visible_message("[user] squeezes [src], which promptly starts to flash red-hot colors!", "You squeeze [src], activating its countdown and attachment mechanism!", - "You hear a mechanical click and a loud beeping!") - return - return ..() - -/obj/item/hot_potato/process() - if(stimulant) - if(isliving(loc)) - var/mob/living/L = loc - L.SetStun(0) - L.SetKnockdown(0) - L.SetSleeping(0) - L.SetImmobilized(0) - L.SetParalyzed(0) - L.SetUnconscious(0) - L.reagents.add_reagent(/datum/reagent/medicine/muscle_stimulant, CLAMP(5 - L.reagents.get_reagent_amount(/datum/reagent/medicine/muscle_stimulant), 0, 5)) //If you don't have legs or get bola'd, tough luck! - colorize(L) - -/obj/item/hot_potato/examine(mob/user) - . = ..() - if(active) - . += "[src] is flashing red-hot! You should probably get rid of it!" - if(show_timer) - . += "[src]'s timer looks to be at [DisplayTimeText(activation_time - world.time)]!" - -/obj/item/hot_potato/equipped(mob/user) - . = ..() - if(active) - to_chat(user, "You have a really bad feeling about [src]!") - -/obj/item/hot_potato/afterattack(atom/target, mob/user, adjacent, params) - . = ..() - if(!adjacent || !ismob(target)) - return - force_onto(target, user) - -/obj/item/hot_potato/proc/force_onto(mob/living/victim, mob/user) - if(!istype(victim) || user != loc || victim == user) - return FALSE - if(!victim.client) - to_chat(user, "[src] refuses to attach to a non-sapient creature!") - if(victim.stat != CONSCIOUS || !victim.get_num_legs()) - to_chat(user, "[src] refuses to attach to someone incapable of using it!") - user.temporarilyRemoveItemFromInventory(src, TRUE) - . = FALSE - if(!victim.put_in_hands(src)) - if(forceful_attachment) - victim.dropItemToGround(victim.get_inactive_held_item()) - if(!victim.put_in_hands(src)) - victim.dropItemToGround(victim.get_active_held_item()) - if(victim.put_in_hands(src)) - . = TRUE - else - . = TRUE - else - . = TRUE - if(.) - log_combat(user, victim, "forced a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") - user.visible_message("[user] forces [src] onto [victim]!", "You force [src] onto [victim]!", "You hear a mechanical click and a beep.") - colorize(null) - else - log_combat(user, victim, "tried to force a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") - user.visible_message("[user] tried to force [src] onto [victim], but it could not attach!", "You try to force [src] onto [victim], but it is unable to attach!", "You hear a mechanical click and two buzzes.") - user.put_in_hands(src) - -/obj/item/hot_potato/dropped(mob/user) - . = ..() - colorize(null) - -/obj/item/hot_potato/proc/activate(delay, mob/user) - if(active) - return - update_icon() - if(sticky) - ADD_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) - name = "primed [name]" - activation_time = timer + world.time - detonation_timerid = addtimer(CALLBACK(src, .proc/detonate), delay, TIMER_STOPPABLE) - START_PROCESSING(SSfastprocess, src) - if(user) - log_bomber(user, "has primed a", src, "for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") - else - log_bomber(null, null, src, "was primed for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") - active = TRUE - -/obj/item/hot_potato/proc/deactivate() - update_icon() - name = initial(name) - REMOVE_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) - deltimer(detonation_timerid) - STOP_PROCESSING(SSfastprocess, src) - detonation_timerid = null - colorize(null) - active = FALSE - -/obj/item/hot_potato/update_icon() - icon_state = active? icon_on : icon_off - -/obj/item/hot_potato/syndicate - detonate_light_range = 4 - detonate_fire_range = 5 - -/obj/item/hot_potato/harmless - detonate_explosion = FALSE - -/obj/item/hot_potato/harmless/toy - desc = "A label on the side of this potato reads \"Product of DonkCo Toys and Recreation department.\" You can attack anyone with it to put it on them instead, if they have a free hand to take it!" - sticky = FALSE - reusable = TRUE - forceful_attachment = FALSE +//CREATOR'S NOTE: DO NOT FUCKING GIVE THIS TO BOTANY! +/obj/item/hot_potato + name = "hot potato" + desc = "A label on the side of this potato reads \"Product of DonkCo Service Wing. Activate far away from populated areas. Device will only attach to sapient creatures.\" You can attack anyone with it to force it on them instead of yourself!" + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "potato" + item_flags = NOBLUDGEON + force = 0 + var/icon_off = "potato" + var/icon_on = "potato_active" + var/detonation_timerid + var/activation_time = 0 + var/timer = 600 //deciseconds + var/show_timer = FALSE + var/reusable = FALSE //absolute madman + var/sticky = TRUE + var/forceful_attachment = TRUE + var/stimulant = TRUE + var/detonate_explosion = TRUE + var/detonate_dev_range = 0 + var/detonate_heavy_range = 0 + var/detonate_light_range = 2 + var/detonate_flash_range = 5 + var/detonate_fire_range = 5 + + var/active = FALSE + + var/color_val = FALSE + + var/datum/weakref/current + +/obj/item/hot_potato/Destroy() + if(active) + deactivate() + return ..() + +/obj/item/hot_potato/proc/colorize(mob/target) + //Clear color from old target + if(current) + var/mob/M = current.resolve() + if(istype(M)) + M.remove_atom_colour(FIXED_COLOUR_PRIORITY) + //Give to new target + current = null + //Swap colors + color_val = !color_val + if(istype(target)) + current = WEAKREF(target) + target.add_atom_colour(color_val? "#ffff00" : "#00ffff", FIXED_COLOUR_PRIORITY) + +/obj/item/hot_potato/proc/detonate() + var/atom/location = loc + location.visible_message("[src] [detonate_explosion? "explodes" : "activates"]!", "[src] activates! You've ran out of time!") + if(detonate_explosion) + explosion(src, detonate_dev_range, detonate_heavy_range, detonate_light_range, detonate_flash_range, flame_range = detonate_fire_range) + deactivate() + if(!reusable) + var/mob/M = loc + if(istype(M)) + M.dropItemToGround(src, TRUE) + qdel(src) + +/obj/item/hot_potato/attack_self(mob/user) + if(activate(timer, user)) + user.visible_message("[user] squeezes [src], which promptly starts to flash red-hot colors!", "You squeeze [src], activating its countdown and attachment mechanism!", + "You hear a mechanical click and a loud beeping!") + return + return ..() + +/obj/item/hot_potato/process() + if(stimulant) + if(isliving(loc)) + var/mob/living/L = loc + L.SetStun(0) + L.SetKnockdown(0) + L.SetSleeping(0) + L.SetImmobilized(0) + L.SetParalyzed(0) + L.SetUnconscious(0) + L.reagents.add_reagent(/datum/reagent/medicine/muscle_stimulant, CLAMP(5 - L.reagents.get_reagent_amount(/datum/reagent/medicine/muscle_stimulant), 0, 5)) //If you don't have legs or get bola'd, tough luck! + colorize(L) + +/obj/item/hot_potato/examine(mob/user) + . = ..() + if(active) + . += "[src] is flashing red-hot! You should probably get rid of it!" + if(show_timer) + . += "[src]'s timer looks to be at [DisplayTimeText(activation_time - world.time)]!" + +/obj/item/hot_potato/equipped(mob/user) + . = ..() + if(active) + to_chat(user, "You have a really bad feeling about [src]!") + +/obj/item/hot_potato/afterattack(atom/target, mob/user, adjacent, params) + . = ..() + if(!adjacent || !ismob(target)) + return + force_onto(target, user) + +/obj/item/hot_potato/proc/force_onto(mob/living/victim, mob/user) + if(!istype(victim) || user != loc || victim == user) + return FALSE + if(!victim.client) + to_chat(user, "[src] refuses to attach to a non-sapient creature!") + if(victim.stat != CONSCIOUS || !victim.get_num_legs()) + to_chat(user, "[src] refuses to attach to someone incapable of using it!") + user.temporarilyRemoveItemFromInventory(src, TRUE) + . = FALSE + if(!victim.put_in_hands(src)) + if(forceful_attachment) + victim.dropItemToGround(victim.get_inactive_held_item()) + if(!victim.put_in_hands(src)) + victim.dropItemToGround(victim.get_active_held_item()) + if(victim.put_in_hands(src)) + . = TRUE + else + . = TRUE + else + . = TRUE + if(.) + log_combat(user, victim, "forced a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") + user.visible_message("[user] forces [src] onto [victim]!", "You force [src] onto [victim]!", "You hear a mechanical click and a beep.") + colorize(null) + else + log_combat(user, victim, "tried to force a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") + user.visible_message("[user] tried to force [src] onto [victim], but it could not attach!", "You try to force [src] onto [victim], but it is unable to attach!", "You hear a mechanical click and two buzzes.") + user.put_in_hands(src) + +/obj/item/hot_potato/dropped(mob/user) + . = ..() + colorize(null) + +/obj/item/hot_potato/proc/activate(delay, mob/user) + if(active) + return + update_icon() + if(sticky) + ADD_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) + name = "primed [name]" + activation_time = timer + world.time + detonation_timerid = addtimer(CALLBACK(src, .proc/detonate), delay, TIMER_STOPPABLE) + START_PROCESSING(SSfastprocess, src) + if(user) + log_bomber(user, "has primed a", src, "for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") + else + log_bomber(null, null, src, "was primed for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") + active = TRUE + +/obj/item/hot_potato/proc/deactivate() + update_icon() + name = initial(name) + REMOVE_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) + deltimer(detonation_timerid) + STOP_PROCESSING(SSfastprocess, src) + detonation_timerid = null + colorize(null) + active = FALSE + +/obj/item/hot_potato/update_icon() + 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 03a45cc98287..182a674bcce5 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, force = FALSE) - if(SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTING, args) & COMPONENT_STOP_IMPLANTING) - return - LAZYINITLIST(target.implants) - if(!force && (!target.can_be_implanted() || !can_be_implanted_in(target))) - return FALSE - for(var/X in target.implants) - var/obj/item/implant/imp_e = X - var/flags = SEND_SIGNAL(imp_e, COMSIG_IMPLANT_OTHER, args, src) - if(flags & COMPONENT_DELETE_NEW_IMPLANT) - UNSETEMPTY(target.implants) - qdel(src) - return TRUE - if(flags & COMPONENT_DELETE_OLD_IMPLANT) - qdel(imp_e) - continue - if(flags & COMPONENT_STOP_IMPLANTING) - UNSETEMPTY(target.implants) - return FALSE - - if(istype(imp_e, type)) - if(!allow_multiple) - if(imp_e.uses < initial(imp_e.uses)*2) - if(uses == -1) - imp_e.uses = -1 - else - imp_e.uses = min(imp_e.uses + uses, initial(imp_e.uses)*2) - qdel(src) - return TRUE - else - return FALSE - - forceMove(target) - imp_in = target - target.implants += src - if(activated) - for(var/X in actions) - var/datum/action/A = X - A.Grant(target) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - H.sec_hud_set_implants() - - if(user) - log_combat(user, target, "implanted", "\a [name]") - - return TRUE - -/obj/item/implant/proc/removed(mob/living/source, silent = FALSE, special = 0) - moveToNullspace() - imp_in = null - source.implants -= src - for(var/X in actions) - var/datum/action/A = X - A.Grant(source) - if(ishuman(source)) - var/mob/living/carbon/human/H = source - H.sec_hud_set_implants() - - return 1 - -/obj/item/implant/Destroy() - if(imp_in) - removed(imp_in) - return ..() - -/obj/item/implant/proc/get_data() - return "No information available" - -/obj/item/implant/dropped(mob/user) - . = 1 - ..() +/obj/item/implant + name = "implant" + icon = 'icons/obj/implants.dmi' + icon_state = "generic" //Shows up as the action button icon + actions_types = list(/datum/action/item_action/hands_free/activate) + var/activated = TRUE //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants + var/mob/living/imp_in = null + 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, force = FALSE) + if(SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTING, args) & COMPONENT_STOP_IMPLANTING) + return + LAZYINITLIST(target.implants) + if(!force && (!target.can_be_implanted() || !can_be_implanted_in(target))) + return FALSE + for(var/X in target.implants) + var/obj/item/implant/imp_e = X + var/flags = SEND_SIGNAL(imp_e, COMSIG_IMPLANT_OTHER, args, src) + if(flags & COMPONENT_DELETE_NEW_IMPLANT) + UNSETEMPTY(target.implants) + qdel(src) + return TRUE + if(flags & COMPONENT_DELETE_OLD_IMPLANT) + qdel(imp_e) + continue + if(flags & COMPONENT_STOP_IMPLANTING) + UNSETEMPTY(target.implants) + return FALSE + + if(istype(imp_e, type)) + if(!allow_multiple) + if(imp_e.uses < initial(imp_e.uses)*2) + if(uses == -1) + imp_e.uses = -1 + else + imp_e.uses = min(imp_e.uses + uses, initial(imp_e.uses)*2) + qdel(src) + return TRUE + else + return FALSE + + forceMove(target) + imp_in = target + target.implants += src + if(activated) + for(var/X in actions) + var/datum/action/A = X + A.Grant(target) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + H.sec_hud_set_implants() + + if(user) + log_combat(user, target, "implanted", "\a [name]") + + return TRUE + +/obj/item/implant/proc/removed(mob/living/source, silent = FALSE, special = 0) + moveToNullspace() + imp_in = null + source.implants -= src + for(var/X in actions) + var/datum/action/A = X + A.Grant(source) + if(ishuman(source)) + var/mob/living/carbon/human/H = source + H.sec_hud_set_implants() + + return 1 + +/obj/item/implant/Destroy() + if(imp_in) + removed(imp_in) + return ..() + +/obj/item/implant/proc/get_data() + return "No information available" + +/obj/item/implant/dropped(mob/user) + . = 1 + ..() diff --git a/code/game/objects/items/implants/implantcase.dm b/code/game/objects/items/implants/implantcase.dm index e26f38d79229..4b8427386c8c 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 390aff96206b..fe84b666506f 100644 --- a/code/game/objects/items/implants/implantchair.dm +++ b/code/game/objects/items/implants/implantchair.dm @@ -1,197 +1,197 @@ -/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, FALSE, 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 || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser()) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - close_machine(target) - -/obj/machinery/implantchair/close_machine(mob/living/user) - if((isnull(user) || istype(user)) && state_open) - ..(user) - if(auto_inject && ready && ready_implants > 0) - implant(user,null) - -/obj/machinery/implantchair/genepurge - name = "Genetic purifier" - desc = "Used to purge a human genome of foreign influences." - special = TRUE - special_name = "Purge genome" - injection_cooldown = 0 - replenish_cooldown = 300 - -/obj/machinery/implantchair/genepurge/implant_action(mob/living/carbon/human/H,mob/user) - if(!istype(H)) - return 0 - H.set_species(/datum/species/human, 1)//lizards go home - purrbation_remove(H)//remove cats - H.dna.remove_all_mutations()//hulks out - return 1 - - -/obj/machinery/implantchair/brainwash - name = "Neural Imprinter" - desc = "Used to indoctrinate rehabilitate hardened recidivists." - special_name = "Imprint" - injection_cooldown = 3000 - auto_inject = FALSE - auto_replenish = FALSE - special = TRUE - var/objective = "Obey the law. Praise Nanotrasen." - var/custom = FALSE - -/obj/machinery/implantchair/brainwash/implant_action(mob/living/C,mob/user) - if(!istype(C) || !C.mind) // I don't know how this makes any sense for silicons but laws trump objectives anyway. - return FALSE - if(custom) - if(!user || !user.Adjacent(src)) - return FALSE - objective = stripped_input(usr,"What order do you want to imprint on [C]?","Enter the order","",120) - message_admins("[ADMIN_LOOKUPFLW(user)] set brainwash machine objective to '[objective]'.") - log_game("[key_name(user)] set brainwash machine objective to '[objective]'.") - if(HAS_TRAIT(C, TRAIT_MINDSHIELD)) - return FALSE - brainwash(C, objective) - message_admins("[ADMIN_LOOKUPFLW(user)] brainwashed [key_name_admin(C)] with objective '[objective]'.") - log_game("[key_name(user)] brainwashed [key_name(C)] with objective '[objective]'.") - return TRUE +/obj/machinery/implantchair + name = "mindshield implanter" + desc = "Used to implant occupants with mindshield implants." + icon = 'icons/obj/machines/implantchair.dmi' + icon_state = "implantchair" + density = TRUE + opacity = 0 + + 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, FALSE, 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 || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser()) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + close_machine(target) + +/obj/machinery/implantchair/close_machine(mob/living/user) + if((isnull(user) || istype(user)) && state_open) + ..(user) + if(auto_inject && ready && ready_implants > 0) + implant(user,null) + +/obj/machinery/implantchair/genepurge + name = "Genetic purifier" + desc = "Used to purge a human genome of foreign influences." + special = TRUE + special_name = "Purge genome" + injection_cooldown = 0 + replenish_cooldown = 300 + +/obj/machinery/implantchair/genepurge/implant_action(mob/living/carbon/human/H,mob/user) + if(!istype(H)) + return 0 + H.set_species(/datum/species/human, 1)//lizards go home + purrbation_remove(H)//remove cats + H.dna.remove_all_mutations()//hulks out + return 1 + + +/obj/machinery/implantchair/brainwash + name = "Neural Imprinter" + desc = "Used to indoctrinate rehabilitate hardened recidivists." + special_name = "Imprint" + injection_cooldown = 3000 + auto_inject = FALSE + auto_replenish = FALSE + special = TRUE + var/objective = "Obey the law. Praise Nanotrasen." + var/custom = FALSE + +/obj/machinery/implantchair/brainwash/implant_action(mob/living/C,mob/user) + if(!istype(C) || !C.mind) // I don't know how this makes any sense for silicons but laws trump objectives anyway. + return FALSE + if(custom) + if(!user || !user.Adjacent(src)) + return FALSE + objective = stripped_input(usr,"What order do you want to imprint on [C]?","Enter the order","",120) + message_admins("[ADMIN_LOOKUPFLW(user)] set brainwash machine objective to '[objective]'.") + log_game("[key_name(user)] set brainwash machine objective to '[objective]'.") + if(HAS_TRAIT(C, TRAIT_MINDSHIELD)) + return FALSE + brainwash(C, objective) + message_admins("[ADMIN_LOOKUPFLW(user)] brainwashed [key_name_admin(C)] with objective '[objective]'.") + log_game("[key_name(user)] brainwashed [key_name(C)] with objective '[objective]'.") + return TRUE diff --git a/code/game/objects/items/implants/implanter.dm b/code/game/objects/items/implants/implanter.dm index c116e2cd291d..b774763defd0 100644 --- a/code/game/objects/items/implants/implanter.dm +++ b/code/game/objects/items/implants/implanter.dm @@ -1,65 +1,65 @@ -/obj/item/implanter - name = "implanter" - desc = "A sterile automatic implant injector." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implanter0" - 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 + 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() diff --git a/code/game/objects/items/implants/implantpad.dm b/code/game/objects/items/implants/implantpad.dm index 5358fdaea650..7bcf3c8638ae 100644 --- a/code/game/objects/items/implants/implantpad.dm +++ b/code/game/objects/items/implants/implantpad.dm @@ -1,78 +1,78 @@ -/obj/item/implantpad - name = "implant pad" - desc = "Used to modify implants." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implantpad-0" - 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 = 5 - w_class = WEIGHT_CLASS_SMALL - var/obj/item/implantcase/case = null - -/obj/item/implantpad/update_icon() - icon_state = "implantpad-[!QDELETED(case)]" - -/obj/item/implantpad/examine(mob/user) - . = ..() - if(Adjacent(user)) - . += "It [case ? "contains \a [case]" : "is currently empty"]." - if(case) - . += "Alt-click to remove [case]." - else - if(case) - . += "There seems to be something inside it, but you can't quite tell what from here..." - -/obj/item/implantpad/handle_atom_del(atom/A) - if(A == case) - case = null - update_icon() - updateSelfDialog() - . = ..() - -/obj/item/implantpad/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(!case) - to_chat(user, "There's no implant to remove from [src].") - return - - user.put_in_hands(case) - - add_fingerprint(user) - case.add_fingerprint(user) - case = null - - updateSelfDialog() - update_icon() - -/obj/item/implantpad/attackby(obj/item/implantcase/C, mob/user, params) - if(istype(C, /obj/item/implantcase) && !case) - if(!user.transferItemToLoc(C, src)) - return - case = C - updateSelfDialog() - update_icon() - else - return ..() - -/obj/item/implantpad/ui_interact(mob/user) - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - user.unset_machine(src) - user << browse(null, "window=implantpad") - return - - user.set_machine(src) - var/dat = "Implant Mini-Computer:
                " - if(case) - if(case.imp) - if(istype(case.imp, /obj/item/implant)) - dat += case.imp.get_data() - else - dat += "The implant casing is empty." - else - dat += "Please insert an implant casing!" - user << browse(dat, "window=implantpad") - onclose(user, "implantpad") +/obj/item/implantpad + name = "implant pad" + desc = "Used to modify implants." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "implantpad-0" + 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 = 5 + w_class = WEIGHT_CLASS_SMALL + var/obj/item/implantcase/case = null + +/obj/item/implantpad/update_icon() + icon_state = "implantpad-[!QDELETED(case)]" + +/obj/item/implantpad/examine(mob/user) + . = ..() + if(Adjacent(user)) + . += "It [case ? "contains \a [case]" : "is currently empty"]." + if(case) + . += "Alt-click to remove [case]." + else + if(case) + . += "There seems to be something inside it, but you can't quite tell what from here..." + +/obj/item/implantpad/handle_atom_del(atom/A) + if(A == case) + case = null + update_icon() + updateSelfDialog() + . = ..() + +/obj/item/implantpad/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(!case) + to_chat(user, "There's no implant to remove from [src].") + return + + user.put_in_hands(case) + + add_fingerprint(user) + case.add_fingerprint(user) + case = null + + updateSelfDialog() + update_icon() + +/obj/item/implantpad/attackby(obj/item/implantcase/C, mob/user, params) + if(istype(C, /obj/item/implantcase) && !case) + if(!user.transferItemToLoc(C, src)) + return + case = C + updateSelfDialog() + update_icon() + else + return ..() + +/obj/item/implantpad/ui_interact(mob/user) + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + user.unset_machine(src) + user << browse(null, "window=implantpad") + return + + user.set_machine(src) + var/dat = "Implant Mini-Computer:
                " + if(case) + if(case.imp) + if(istype(case.imp, /obj/item/implant)) + dat += case.imp.get_data() + else + dat += "The implant casing is empty." + else + dat += "Please insert an implant casing!" + user << browse(dat, "window=implantpad") + onclose(user, "implantpad") diff --git a/code/game/objects/items/implants/implantuplink.dm b/code/game/objects/items/implants/implantuplink.dm index 9000fbbe3433..0cac8f838acb 100644 --- a/code/game/objects/items/implants/implantuplink.dm +++ b/code/game/objects/items/implants/implantuplink.dm @@ -1,23 +1,23 @@ -/obj/item/implant/uplink - name = "uplink implant" - desc = "Sneeki breeki." - icon = 'icons/obj/radio.dmi' - icon_state = "radio" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - var/starting_tc = 0 - -/obj/item/implant/uplink/Initialize(mapload, _owner) - . = ..() - AddComponent(/datum/component/uplink, _owner, TRUE, FALSE, null, starting_tc) - -/obj/item/implanter/uplink - name = "implanter (uplink)" - imp_type = /obj/item/implant/uplink - -/obj/item/implanter/uplink/precharged - name = "implanter (precharged uplink)" - imp_type = /obj/item/implant/uplink/precharged - -/obj/item/implant/uplink/precharged - starting_tc = 10 +/obj/item/implant/uplink + name = "uplink implant" + desc = "Sneeki breeki." + icon = 'icons/obj/radio.dmi' + icon_state = "radio" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + var/starting_tc = 0 + +/obj/item/implant/uplink/Initialize(mapload, _owner) + . = ..() + AddComponent(/datum/component/uplink, _owner, TRUE, FALSE, null, starting_tc) + +/obj/item/implanter/uplink + name = "implanter (uplink)" + imp_type = /obj/item/implant/uplink + +/obj/item/implanter/uplink/precharged + name = "implanter (precharged uplink)" + imp_type = /obj/item/implant/uplink/precharged + +/obj/item/implant/uplink/precharged + starting_tc = 10 diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm index 88e4590656e9..68bdbd706f46 100644 --- a/code/game/objects/items/kitchen.dm +++ b/code/game/objects/items/kitchen.dm @@ -1,191 +1,191 @@ -/* Kitchen tools - * Contains: - * Fork - * Kitchen knives - * Ritual Knife - * Butcher's cleaver - * Combat Knife - * Rolling Pins - */ - -/obj/item/kitchen - icon = 'yogstation/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] is trying to feed [M] a delicious forkful of omelette!") //yogs start - if(!do_mob(user, M)) - return - log_combat(user, M, "fed omelette", forkload.type) //yogs end - 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) - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - M = user - return eyestab(M,user) - else - return ..() - - -/obj/item/kitchen/knife - name = "kitchen knife" - icon_state = "knife" - item_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? - custom_price = 30 - -/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) - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - M = user - 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" - 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" - item_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 - custom_price = 60 - -/obj/item/kitchen/knife/combat - name = "combat knife" - icon_state = "buckknife" - desc = "A military combat utility survival knife." - embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 65, "embedded_fall_chance" = 10, "embedded_ignore_throwspeed_threshold" = TRUE) - 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" - embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 35, "embedded_fall_chance" = 10) - 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." - embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 35, "embedded_fall_chance" = 10) - 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/knife/carrotshiv/suicide_act(mob/living/carbon/user) - user.visible_message("[user] forcefully drives \the [src] into [user.p_their()] eye! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/kitchen/rollingpin - name = "rolling pin" - desc = "Used to knock out the Bartender." - icon_state = "rolling_pin" - force = 8 - throwforce = 5 - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - custom_price = 20 - -/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 */ +/* Kitchen tools + * Contains: + * Fork + * Kitchen knives + * Ritual Knife + * Butcher's cleaver + * Combat Knife + * Rolling Pins + */ + +/obj/item/kitchen + icon = 'yogstation/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] is trying to feed [M] a delicious forkful of omelette!") //yogs start + if(!do_mob(user, M)) + return + log_combat(user, M, "fed omelette", forkload.type) //yogs end + 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) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + M = user + return eyestab(M,user) + else + return ..() + + +/obj/item/kitchen/knife + name = "kitchen knife" + icon_state = "knife" + item_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? + custom_price = 30 + +/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) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + M = user + 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" + 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" + item_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 + custom_price = 60 + +/obj/item/kitchen/knife/combat + name = "combat knife" + icon_state = "buckknife" + desc = "A military combat utility survival knife." + embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 65, "embedded_fall_chance" = 10, "embedded_ignore_throwspeed_threshold" = TRUE) + 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" + embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 35, "embedded_fall_chance" = 10) + 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." + embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 35, "embedded_fall_chance" = 10) + 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/knife/carrotshiv/suicide_act(mob/living/carbon/user) + user.visible_message("[user] forcefully drives \the [src] into [user.p_their()] eye! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/kitchen/rollingpin + name = "rolling pin" + desc = "Used to knock out the Bartender." + icon_state = "rolling_pin" + force = 8 + throwforce = 5 + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + custom_price = 20 + +/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 */ diff --git a/code/game/objects/items/latexballoon.dm b/code/game/objects/items/latexballoon.dm index eb8359fc3586..08d3532f0023 100644 --- a/code/game/objects/items/latexballoon.dm +++ b/code/game/objects/items/latexballoon.dm @@ -1,58 +1,58 @@ -/obj/item/latexballon - name = "latex glove" - desc = "Sterile and airtight." - icon_state = "latexballon" - item_state = "lgloves" - force = 0 - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 1 - throw_range = 7 - var/state - var/datum/gas_mixture/air_contents = null - -/obj/item/latexballon/proc/blow(obj/item/tank/tank, mob/user) - if (icon_state == "latexballon_bursted") - return - icon_state = "latexballon_blow" - item_state = "latexballon" - user.update_inv_hands() - to_chat(user, "You blow up [src] with [tank].") - air_contents = tank.remove_air_volume(3) - -/obj/item/latexballon/proc/burst() - if (!air_contents || icon_state != "latexballon_blow") - return - playsound(src, 'sound/weapons/gunshot.ogg', 100, 1) - icon_state = "latexballon_bursted" - item_state = "lgloves" - if(isliving(loc)) - var/mob/living/user = src.loc - user.update_inv_hands() - loc.assume_air(air_contents) - -/obj/item/latexballon/ex_act(severity, target) - burst() - switch(severity) - if (1) - qdel(src) - if (2) - if (prob(50)) - qdel(src) - -/obj/item/latexballon/bullet_act(obj/item/projectile/P) - if(!P.nodamage) - burst() - return ..() - -/obj/item/latexballon/temperature_expose(datum/gas_mixture/air, temperature, volume) - if(temperature > T0C+100) - burst() - -/obj/item/latexballon/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - var/obj/item/tank/T = W - blow(T, user) - return - if (W.is_sharp() || W.is_hot() || is_pointed(W)) - burst() +/obj/item/latexballon + name = "latex glove" + desc = "Sterile and airtight." + icon_state = "latexballon" + item_state = "lgloves" + force = 0 + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 1 + throw_range = 7 + var/state + var/datum/gas_mixture/air_contents = null + +/obj/item/latexballon/proc/blow(obj/item/tank/tank, mob/user) + if (icon_state == "latexballon_bursted") + return + icon_state = "latexballon_blow" + item_state = "latexballon" + user.update_inv_hands() + to_chat(user, "You blow up [src] with [tank].") + air_contents = tank.remove_air_volume(3) + +/obj/item/latexballon/proc/burst() + if (!air_contents || icon_state != "latexballon_blow") + return + playsound(src, 'sound/weapons/gunshot.ogg', 100, 1) + icon_state = "latexballon_bursted" + item_state = "lgloves" + if(isliving(loc)) + var/mob/living/user = src.loc + user.update_inv_hands() + loc.assume_air(air_contents) + +/obj/item/latexballon/ex_act(severity, target) + burst() + switch(severity) + if (1) + qdel(src) + if (2) + if (prob(50)) + qdel(src) + +/obj/item/latexballon/bullet_act(obj/item/projectile/P) + if(!P.nodamage) + burst() + return ..() + +/obj/item/latexballon/temperature_expose(datum/gas_mixture/air, temperature, volume) + if(temperature > T0C+100) + burst() + +/obj/item/latexballon/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + var/obj/item/tank/T = W + blow(T, user) + return + if (W.is_sharp() || W.is_hot() || is_pointed(W)) + burst() diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm index bf525b71b959..8b9b80ca1abe 100644 --- a/code/game/objects/items/manuals.dm +++ b/code/game/objects/items/manuals.dm @@ -1,455 +1,455 @@ -/*********************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 meatball, doughslice into spaghetti, potato into fries,etc...) - -

                Gibber:

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

                Meat spike:

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

                Example recipes:

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

                Sharing your food:

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

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

                You start skimming through the manual...

                - - - - - - "} - -/obj/item/book/manual/wiki/chemistry - name = "Chemistry Textbook" - icon_state ="chemistrybook" - author = "Nanotrasen" - title = "Chemistry Textbook" - page_link = "Guide_to_chemistry" - -/obj/item/book/manual/wiki/engineering_construction - name = "Station Repairs and Construction" - icon_state ="bookEngineering" - author = "Engineering Encyclopedia" - title = "Station Repairs and Construction" - page_link = "Guide_to_construction" - -/obj/item/book/manual/wiki/engineering_guide - name = "Engineering Textbook" - icon_state ="bookEngineering2" - author = "Engineering Encyclopedia" - title = "Engineering Textbook" - page_link = "Guide_to_engineering" - -/obj/item/book/manual/wiki/engineering_singulo_tesla - name = "Singularity and Tesla for Dummies" - icon_state ="bookEngineeringSingularitySafety" - author = "Engineering Encyclopedia" - title = "Singularity and Tesla for Dummies" - page_link = "Singularity_and_Tesla_engines" - -/obj/item/book/manual/wiki/security_space_law - name = "Space Law" - desc = "A set of Nanotrasen guidelines for keeping law and order on their space stations." - icon_state = "bookSpaceLaw" - author = "Nanotrasen" - title = "Space Law" - page_link = "Space_Law" - -/obj/item/book/manual/wiki/security_space_law/suicide_act(mob/living/user) - user.visible_message("[user] pretends to read \the [src] intently... then promptly dies of laughter!") - return OXYLOSS - -/obj/item/book/manual/wiki/infections - name = "Infections - Making your own pandemic!" - icon_state = "bookInfections" - author = "Infections Encyclopedia" - title = "Infections - Making your own pandemic!" - page_link = "Infections" - -/obj/item/book/manual/wiki/telescience - name = "Teleportation Science - Bluespace for dummies!" - icon_state = "book7" - author = "University of Bluespace" - title = "Teleportation Science - Bluespace for dummies!" - page_link = "Guide_to_telescience" - -/obj/item/book/manual/wiki/engineering_hacking - name = "Hacking" - icon_state ="bookHacking" - author = "Engineering Encyclopedia" - title = "Hacking" - page_link = "Hacking" - -/obj/item/book/manual/wiki/detective - name = "The Film Noir: Proper Procedures for Investigations" - icon_state ="bookDetective" - author = "Nanotrasen" - title = "The Film Noir: Proper Procedures for Investigations" - page_link = "Detective" - -/obj/item/book/manual/wiki/barman_recipes - name = "Barman Recipes: Mixing Drinks and Changing Lives" - icon_state = "barbook" - author = "Sir John Rose" - title = "Barman Recipes: Mixing Drinks and Changing Lives" - page_link = "Guide_to_food_and_drinks" - -/obj/item/book/manual/wiki/robotics_cyborgs - name = "Robotics for Dummies" - icon_state = "borgbook" - author = "XISC" - title = "Robotics for Dummies" - page_link = "Guide_to_robotics" - -/obj/item/book/manual/wiki/research_and_development - name = "Research and Development 101" - icon_state = "rdbook" - author = "Dr. L. Ight" - title = "Research and Development 101" - page_link = "Guide_to_Research_and_Development" - -/obj/item/book/manual/wiki/experimentor - name = "Mentoring your Experiments" - icon_state = "rdbook" - author = "Dr. H.P. Kritz" - title = "Mentoring your Experiments" - page_link = "Experimentor" - -/obj/item/book/manual/wiki/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/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) - -//YOGS start -/obj/item/book/manual/wiki/supermatter - name = "Supermatter for Dummies" - icon = 'yogstation/icons/obj/library.dmi' - icon_state = "sm_book" - author = "Engineering Encyclopedia" - title = "Easy Guide to Setting up the Supermatter Engine" - page_link = "Supermatter" +/*********************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 meatball, doughslice into spaghetti, potato into fries,etc...) + +

                Gibber:

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

                Meat spike:

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

                Example recipes:

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

                Sharing your food:

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

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

                You start skimming through the manual...

                + + + + + + "} + +/obj/item/book/manual/wiki/chemistry + name = "Chemistry Textbook" + icon_state ="chemistrybook" + author = "Nanotrasen" + title = "Chemistry Textbook" + page_link = "Guide_to_chemistry" + +/obj/item/book/manual/wiki/engineering_construction + name = "Station Repairs and Construction" + icon_state ="bookEngineering" + author = "Engineering Encyclopedia" + title = "Station Repairs and Construction" + page_link = "Guide_to_construction" + +/obj/item/book/manual/wiki/engineering_guide + name = "Engineering Textbook" + icon_state ="bookEngineering2" + author = "Engineering Encyclopedia" + title = "Engineering Textbook" + page_link = "Guide_to_engineering" + +/obj/item/book/manual/wiki/engineering_singulo_tesla + name = "Singularity and Tesla for Dummies" + icon_state ="bookEngineeringSingularitySafety" + author = "Engineering Encyclopedia" + title = "Singularity and Tesla for Dummies" + page_link = "Singularity_and_Tesla_engines" + +/obj/item/book/manual/wiki/security_space_law + name = "Space Law" + desc = "A set of Nanotrasen guidelines for keeping law and order on their space stations." + icon_state = "bookSpaceLaw" + author = "Nanotrasen" + title = "Space Law" + page_link = "Space_Law" + +/obj/item/book/manual/wiki/security_space_law/suicide_act(mob/living/user) + user.visible_message("[user] pretends to read \the [src] intently... then promptly dies of laughter!") + return OXYLOSS + +/obj/item/book/manual/wiki/infections + name = "Infections - Making your own pandemic!" + icon_state = "bookInfections" + author = "Infections Encyclopedia" + title = "Infections - Making your own pandemic!" + page_link = "Infections" + +/obj/item/book/manual/wiki/telescience + name = "Teleportation Science - Bluespace for dummies!" + icon_state = "book7" + author = "University of Bluespace" + title = "Teleportation Science - Bluespace for dummies!" + page_link = "Guide_to_telescience" + +/obj/item/book/manual/wiki/engineering_hacking + name = "Hacking" + icon_state ="bookHacking" + author = "Engineering Encyclopedia" + title = "Hacking" + page_link = "Hacking" + +/obj/item/book/manual/wiki/detective + name = "The Film Noir: Proper Procedures for Investigations" + icon_state ="bookDetective" + author = "Nanotrasen" + title = "The Film Noir: Proper Procedures for Investigations" + page_link = "Detective" + +/obj/item/book/manual/wiki/barman_recipes + name = "Barman Recipes: Mixing Drinks and Changing Lives" + icon_state = "barbook" + author = "Sir John Rose" + title = "Barman Recipes: Mixing Drinks and Changing Lives" + page_link = "Guide_to_food_and_drinks" + +/obj/item/book/manual/wiki/robotics_cyborgs + name = "Robotics for Dummies" + icon_state = "borgbook" + author = "XISC" + title = "Robotics for Dummies" + page_link = "Guide_to_robotics" + +/obj/item/book/manual/wiki/research_and_development + name = "Research and Development 101" + icon_state = "rdbook" + author = "Dr. L. Ight" + title = "Research and Development 101" + page_link = "Guide_to_Research_and_Development" + +/obj/item/book/manual/wiki/experimentor + name = "Mentoring your Experiments" + icon_state = "rdbook" + author = "Dr. H.P. Kritz" + title = "Mentoring your Experiments" + page_link = "Experimentor" + +/obj/item/book/manual/wiki/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/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) + +//YOGS start +/obj/item/book/manual/wiki/supermatter + name = "Supermatter for Dummies" + icon = 'yogstation/icons/obj/library.dmi' + icon_state = "sm_book" + author = "Engineering Encyclopedia" + title = "Easy Guide to Setting up the Supermatter Engine" + page_link = "Supermatter" //YOGS end \ No newline at end of file diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index 4f34048aa456..6d34d7d13b3b 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -1,234 +1,234 @@ -/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 - -/obj/item/melee/transforming/energy/Initialize() - . = ..() - if(active) - set_light(brightness_on) - START_PROCESSING(SSobj, src) - -/obj/item/melee/transforming/energy/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/melee/transforming/energy/suicide_act(mob/user) - if(!active) - transform_weapon(user, TRUE) - user.visible_message("[user] is [pick("slitting [user.p_their()] stomach open with", "falling on")] [src]! It looks like [user.p_theyre()] trying to commit seppuku!") - return (BRUTELOSS|FIRELOSS) - -/obj/item/melee/transforming/energy/add_blood_DNA(list/blood_dna) - return FALSE - -/obj/item/melee/transforming/energy/is_sharp() - 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/is_hot() - 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" - -/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" - -/obj/item/melee/transforming/energy/sword/cyborg/saw/cyborg_unequip(mob/user) - if(!active) - return - transform_weapon(user, TRUE) - -/obj/item/melee/transforming/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 - -/obj/item/melee/transforming/energy/sword/saber - var/list/possible_colors = list("red" = LIGHT_COLOR_RED, "blue" = LIGHT_COLOR_LIGHT_CYAN, "green" = LIGHT_COLOR_GREEN, "purple" = LIGHT_COLOR_LAVENDER) - var/hacked = FALSE - -/obj/item/melee/transforming/energy/sword/saber/Initialize(mapload) - . = ..() - if(LAZYLEN(possible_colors)) - var/set_color = pick(possible_colors) - 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(W.tool_behaviour == TOOL_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" +/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 + +/obj/item/melee/transforming/energy/Initialize() + . = ..() + if(active) + set_light(brightness_on) + START_PROCESSING(SSobj, src) + +/obj/item/melee/transforming/energy/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/melee/transforming/energy/suicide_act(mob/user) + if(!active) + transform_weapon(user, TRUE) + user.visible_message("[user] is [pick("slitting [user.p_their()] stomach open with", "falling on")] [src]! It looks like [user.p_theyre()] trying to commit seppuku!") + return (BRUTELOSS|FIRELOSS) + +/obj/item/melee/transforming/energy/add_blood_DNA(list/blood_dna) + return FALSE + +/obj/item/melee/transforming/energy/is_sharp() + 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/is_hot() + 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" + +/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" + +/obj/item/melee/transforming/energy/sword/cyborg/saw/cyborg_unequip(mob/user) + if(!active) + return + transform_weapon(user, TRUE) + +/obj/item/melee/transforming/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + return 0 + +/obj/item/melee/transforming/energy/sword/saber + var/list/possible_colors = list("red" = LIGHT_COLOR_RED, "blue" = LIGHT_COLOR_LIGHT_CYAN, "green" = LIGHT_COLOR_GREEN, "purple" = LIGHT_COLOR_LAVENDER) + var/hacked = FALSE + +/obj/item/melee/transforming/energy/sword/saber/Initialize(mapload) + . = ..() + if(LAZYLEN(possible_colors)) + var/set_color = pick(possible_colors) + 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(W.tool_behaviour == TOOL_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" diff --git a/code/game/objects/items/paiwire.dm b/code/game/objects/items/paiwire.dm index ef98ed62a139..331a51ad671c 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 d888aa0ce536..a686529c3eff 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -1,743 +1,743 @@ -/********************************************************************** - Cyborg Spec Items -***********************************************************************/ -/obj/item/borg - icon = 'icons/mob/robot_items.dmi' - - -/obj/item/borg/stun - name = "electrically-charged arm" - icon_state = "elecarm" - var/charge_cost = 30 - -/obj/item/borg/stun/attack(mob/living/M, mob/living/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) - playsound(M, 'sound/weapons/genhit.ogg', 50, 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.Paralyze(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(!(user.mobility_flags & MOBILITY_STAND)) - user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ - "You shake [M] trying to get [M.p_them()] up!") - else - user.visible_message("[user] hugs [M] to make [M.p_them()] feel better!", \ - "You hug [M] to make [M.p_them()] feel better!") - if(M.resting) - M.set_resting(FALSE, TRUE) - else - user.visible_message("[user] pets [M]!", \ - "You pet [M]!") - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - if(1) - if(M.health >= 0) - if(ishuman(M)) - if(!(M.mobility_flags & MOBILITY_STAND)) - user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ - "You shake [M] trying to get [M.p_them()] up!") - else if(user.zone_selected == BODY_ZONE_HEAD) - user.visible_message("[user] bops [M] on the head!", \ - "You bop [M] on the head!") - user.do_attack_animation(M, ATTACK_EFFECT_PUNCH) - else - user.visible_message("[user] hugs [M] in a firm bear-hug! [M] looks uncomfortable...", \ - "You hug [M] firmly to make [M.p_them()] feel better! [M] looks uncomfortable...") - if(M.resting) - M.set_resting(FALSE, TRUE) - else - user.visible_message("[user] bops [M] on the head!", \ - "You bop [M] on the head!") - playsound(loc, 'sound/weapons/tap.ogg', 50, 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_mobility() - else - if(!iscyborg(M)) - M.adjustFireLoss(10) - user.visible_message("[user] shocks [M]!", \ - "You shock [M]!") - else - user.visible_message("[user] shocks [M]. It does not seem to have an effect", \ - "You shock [M] to no effect.") - playsound(loc, 'sound/effects/sparks2.ogg', 50, 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]!") - -/obj/item/harmalarm/attack_self(mob/user) - var/safety = !(obj_flags & EMAGGED) - if(cooldown > world.time) - to_chat(user, "The device is still recharging!") - return - - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - if(!R.cell || R.cell.charge < 1200) - to_chat(user, "You don't have enough charge to do this!") - return - R.cell.charge -= 1000 - if(R.emagged) - safety = FALSE - - if(safety == TRUE) - user.visible_message("[user] blares out a near-deafening siren from its speakers!", \ - "The siren pierces your hearing and confuses you!", \ - "The siren pierces your hearing!") - for(var/mob/living/carbon/M in get_hearers_in_view(9, user)) - if(M.get_ear_protection() == FALSE) - M.confused += 6 - audible_message("HUMAN HARM") - playsound(get_turf(src), 'sound/ai/harmalarm.ogg', 70, 3) - cooldown = world.time + 200 - 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.Paralyze(40) - C.confused += 10 - C.stuttering += 15 - C.Jitter(25) - playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3) - cooldown = world.time + 600 - 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/Initialize() - . = ..() - 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/Initialize() - . = ..() - hud = new /obj/item/clothing/glasses/hud/health(src) - - -/obj/item/borg/sight/hud/sec - name = "security hud" - icon_state = "securityhud" - -/obj/item/borg/sight/hud/sec/Initialize() - . = ..() - hud = new /obj/item/clothing/glasses/hud/security(src) +/********************************************************************** + Cyborg Spec Items +***********************************************************************/ +/obj/item/borg + icon = 'icons/mob/robot_items.dmi' + + +/obj/item/borg/stun + name = "electrically-charged arm" + icon_state = "elecarm" + var/charge_cost = 30 + +/obj/item/borg/stun/attack(mob/living/M, mob/living/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) + playsound(M, 'sound/weapons/genhit.ogg', 50, 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.Paralyze(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(!(user.mobility_flags & MOBILITY_STAND)) + user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ + "You shake [M] trying to get [M.p_them()] up!") + else + user.visible_message("[user] hugs [M] to make [M.p_them()] feel better!", \ + "You hug [M] to make [M.p_them()] feel better!") + if(M.resting) + M.set_resting(FALSE, TRUE) + else + user.visible_message("[user] pets [M]!", \ + "You pet [M]!") + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + if(1) + if(M.health >= 0) + if(ishuman(M)) + if(!(M.mobility_flags & MOBILITY_STAND)) + user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ + "You shake [M] trying to get [M.p_them()] up!") + else if(user.zone_selected == BODY_ZONE_HEAD) + user.visible_message("[user] bops [M] on the head!", \ + "You bop [M] on the head!") + user.do_attack_animation(M, ATTACK_EFFECT_PUNCH) + else + user.visible_message("[user] hugs [M] in a firm bear-hug! [M] looks uncomfortable...", \ + "You hug [M] firmly to make [M.p_them()] feel better! [M] looks uncomfortable...") + if(M.resting) + M.set_resting(FALSE, TRUE) + else + user.visible_message("[user] bops [M] on the head!", \ + "You bop [M] on the head!") + playsound(loc, 'sound/weapons/tap.ogg', 50, 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_mobility() + else + if(!iscyborg(M)) + M.adjustFireLoss(10) + user.visible_message("[user] shocks [M]!", \ + "You shock [M]!") + else + user.visible_message("[user] shocks [M]. It does not seem to have an effect", \ + "You shock [M] to no effect.") + playsound(loc, 'sound/effects/sparks2.ogg', 50, 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]!") + +/obj/item/harmalarm/attack_self(mob/user) + var/safety = !(obj_flags & EMAGGED) + if(cooldown > world.time) + to_chat(user, "The device is still recharging!") + return + + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + if(!R.cell || R.cell.charge < 1200) + to_chat(user, "You don't have enough charge to do this!") + return + R.cell.charge -= 1000 + if(R.emagged) + safety = FALSE + + if(safety == TRUE) + user.visible_message("[user] blares out a near-deafening siren from its speakers!", \ + "The siren pierces your hearing and confuses you!", \ + "The siren pierces your hearing!") + for(var/mob/living/carbon/M in get_hearers_in_view(9, user)) + if(M.get_ear_protection() == FALSE) + M.confused += 6 + audible_message("HUMAN HARM") + playsound(get_turf(src), 'sound/ai/harmalarm.ogg', 70, 3) + cooldown = world.time + 200 + 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.Paralyze(40) + C.confused += 10 + C.stuttering += 15 + C.Jitter(25) + playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3) + cooldown = world.time + 600 + 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/Initialize() + . = ..() + 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/Initialize() + . = ..() + hud = new /obj/item/clothing/glasses/hud/health(src) + + +/obj/item/borg/sight/hud/sec + name = "security hud" + icon_state = "securityhud" + +/obj/item/borg/sight/hud/sec/Initialize() + . = ..() + hud = new /obj/item/clothing/glasses/hud/security(src) diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm index 8fc37895db62..f51364accfcf 100644 --- a/code/game/objects/items/robot/robot_parts.dm +++ b/code/game/objects/items/robot/robot_parts.dm @@ -1,421 +1,421 @@ - - -//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" - w_class = WEIGHT_CLASS_BULKY // yogs - can not fit in backpacks - var/obj/item/bodypart/l_arm/robot/l_arm = null - var/obj/item/bodypart/r_arm/robot/r_arm = null - var/obj/item/bodypart/l_leg/robot/l_leg = null - var/obj/item/bodypart/r_leg/robot/r_leg = null - var/obj/item/bodypart/chest/robot/chest = null - var/obj/item/bodypart/head/robot/head = null - - var/created_name = "" - var/mob/living/silicon/ai/forced_ai - var/locomotion = 1 - var/lawsync = 1 - var/aisync = 1 - var/panel_locked = TRUE - -/obj/item/robot_suit/Initialize() - . = ..() - update_icon() - -/obj/item/robot_suit/prebuilt/Initialize() - . = ..() - l_arm = new(src) - r_arm = new(src) - l_leg = new(src) - r_leg = new(src) - head = new(src) - head.flash1 = new(head) - head.flash2 = new(head) - chest = new(src) - chest.wired = TRUE - chest.cell = new /obj/item/stock_parts/cell/high/plus(chest) - -/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(l_leg) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - l_leg = W - update_icon() - - else if(istype(W, /obj/item/bodypart/r_leg/robot)) - if(src.r_leg) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - r_leg = W - update_icon() - - else if(istype(W, /obj/item/bodypart/l_arm/robot)) - if(l_arm) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - l_arm = W - update_icon() - - else if(istype(W, /obj/item/bodypart/r_arm/robot)) - if(r_arm) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state)//in case it is a dismembered robotic limb - W.cut_overlays() - r_arm = W - update_icon() - - else if(istype(W, /obj/item/bodypart/chest/robot)) - var/obj/item/bodypart/chest/robot/CH = W - if(chest) - return - if(CH.wired && CH.cell) - if(!user.transferItemToLoc(CH, src)) - return - CH.icon_state = initial(CH.icon_state) //in case it is a dismembered robotic limb - CH.cut_overlays() - chest = CH - update_icon() - else if(!CH.wired) - to_chat(user, "You need to attach wires to it first!") - else - to_chat(user, "You need to attach a cell to it first!") - - else if(istype(W, /obj/item/bodypart/head/robot)) - var/obj/item/bodypart/head/robot/HD = W - for(var/X in HD.contents) - if(istype(X, /obj/item/organ)) - to_chat(user, "There are organs inside [HD]!") - return - if(head) - return - if(HD.flash2 && HD.flash1) - if(!user.transferItemToLoc(HD, src)) - return - HD.icon_state = initial(HD.icon_state)//in case it is a dismembered robotic limb - HD.cut_overlays() - head = HD - update_icon() - else - to_chat(user, "You need to attach a flash to it first!") - - else if (W.tool_behaviour == TOOL_MULTITOOL) - if(check_completion()) - Interact(user) - else - to_chat(user, "The endoskeleton must be assembled before debugging can begin!") - - else if(istype(W, /obj/item/mmi)) - var/obj/item/mmi/M = W - if(check_completion()) - if(!chest.cell) - to_chat(user, "The endoskeleton still needs a power cell!") - return - if(!isturf(loc)) - to_chat(user, "You can't put [M] in, the frame has to be standing on the ground to be perfectly precise!") - return - if(!M.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 || BM.suiciding || (M.brain && (M.brain.brain_death || M.brain.suicided))) - to_chat(user, "Sticking a dead brain into the frame would sort of defeat the purpose!") - return - - if(M.brain?.organ_flags & ORGAN_FAILING) - to_chat(user, "The MMI indicates that the brain is damaged!") - return - - if(is_banned_from(BM.ckey, "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) - - 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(BM.client) - - 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.") - - SSblackbox.record_feedback("amount", "cyborg_birth", 1) - forceMove(O) - O.robot_suit = src - - if(!locomotion) - O.lockcharge = TRUE - O.update_mobility() - to_chat(O, "Error: Servo motors unresponsive.") - - else - to_chat(user, "The MMI must go in after everything else!") - - else if(istype(W, /obj/item/borg/upgrade/ai)) - var/obj/item/borg/upgrade/ai/M = W - if(check_completion()) - if(!isturf(loc)) - to_chat(user, "You cannot install[M], the frame has to be standing on the ground to be perfectly precise!") - return - if(!user.temporarilyRemoveItemFromInventory(M)) - to_chat(user, "[M] is stuck to your hand!") - return - qdel(M) - var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/shell(get_turf(src)) - - if(!aisync) - lawsync = FALSE - O.connected_ai = null - else - if(forced_ai) - O.connected_ai = forced_ai - O.notify_ai(AI_SHELL) - if(!lawsync) - O.lawupdate = FALSE - O.make_laws() - - - O.cell = chest.cell - chest.cell.forceMove(O) - chest.cell = null - O.locked = panel_locked - O.job = "Cyborg" - forceMove(O) - O.robot_suit = src - if(!locomotion) - O.lockcharge = TRUE - O.update_mobility() - - else if(istype(W, /obj/item/pen)) - to_chat(user, "You need to use a multitool to name [src]!") - else - return ..() - -/obj/item/robot_suit/proc/Interact(mob/user) - var/t1 = "Designation: [(created_name ? "[created_name]" : "Default Cyborg")]
                \n" - t1 += "Master AI: [(forced_ai ? "[forced_ai.name]" : "Automatic")]

                \n" - - t1 += "LawSync Port: [(lawsync ? "Open" : "Closed")]
                \n" - t1 += "AI Connection Port: [(aisync ? "Open" : "Closed")]
                \n" - t1 += "Servo Motor Functions: [(locomotion ? "Unlocked" : "Locked")]
                \n" - t1 += "Panel Lock: [(panel_locked ? "Engaged" : "Disengaged")]
                \n" - var/datum/browser/popup = new(user, "robotdebug", "Cyborg Boot Debug", 310, 220) - popup.set_content(t1) - popup.open() - -/obj/item/robot_suit/Topic(href, href_list) - if(usr.incapacitated() || !Adjacent(usr)) - return - - var/mob/living/living_user = usr - var/obj/item/item_in_hand = living_user.get_active_held_item() - if(!item_in_hand || item_in_hand.tool_behaviour != TOOL_MULTITOOL) - to_chat(living_user, "You need a multitool!") - return - - if(href_list["Name"]) - var/new_name = reject_bad_name(input(usr, "Enter new designation. Set to blank to reset to default.", "Cyborg Debug", src.created_name),1) - if(!in_range(src, usr) && src.loc != usr) - return - if(new_name) - created_name = new_name - else - created_name = "" - - else if(href_list["Master"]) - forced_ai = select_active_ai(usr) - 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" + w_class = WEIGHT_CLASS_BULKY // yogs - can not fit in backpacks + var/obj/item/bodypart/l_arm/robot/l_arm = null + var/obj/item/bodypart/r_arm/robot/r_arm = null + var/obj/item/bodypart/l_leg/robot/l_leg = null + var/obj/item/bodypart/r_leg/robot/r_leg = null + var/obj/item/bodypart/chest/robot/chest = null + var/obj/item/bodypart/head/robot/head = null + + var/created_name = "" + var/mob/living/silicon/ai/forced_ai + var/locomotion = 1 + var/lawsync = 1 + var/aisync = 1 + var/panel_locked = TRUE + +/obj/item/robot_suit/Initialize() + . = ..() + update_icon() + +/obj/item/robot_suit/prebuilt/Initialize() + . = ..() + l_arm = new(src) + r_arm = new(src) + l_leg = new(src) + r_leg = new(src) + head = new(src) + head.flash1 = new(head) + head.flash2 = new(head) + chest = new(src) + chest.wired = TRUE + chest.cell = new /obj/item/stock_parts/cell/high/plus(chest) + +/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(l_leg) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + l_leg = W + update_icon() + + else if(istype(W, /obj/item/bodypart/r_leg/robot)) + if(src.r_leg) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + r_leg = W + update_icon() + + else if(istype(W, /obj/item/bodypart/l_arm/robot)) + if(l_arm) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + l_arm = W + update_icon() + + else if(istype(W, /obj/item/bodypart/r_arm/robot)) + if(r_arm) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state)//in case it is a dismembered robotic limb + W.cut_overlays() + r_arm = W + update_icon() + + else if(istype(W, /obj/item/bodypart/chest/robot)) + var/obj/item/bodypart/chest/robot/CH = W + if(chest) + return + if(CH.wired && CH.cell) + if(!user.transferItemToLoc(CH, src)) + return + CH.icon_state = initial(CH.icon_state) //in case it is a dismembered robotic limb + CH.cut_overlays() + chest = CH + update_icon() + else if(!CH.wired) + to_chat(user, "You need to attach wires to it first!") + else + to_chat(user, "You need to attach a cell to it first!") + + else if(istype(W, /obj/item/bodypart/head/robot)) + var/obj/item/bodypart/head/robot/HD = W + for(var/X in HD.contents) + if(istype(X, /obj/item/organ)) + to_chat(user, "There are organs inside [HD]!") + return + if(head) + return + if(HD.flash2 && HD.flash1) + if(!user.transferItemToLoc(HD, src)) + return + HD.icon_state = initial(HD.icon_state)//in case it is a dismembered robotic limb + HD.cut_overlays() + head = HD + update_icon() + else + to_chat(user, "You need to attach a flash to it first!") + + else if (W.tool_behaviour == TOOL_MULTITOOL) + if(check_completion()) + Interact(user) + else + to_chat(user, "The endoskeleton must be assembled before debugging can begin!") + + else if(istype(W, /obj/item/mmi)) + var/obj/item/mmi/M = W + if(check_completion()) + if(!chest.cell) + to_chat(user, "The endoskeleton still needs a power cell!") + return + if(!isturf(loc)) + to_chat(user, "You can't put [M] in, the frame has to be standing on the ground to be perfectly precise!") + return + if(!M.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 || BM.suiciding || (M.brain && (M.brain.brain_death || M.brain.suicided))) + to_chat(user, "Sticking a dead brain into the frame would sort of defeat the purpose!") + return + + if(M.brain?.organ_flags & ORGAN_FAILING) + to_chat(user, "The MMI indicates that the brain is damaged!") + return + + if(is_banned_from(BM.ckey, "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) + + 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(BM.client) + + 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.") + + SSblackbox.record_feedback("amount", "cyborg_birth", 1) + forceMove(O) + O.robot_suit = src + + if(!locomotion) + O.lockcharge = TRUE + O.update_mobility() + to_chat(O, "Error: Servo motors unresponsive.") + + else + to_chat(user, "The MMI must go in after everything else!") + + else if(istype(W, /obj/item/borg/upgrade/ai)) + var/obj/item/borg/upgrade/ai/M = W + if(check_completion()) + if(!isturf(loc)) + to_chat(user, "You cannot install[M], the frame has to be standing on the ground to be perfectly precise!") + return + if(!user.temporarilyRemoveItemFromInventory(M)) + to_chat(user, "[M] is stuck to your hand!") + return + qdel(M) + var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/shell(get_turf(src)) + + if(!aisync) + lawsync = FALSE + O.connected_ai = null + else + if(forced_ai) + O.connected_ai = forced_ai + O.notify_ai(AI_SHELL) + if(!lawsync) + O.lawupdate = FALSE + O.make_laws() + + + O.cell = chest.cell + chest.cell.forceMove(O) + chest.cell = null + O.locked = panel_locked + O.job = "Cyborg" + forceMove(O) + O.robot_suit = src + if(!locomotion) + O.lockcharge = TRUE + O.update_mobility() + + else if(istype(W, /obj/item/pen)) + to_chat(user, "You need to use a multitool to name [src]!") + else + return ..() + +/obj/item/robot_suit/proc/Interact(mob/user) + var/t1 = "Designation: [(created_name ? "[created_name]" : "Default Cyborg")]
                \n" + t1 += "Master AI: [(forced_ai ? "[forced_ai.name]" : "Automatic")]

                \n" + + t1 += "LawSync Port: [(lawsync ? "Open" : "Closed")]
                \n" + t1 += "AI Connection Port: [(aisync ? "Open" : "Closed")]
                \n" + t1 += "Servo Motor Functions: [(locomotion ? "Unlocked" : "Locked")]
                \n" + t1 += "Panel Lock: [(panel_locked ? "Engaged" : "Disengaged")]
                \n" + var/datum/browser/popup = new(user, "robotdebug", "Cyborg Boot Debug", 310, 220) + popup.set_content(t1) + popup.open() + +/obj/item/robot_suit/Topic(href, href_list) + if(usr.incapacitated() || !Adjacent(usr)) + return + + var/mob/living/living_user = usr + var/obj/item/item_in_hand = living_user.get_active_held_item() + if(!item_in_hand || item_in_hand.tool_behaviour != TOOL_MULTITOOL) + to_chat(living_user, "You need a multitool!") + return + + if(href_list["Name"]) + var/new_name = reject_bad_name(input(usr, "Enter new designation. Set to blank to reset to default.", "Cyborg Debug", src.created_name),1) + if(!in_range(src, usr) && src.loc != usr) + return + if(new_name) + created_name = new_name + else + created_name = "" + + else if(href_list["Master"]) + forced_ai = select_active_ai(usr) + 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 609fed0463fe..6b2debd9de47 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -1,664 +1,664 @@ -// 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/module_type = null - // if true, is not stored in the robot to be ejected - // if module is reset - var/one_use = FALSE - -/obj/item/borg/upgrade/proc/action(mob/living/silicon/robot/R, user = usr) - if(R.stat == DEAD) - to_chat(user, "[src] will not function on a deceased cyborg.") - return FALSE - if(module_type && !istype(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.speed < 0) - 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. - -/obj/item/borg/upgrade/vtec/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.speed = initial(R.speed) - -/obj/item/borg/upgrade/disablercooler - name = "cyborg rapid disabler cooling module" - desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = /obj/item/robot_module/security - -/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules - if(!T) - to_chat(user, "There's no disabler in this unit!") - return FALSE - if(T.charge_delay <= 2) - to_chat(R, "A cooling unit is already installed!") - to_chat(user, "There's no room for another cooling unit!") - return FALSE - - T.charge_delay = max(2 , T.charge_delay - 4) - -/obj/item/borg/upgrade/disablercooler/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules - if(!T) - return FALSE - T.charge_delay = initial(T.charge_delay) - -/obj/item/borg/upgrade/thrusters - name = "ion thruster upgrade" - desc = "An energy-operated thruster system for cyborgs." - icon_state = "cyborg_upgrade3" - -/obj/item/borg/upgrade/thrusters/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(R.ionpulse) - to_chat(user, "This unit already has ion thrusters installed!") - return FALSE - - R.ionpulse = TRUE - -/obj/item/borg/upgrade/thrusters/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.ionpulse = FALSE - -/obj/item/borg/upgrade/ddrill - name = "mining cyborg diamond drill" - desc = "A diamond drill replacement for the mining module's standard drill." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = /obj/item/robot_module/miner - -/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/pickaxe/drill/cyborg/D in R.module) - R.module.remove_module(D, TRUE) - for(var/obj/item/shovel/S in R.module) - R.module.remove_module(S, TRUE) - - var/obj/item/pickaxe/drill/cyborg/diamond/DD = new /obj/item/pickaxe/drill/cyborg/diamond(R.module) - R.module.basic_modules += DD - R.module.add_module(DD, FALSE, TRUE) - -/obj/item/borg/upgrade/ddrill/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/pickaxe/drill/cyborg/diamond/DD in R.module) - R.module.remove_module(DD, TRUE) - - var/obj/item/pickaxe/drill/cyborg/D = new (R.module) - R.module.basic_modules += D - R.module.add_module(D, FALSE, TRUE) - var/obj/item/shovel/S = new (R.module) - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/soh - name = "mining cyborg satchel of holding" - desc = "A satchel of holding replacement for mining cyborg's ore satchel module." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = /obj/item/robot_module/miner - -/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R , user = usr) //yogs single line - . = ..() - if(.) - for(var/obj/item/storage/bag/ore/cyborg/S in R.module) - R.module.remove_module(S, TRUE) - - var/obj/item/storage/bag/ore/holding/H = locate() in R //yogs start - if(H) - to_chat(user, "This unit is already equipped with a satchel of holding.") - return FALSE - - H = new /obj/item/storage/bag/ore/holding(R.module) //yogs end - R.module.basic_modules += H - R.module.add_module(H, FALSE, TRUE) - -/obj/item/borg/upgrade/soh/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/storage/bag/ore/holding/H in R.module) - R.module.remove_module(H, TRUE) - - var/obj/item/storage/bag/ore/cyborg/S = new (R.module) - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/tboh - name = "janitor cyborg trash bag of holding" - desc = "A trash bag of holding replacement for the janiborg's standard trash bag." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = /obj/item/robot_module/janitor - -/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R, user = usr)//yogs single line - . = ..() - 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 = locate() in R //yogs start - if(B) - to_chat(user, "This unit is already equipped with a trash bag of holding.") - return FALSE - - B = new /obj/item/storage/bag/trash/bluespace/cyborg(R.module) //yogs end - 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 = /obj/item/robot_module/janitor - -/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R, user = usr)//yogs single line - . = ..() - 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 = locate() in R //yogs start - if(A) - to_chat(user, "This unit is already equipped with a advanced mop module.") - return FALSE - - A = new /obj/item/mop/advanced/cyborg(R.module) //yogs end - 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 = /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 = /obj/item/robot_module/medical - var/list/additional_reagents = list() - -/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - if(H.accepts_reagent_upgrades) - for(var/re in additional_reagents) - H.add_reagent(re) - -/obj/item/borg/upgrade/hypospray/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - if(H.accepts_reagent_upgrades) - for(var/re in additional_reagents) - H.del_reagent(re) - -/obj/item/borg/upgrade/hypospray/expanded - name = "medical cyborg expanded hypospray" - desc = "An upgrade to the Medical module's hypospray, allowing it \ - to treat a wider range of conditions and problems." - additional_reagents = list(/datum/reagent/medicine/mannitol, /datum/reagent/medicine/oculine, /datum/reagent/medicine/inacusiate, - /datum/reagent/medicine/mutadone, /datum/reagent/medicine/haloperidol, /datum/reagent/medicine/oxandrolone, /datum/reagent/medicine/sal_acid, /datum/reagent/medicine/rezadone, - /datum/reagent/medicine/pen_acid) - -/obj/item/borg/upgrade/piercing_hypospray - name = "cyborg piercing hypospray" - desc = "An upgrade to a cyborg's hypospray, allowing it to \ - pierce armor and thick material." - icon_state = "cyborg_upgrade3" - -/obj/item/borg/upgrade/piercing_hypospray/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/found_hypo = FALSE - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - if(H.bypass_protection == TRUE) //yogs start - to_chat(user, "This unit is already equipped with a piercing hypospray module.") - return FALSE //yogs end - - H.bypass_protection = TRUE - found_hypo = TRUE - - if(!found_hypo) - return FALSE - -/obj/item/borg/upgrade/piercing_hypospray/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - H.bypass_protection = initial(H.bypass_protection) - -/obj/item/borg/upgrade/defib - name = "medical cyborg defibrillator" - desc = "An upgrade to the Medical module, installing a built-in \ - defibrillator, for on the scene revival." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = /obj/item/robot_module/medical - -/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/twohanded/shockpaddles/cyborg/S = locate() in R //yogs start - if(S) - to_chat(user, "This unit is already equipped with a defibrillator module.") - return FALSE - - S = new(R.module) //yogs end - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/defib/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/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 = /obj/item/robot_module/medical - -/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/surgical_processor/SP = locate() in R //yogs start - if(SP) - to_chat(user, "This unit is already equipped with a surgical processor module.") - return FALSE - - SP = new(R.module) //yogs end - R.module.basic_modules += SP - R.module.add_module(SP, FALSE, TRUE) - -/obj/item/borg/upgrade/processor/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/surgical_processor/SP = locate() in R.module - R.module.remove_module(SP, TRUE) - -/obj/item/borg/upgrade/ai - name = "B.O.R.I.S. module" - desc = "Bluespace Optimized Remote Intelligence Synchronization. An uplink device which takes the place of an MMI in cyborg endoskeletons, creating a robotic shell controlled by an AI." - icon_state = "boris" - -/obj/item/borg/upgrade/ai/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(R.shell) - to_chat(user, "This unit is already an AI shell!") - return FALSE - if(R.key) //You cannot replace a player unless the key is completely removed. - to_chat(user, "Intelligence patterns detected in this [R.braintype]. Aborting.") - return FALSE - - R.make_shell(src) - -/obj/item/borg/upgrade/ai/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - if(R.shell) - R.undeploy() - R.notify_ai(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 (.) - if (R.hasExpanded) - R.hasExpanded = FALSE - R.resize = 0.5 - R.update_transform() - -/obj/item/borg/upgrade/rped - name = "engineering cyborg RPED" - desc = "A rapid part exchange device for the engineering cyborg." - icon = 'icons/obj/storage.dmi' - icon_state = "borgrped" - require_module = TRUE - module_type = /obj/item/robot_module/engineering - -/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - - var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R - if(RPED) - to_chat(user, "This unit is already equipped with a RPED module.") - return FALSE - - RPED = new(R.module) - R.module.basic_modules += RPED - R.module.add_module(RPED, FALSE, TRUE) - -/obj/item/borg/upgrade/rped/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R.module - if (RPED) - R.module.remove_module(RPED, TRUE) - -/obj/item/borg/upgrade/pinpointer - name = "medical cyborg crew pinpointer" - desc = "A crew pinpointer module for the medical cyborg." - icon = 'icons/obj/device.dmi' - icon_state = "pinpointer_crew" - require_module = TRUE - module_type = /obj/item/robot_module/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 +// 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/module_type = null + // if true, is not stored in the robot to be ejected + // if module is reset + var/one_use = FALSE + +/obj/item/borg/upgrade/proc/action(mob/living/silicon/robot/R, user = usr) + if(R.stat == DEAD) + to_chat(user, "[src] will not function on a deceased cyborg.") + return FALSE + if(module_type && !istype(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.speed < 0) + 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. + +/obj/item/borg/upgrade/vtec/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.speed = initial(R.speed) + +/obj/item/borg/upgrade/disablercooler + name = "cyborg rapid disabler cooling module" + desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = /obj/item/robot_module/security + +/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules + if(!T) + to_chat(user, "There's no disabler in this unit!") + return FALSE + if(T.charge_delay <= 2) + to_chat(R, "A cooling unit is already installed!") + to_chat(user, "There's no room for another cooling unit!") + return FALSE + + T.charge_delay = max(2 , T.charge_delay - 4) + +/obj/item/borg/upgrade/disablercooler/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules + if(!T) + return FALSE + T.charge_delay = initial(T.charge_delay) + +/obj/item/borg/upgrade/thrusters + name = "ion thruster upgrade" + desc = "An energy-operated thruster system for cyborgs." + icon_state = "cyborg_upgrade3" + +/obj/item/borg/upgrade/thrusters/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(R.ionpulse) + to_chat(user, "This unit already has ion thrusters installed!") + return FALSE + + R.ionpulse = TRUE + +/obj/item/borg/upgrade/thrusters/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.ionpulse = FALSE + +/obj/item/borg/upgrade/ddrill + name = "mining cyborg diamond drill" + desc = "A diamond drill replacement for the mining module's standard drill." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = /obj/item/robot_module/miner + +/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/pickaxe/drill/cyborg/D in R.module) + R.module.remove_module(D, TRUE) + for(var/obj/item/shovel/S in R.module) + R.module.remove_module(S, TRUE) + + var/obj/item/pickaxe/drill/cyborg/diamond/DD = new /obj/item/pickaxe/drill/cyborg/diamond(R.module) + R.module.basic_modules += DD + R.module.add_module(DD, FALSE, TRUE) + +/obj/item/borg/upgrade/ddrill/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/pickaxe/drill/cyborg/diamond/DD in R.module) + R.module.remove_module(DD, TRUE) + + var/obj/item/pickaxe/drill/cyborg/D = new (R.module) + R.module.basic_modules += D + R.module.add_module(D, FALSE, TRUE) + var/obj/item/shovel/S = new (R.module) + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/soh + name = "mining cyborg satchel of holding" + desc = "A satchel of holding replacement for mining cyborg's ore satchel module." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = /obj/item/robot_module/miner + +/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R , user = usr) //yogs single line + . = ..() + if(.) + for(var/obj/item/storage/bag/ore/cyborg/S in R.module) + R.module.remove_module(S, TRUE) + + var/obj/item/storage/bag/ore/holding/H = locate() in R //yogs start + if(H) + to_chat(user, "This unit is already equipped with a satchel of holding.") + return FALSE + + H = new /obj/item/storage/bag/ore/holding(R.module) //yogs end + R.module.basic_modules += H + R.module.add_module(H, FALSE, TRUE) + +/obj/item/borg/upgrade/soh/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/storage/bag/ore/holding/H in R.module) + R.module.remove_module(H, TRUE) + + var/obj/item/storage/bag/ore/cyborg/S = new (R.module) + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/tboh + name = "janitor cyborg trash bag of holding" + desc = "A trash bag of holding replacement for the janiborg's standard trash bag." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = /obj/item/robot_module/janitor + +/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R, user = usr)//yogs single line + . = ..() + 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 = locate() in R //yogs start + if(B) + to_chat(user, "This unit is already equipped with a trash bag of holding.") + return FALSE + + B = new /obj/item/storage/bag/trash/bluespace/cyborg(R.module) //yogs end + 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 = /obj/item/robot_module/janitor + +/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R, user = usr)//yogs single line + . = ..() + 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 = locate() in R //yogs start + if(A) + to_chat(user, "This unit is already equipped with a advanced mop module.") + return FALSE + + A = new /obj/item/mop/advanced/cyborg(R.module) //yogs end + 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 = /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 = /obj/item/robot_module/medical + var/list/additional_reagents = list() + +/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + if(H.accepts_reagent_upgrades) + for(var/re in additional_reagents) + H.add_reagent(re) + +/obj/item/borg/upgrade/hypospray/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + if(H.accepts_reagent_upgrades) + for(var/re in additional_reagents) + H.del_reagent(re) + +/obj/item/borg/upgrade/hypospray/expanded + name = "medical cyborg expanded hypospray" + desc = "An upgrade to the Medical module's hypospray, allowing it \ + to treat a wider range of conditions and problems." + additional_reagents = list(/datum/reagent/medicine/mannitol, /datum/reagent/medicine/oculine, /datum/reagent/medicine/inacusiate, + /datum/reagent/medicine/mutadone, /datum/reagent/medicine/haloperidol, /datum/reagent/medicine/oxandrolone, /datum/reagent/medicine/sal_acid, /datum/reagent/medicine/rezadone, + /datum/reagent/medicine/pen_acid) + +/obj/item/borg/upgrade/piercing_hypospray + name = "cyborg piercing hypospray" + desc = "An upgrade to a cyborg's hypospray, allowing it to \ + pierce armor and thick material." + icon_state = "cyborg_upgrade3" + +/obj/item/borg/upgrade/piercing_hypospray/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/found_hypo = FALSE + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + if(H.bypass_protection == TRUE) //yogs start + to_chat(user, "This unit is already equipped with a piercing hypospray module.") + return FALSE //yogs end + + H.bypass_protection = TRUE + found_hypo = TRUE + + if(!found_hypo) + return FALSE + +/obj/item/borg/upgrade/piercing_hypospray/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + H.bypass_protection = initial(H.bypass_protection) + +/obj/item/borg/upgrade/defib + name = "medical cyborg defibrillator" + desc = "An upgrade to the Medical module, installing a built-in \ + defibrillator, for on the scene revival." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = /obj/item/robot_module/medical + +/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/twohanded/shockpaddles/cyborg/S = locate() in R //yogs start + if(S) + to_chat(user, "This unit is already equipped with a defibrillator module.") + return FALSE + + S = new(R.module) //yogs end + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/defib/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/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 = /obj/item/robot_module/medical + +/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/surgical_processor/SP = locate() in R //yogs start + if(SP) + to_chat(user, "This unit is already equipped with a surgical processor module.") + return FALSE + + SP = new(R.module) //yogs end + R.module.basic_modules += SP + R.module.add_module(SP, FALSE, TRUE) + +/obj/item/borg/upgrade/processor/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/surgical_processor/SP = locate() in R.module + R.module.remove_module(SP, TRUE) + +/obj/item/borg/upgrade/ai + name = "B.O.R.I.S. module" + desc = "Bluespace Optimized Remote Intelligence Synchronization. An uplink device which takes the place of an MMI in cyborg endoskeletons, creating a robotic shell controlled by an AI." + icon_state = "boris" + +/obj/item/borg/upgrade/ai/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(R.shell) + to_chat(user, "This unit is already an AI shell!") + return FALSE + if(R.key) //You cannot replace a player unless the key is completely removed. + to_chat(user, "Intelligence patterns detected in this [R.braintype]. Aborting.") + return FALSE + + R.make_shell(src) + +/obj/item/borg/upgrade/ai/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + if(R.shell) + R.undeploy() + R.notify_ai(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 (.) + if (R.hasExpanded) + R.hasExpanded = FALSE + R.resize = 0.5 + R.update_transform() + +/obj/item/borg/upgrade/rped + name = "engineering cyborg RPED" + desc = "A rapid part exchange device for the engineering cyborg." + icon = 'icons/obj/storage.dmi' + icon_state = "borgrped" + require_module = TRUE + module_type = /obj/item/robot_module/engineering + +/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + + var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R + if(RPED) + to_chat(user, "This unit is already equipped with a RPED module.") + return FALSE + + RPED = new(R.module) + R.module.basic_modules += RPED + R.module.add_module(RPED, FALSE, TRUE) + +/obj/item/borg/upgrade/rped/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R.module + if (RPED) + R.module.remove_module(RPED, TRUE) + +/obj/item/borg/upgrade/pinpointer + name = "medical cyborg crew pinpointer" + desc = "A crew pinpointer module for the medical cyborg." + icon = 'icons/obj/device.dmi' + icon_state = "pinpointer_crew" + require_module = TRUE + module_type = /obj/item/robot_module/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 diff --git a/code/game/objects/items/scrolls.dm b/code/game/objects/items/scrolls.dm index 28a4664a2449..d58f670dc4e4 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 6ea3dd7996a8..40c426903a5f 100644 --- a/code/game/objects/items/shields.dm +++ b/code/game/objects/items/shields.dm @@ -1,273 +1,273 @@ -/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) - var/transparent = FALSE // makes beam projectiles pass through the shield - -/obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) - return TRUE - -/obj/item/shield/riot - name = "riot shield" - desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." - icon = '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 - transparent = TRUE - max_integrity = 75 - -/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(transparent && (hitby.pass_flags & PASSGLASS)) - return FALSE - if(attack_type == THROWN_PROJECTILE_ATTACK) - final_block_chance += 30 - if(attack_type == LEAP_ATTACK) - final_block_chance = 100 - return ..() - -/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 if(istype(W, /obj/item/stack/sheet/mineral/titanium)) - if (obj_integrity >= max_integrity) - to_chat(user, "[src] is already in perfect condition.") - else - var/obj/item/stack/sheet/mineral/titanium/T = W - T.use(1) - obj_integrity = max_integrity - to_chat(user, "You repair [src] with [T].") - else - return ..() - -/obj/item/shield/riot/examine(mob/user) - . = ..() - var/healthpercent = round((obj_integrity/max_integrity) * 100, 1) - switch(healthpercent) - if(50 to 99) - . += "It looks slightly damaged." - if(25 to 50) - . += "It appears heavily damaged." - if(0 to 25) - . += "It's falling apart!" - -/obj/item/shield/riot/proc/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/glassbr3.ogg', 100) - new /obj/item/shard((get_turf(src))) - -/obj/item/shield/riot/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) - if (obj_integrity <= damage) - var/turf/T = get_turf(owner) - T.visible_message("[hitby] destroys [src]!") - shatter(owner) - qdel(src) - return FALSE - take_damage(damage) - return ..() - -/obj/item/shield/riot/roman - name = "\improper Roman shield" - desc = "Bears an inscription on the inside: \"Romanes venio domus\"." - icon_state = "roman_shield" - item_state = "roman_shield" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - transparent = FALSE - materials = list(MAT_METAL=8500) - max_integrity = 65 - -/obj/item/shield/riot/roman/fake - desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." - block_chance = 0 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - max_integrity = 30 - -/obj/item/shield/riot/roman/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/grillehit.ogg', 100) - new /obj/item/stack/sheet/metal(get_turf(src)) - -/obj/item/shield/riot/buckler - name = "wooden buckler" - desc = "A medieval wooden buckler." - icon_state = "buckler" - 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 - transparent = FALSE - max_integrity = 55 - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/shield/riot/buckler/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/bang.ogg', 50) - new /obj/item/stack/sheet/mineral/wood(get_turf(src)) - -/obj/item/shield/riot/flash - name = "strobe shield" - desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs." - icon_state = "flashshield" - item_state = "flashshield" - var/obj/item/assembly/flash/handheld/embedded_flash - -/obj/item/shield/riot/flash/Initialize() - . = ..() - embedded_flash = new(src) - -/obj/item/shield/riot/flash/attack(mob/living/M, mob/user) - . = embedded_flash.attack(M, user) - update_icon() - -/obj/item/shield/riot/flash/attack_self(mob/living/carbon/user) - . = embedded_flash.attack_self(user) - update_icon() - -/obj/item/shield/riot/flash/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - . = ..() - if (. && !embedded_flash.burnt_out) - embedded_flash.activate() - update_icon() - - -/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user) - if(istype(W, /obj/item/assembly/flash/handheld)) - var/obj/item/assembly/flash/handheld/flash = W - if(flash.burnt_out) - to_chat(user, "No sense replacing it with a broken bulb.") - return - else - to_chat(user, "You begin to replace the bulb.") - if(do_after(user, 20, target = user)) - if(flash.burnt_out || !flash || QDELETED(flash)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - qdel(embedded_flash) - embedded_flash = flash - flash.forceMove(src) - update_icon() - return - ..() - -/obj/item/shield/riot/flash/emp_act(severity) - . = ..() - embedded_flash.emp_act(severity) - update_icon() - -/obj/item/shield/riot/flash/update_icon() - if(!embedded_flash || embedded_flash.burnt_out) - icon_state = "riot" - item_state = "riot" - else - icon_state = "flashshield" - item_state = "flashshield" - -/obj/item/shield/riot/flash/examine(mob/user) - . = ..() - if (embedded_flash?.burnt_out) - . += "The mounted bulb has burnt out. You can try replacing it with a new one." - -/obj/item/shield/energy - name = "energy combat shield" - desc = "A shield that reflects almost all energy projectiles, but is useless against physical attacks. It can be retracted, expanded, and stored anywhere." - 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 + 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) + var/transparent = FALSE // makes beam projectiles pass through the shield + +/obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) + return TRUE + +/obj/item/shield/riot + name = "riot shield" + desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." + icon = '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 + transparent = TRUE + max_integrity = 75 + +/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(transparent && (hitby.pass_flags & PASSGLASS)) + return FALSE + if(attack_type == THROWN_PROJECTILE_ATTACK) + final_block_chance += 30 + if(attack_type == LEAP_ATTACK) + final_block_chance = 100 + return ..() + +/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 if(istype(W, /obj/item/stack/sheet/mineral/titanium)) + if (obj_integrity >= max_integrity) + to_chat(user, "[src] is already in perfect condition.") + else + var/obj/item/stack/sheet/mineral/titanium/T = W + T.use(1) + obj_integrity = max_integrity + to_chat(user, "You repair [src] with [T].") + else + return ..() + +/obj/item/shield/riot/examine(mob/user) + . = ..() + var/healthpercent = round((obj_integrity/max_integrity) * 100, 1) + switch(healthpercent) + if(50 to 99) + . += "It looks slightly damaged." + if(25 to 50) + . += "It appears heavily damaged." + if(0 to 25) + . += "It's falling apart!" + +/obj/item/shield/riot/proc/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/glassbr3.ogg', 100) + new /obj/item/shard((get_turf(src))) + +/obj/item/shield/riot/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) + if (obj_integrity <= damage) + var/turf/T = get_turf(owner) + T.visible_message("[hitby] destroys [src]!") + shatter(owner) + qdel(src) + return FALSE + take_damage(damage) + return ..() + +/obj/item/shield/riot/roman + name = "\improper Roman shield" + desc = "Bears an inscription on the inside: \"Romanes venio domus\"." + icon_state = "roman_shield" + item_state = "roman_shield" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + transparent = FALSE + materials = list(MAT_METAL=8500) + max_integrity = 65 + +/obj/item/shield/riot/roman/fake + desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." + block_chance = 0 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + max_integrity = 30 + +/obj/item/shield/riot/roman/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/grillehit.ogg', 100) + new /obj/item/stack/sheet/metal(get_turf(src)) + +/obj/item/shield/riot/buckler + name = "wooden buckler" + desc = "A medieval wooden buckler." + icon_state = "buckler" + 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 + transparent = FALSE + max_integrity = 55 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/shield/riot/buckler/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/bang.ogg', 50) + new /obj/item/stack/sheet/mineral/wood(get_turf(src)) + +/obj/item/shield/riot/flash + name = "strobe shield" + desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs." + icon_state = "flashshield" + item_state = "flashshield" + var/obj/item/assembly/flash/handheld/embedded_flash + +/obj/item/shield/riot/flash/Initialize() + . = ..() + embedded_flash = new(src) + +/obj/item/shield/riot/flash/attack(mob/living/M, mob/user) + . = embedded_flash.attack(M, user) + update_icon() + +/obj/item/shield/riot/flash/attack_self(mob/living/carbon/user) + . = embedded_flash.attack_self(user) + update_icon() + +/obj/item/shield/riot/flash/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + . = ..() + if (. && !embedded_flash.burnt_out) + embedded_flash.activate() + update_icon() + + +/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user) + if(istype(W, /obj/item/assembly/flash/handheld)) + var/obj/item/assembly/flash/handheld/flash = W + if(flash.burnt_out) + to_chat(user, "No sense replacing it with a broken bulb.") + return + else + to_chat(user, "You begin to replace the bulb.") + if(do_after(user, 20, target = user)) + if(flash.burnt_out || !flash || QDELETED(flash)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + qdel(embedded_flash) + embedded_flash = flash + flash.forceMove(src) + update_icon() + return + ..() + +/obj/item/shield/riot/flash/emp_act(severity) + . = ..() + embedded_flash.emp_act(severity) + update_icon() + +/obj/item/shield/riot/flash/update_icon() + if(!embedded_flash || embedded_flash.burnt_out) + icon_state = "riot" + item_state = "riot" + else + icon_state = "flashshield" + item_state = "flashshield" + +/obj/item/shield/riot/flash/examine(mob/user) + . = ..() + if (embedded_flash?.burnt_out) + . += "The mounted bulb has burnt out. You can try replacing it with a new one." + +/obj/item/shield/energy + name = "energy combat shield" + desc = "A shield that reflects almost all energy projectiles, but is useless against physical attacks. It can be retracted, expanded, and stored anywhere." + 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) diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 3e8ca31f77d5..2f40604719a8 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 BULLET_ACT_HIT - return BULLET_ACT_FORCE_PIERCE - -#undef DECALTYPE_SCORCH -#undef DECALTYPE_BULLET +/obj/item/target + name = "shooting target" + desc = "A shooting target." + icon = 'icons/obj/objects.dmi' + icon_state = "target_h" + density = FALSE + var/hp = 1800 + var/obj/structure/target_stake/pinnedLoc + +/obj/item/target/Destroy() + removeOverlays() + if(pinnedLoc) + pinnedLoc.nullPinnedTarget() + return ..() + +/obj/item/target/proc/nullPinnedLoc() + pinnedLoc = null + density = FALSE + +/obj/item/target/proc/removeOverlays() + cut_overlays() + +/obj/item/target/Move() + . = ..() + if(pinnedLoc) + pinnedLoc.forceMove(loc) + +/obj/item/target/welder_act(mob/living/user, obj/item/I) + if(I.use_tool(src, user, 0, volume=40)) + removeOverlays() + to_chat(user, "You slice off [src]'s uneven chunks of aluminium and scorch marks.") + return TRUE + +/obj/item/target/attack_hand(mob/user) + . = ..() + if(.) + return + if(pinnedLoc) + pinnedLoc.removeTarget(user) + +/obj/item/target/syndicate + icon_state = "target_s" + desc = "A shooting target that looks like syndicate scum." + hp = 2600 + +/obj/item/target/alien + icon_state = "target_q" + desc = "A shooting target that looks like a xenomorphic alien." + hp = 2350 + +/obj/item/target/alien/anchored + anchored = TRUE + +/obj/item/target/clown + icon_state = "target_c" + desc = "A shooting target that looks like a useless clown." + hp = 2000 + +#define DECALTYPE_SCORCH 1 +#define DECALTYPE_BULLET 2 + +/obj/item/target/clown/bullet_act(obj/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 BULLET_ACT_HIT + return BULLET_ACT_FORCE_PIERCE + +#undef DECALTYPE_SCORCH +#undef DECALTYPE_BULLET diff --git a/code/game/objects/items/singularityhammer.dm b/code/game/objects/items/singularityhammer.dm index e950689f7e39..f919be0fd806 100644 --- a/code/game/objects/items/singularityhammer.dm +++ b/code/game/objects/items/singularityhammer.dm @@ -1,113 +1,113 @@ -/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" - -/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_PARALYZE, 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 - -/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/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(isliving(hit_atom)) - shock(hit_atom) - -/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" + +/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_PARALYZE, 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 + +/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/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(isliving(hit_atom)) + shock(hit_atom) + +/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 7df139201a15..d7ebf8cd7af0 100644 --- a/code/game/objects/items/stacks/bscrystal.dm +++ b/code/game/objects/items/stacks/bscrystal.dm @@ -1,90 +1,90 @@ -//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" - item_color = "cosmos" - 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, datum/thrownthing/throwingdatum) - if(!..()) // not caught in mid-air - visible_message("[src] fizzles and disappears upon impact!") - var/turf/T = get_turf(hit_atom) - new /obj/effect/particle_effect/sparks(T) - playsound(loc, "sparks", 50, 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" + item_color = "cosmos" + 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, datum/thrownthing/throwingdatum) + if(!..()) // not caught in mid-air + visible_message("[src] fizzles and disappears upon impact!") + var/turf/T = get_turf(hit_atom) + new /obj/effect/particle_effect/sparks(T) + playsound(loc, "sparks", 50, 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 782f795e45e4..3028f5fc70d6 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -1,151 +1,151 @@ -/obj/item/stack/medical - name = "medical pack" - singular_name = "medical pack" - icon = 'icons/obj/stack_objects.dmi' - amount = 6 - max_amount = 6 - w_class = WEIGHT_CLASS_TINY - full_w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - resistance_flags = FLAMMABLE - max_integrity = 40 - novariants = FALSE - item_flags = NOBLUDGEON - var/self_delay = 50 - -/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/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 = 40 - 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, but does not heal wounds." - gender = PLURAL - singular_name = "medical gauze" - icon_state = "gauze" - var/stop_bleeding = 1800 - self_delay = 20 - max_amount = 12 - -/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.is_sharp()) - 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 - -/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 = 40 - 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 = 6 + max_amount = 6 + w_class = WEIGHT_CLASS_TINY + full_w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + resistance_flags = FLAMMABLE + max_integrity = 40 + novariants = FALSE + item_flags = NOBLUDGEON + var/self_delay = 50 + +/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/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 = 40 + 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, but does not heal wounds." + gender = PLURAL + singular_name = "medical gauze" + icon_state = "gauze" + var/stop_bleeding = 1800 + self_delay = 20 + max_amount = 12 + +/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.is_sharp()) + 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 + +/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 = 40 + 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 4cc2363d88b1..757d7f75f986 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -1,95 +1,95 @@ -GLOBAL_LIST_INIT(rod_recipes, list ( \ - new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = TRUE, on_floor = FALSE), \ - new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 25, one_per_turf = 0), \ - new/datum/stack_recipe("linen bin", /obj/structure/bedsheetbin/empty, 2, time = 5, one_per_turf = 0), \ - // yogs start - null, \ - new/datum/stack_recipe("fore port spacepod frame", /obj/item/pod_parts/pod_frame/fore_port, 15, time = 30, one_per_turf = 0), \ - new/datum/stack_recipe("fore starboard spacepod frame", /obj/item/pod_parts/pod_frame/fore_starboard, 15, time = 30, one_per_turf = 0), \ - new/datum/stack_recipe("aft port spacepod frame", /obj/item/pod_parts/pod_frame/aft_port, 15, time = 30, one_per_turf = 0), \ - new/datum/stack_recipe("aft starboard spacepod frame", /obj/item/pod_parts/pod_frame/aft_starboard, 15, time = 30, one_per_turf = 0), \ - // yogs end - )) - -/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(W.tool_behaviour == TOOL_WELDER) - if(get_amount() < 2) - to_chat(user, "You need at least two rods to do this!") - return - - if(W.use_tool(src, user, 0, volume=40)) - var/obj/item/stack/sheet/metal/new_item = new(usr.loc) - user.visible_message("[user.name] shaped [src] into metal with [W].", \ - "You shape [src] into metal with [W].", \ - "You hear welding.") - var/obj/item/stack/rods/R = src - src = null - var/replace = (user.get_inactive_held_item()==R) - R.use(2) - if (!R && replace) - user.put_in_hands(new_item) - - else if(istype(W, /obj/item/reagent_containers/food/snacks)) - var/obj/item/reagent_containers/food/snacks/S = W - if(amount != 1) - to_chat(user, "You must use a single rod!") - else if(S.w_class > WEIGHT_CLASS_SMALL) - to_chat(user, "The ingredient is too big for [src]!") - else - var/obj/item/reagent_containers/food/snacks/customizable/A = new/obj/item/reagent_containers/food/snacks/customizable/kebab(get_turf(src)) - A.initialize_custom_food(src, S, user) - else - return ..() - -/obj/item/stack/rods/cyborg - 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 = TRUE, on_floor = FALSE), \ + new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 25, one_per_turf = 0), \ + new/datum/stack_recipe("linen bin", /obj/structure/bedsheetbin/empty, 2, time = 5, one_per_turf = 0), \ + // yogs start + null, \ + new/datum/stack_recipe("fore port spacepod frame", /obj/item/pod_parts/pod_frame/fore_port, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("fore starboard spacepod frame", /obj/item/pod_parts/pod_frame/fore_starboard, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("aft port spacepod frame", /obj/item/pod_parts/pod_frame/aft_port, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("aft starboard spacepod frame", /obj/item/pod_parts/pod_frame/aft_starboard, 15, time = 30, one_per_turf = 0), \ + // yogs end + )) + +/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(W.tool_behaviour == TOOL_WELDER) + if(get_amount() < 2) + to_chat(user, "You need at least two rods to do this!") + return + + if(W.use_tool(src, user, 0, volume=40)) + var/obj/item/stack/sheet/metal/new_item = new(usr.loc) + user.visible_message("[user.name] shaped [src] into metal with [W].", \ + "You shape [src] into metal with [W].", \ + "You hear welding.") + var/obj/item/stack/rods/R = src + src = null + var/replace = (user.get_inactive_held_item()==R) + R.use(2) + if (!R && replace) + user.put_in_hands(new_item) + + else if(istype(W, /obj/item/reagent_containers/food/snacks)) + var/obj/item/reagent_containers/food/snacks/S = W + if(amount != 1) + to_chat(user, "You must use a single rod!") + else if(S.w_class > WEIGHT_CLASS_SMALL) + to_chat(user, "The ingredient is too big for [src]!") + else + var/obj/item/reagent_containers/food/snacks/customizable/A = new/obj/item/reagent_containers/food/snacks/customizable/kebab(get_turf(src)) + A.initialize_custom_food(src, S, user) + else + return ..() + +/obj/item/stack/rods/cyborg + 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 49b9caeca212..a4556ecea1b3 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -1,339 +1,339 @@ -/* 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 - -/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/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 if(istype(W, /obj/item/lightreplacer/cyborg)) //yogs start janiborgs can refill lightreplacers with glass now - var/obj/item/lightreplacer/cyborg/G = W - if(G.uses >= G.max_uses) - to_chat(user, "[W.name] is full.") - return - else if(src.use(1)) - G.AddUses(G.increment) - to_chat(user, "You insert a piece of glass into \the [G.name]. You have [G.uses] light\s remaining.") - return - else - to_chat(user, "You need one sheet of glass to replace lights!") //yogs end - 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) - -/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]" - -/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" +/* 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 + +/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/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 if(istype(W, /obj/item/lightreplacer/cyborg)) //yogs start janiborgs can refill lightreplacers with glass now + var/obj/item/lightreplacer/cyborg/G = W + if(G.uses >= G.max_uses) + to_chat(user, "[W.name] is full.") + return + else if(src.use(1)) + G.AddUses(G.increment) + to_chat(user, "You insert a piece of glass into \the [G.name]. You have [G.uses] light\s remaining.") + return + else + to_chat(user, "You need one sheet of glass to replace lights!") //yogs end + 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) + +/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]" + +/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" diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index 228c87154f57..c1ea557535e4 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -1,247 +1,247 @@ -/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), \ - )) - -/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/cable/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.is_sharp()) - 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), \ + )) + +/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/cable/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.is_sharp()) + 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 c42cfbe571b9..d0a916da3445 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 53388ef325e8..9ca043ba19e5 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 af44d8bd018e..8ddbf929968e 100644 --- a/code/game/objects/items/storage/backpack.dm +++ b/code/game/objects/items/storage/backpack.dm @@ -1,610 +1,610 @@ -/* 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 bluespace." - 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 - -/obj/item/storage/backpack/holding/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.allow_big_nesting = TRUE - STR.max_w_class = WEIGHT_CLASS_GIGANTIC - STR.max_combined_w_class = 35 - -/obj/item/storage/backpack/holding/suicide_act(mob/living/user) - user.visible_message("[user] is jumping into [src]! It looks like [user.p_theyre()] trying to commit suicide.") - user.dropItemToGround(src, TRUE) - user.Stun(100, ignore_canstun = TRUE) - sleep(20) - playsound(src, "rustle", 50, 1, -5) - qdel(user) - -/obj/item/storage/backpack/holding/singularity_act(current_size) - var/dist = max((current_size - 2),1) - explosion(src.loc,(dist),(dist*2),(dist*4)) - -/obj/item/storage/backpack/santabag - name = "Santa's Gift Bag" - desc = "Space Santa uses this to deliver presents to all the nice children in space in Christmas! Wow, it's pretty big!" - icon_state = "giftbag0" - item_state = "giftbag" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/backpack/santabag/Initialize() - . = ..() - regenerate_presents() - -/obj/item/storage/backpack/santabag/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 60 - -/obj/item/storage/backpack/santabag/suicide_act(mob/user) - user.visible_message("[user] places [src] over [user.p_their()] head and pulls it tight! It looks like [user.p_they()] [user.p_are()]n't in the Christmas spirit...") - return (OXYLOSS) - -/obj/item/storage/backpack/santabag/proc/regenerate_presents() - addtimer(CALLBACK(src, .proc/regenerate_presents), rand(30 SECONDS, 60 SECONDS)) - - var/mob/M = get(loc, /mob) - if(!istype(M)) - return - if(HAS_TRAIT(M, TRAIT_CANNOT_OPEN_PRESENTS)) - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - var/turf/floor = get_turf(src) - var/obj/item/I = new /obj/item/a_gift/anything(floor) - if(STR.can_be_inserted(I, stop_messages=TRUE)) - STR.handle_item_insertion(I, prevent_warning=TRUE) - else - qdel(I) - - -/obj/item/storage/backpack/cultpack - name = "trophy rack" - desc = "It's useful for both carrying extra gear and proudly declaring your insanity." - icon_state = "cultpack" - 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" - -/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 - -/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" - -/obj/item/storage/backpack/ert - name = "emergency response team commander backpack" - desc = "A spacious backpack with lots of pockets, worn by the Commander of an Emergency Response Team." - icon_state = "ert_commander" - item_state = "securitypack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/ert/security - name = "emergency response team security backpack" - desc = "A spacious backpack with lots of pockets, worn by Security Officers of an Emergency Response Team." - icon_state = "ert_security" - -/obj/item/storage/backpack/ert/medical - name = "emergency response team medical backpack" - desc = "A spacious backpack with lots of pockets, worn by Medical Officers of an Emergency Response Team." - icon_state = "ert_medical" - -/obj/item/storage/backpack/ert/engineer - name = "emergency response team engineer backpack" - desc = "A spacious backpack with lots of pockets, worn by Engineers of an Emergency Response Team." - icon_state = "ert_engineering" - -/* - * 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/fireproof - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel/eng - name = "industrial satchel" - desc = "A tough satchel with extra pockets." - icon_state = "satchel-eng" - item_state = "engiepack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel/med - name = "medical satchel" - desc = "A sterile satchel used in medical departments." - icon_state = "satchel-med" - 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/cap - name = "captain's satchel" - desc = "An exclusive satchel for Nanotrasen officers." - icon_state = "satchel-cap" - item_state = "captainpack" - -/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_NORMAL //Can fit in backpacks itself. - level = 1 - -/obj/item/storage/backpack/satchel/flat/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 15 - STR.set_holdable(null, list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks) - -/obj/item/storage/backpack/satchel/flat/hide(intact) - if(intact) - invisibility = INVISIBILITY_OBSERVER - anchored = TRUE //otherwise you can start pulling, cover it, and drag around an invisible backpack. - icon_state = "[initial(icon_state)]2" - ADD_TRAIT(src, TRAIT_T_RAY_VISIBLE, TRAIT_GENERIC) - else - invisibility = initial(invisibility) - anchored = FALSE - icon_state = initial(icon_state) - REMOVE_TRAIT(src, TRAIT_T_RAY_VISIBLE, TRAIT_GENERIC) - -/obj/item/storage/backpack/satchel/flat/PopulateContents() - var/datum/supply_pack/costumes_toys/randomised/contraband/C = new - for(var/i in 1 to 2) - var/ctype = pick(C.contains) - new ctype(src) - - qdel(C) - -/obj/item/storage/backpack/satchel/flat/with_tools/PopulateContents() - new /obj/item/stack/tile/plasteel(src) - new /obj/item/crowbar(src) - - ..() - -/obj/item/storage/backpack/satchel/flat/empty/PopulateContents() - return - -/obj/item/storage/backpack/duffelbag - name = "duffel bag" - desc = "A large duffel bag for holding extra things." - icon_state = "duffel" - 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/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) - -/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 - -/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 - -/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) - -/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/fireproof - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/syndie - name = "suspicious looking duffel bag" - desc = "A large duffel bag for holding extra tactical supplies." - icon_state = "duffel-syndie" - item_state = "duffel-syndieammo" - slowdown = 0 - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/syndie/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.silent = TRUE - -/obj/item/storage/backpack/duffelbag/syndie/hitman - desc = "A large duffel bag for holding extra things. There is a Nanotrasen logo on the back." - icon_state = "duffel-syndieammo" - 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) - -/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 magazines." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/ammo_box/magazine/m12g/slug(src) - new /obj/item/ammo_box/magazine/m12g/slug(src) - new /obj/item/ammo_box/magazine/m12g/dragon(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/smg - desc = "A large duffel bag, packed to the brim with C-20r magazines." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/smg/PopulateContents() - for(var/i in 1 to 9) - new /obj/item/ammo_box/magazine/smgm45(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/dark_gygax - desc = "A large duffel bag, packed to the brim with various exosuit ammo." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/dark_gygax/PopulateContents() - new /obj/item/mecha_ammo/incendiary(src) - new /obj/item/mecha_ammo/incendiary(src) - new /obj/item/mecha_ammo/incendiary(src) - new /obj/item/mecha_ammo/flashbang(src) - new /obj/item/mecha_ammo/flashbang(src) - new /obj/item/mecha_ammo/flashbang(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler - desc = "A large duffel bag, packed to the brim with various exosuit ammo." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler/PopulateContents() - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/missiles_he(src) - new /obj/item/mecha_ammo/missiles_he(src) - new /obj/item/mecha_ammo/missiles_he(src) - -/obj/item/storage/backpack/duffelbag/syndie/c20rbundle - desc = "A large duffel bag containing a C-20r, some magazines, and a cheap looking suppressor." - -/obj/item/storage/backpack/duffelbag/syndie/c20rbundle/PopulateContents() - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/gun/ballistic/automatic/c20r(src) - new /obj/item/suppressor/specialoffer(src) - -/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle - desc = "A large duffel bag containing a Bulldog, some drums, and a pair of thermal imaging glasses." - -/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle/PopulateContents() - new /obj/item/gun/ballistic/shotgun/bulldog(src) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/clothing/glasses/thermal/syndi(src) - -/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle - desc = "A large duffel bag containing a 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(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/medicalbundle - desc = "A large duffel bag containing a medical equipment, a Donksoft LMG, a big jumbo box of riot darts, and a knock-off pair of magboots." - -/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle/PopulateContents() - new /obj/item/clothing/shoes/magboots/syndie(src) - new /obj/item/storage/firstaid/tactical(src) - new /obj/item/gun/ballistic/automatic/l6_saw/toy(src) - new /obj/item/ammo_box/foambox/riot(src) - -/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle - desc = "A large duffel bag containing deadly chemicals, a handheld chem sprayer, Bioterror foam grenade, a Donksoft assault rifle, box of riot grade darts, a dart pistol, and a box of syringes." - -/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle/PopulateContents() - new /obj/item/reagent_containers/spray/chemsprayer/bioterror(src) - new /obj/item/storage/box/syndie_kit/chemical(src) - new /obj/item/gun/syringe/syndicate(src) - new /obj/item/gun/ballistic/automatic/c20r/toy(src) - new /obj/item/storage/box/syringes(src) - new /obj/item/ammo_box/foambox/riot(src) - new /obj/item/grenade/chem_grenade/bioterrorfoam(src) - if(prob(5)) - new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) - -/obj/item/storage/backpack/duffelbag/syndie/c4/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/grenade/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) +/* 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 bluespace." + 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 + +/obj/item/storage/backpack/holding/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.allow_big_nesting = TRUE + STR.max_w_class = WEIGHT_CLASS_GIGANTIC + STR.max_combined_w_class = 35 + +/obj/item/storage/backpack/holding/suicide_act(mob/living/user) + user.visible_message("[user] is jumping into [src]! It looks like [user.p_theyre()] trying to commit suicide.") + user.dropItemToGround(src, TRUE) + user.Stun(100, ignore_canstun = TRUE) + sleep(20) + playsound(src, "rustle", 50, 1, -5) + qdel(user) + +/obj/item/storage/backpack/holding/singularity_act(current_size) + var/dist = max((current_size - 2),1) + explosion(src.loc,(dist),(dist*2),(dist*4)) + +/obj/item/storage/backpack/santabag + name = "Santa's Gift Bag" + desc = "Space Santa uses this to deliver presents to all the nice children in space in Christmas! Wow, it's pretty big!" + icon_state = "giftbag0" + item_state = "giftbag" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/backpack/santabag/Initialize() + . = ..() + regenerate_presents() + +/obj/item/storage/backpack/santabag/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 60 + +/obj/item/storage/backpack/santabag/suicide_act(mob/user) + user.visible_message("[user] places [src] over [user.p_their()] head and pulls it tight! It looks like [user.p_they()] [user.p_are()]n't in the Christmas spirit...") + return (OXYLOSS) + +/obj/item/storage/backpack/santabag/proc/regenerate_presents() + addtimer(CALLBACK(src, .proc/regenerate_presents), rand(30 SECONDS, 60 SECONDS)) + + var/mob/M = get(loc, /mob) + if(!istype(M)) + return + if(HAS_TRAIT(M, TRAIT_CANNOT_OPEN_PRESENTS)) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + var/turf/floor = get_turf(src) + var/obj/item/I = new /obj/item/a_gift/anything(floor) + if(STR.can_be_inserted(I, stop_messages=TRUE)) + STR.handle_item_insertion(I, prevent_warning=TRUE) + else + qdel(I) + + +/obj/item/storage/backpack/cultpack + name = "trophy rack" + desc = "It's useful for both carrying extra gear and proudly declaring your insanity." + icon_state = "cultpack" + 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" + +/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 + +/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" + +/obj/item/storage/backpack/ert + name = "emergency response team commander backpack" + desc = "A spacious backpack with lots of pockets, worn by the Commander of an Emergency Response Team." + icon_state = "ert_commander" + item_state = "securitypack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/ert/security + name = "emergency response team security backpack" + desc = "A spacious backpack with lots of pockets, worn by Security Officers of an Emergency Response Team." + icon_state = "ert_security" + +/obj/item/storage/backpack/ert/medical + name = "emergency response team medical backpack" + desc = "A spacious backpack with lots of pockets, worn by Medical Officers of an Emergency Response Team." + icon_state = "ert_medical" + +/obj/item/storage/backpack/ert/engineer + name = "emergency response team engineer backpack" + desc = "A spacious backpack with lots of pockets, worn by Engineers of an Emergency Response Team." + icon_state = "ert_engineering" + +/* + * 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/fireproof + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel/eng + name = "industrial satchel" + desc = "A tough satchel with extra pockets." + icon_state = "satchel-eng" + item_state = "engiepack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel/med + name = "medical satchel" + desc = "A sterile satchel used in medical departments." + icon_state = "satchel-med" + 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/cap + name = "captain's satchel" + desc = "An exclusive satchel for Nanotrasen officers." + icon_state = "satchel-cap" + item_state = "captainpack" + +/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_NORMAL //Can fit in backpacks itself. + level = 1 + +/obj/item/storage/backpack/satchel/flat/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 15 + STR.set_holdable(null, list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks) + +/obj/item/storage/backpack/satchel/flat/hide(intact) + if(intact) + invisibility = INVISIBILITY_OBSERVER + anchored = TRUE //otherwise you can start pulling, cover it, and drag around an invisible backpack. + icon_state = "[initial(icon_state)]2" + ADD_TRAIT(src, TRAIT_T_RAY_VISIBLE, TRAIT_GENERIC) + else + invisibility = initial(invisibility) + anchored = FALSE + icon_state = initial(icon_state) + REMOVE_TRAIT(src, TRAIT_T_RAY_VISIBLE, TRAIT_GENERIC) + +/obj/item/storage/backpack/satchel/flat/PopulateContents() + var/datum/supply_pack/costumes_toys/randomised/contraband/C = new + for(var/i in 1 to 2) + var/ctype = pick(C.contains) + new ctype(src) + + qdel(C) + +/obj/item/storage/backpack/satchel/flat/with_tools/PopulateContents() + new /obj/item/stack/tile/plasteel(src) + new /obj/item/crowbar(src) + + ..() + +/obj/item/storage/backpack/satchel/flat/empty/PopulateContents() + return + +/obj/item/storage/backpack/duffelbag + name = "duffel bag" + desc = "A large duffel bag for holding extra things." + icon_state = "duffel" + 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/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) + +/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 + +/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 + +/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) + +/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/fireproof + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/syndie + name = "suspicious looking duffel bag" + desc = "A large duffel bag for holding extra tactical supplies." + icon_state = "duffel-syndie" + item_state = "duffel-syndieammo" + slowdown = 0 + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/syndie/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.silent = TRUE + +/obj/item/storage/backpack/duffelbag/syndie/hitman + desc = "A large duffel bag for holding extra things. There is a Nanotrasen logo on the back." + icon_state = "duffel-syndieammo" + 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) + +/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 magazines." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/ammo_box/magazine/m12g/slug(src) + new /obj/item/ammo_box/magazine/m12g/slug(src) + new /obj/item/ammo_box/magazine/m12g/dragon(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/smg + desc = "A large duffel bag, packed to the brim with C-20r magazines." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/smg/PopulateContents() + for(var/i in 1 to 9) + new /obj/item/ammo_box/magazine/smgm45(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/dark_gygax + desc = "A large duffel bag, packed to the brim with various exosuit ammo." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/dark_gygax/PopulateContents() + new /obj/item/mecha_ammo/incendiary(src) + new /obj/item/mecha_ammo/incendiary(src) + new /obj/item/mecha_ammo/incendiary(src) + new /obj/item/mecha_ammo/flashbang(src) + new /obj/item/mecha_ammo/flashbang(src) + new /obj/item/mecha_ammo/flashbang(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler + desc = "A large duffel bag, packed to the brim with various exosuit ammo." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler/PopulateContents() + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/missiles_he(src) + new /obj/item/mecha_ammo/missiles_he(src) + new /obj/item/mecha_ammo/missiles_he(src) + +/obj/item/storage/backpack/duffelbag/syndie/c20rbundle + desc = "A large duffel bag containing a C-20r, some magazines, and a cheap looking suppressor." + +/obj/item/storage/backpack/duffelbag/syndie/c20rbundle/PopulateContents() + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/gun/ballistic/automatic/c20r(src) + new /obj/item/suppressor/specialoffer(src) + +/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle + desc = "A large duffel bag containing a Bulldog, some drums, and a pair of thermal imaging glasses." + +/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle/PopulateContents() + new /obj/item/gun/ballistic/shotgun/bulldog(src) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/clothing/glasses/thermal/syndi(src) + +/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle + desc = "A large duffel bag containing a 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(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/medicalbundle + desc = "A large duffel bag containing a medical equipment, a Donksoft LMG, a big jumbo box of riot darts, and a knock-off pair of magboots." + +/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle/PopulateContents() + new /obj/item/clothing/shoes/magboots/syndie(src) + new /obj/item/storage/firstaid/tactical(src) + new /obj/item/gun/ballistic/automatic/l6_saw/toy(src) + new /obj/item/ammo_box/foambox/riot(src) + +/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle + desc = "A large duffel bag containing deadly chemicals, a handheld chem sprayer, Bioterror foam grenade, a Donksoft assault rifle, box of riot grade darts, a dart pistol, and a box of syringes." + +/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle/PopulateContents() + new /obj/item/reagent_containers/spray/chemsprayer/bioterror(src) + new /obj/item/storage/box/syndie_kit/chemical(src) + new /obj/item/gun/syringe/syndicate(src) + new /obj/item/gun/ballistic/automatic/c20r/toy(src) + new /obj/item/storage/box/syringes(src) + new /obj/item/ammo_box/foambox/riot(src) + new /obj/item/grenade/chem_grenade/bioterrorfoam(src) + if(prob(5)) + new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) + +/obj/item/storage/backpack/duffelbag/syndie/c4/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/grenade/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) diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm index 9b9c9e5cd46b..5569bd7b7512 100644 --- a/code/game/objects/items/storage/bags.dm +++ b/code/game/objects/items/storage/bags.dm @@ -1,393 +1,393 @@ -/* - * 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 = 'yogstation/icons/obj/janitor.dmi' // yogs -- Janitor icons - 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.set_holdable(null, list(/obj/item/disk/nuclear)) - -/obj/item/storage/bag/trash/suicide_act(mob/user) - user.visible_message("[user] puts [src] over [user.p_their()] head and starts chomping at the insides! Disgusting!") - playsound(loc, 'sound/items/eatfood.ogg', 50, 1, -1) - return (TOXLOSS) - -/obj/item/storage/bag/trash/update_icon() - //yogs start - if(icon_state == "[initial(icon_state)]_broken") - return - //yogs end - 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 - -/obj/item/storage/bag/trash/bluespace/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 60 - STR.max_items = 60 - -/obj/item/storage/bag/trash/bluespace/cyborg - insertable = FALSE - -// ----------------------------- -// Mining Satchel -// ----------------------------- - -/obj/item/storage/bag/ore - name = "mining satchel" - desc = "This little bugger can be used to store and transport ores." - icon = 'icons/obj/mining.dmi' - icon_state = "satchel" - 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 - -/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.set_holdable(list(/obj/item/stack/ore)) - STR.max_w_class = WEIGHT_CLASS_HUGE - STR.max_combined_stack_amount = 50 - -/obj/item/storage/bag/ore/equipped(mob/user) - . = ..() - if(listeningTo == user) - return - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) - RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/Pickup_ores) - listeningTo = user - -/obj/item/storage/bag/ore/dropped() - . = ..() - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) - listeningTo = null - -/obj/item/storage/bag/ore/proc/Pickup_ores(mob/living/user) - var/show_message = FALSE - var/obj/structure/ore_box/box - var/turf/tile = user.loc - if (!isturf(tile)) - return - if (istype(user.pulling, /obj/structure/ore_box)) - box = user.pulling - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - if(STR) - for(var/A in tile) - if (!is_type_in_typecache(A, STR.can_hold)) - continue - if (box) - user.transferItemToLoc(A, box) - show_message = TRUE - else if(SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, A, user, TRUE)) - show_message = TRUE - else - if(!spam_protection) - to_chat(user, "Your [name] is full and can't hold any more!") - spam_protection = TRUE - continue - if(show_message) - playsound(user, "rustle", 50, TRUE) - if (box) - user.visible_message("[user] offloads the ores beneath [user.p_them()] into [box].", \ - "You offload the ores beneath you into your [box].") - else - user.visible_message("[user] scoops up the ores beneath [user.p_them()].", \ - "You scoop up the ores beneath you with your [name].") - spam_protection = FALSE - -/obj/item/storage/bag/ore/cyborg - name = "cyborg mining satchel" - -/obj/item/storage/bag/ore/holding //miners, your messiah has arrived - name = "mining satchel of holding" - desc = "A revolution in convenience, this satchel allows for huge amounts of ore storage. It's been outfitted with anti-malfunction safety measures." - icon_state = "satchel_bspace" - -/obj/item/storage/bag/ore/holding/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.max_items = INFINITY - STR.max_combined_w_class = INFINITY - STR.max_combined_stack_amount = INFINITY - -// ----------------------------- -// Plant bag -// ----------------------------- - -/obj/item/storage/bag/plants - name = "plant bag" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "plantbag" - 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.set_holdable(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.incapacitated()) - return - for(var/obj/item/O in contents) - seedify(O, 1) - -// ----------------------------- -// Sheet Snatcher -// ----------------------------- -// Because it stacks stacks, this doesn't operate normally. -// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu - -/obj/item/storage/bag/sheetsnatcher - name = "sheet snatcher" - desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet." - icon = 'icons/obj/mining.dmi' - icon_state = "sheetsnatcher" - - 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.set_holdable(list(/obj/item/stack/sheet), 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.set_holdable(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() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.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.Paralyze(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.set_holdable(list(/obj/item/reagent_containers/pill, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/medspray, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/dropper)) - -/* - * 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.set_holdable(list(/obj/item/slime_extract, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/blood, /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/food/snacks/deadmouse, /obj/item/reagent_containers/food/snacks/monkeycube)) - -/* - * Construction bag (for engineering, holds stock parts and electronics) - */ - -/obj/item/storage/bag/construction - name = "construction bag" - icon = 'icons/obj/tools.dmi' - icon_state = "construction_bag" - desc = "A bag for storing small construction components." - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/construction/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 100 - STR.max_items = 50 - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.insert_preposition = "in" - STR.set_holdable(list(/obj/item/stack/ore/bluespace_crystal, /obj/item/assembly, /obj/item/stock_parts, /obj/item/reagent_containers/glass/beaker, /obj/item/stack/cable_coil, /obj/item/circuitboard, /obj/item/electronics)) +/* + * 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 = 'yogstation/icons/obj/janitor.dmi' // yogs -- Janitor icons + 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.set_holdable(null, list(/obj/item/disk/nuclear)) + +/obj/item/storage/bag/trash/suicide_act(mob/user) + user.visible_message("[user] puts [src] over [user.p_their()] head and starts chomping at the insides! Disgusting!") + playsound(loc, 'sound/items/eatfood.ogg', 50, 1, -1) + return (TOXLOSS) + +/obj/item/storage/bag/trash/update_icon() + //yogs start + if(icon_state == "[initial(icon_state)]_broken") + return + //yogs end + 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 + +/obj/item/storage/bag/trash/bluespace/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 60 + STR.max_items = 60 + +/obj/item/storage/bag/trash/bluespace/cyborg + insertable = FALSE + +// ----------------------------- +// Mining Satchel +// ----------------------------- + +/obj/item/storage/bag/ore + name = "mining satchel" + desc = "This little bugger can be used to store and transport ores." + icon = 'icons/obj/mining.dmi' + icon_state = "satchel" + 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 + +/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.set_holdable(list(/obj/item/stack/ore)) + STR.max_w_class = WEIGHT_CLASS_HUGE + STR.max_combined_stack_amount = 50 + +/obj/item/storage/bag/ore/equipped(mob/user) + . = ..() + if(listeningTo == user) + return + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/Pickup_ores) + listeningTo = user + +/obj/item/storage/bag/ore/dropped() + . = ..() + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + listeningTo = null + +/obj/item/storage/bag/ore/proc/Pickup_ores(mob/living/user) + var/show_message = FALSE + var/obj/structure/ore_box/box + var/turf/tile = user.loc + if (!isturf(tile)) + return + if (istype(user.pulling, /obj/structure/ore_box)) + box = user.pulling + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + if(STR) + for(var/A in tile) + if (!is_type_in_typecache(A, STR.can_hold)) + continue + if (box) + user.transferItemToLoc(A, box) + show_message = TRUE + else if(SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, A, user, TRUE)) + show_message = TRUE + else + if(!spam_protection) + to_chat(user, "Your [name] is full and can't hold any more!") + spam_protection = TRUE + continue + if(show_message) + playsound(user, "rustle", 50, TRUE) + if (box) + user.visible_message("[user] offloads the ores beneath [user.p_them()] into [box].", \ + "You offload the ores beneath you into your [box].") + else + user.visible_message("[user] scoops up the ores beneath [user.p_them()].", \ + "You scoop up the ores beneath you with your [name].") + spam_protection = FALSE + +/obj/item/storage/bag/ore/cyborg + name = "cyborg mining satchel" + +/obj/item/storage/bag/ore/holding //miners, your messiah has arrived + name = "mining satchel of holding" + desc = "A revolution in convenience, this satchel allows for huge amounts of ore storage. It's been outfitted with anti-malfunction safety measures." + icon_state = "satchel_bspace" + +/obj/item/storage/bag/ore/holding/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.max_items = INFINITY + STR.max_combined_w_class = INFINITY + STR.max_combined_stack_amount = INFINITY + +// ----------------------------- +// Plant bag +// ----------------------------- + +/obj/item/storage/bag/plants + name = "plant bag" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "plantbag" + 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.set_holdable(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.incapacitated()) + return + for(var/obj/item/O in contents) + seedify(O, 1) + +// ----------------------------- +// Sheet Snatcher +// ----------------------------- +// Because it stacks stacks, this doesn't operate normally. +// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu + +/obj/item/storage/bag/sheetsnatcher + name = "sheet snatcher" + desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet." + icon = 'icons/obj/mining.dmi' + icon_state = "sheetsnatcher" + + 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.set_holdable(list(/obj/item/stack/sheet), 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.set_holdable(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() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.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.Paralyze(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.set_holdable(list(/obj/item/reagent_containers/pill, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/medspray, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/dropper)) + +/* + * 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.set_holdable(list(/obj/item/slime_extract, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/blood, /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/food/snacks/deadmouse, /obj/item/reagent_containers/food/snacks/monkeycube)) + +/* + * Construction bag (for engineering, holds stock parts and electronics) + */ + +/obj/item/storage/bag/construction + name = "construction bag" + icon = 'icons/obj/tools.dmi' + icon_state = "construction_bag" + desc = "A bag for storing small construction components." + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/construction/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 100 + STR.max_items = 50 + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.insert_preposition = "in" + STR.set_holdable(list(/obj/item/stack/ore/bluespace_crystal, /obj/item/assembly, /obj/item/stock_parts, /obj/item/reagent_containers/glass/beaker, /obj/item/stack/cable_coil, /obj/item/circuitboard, /obj/item/electronics)) diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index da2352c99a30..aa6ba4f2f158 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -1,685 +1,685 @@ -/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 - -/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/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 - custom_price = 50 - -/obj/item/storage/belt/utility/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(list( - /obj/item/multitool/tricorder, //yogs tricorder: 'cause making it into the yogs belt dm makes it the only thing a belt can hold - /obj/item/crowbar, - /obj/item/screwdriver, - /obj/item/weldingtool, - /obj/item/wirecutters, - /obj/item/wrench, - /obj/item/multitool, - /obj/item/flashlight, - /obj/item/stack/cable_coil, - /obj/item/t_scanner, - /obj/item/analyzer, - /obj/item/geiger_counter, - /obj/item/extinguisher/mini, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/holosign_creator/atmos, - /obj/item/holosign_creator/engineering, - /obj/item/forcefield_projector, - /obj/item/assembly/signaler, - /obj/item/lightreplacer - )) - -/obj/item/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/tricorder(src) //yogs: changes the multitool to the tricorder and removes the analyzer - new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) - new /obj/item/extinguisher/mini(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,MAXCOIL,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,MAXCOIL,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, MAXCOIL, "yellow") - -/obj/item/storage/belt/medical - name = "medical belt" - desc = "Can hold various medical equipment." - icon_state = "medicalbelt" - item_state = "medical" - -/obj/item/storage/belt/medical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.set_holdable(list( - /obj/item/healthanalyzer, - /obj/item/dnainjector, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/pill, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/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/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/holosign_creator/medical - )) - -/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.set_holdable(list( - /obj/item/melee/baton, - /obj/item/melee/classic_baton, - /obj/item/grenade, - /obj/item/reagent_containers/spray/pepper, - /obj/item/restraints/handcuffs, - /obj/item/assembly/flash/handheld, - /obj/item/clothing/glasses, - /obj/item/ammo_casing/shotgun, - /obj/item/ammo_box, - /obj/item/reagent_containers/food/snacks/donut, - /obj/item/kitchen/knife/combat, - /obj/item/flashlight/seclite, - /obj/item/melee/classic_baton/telescopic, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/restraints/legcuffs/bola, - /obj/item/holosign_creator/security - )) - -/obj/item/storage/belt/security/full/PopulateContents() - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/melee/baton/loaded(src) - update_icon() - -/obj/item/storage/belt/security/webbing - name = "security webbing" - desc = "Unique and versatile chest rig, can hold security gear." - icon_state = "securitywebbing" - item_state = "securitywebbing" - content_overlays = FALSE - custom_premium_price = 800 - -/obj/item/storage/belt/security/webbing/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - -/obj/item/storage/belt/mining - name = "explorer's webbing" - desc = "A versatile chest rig, cherished by miners and hunters alike." - icon_state = "explorer1" - 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.set_holdable(list( - /obj/item/crowbar, - /obj/item/screwdriver, - /obj/item/weldingtool, - /obj/item/wirecutters, - /obj/item/wrench, - /obj/item/multitool, - /obj/item/flashlight, - /obj/item/stack/cable_coil, - /obj/item/analyzer, - /obj/item/extinguisher/mini, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/resonator, - /obj/item/mining_scanner, - /obj/item/pickaxe, - /obj/item/shovel, - /obj/item/stack/sheet/animalhide, - /obj/item/stack/sheet/sinew, - /obj/item/stack/sheet/bone, - /obj/item/lighter, - /obj/item/storage/fancy/cigarettes, - /obj/item/reagent_containers/food/drinks/bottle, - /obj/item/stack/medical, - /obj/item/kitchen/knife, - /obj/item/reagent_containers/hypospray, - /obj/item/gps, - /obj/item/storage/bag/ore, - /obj/item/survivalcapsule, - /obj/item/t_scanner/adv_mining_scanner, - /obj/item/reagent_containers/pill, - /obj/item/storage/pill_bottle, - /obj/item/stack/ore, - /obj/item/reagent_containers/food/drinks, - /obj/item/organ/regenerative_core, - /obj/item/wormhole_jaunter, - /obj/item/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.set_holdable(list( - /obj/item/soulstone - )) - -/obj/item/storage/belt/soulstone/full/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/soulstone(src) - -/obj/item/storage/belt/soulstone/full/chappy/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/soulstone/anybody/chaplain(src) - -/obj/item/storage/belt/champion - name = "championship belt" - desc = "Proves to the world that you are the strongest!" - icon_state = "championbelt" - 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.set_holdable(list( - /obj/item/clothing/mask/luchador - )) - -/obj/item/storage/belt/military - name = "chest rig" - desc = "A set of tactical webbing worn by Syndicate boarding parties." - icon_state = "militarywebbing" - item_state = "militarywebbing" - resistance_flags = FIRE_PROOF - -/obj/item/storage/belt/military/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/belt/military/snack - name = "tactical snack rig" - -/obj/item/storage/belt/military/snack/Initialize() - . = ..() - var/sponsor = pick("DonkCo", "Waffle Co.", "Roffle Co.", "Gorlax Marauders", "Tiger Cooperative") - desc = "A set of snack-tical webbing worn by athletes of the [sponsor] VR sports division." - -/obj/item/storage/belt/military/snack/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.set_holdable(list( - /obj/item/reagent_containers/food/snacks, - /obj/item/reagent_containers/food/drinks - )) - - var/amount = 5 - var/rig_snacks - while(contents.len <= amount) - rig_snacks = pick(list( - /obj/item/reagent_containers/food/snacks/candy, - /obj/item/reagent_containers/food/drinks/dry_ramen, - /obj/item/reagent_containers/food/snacks/chips, - /obj/item/reagent_containers/food/snacks/sosjerky, - /obj/item/reagent_containers/food/snacks/syndicake, - /obj/item/reagent_containers/food/snacks/spacetwinkie, - /obj/item/reagent_containers/food/snacks/cheesiehonkers, - /obj/item/reagent_containers/food/snacks/nachos, - /obj/item/reagent_containers/food/snacks/cheesynachos, - /obj/item/reagent_containers/food/snacks/cubannachos, - /obj/item/reagent_containers/food/snacks/nugget, - /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato, - /obj/item/reagent_containers/food/snacks/rofflewaffles, - /obj/item/reagent_containers/food/snacks/donkpocket, - /obj/item/reagent_containers/food/drinks/soda_cans/cola, - /obj/item/reagent_containers/food/drinks/soda_cans/space_mountain_wind, - /obj/item/reagent_containers/food/drinks/soda_cans/dr_gibb, - /obj/item/reagent_containers/food/drinks/soda_cans/starkist, - /obj/item/reagent_containers/food/drinks/soda_cans/space_up, - /obj/item/reagent_containers/food/drinks/soda_cans/pwr_game, - /obj/item/reagent_containers/food/drinks/soda_cans/lemon_lime, - /obj/item/reagent_containers/food/drinks/drinkingglass/filled/nuka_cola - )) - new rig_snacks(src) - -/obj/item/storage/belt/military/abductor - name = "agent belt" - desc = "A belt used by abductor agents." - icon = 'icons/obj/abductor.dmi' - icon_state = "belt" - 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,MAXCOIL,"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/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.set_holdable(list( - /obj/item/grenade, - /obj/item/screwdriver, - /obj/item/lighter, - /obj/item/multitool, - /obj/item/reagent_containers/food/drinks/bottle/molotov, - /obj/item/grenade/plastic/c4, - )) - -/obj/item/storage/belt/grenade/full/PopulateContents() - var/static/items_inside = list( - /obj/item/grenade/flashbang = 1, - /obj/item/grenade/smokebomb = 4, - /obj/item/grenade/empgrenade = 1, - /obj/item/grenade/empgrenade = 1, - /obj/item/grenade/syndieminibomb/concussion/frag = 10, - /obj/item/grenade/gluon = 4, - /obj/item/grenade/chem_grenade/incendiary = 2, - /obj/item/grenade/chem_grenade/facid = 1, - /obj/item/grenade/syndieminibomb = 2, - /obj/item/screwdriver = 1, - /obj/item/multitool = 1) - generate_items_inside(items_inside,src) - - -/obj/item/storage/belt/wands - name = "wand belt" - desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic." - icon_state = "soulstonebelt" - item_state = "soulstonebelt" - -/obj/item/storage/belt/wands/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list( - /obj/item/gun/magic/wand - )) - -/obj/item/storage/belt/wands/full/PopulateContents() - new /obj/item/gun/magic/wand/death(src) - new /obj/item/gun/magic/wand/resurrection(src) - new /obj/item/gun/magic/wand/polymorph(src) - new /obj/item/gun/magic/wand/teleport(src) - new /obj/item/gun/magic/wand/door(src) - new /obj/item/gun/magic/wand/fireball(src) - - for(var/obj/item/gun/magic/wand/W in contents) //All wands in this pack come in the best possible condition - W.max_charges = initial(W.max_charges) - W.charges = W.max_charges - -/obj/item/storage/belt/janitor - name = "janibelt" - desc = "A belt used to hold most janitorial supplies." - icon_state = "janibelt" - 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.set_holdable(list( - /obj/item/grenade/chem_grenade, - /obj/item/lightreplacer, - /obj/item/flashlight, - /obj/item/reagent_containers/spray, - /obj/item/soap, - /obj/item/holosign_creator/janibarrier, - /obj/item/forcefield_projector, - /obj/item/key/janitor, - /obj/item/clothing/gloves, - /obj/item/melee/flyswatter, - /obj/item/assembly/mousetrap - )) - -/obj/item/storage/belt/janitor/full/PopulateContents() - new /obj/item/lightreplacer(src) - new /obj/item/reagent_containers/spray/cleaner(src) - new /obj/item/soap/nanotrasen(src) - new /obj/item/holosign_creator/janibarrier(src) - new /obj/item/melee/flyswatter(src) - -/obj/item/storage/belt/bandolier - name = "bandolier" - desc = "A bandolier for holding shotgun 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.set_holdable(list( - /obj/item/ammo_casing/shotgun - )) - -/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.set_holdable(list( - /obj/item/gun/ballistic/automatic/pistol, - /obj/item/gun/ballistic/revolver, - /obj/item/ammo_box, - /obj/item/gun/energy/e_gun/mini - )) - -/obj/item/storage/belt/holster/full/PopulateContents() - var/static/items_inside = list( - /obj/item/gun/ballistic/revolver/detective = 1, - /obj/item/ammo_box/c38 = 2) - generate_items_inside(items_inside,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" - item_color = "fannypackleather" - custom_price = 15 - -/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" - item_color = "black" - -/obj/item/storage/belt/fannypack/red - name = "red fannypack" - icon_state = "fannypack_red" - item_state = "fannypack_red" - item_color = "red" - -/obj/item/storage/belt/fannypack/purple - name = "purple fannypack" - icon_state = "fannypack_purple" - item_state = "fannypack_purple" - item_color = "purple" - -/obj/item/storage/belt/fannypack/blue - name = "blue fannypack" - icon_state = "fannypack_blue" - item_state = "fannypack_blue" - item_color = "blue" - -/obj/item/storage/belt/fannypack/orange - name = "orange fannypack" - icon_state = "fannypack_orange" - item_state = "fannypack_orange" - item_color = "orange" - -/obj/item/storage/belt/fannypack/white - name = "white fannypack" - icon_state = "fannypack_white" - item_state = "fannypack_white" - item_color = "white" - -/obj/item/storage/belt/fannypack/green - name = "green fannypack" - icon_state = "fannypack_green" - item_state = "fannypack_green" - item_color = "green" - -/obj/item/storage/belt/fannypack/pink - name = "pink fannypack" - icon_state = "fannypack_pink" - item_state = "fannypack_pink" - item_color = "pink" - -/obj/item/storage/belt/fannypack/cyan - name = "cyan fannypack" - icon_state = "fannypack_cyan" - item_state = "fannypack_cyan" - item_color = "cyan" - -/obj/item/storage/belt/fannypack/yellow - name = "yellow fannypack" - icon_state = "fannypack_yellow" - item_state = "fannypack_yellow" - item_color = "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 - -/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.set_holdable(list( - /obj/item/melee/sabre - )) - -/obj/item/storage/belt/sabre/examine(mob/user) - . = ..() - if(length(contents)) - . += "Alt-click it to quickly draw the blade." - -/obj/item/storage/belt/sabre/AltClick(mob/user) - if(!iscarbon(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - if(length(contents)) - var/obj/item/I = contents[1] - user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].") - user.put_in_hands(I) - update_icon() - else - to_chat(user, "[src] is empty.") - -/obj/item/storage/belt/sabre/update_icon() - icon_state = "sheath" - item_state = "sheath" - if(contents.len) - icon_state += "-sabre" - item_state += "-sabre" - if(loc && isliving(loc)) - var/mob/living/L = loc - L.regenerate_icons() - ..() - -/obj/item/storage/belt/sabre/PopulateContents() - new /obj/item/melee/sabre(src) - update_icon() +/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 + +/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/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 + custom_price = 50 + +/obj/item/storage/belt/utility/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(list( + /obj/item/multitool/tricorder, //yogs tricorder: 'cause making it into the yogs belt dm makes it the only thing a belt can hold + /obj/item/crowbar, + /obj/item/screwdriver, + /obj/item/weldingtool, + /obj/item/wirecutters, + /obj/item/wrench, + /obj/item/multitool, + /obj/item/flashlight, + /obj/item/stack/cable_coil, + /obj/item/t_scanner, + /obj/item/analyzer, + /obj/item/geiger_counter, + /obj/item/extinguisher/mini, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/holosign_creator/atmos, + /obj/item/holosign_creator/engineering, + /obj/item/forcefield_projector, + /obj/item/assembly/signaler, + /obj/item/lightreplacer + )) + +/obj/item/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/tricorder(src) //yogs: changes the multitool to the tricorder and removes the analyzer + new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) + new /obj/item/extinguisher/mini(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,MAXCOIL,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,MAXCOIL,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, MAXCOIL, "yellow") + +/obj/item/storage/belt/medical + name = "medical belt" + desc = "Can hold various medical equipment." + icon_state = "medicalbelt" + item_state = "medical" + +/obj/item/storage/belt/medical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.set_holdable(list( + /obj/item/healthanalyzer, + /obj/item/dnainjector, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/pill, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/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/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/holosign_creator/medical + )) + +/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.set_holdable(list( + /obj/item/melee/baton, + /obj/item/melee/classic_baton, + /obj/item/grenade, + /obj/item/reagent_containers/spray/pepper, + /obj/item/restraints/handcuffs, + /obj/item/assembly/flash/handheld, + /obj/item/clothing/glasses, + /obj/item/ammo_casing/shotgun, + /obj/item/ammo_box, + /obj/item/reagent_containers/food/snacks/donut, + /obj/item/kitchen/knife/combat, + /obj/item/flashlight/seclite, + /obj/item/melee/classic_baton/telescopic, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/restraints/legcuffs/bola, + /obj/item/holosign_creator/security + )) + +/obj/item/storage/belt/security/full/PopulateContents() + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/melee/baton/loaded(src) + update_icon() + +/obj/item/storage/belt/security/webbing + name = "security webbing" + desc = "Unique and versatile chest rig, can hold security gear." + icon_state = "securitywebbing" + item_state = "securitywebbing" + content_overlays = FALSE + custom_premium_price = 800 + +/obj/item/storage/belt/security/webbing/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + +/obj/item/storage/belt/mining + name = "explorer's webbing" + desc = "A versatile chest rig, cherished by miners and hunters alike." + icon_state = "explorer1" + 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.set_holdable(list( + /obj/item/crowbar, + /obj/item/screwdriver, + /obj/item/weldingtool, + /obj/item/wirecutters, + /obj/item/wrench, + /obj/item/multitool, + /obj/item/flashlight, + /obj/item/stack/cable_coil, + /obj/item/analyzer, + /obj/item/extinguisher/mini, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/resonator, + /obj/item/mining_scanner, + /obj/item/pickaxe, + /obj/item/shovel, + /obj/item/stack/sheet/animalhide, + /obj/item/stack/sheet/sinew, + /obj/item/stack/sheet/bone, + /obj/item/lighter, + /obj/item/storage/fancy/cigarettes, + /obj/item/reagent_containers/food/drinks/bottle, + /obj/item/stack/medical, + /obj/item/kitchen/knife, + /obj/item/reagent_containers/hypospray, + /obj/item/gps, + /obj/item/storage/bag/ore, + /obj/item/survivalcapsule, + /obj/item/t_scanner/adv_mining_scanner, + /obj/item/reagent_containers/pill, + /obj/item/storage/pill_bottle, + /obj/item/stack/ore, + /obj/item/reagent_containers/food/drinks, + /obj/item/organ/regenerative_core, + /obj/item/wormhole_jaunter, + /obj/item/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.set_holdable(list( + /obj/item/soulstone + )) + +/obj/item/storage/belt/soulstone/full/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/soulstone(src) + +/obj/item/storage/belt/soulstone/full/chappy/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/soulstone/anybody/chaplain(src) + +/obj/item/storage/belt/champion + name = "championship belt" + desc = "Proves to the world that you are the strongest!" + icon_state = "championbelt" + 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.set_holdable(list( + /obj/item/clothing/mask/luchador + )) + +/obj/item/storage/belt/military + name = "chest rig" + desc = "A set of tactical webbing worn by Syndicate boarding parties." + icon_state = "militarywebbing" + item_state = "militarywebbing" + resistance_flags = FIRE_PROOF + +/obj/item/storage/belt/military/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/belt/military/snack + name = "tactical snack rig" + +/obj/item/storage/belt/military/snack/Initialize() + . = ..() + var/sponsor = pick("DonkCo", "Waffle Co.", "Roffle Co.", "Gorlax Marauders", "Tiger Cooperative") + desc = "A set of snack-tical webbing worn by athletes of the [sponsor] VR sports division." + +/obj/item/storage/belt/military/snack/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.set_holdable(list( + /obj/item/reagent_containers/food/snacks, + /obj/item/reagent_containers/food/drinks + )) + + var/amount = 5 + var/rig_snacks + while(contents.len <= amount) + rig_snacks = pick(list( + /obj/item/reagent_containers/food/snacks/candy, + /obj/item/reagent_containers/food/drinks/dry_ramen, + /obj/item/reagent_containers/food/snacks/chips, + /obj/item/reagent_containers/food/snacks/sosjerky, + /obj/item/reagent_containers/food/snacks/syndicake, + /obj/item/reagent_containers/food/snacks/spacetwinkie, + /obj/item/reagent_containers/food/snacks/cheesiehonkers, + /obj/item/reagent_containers/food/snacks/nachos, + /obj/item/reagent_containers/food/snacks/cheesynachos, + /obj/item/reagent_containers/food/snacks/cubannachos, + /obj/item/reagent_containers/food/snacks/nugget, + /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato, + /obj/item/reagent_containers/food/snacks/rofflewaffles, + /obj/item/reagent_containers/food/snacks/donkpocket, + /obj/item/reagent_containers/food/drinks/soda_cans/cola, + /obj/item/reagent_containers/food/drinks/soda_cans/space_mountain_wind, + /obj/item/reagent_containers/food/drinks/soda_cans/dr_gibb, + /obj/item/reagent_containers/food/drinks/soda_cans/starkist, + /obj/item/reagent_containers/food/drinks/soda_cans/space_up, + /obj/item/reagent_containers/food/drinks/soda_cans/pwr_game, + /obj/item/reagent_containers/food/drinks/soda_cans/lemon_lime, + /obj/item/reagent_containers/food/drinks/drinkingglass/filled/nuka_cola + )) + new rig_snacks(src) + +/obj/item/storage/belt/military/abductor + name = "agent belt" + desc = "A belt used by abductor agents." + icon = 'icons/obj/abductor.dmi' + icon_state = "belt" + 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,MAXCOIL,"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/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.set_holdable(list( + /obj/item/grenade, + /obj/item/screwdriver, + /obj/item/lighter, + /obj/item/multitool, + /obj/item/reagent_containers/food/drinks/bottle/molotov, + /obj/item/grenade/plastic/c4, + )) + +/obj/item/storage/belt/grenade/full/PopulateContents() + var/static/items_inside = list( + /obj/item/grenade/flashbang = 1, + /obj/item/grenade/smokebomb = 4, + /obj/item/grenade/empgrenade = 1, + /obj/item/grenade/empgrenade = 1, + /obj/item/grenade/syndieminibomb/concussion/frag = 10, + /obj/item/grenade/gluon = 4, + /obj/item/grenade/chem_grenade/incendiary = 2, + /obj/item/grenade/chem_grenade/facid = 1, + /obj/item/grenade/syndieminibomb = 2, + /obj/item/screwdriver = 1, + /obj/item/multitool = 1) + generate_items_inside(items_inside,src) + + +/obj/item/storage/belt/wands + name = "wand belt" + desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic." + icon_state = "soulstonebelt" + item_state = "soulstonebelt" + +/obj/item/storage/belt/wands/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list( + /obj/item/gun/magic/wand + )) + +/obj/item/storage/belt/wands/full/PopulateContents() + new /obj/item/gun/magic/wand/death(src) + new /obj/item/gun/magic/wand/resurrection(src) + new /obj/item/gun/magic/wand/polymorph(src) + new /obj/item/gun/magic/wand/teleport(src) + new /obj/item/gun/magic/wand/door(src) + new /obj/item/gun/magic/wand/fireball(src) + + for(var/obj/item/gun/magic/wand/W in contents) //All wands in this pack come in the best possible condition + W.max_charges = initial(W.max_charges) + W.charges = W.max_charges + +/obj/item/storage/belt/janitor + name = "janibelt" + desc = "A belt used to hold most janitorial supplies." + icon_state = "janibelt" + 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.set_holdable(list( + /obj/item/grenade/chem_grenade, + /obj/item/lightreplacer, + /obj/item/flashlight, + /obj/item/reagent_containers/spray, + /obj/item/soap, + /obj/item/holosign_creator/janibarrier, + /obj/item/forcefield_projector, + /obj/item/key/janitor, + /obj/item/clothing/gloves, + /obj/item/melee/flyswatter, + /obj/item/assembly/mousetrap + )) + +/obj/item/storage/belt/janitor/full/PopulateContents() + new /obj/item/lightreplacer(src) + new /obj/item/reagent_containers/spray/cleaner(src) + new /obj/item/soap/nanotrasen(src) + new /obj/item/holosign_creator/janibarrier(src) + new /obj/item/melee/flyswatter(src) + +/obj/item/storage/belt/bandolier + name = "bandolier" + desc = "A bandolier for holding shotgun 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.set_holdable(list( + /obj/item/ammo_casing/shotgun + )) + +/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.set_holdable(list( + /obj/item/gun/ballistic/automatic/pistol, + /obj/item/gun/ballistic/revolver, + /obj/item/ammo_box, + /obj/item/gun/energy/e_gun/mini + )) + +/obj/item/storage/belt/holster/full/PopulateContents() + var/static/items_inside = list( + /obj/item/gun/ballistic/revolver/detective = 1, + /obj/item/ammo_box/c38 = 2) + generate_items_inside(items_inside,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" + item_color = "fannypackleather" + custom_price = 15 + +/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" + item_color = "black" + +/obj/item/storage/belt/fannypack/red + name = "red fannypack" + icon_state = "fannypack_red" + item_state = "fannypack_red" + item_color = "red" + +/obj/item/storage/belt/fannypack/purple + name = "purple fannypack" + icon_state = "fannypack_purple" + item_state = "fannypack_purple" + item_color = "purple" + +/obj/item/storage/belt/fannypack/blue + name = "blue fannypack" + icon_state = "fannypack_blue" + item_state = "fannypack_blue" + item_color = "blue" + +/obj/item/storage/belt/fannypack/orange + name = "orange fannypack" + icon_state = "fannypack_orange" + item_state = "fannypack_orange" + item_color = "orange" + +/obj/item/storage/belt/fannypack/white + name = "white fannypack" + icon_state = "fannypack_white" + item_state = "fannypack_white" + item_color = "white" + +/obj/item/storage/belt/fannypack/green + name = "green fannypack" + icon_state = "fannypack_green" + item_state = "fannypack_green" + item_color = "green" + +/obj/item/storage/belt/fannypack/pink + name = "pink fannypack" + icon_state = "fannypack_pink" + item_state = "fannypack_pink" + item_color = "pink" + +/obj/item/storage/belt/fannypack/cyan + name = "cyan fannypack" + icon_state = "fannypack_cyan" + item_state = "fannypack_cyan" + item_color = "cyan" + +/obj/item/storage/belt/fannypack/yellow + name = "yellow fannypack" + icon_state = "fannypack_yellow" + item_state = "fannypack_yellow" + item_color = "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 + +/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.set_holdable(list( + /obj/item/melee/sabre + )) + +/obj/item/storage/belt/sabre/examine(mob/user) + . = ..() + if(length(contents)) + . += "Alt-click it to quickly draw the blade." + +/obj/item/storage/belt/sabre/AltClick(mob/user) + if(!iscarbon(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + if(length(contents)) + var/obj/item/I = contents[1] + user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].") + user.put_in_hands(I) + update_icon() + else + to_chat(user, "[src] is empty.") + +/obj/item/storage/belt/sabre/update_icon() + icon_state = "sheath" + item_state = "sheath" + if(contents.len) + icon_state += "-sabre" + item_state += "-sabre" + if(loc && isliving(loc)) + var/mob/living/L = loc + L.regenerate_icons() + ..() + +/obj/item/storage/belt/sabre/PopulateContents() + new /obj/item/melee/sabre(src) + update_icon() diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index 52105fa255b9..73a526c25ed5 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -1,1090 +1,1090 @@ -/* - * Everything derived from the common cardboard box. - * Basically everything except the original is a kit (starts full). - * - * Contains: - * Empty box, starter boxes (survival/engineer), - * Latex glove and sterile mask boxes, - * Syringe, beaker, dna injector boxes, - * Blanks, flashbangs, and EMP grenade boxes, - * Tracking and chemical implant boxes, - * Prescription glasses and drinking glass boxes, - * Condiment bottle and silly cup boxes, - * Donkpocket and monkeycube boxes, - * ID and security PDA cart boxes, - * Handcuff, mousetrap, and pillbottle boxes, - * Snap-pops and matchboxes, - * Replacement light boxes. - * Action Figure Boxes - * Various paper bags. - * - * For syndicate call-ins see uplink_kits.dm - */ - -/obj/item/storage/box - name = "box" - desc = "It's just an ordinary box." - icon_state = "box" - 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" - -/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 ..() - -//Mime spell boxes - -/obj/item/storage/box/mime - name = "invisible box" - desc = "Unfortunately not large enough to trap the mime." - foldable = null - icon_state = "box" - item_state = null - alpha = 0 - -/obj/item/storage/box/mime/attack_hand(mob/user) - ..() - if(user.mind.miming) - alpha = 255 - -/obj/item/storage/box/mime/Moved(oldLoc, dir) - if (iscarbon(oldLoc)) - alpha = 0 - ..() - -//Disk boxes - -/obj/item/storage/box/disks - name = "diskette box" - illustration = "disk_kit" - -/obj/item/storage/box/disks/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/data(src) - - -/obj/item/storage/box/disks_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/tank/internals/emergency_oxygen(src) - new /obj/item/reagent_containers/hypospray/medipen(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/tank/internals/emergency_oxygen(src) - new /obj/item/crowbar/red(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - - -// Engineer survival box -/obj/item/storage/box/engineer/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - new /obj/item/reagent_containers/hypospray/medipen(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) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - -// Security survival box -/obj/item/storage/box/security/PopulateContents() - new /obj/item/clothing/mask/gas/sechailer(src) - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - -/obj/item/storage/box/security/radio/PopulateContents() - ..() // we want the regular stuff too - new /obj/item/radio/off(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/syringes/variety - name = "syringe variety box" - -/obj/item/storage/box/syringes/variety/PopulateContents() - new /obj/item/reagent_containers/syringe(src) - new /obj/item/reagent_containers/syringe/lethal(src) - new /obj/item/reagent_containers/syringe/noreact(src) - new /obj/item/reagent_containers/syringe/piercing(src) - new /obj/item/reagent_containers/syringe/bluespace(src) - -/obj/item/storage/box/medipens - name = "box of medipens" - desc = "A box full of epinephrine MediPens." - illustration = "syringe" - -/obj/item/storage/box/medipens/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/hypospray/medipen(src) - -/obj/item/storage/box/medipens/utility - name = "stimpack value kit" - desc = "A box with several stimpack medipens for the economical miner." - illustration = "syringe" - -/obj/item/storage/box/medipens/utility/PopulateContents() - ..() // includes regular medipens. - for(var/i in 1 to 5) - new /obj/item/reagent_containers/hypospray/medipen/stimpack(src) - -/obj/item/storage/box/beakers - name = "box of beakers" - illustration = "beaker" - -/obj/item/storage/box/beakers/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker( src ) - -/obj/item/storage/box/beakers/bluespace - name = "box of bluespace beakers" - illustration = "beaker" - -/obj/item/storage/box/beakers/bluespace/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker/bluespace(src) - -/obj/item/storage/box/beakers/variety - name = "beaker variety box" - -/obj/item/storage/box/beakers/variety/PopulateContents() - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/glass/beaker/large(src) - new /obj/item/reagent_containers/glass/beaker/plastic(src) - new /obj/item/reagent_containers/glass/beaker/meta(src) - new /obj/item/reagent_containers/glass/beaker/noreact(src) - new /obj/item/reagent_containers/glass/beaker/bluespace(src) - -/obj/item/storage/box/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() - var/static/items_inside = list( - /obj/item/dnainjector/h2m = 3, - /obj/item/dnainjector/m2h = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/flashbangs - name = "box of flashbangs (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause blindness or deafness in repeated use." - icon_state = "secbox" - illustration = "flashbang" - -/obj/item/storage/box/flashbangs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/flashbang(src) - -/obj/item/storage/box/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() - var/static/items_inside = list( - /obj/item/implantcase/tracking = 4, - /obj/item/implanter = 1, - /obj/item/implantpad = 1, - /obj/item/locator = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/minertracker - name = "boxed tracking implant kit" - desc = "For finding those who have died on the accursed lavaworld." - illustration = "implant" - -/obj/item/storage/box/minertracker/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/tracking = 3, - /obj/item/implanter = 1, - /obj/item/implantpad = 1, - /obj/item/locator = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/chemimp - name = "boxed chemical implant kit" - desc = "Box of stuff used to implant chemicals." - illustration = "implant" - -/obj/item/storage/box/chemimp/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/chem = 5, - /obj/item/implanter = 1, - /obj/item/implantpad = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/exileimp - name = "boxed exile implant kit" - desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway." - illustration = "implant" - -/obj/item/storage/box/exileimp/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/exile = 5, - /obj/item/implanter = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/bodybags - name = "body bags" - desc = "The label indicates that it contains body bags." - illustration = "bodybags" - -/obj/item/storage/box/bodybags/PopulateContents() - ..() - for(var/i in 1 to 7) - new /obj/item/bodybag(src) - -/obj/item/storage/box/rxglasses - name = "box of prescription glasses" - desc = "This box contains nerd glasses." - illustration = "glasses" - -/obj/item/storage/box/rxglasses/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/glasses/regular(src) - -/obj/item/storage/box/drinkingglasses - name = "box of drinking glasses" - desc = "It has a picture of drinking glasses on it." - -/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.set_holdable(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 - var/cube_type = /obj/item/reagent_containers/food/snacks/monkeycube - -/obj/item/storage/box/monkeycubes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 7 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) - -/obj/item/storage/box/monkeycubes/PopulateContents() - for(var/i in 1 to 5) - new cube_type(src) - -/obj/item/storage/box/monkeycubes/syndicate - desc = "Waffle Co. brand monkey cubes. Just add water and a dash of subterfuge!" - cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/syndicate - -/obj/item/storage/box/gorillacubes - name = "gorilla cube box" - desc = "Waffle Co. brand gorilla cubes. Do not taunt." - icon_state = "monkeycubebox" - illustration = null - -/obj/item/storage/box/gorillacubes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 3 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) - -/obj/item/storage/box/gorillacubes/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/monkeycube/gorilla(src) - -/obj/item/storage/box/ids - name = "box of spare IDs" - desc = "Has so many empty IDs." - illustration = "id" - -/obj/item/storage/box/ids/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/card/id(src) - -//Some spare PDAs in a box -/obj/item/storage/box/PDAs - name = "spare PDAs" - desc = "A box of spare PDA microcomputers." - illustration = "pda" - -/obj/item/storage/box/PDAs/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/pda(src) - new /obj/item/cartridge/head(src) - - var/newcart = pick( /obj/item/cartridge/engineering, - /obj/item/cartridge/security, - /obj/item/cartridge/medical, - /obj/item/cartridge/signal/toxins, - /obj/item/cartridge/quartermaster) - new newcart(src) - -/obj/item/storage/box/silver_ids - name = "box of spare silver IDs" - desc = "Shiny IDs for important people." - illustration = "id" - -/obj/item/storage/box/silver_ids/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/card/id/silver(src) - -/obj/item/storage/box/prisoner - name = "box of prisoner IDs" - desc = "Take away their last shred of dignity, their name." - 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 = "id" - -/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 = "id" - -/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 = "mousetrap" - -/obj/item/storage/box/mousetraps/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/assembly/mousetrap(src) - -/obj/item/storage/box/pillbottles - name = "box of pill bottles" - desc = "It has pictures of pill bottles on its front." - illustration = "pillbox" - -/obj/item/storage/box/pillbottles/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/storage/pill_bottle(src) - -/obj/item/storage/box/snappops - name = "snap pop box" - desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children." - icon = 'icons/obj/toy.dmi' - icon_state = "spbox" - -/obj/item/storage/box/snappops/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(list(/obj/item/toy/snappop)) - STR.max_items = 8 - -/obj/item/storage/box/snappops/PopulateContents() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop) - -/obj/item/storage/box/matches - name = "matchbox" - desc = "A small box of Almost But Not Quite Plasma Premium Matches." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "matchbox" - 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.set_holdable(list(/obj/item/match)) - -/obj/item/storage/box/matches/PopulateContents() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match) - -/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/match)) - W.matchignite() - -/obj/item/storage/box/lights - name = "box of replacement bulbs" - icon = 'icons/obj/storage.dmi' - illustration = "light" - desc = "This box is shaped on the inside so that only light tubes and bulbs fit." - 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.set_holdable(list(/obj/item/light/tube, /obj/item/light/bulb)) - STR.max_combined_w_class = 21 - STR.click_gather = FALSE //temp workaround to re-enable filling the light replacer with the box - -/obj/item/storage/box/lights/bulbs/PopulateContents() - for(var/i in 1 to 21) - new /obj/item/light/bulb(src) - -/obj/item/storage/box/lights/tubes - name = "box of replacement tubes" - illustration = "lighttube" - -/obj/item/storage/box/lights/tubes/PopulateContents() - for(var/i in 1 to 21) - new /obj/item/light/tube(src) - -/obj/item/storage/box/lights/mixed - name = "box of replacement lights" - illustration = "lightmixed" - -/obj/item/storage/box/lights/mixed/PopulateContents() - for(var/i in 1 to 14) - new /obj/item/light/tube(src) - for(var/i in 1 to 7) - new /obj/item/light/bulb(src) - - -/obj/item/storage/box/deputy - name = "box of deputy armbands" - desc = "To be issued to those authorized to act as deputy of security." - -/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].") - -/////clown box & honkbot assembly -/obj/item/storage/box/clown - name = "clown box" - desc = "A colorful cardboard box for the clown" - illustration = "clown" - -/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params) - if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot))) - if(contents.len) //prevent accidently deleting contents - to_chat(user, "You need to empty [src] out first!") - return - if(!user.temporarilyRemoveItemFromInventory(I)) - return - qdel(I) - to_chat(user, "You add some wheels to the [src]! You've got a honkbot assembly now! Honk!") - var/obj/item/bot_assembly/honkbot/A = new - qdel(src) - user.put_in_hands(A) - else - return ..() - -////// -/obj/item/storage/box/hug/medical/PopulateContents() - new /obj/item/stack/medical/bruise_pack(src) - new /obj/item/stack/medical/ointment(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - -/obj/item/storage/box/hug/survival/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - -/obj/item/storage/box/rubbershot - name = "box of rubber shots" - desc = "A box full of rubber shots, designed for riot shotguns." - icon_state = "rubbershot_box" - illustration = null - -/obj/item/storage/box/rubbershot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/rubbershot(src) - -/obj/item/storage/box/lethalshot - name = "box of lethal shotgun shots" - desc = "A box full of lethal shots, designed for riot shotguns." - icon_state = "lethalshot_box" - illustration = null - -/obj/item/storage/box/lethalshot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/buckshot(src) - -/obj/item/storage/box/beanbag - name = "box of beanbags" - desc = "A box full of beanbag shells." - icon_state = "rubbershot_box" - illustration = null - -/obj/item/storage/box/beanbag/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/ammo_casing/shotgun/beanbag(src) - -/obj/item/storage/box/actionfigure - name = "box of action figures" - desc = "The latest set of collectable action figures." - icon_state = "box" - -/obj/item/storage/box/actionfigure/PopulateContents() - for(var/i in 1 to 4) - var/randomFigure = pick(subtypesof(/obj/item/toy/figure)) - new randomFigure(src) - -#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.is_sharp()) - if(!contents.len) - if(item_state == "paperbag_None") - user.show_message("You cut eyeholes into [src].", 1) - 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.", 1) - 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 randomely chosen version the chef spawns with, it shouldn't actually exist. - name = "ingredients box" - illustration = "fruit" - var/theme_name - -/obj/item/storage/box/ingredients/Initialize() - . = ..() - if(theme_name) - name = "[name] ([theme_name])" - desc = "A box containing supplementary ingredients for the aspiring chef. The box's theme is '[theme_name]'." - 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/meatball(src) //YOGS - bigotry rule - new /obj/item/reagent_containers/food/drinks/bottle/wine(src) - -/obj/item/storage/box/ingredients/vegetarian - theme_name = "vegetarian" - -/obj/item/storage/box/ingredients/vegetarian/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/carrot(src) - new /obj/item/reagent_containers/food/snacks/grown/eggplant(src) - new /obj/item/reagent_containers/food/snacks/grown/potato(src) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - -/obj/item/storage/box/ingredients/american - theme_name = "american" - -/obj/item/storage/box/ingredients/american/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/potato(src) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) //YOGS - bigotry rule - -/obj/item/storage/box/ingredients/fruity - theme_name = "fruity" - -/obj/item/storage/box/ingredients/fruity/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/orange(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/lime(src) - new /obj/item/reagent_containers/food/snacks/grown/watermelon(src) - -/obj/item/storage/box/ingredients/sweets - theme_name = "sweets" - -/obj/item/storage/box/ingredients/sweets/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/cherries(src) - new /obj/item/reagent_containers/food/snacks/grown/banana(src) - new /obj/item/reagent_containers/food/snacks/chocolatebar(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - -/obj/item/storage/box/ingredients/delights - theme_name = "delights" - -/obj/item/storage/box/ingredients/delights/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/potato/sweet(src) - new /obj/item/reagent_containers/food/snacks/grown/bluecherries(src) - new /obj/item/reagent_containers/food/snacks/grown/vanillapod(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/food/snacks/grown/berries(src) - -/obj/item/storage/box/ingredients/grains - theme_name = "grains" - -/obj/item/storage/box/ingredients/grains/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/grown/oat(src) - new /obj/item/reagent_containers/food/snacks/grown/wheat(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/honeycomb(src) - new /obj/item/seeds/poppy(src) - -/obj/item/storage/box/ingredients/carnivore - theme_name = "carnivore" - -/obj/item/storage/box/ingredients/carnivore/PopulateContents() - new /obj/item/reagent_containers/food/snacks/meat/slab/bear(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/spider(src) - new /obj/item/reagent_containers/food/snacks/spidereggs(src) - new /obj/item/reagent_containers/food/snacks/carpmeat(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/xeno(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) //YOGS - bigotry rule - -/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() - var/static/items_inside = list( - /obj/item/stock_parts/capacitor = 3, - /obj/item/stock_parts/scanning_module = 3, - /obj/item/stock_parts/manipulator = 3, - /obj/item/stock_parts/micro_laser = 3, - /obj/item/stock_parts/matter_bin = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/stockparts/deluxe - name = "box of deluxe stock parts" - desc = "Contains a variety of deluxe stock parts." - icon_state = "syndiebox" - -/obj/item/storage/box/stockparts/deluxe/PopulateContents() - var/static/items_inside = list( - /obj/item/stock_parts/capacitor/quadratic = 3, - /obj/item/stock_parts/scanning_module/triphasic = 3, - /obj/item/stock_parts/manipulator/femto = 3, - /obj/item/stock_parts/micro_laser/quadultra = 3, - /obj/item/stock_parts/matter_bin/bluespace = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/dishdrive - name = "DIY Dish Drive Kit" - desc = "Contains everything you need to build your own Dish Drive!" - custom_premium_price = 200 - -/obj/item/storage/box/dishdrive/PopulateContents() - var/static/items_inside = list( - /obj/item/stack/sheet/metal/five = 1, - /obj/item/stack/cable_coil/random/five = 1, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/matter_bin = 2, - /obj/item/screwdriver = 1) +/* + * Everything derived from the common cardboard box. + * Basically everything except the original is a kit (starts full). + * + * Contains: + * Empty box, starter boxes (survival/engineer), + * Latex glove and sterile mask boxes, + * Syringe, beaker, dna injector boxes, + * Blanks, flashbangs, and EMP grenade boxes, + * Tracking and chemical implant boxes, + * Prescription glasses and drinking glass boxes, + * Condiment bottle and silly cup boxes, + * Donkpocket and monkeycube boxes, + * ID and security PDA cart boxes, + * Handcuff, mousetrap, and pillbottle boxes, + * Snap-pops and matchboxes, + * Replacement light boxes. + * Action Figure Boxes + * Various paper bags. + * + * For syndicate call-ins see uplink_kits.dm + */ + +/obj/item/storage/box + name = "box" + desc = "It's just an ordinary box." + icon_state = "box" + 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" + +/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 ..() + +//Mime spell boxes + +/obj/item/storage/box/mime + name = "invisible box" + desc = "Unfortunately not large enough to trap the mime." + foldable = null + icon_state = "box" + item_state = null + alpha = 0 + +/obj/item/storage/box/mime/attack_hand(mob/user) + ..() + if(user.mind.miming) + alpha = 255 + +/obj/item/storage/box/mime/Moved(oldLoc, dir) + if (iscarbon(oldLoc)) + alpha = 0 + ..() + +//Disk boxes + +/obj/item/storage/box/disks + name = "diskette box" + illustration = "disk_kit" + +/obj/item/storage/box/disks/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/data(src) + + +/obj/item/storage/box/disks_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/tank/internals/emergency_oxygen(src) + new /obj/item/reagent_containers/hypospray/medipen(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/tank/internals/emergency_oxygen(src) + new /obj/item/crowbar/red(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + + +// Engineer survival box +/obj/item/storage/box/engineer/PopulateContents() + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + new /obj/item/reagent_containers/hypospray/medipen(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) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + +// Security survival box +/obj/item/storage/box/security/PopulateContents() + new /obj/item/clothing/mask/gas/sechailer(src) + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + +/obj/item/storage/box/security/radio/PopulateContents() + ..() // we want the regular stuff too + new /obj/item/radio/off(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/syringes/variety + name = "syringe variety box" + +/obj/item/storage/box/syringes/variety/PopulateContents() + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/syringe/lethal(src) + new /obj/item/reagent_containers/syringe/noreact(src) + new /obj/item/reagent_containers/syringe/piercing(src) + new /obj/item/reagent_containers/syringe/bluespace(src) + +/obj/item/storage/box/medipens + name = "box of medipens" + desc = "A box full of epinephrine MediPens." + illustration = "syringe" + +/obj/item/storage/box/medipens/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/hypospray/medipen(src) + +/obj/item/storage/box/medipens/utility + name = "stimpack value kit" + desc = "A box with several stimpack medipens for the economical miner." + illustration = "syringe" + +/obj/item/storage/box/medipens/utility/PopulateContents() + ..() // includes regular medipens. + for(var/i in 1 to 5) + new /obj/item/reagent_containers/hypospray/medipen/stimpack(src) + +/obj/item/storage/box/beakers + name = "box of beakers" + illustration = "beaker" + +/obj/item/storage/box/beakers/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker( src ) + +/obj/item/storage/box/beakers/bluespace + name = "box of bluespace beakers" + illustration = "beaker" + +/obj/item/storage/box/beakers/bluespace/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker/bluespace(src) + +/obj/item/storage/box/beakers/variety + name = "beaker variety box" + +/obj/item/storage/box/beakers/variety/PopulateContents() + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/glass/beaker/large(src) + new /obj/item/reagent_containers/glass/beaker/plastic(src) + new /obj/item/reagent_containers/glass/beaker/meta(src) + new /obj/item/reagent_containers/glass/beaker/noreact(src) + new /obj/item/reagent_containers/glass/beaker/bluespace(src) + +/obj/item/storage/box/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() + var/static/items_inside = list( + /obj/item/dnainjector/h2m = 3, + /obj/item/dnainjector/m2h = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/flashbangs + name = "box of flashbangs (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause blindness or deafness in repeated use." + icon_state = "secbox" + illustration = "flashbang" + +/obj/item/storage/box/flashbangs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/flashbang(src) + +/obj/item/storage/box/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() + var/static/items_inside = list( + /obj/item/implantcase/tracking = 4, + /obj/item/implanter = 1, + /obj/item/implantpad = 1, + /obj/item/locator = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/minertracker + name = "boxed tracking implant kit" + desc = "For finding those who have died on the accursed lavaworld." + illustration = "implant" + +/obj/item/storage/box/minertracker/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/tracking = 3, + /obj/item/implanter = 1, + /obj/item/implantpad = 1, + /obj/item/locator = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/chemimp + name = "boxed chemical implant kit" + desc = "Box of stuff used to implant chemicals." + illustration = "implant" + +/obj/item/storage/box/chemimp/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/chem = 5, + /obj/item/implanter = 1, + /obj/item/implantpad = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/exileimp + name = "boxed exile implant kit" + desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway." + illustration = "implant" + +/obj/item/storage/box/exileimp/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/exile = 5, + /obj/item/implanter = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/bodybags + name = "body bags" + desc = "The label indicates that it contains body bags." + illustration = "bodybags" + +/obj/item/storage/box/bodybags/PopulateContents() + ..() + for(var/i in 1 to 7) + new /obj/item/bodybag(src) + +/obj/item/storage/box/rxglasses + name = "box of prescription glasses" + desc = "This box contains nerd glasses." + illustration = "glasses" + +/obj/item/storage/box/rxglasses/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/glasses/regular(src) + +/obj/item/storage/box/drinkingglasses + name = "box of drinking glasses" + desc = "It has a picture of drinking glasses on it." + +/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.set_holdable(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 + var/cube_type = /obj/item/reagent_containers/food/snacks/monkeycube + +/obj/item/storage/box/monkeycubes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 7 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) + +/obj/item/storage/box/monkeycubes/PopulateContents() + for(var/i in 1 to 5) + new cube_type(src) + +/obj/item/storage/box/monkeycubes/syndicate + desc = "Waffle Co. brand monkey cubes. Just add water and a dash of subterfuge!" + cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/syndicate + +/obj/item/storage/box/gorillacubes + name = "gorilla cube box" + desc = "Waffle Co. brand gorilla cubes. Do not taunt." + icon_state = "monkeycubebox" + illustration = null + +/obj/item/storage/box/gorillacubes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 3 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) + +/obj/item/storage/box/gorillacubes/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/monkeycube/gorilla(src) + +/obj/item/storage/box/ids + name = "box of spare IDs" + desc = "Has so many empty IDs." + illustration = "id" + +/obj/item/storage/box/ids/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/card/id(src) + +//Some spare PDAs in a box +/obj/item/storage/box/PDAs + name = "spare PDAs" + desc = "A box of spare PDA microcomputers." + illustration = "pda" + +/obj/item/storage/box/PDAs/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/pda(src) + new /obj/item/cartridge/head(src) + + var/newcart = pick( /obj/item/cartridge/engineering, + /obj/item/cartridge/security, + /obj/item/cartridge/medical, + /obj/item/cartridge/signal/toxins, + /obj/item/cartridge/quartermaster) + new newcart(src) + +/obj/item/storage/box/silver_ids + name = "box of spare silver IDs" + desc = "Shiny IDs for important people." + illustration = "id" + +/obj/item/storage/box/silver_ids/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/card/id/silver(src) + +/obj/item/storage/box/prisoner + name = "box of prisoner IDs" + desc = "Take away their last shred of dignity, their name." + 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 = "id" + +/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 = "id" + +/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 = "mousetrap" + +/obj/item/storage/box/mousetraps/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/assembly/mousetrap(src) + +/obj/item/storage/box/pillbottles + name = "box of pill bottles" + desc = "It has pictures of pill bottles on its front." + illustration = "pillbox" + +/obj/item/storage/box/pillbottles/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/storage/pill_bottle(src) + +/obj/item/storage/box/snappops + name = "snap pop box" + desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children." + icon = 'icons/obj/toy.dmi' + icon_state = "spbox" + +/obj/item/storage/box/snappops/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(list(/obj/item/toy/snappop)) + STR.max_items = 8 + +/obj/item/storage/box/snappops/PopulateContents() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop) + +/obj/item/storage/box/matches + name = "matchbox" + desc = "A small box of Almost But Not Quite Plasma Premium Matches." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "matchbox" + 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.set_holdable(list(/obj/item/match)) + +/obj/item/storage/box/matches/PopulateContents() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match) + +/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/match)) + W.matchignite() + +/obj/item/storage/box/lights + name = "box of replacement bulbs" + icon = 'icons/obj/storage.dmi' + illustration = "light" + desc = "This box is shaped on the inside so that only light tubes and bulbs fit." + 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.set_holdable(list(/obj/item/light/tube, /obj/item/light/bulb)) + STR.max_combined_w_class = 21 + STR.click_gather = FALSE //temp workaround to re-enable filling the light replacer with the box + +/obj/item/storage/box/lights/bulbs/PopulateContents() + for(var/i in 1 to 21) + new /obj/item/light/bulb(src) + +/obj/item/storage/box/lights/tubes + name = "box of replacement tubes" + illustration = "lighttube" + +/obj/item/storage/box/lights/tubes/PopulateContents() + for(var/i in 1 to 21) + new /obj/item/light/tube(src) + +/obj/item/storage/box/lights/mixed + name = "box of replacement lights" + illustration = "lightmixed" + +/obj/item/storage/box/lights/mixed/PopulateContents() + for(var/i in 1 to 14) + new /obj/item/light/tube(src) + for(var/i in 1 to 7) + new /obj/item/light/bulb(src) + + +/obj/item/storage/box/deputy + name = "box of deputy armbands" + desc = "To be issued to those authorized to act as deputy of security." + +/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].") + +/////clown box & honkbot assembly +/obj/item/storage/box/clown + name = "clown box" + desc = "A colorful cardboard box for the clown" + illustration = "clown" + +/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params) + if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot))) + if(contents.len) //prevent accidently deleting contents + to_chat(user, "You need to empty [src] out first!") + return + if(!user.temporarilyRemoveItemFromInventory(I)) + return + qdel(I) + to_chat(user, "You add some wheels to the [src]! You've got a honkbot assembly now! Honk!") + var/obj/item/bot_assembly/honkbot/A = new + qdel(src) + user.put_in_hands(A) + else + return ..() + +////// +/obj/item/storage/box/hug/medical/PopulateContents() + new /obj/item/stack/medical/bruise_pack(src) + new /obj/item/stack/medical/ointment(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + +/obj/item/storage/box/hug/survival/PopulateContents() + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + +/obj/item/storage/box/rubbershot + name = "box of rubber shots" + desc = "A box full of rubber shots, designed for riot shotguns." + icon_state = "rubbershot_box" + illustration = null + +/obj/item/storage/box/rubbershot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/rubbershot(src) + +/obj/item/storage/box/lethalshot + name = "box of lethal shotgun shots" + desc = "A box full of lethal shots, designed for riot shotguns." + icon_state = "lethalshot_box" + illustration = null + +/obj/item/storage/box/lethalshot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/buckshot(src) + +/obj/item/storage/box/beanbag + name = "box of beanbags" + desc = "A box full of beanbag shells." + icon_state = "rubbershot_box" + illustration = null + +/obj/item/storage/box/beanbag/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/ammo_casing/shotgun/beanbag(src) + +/obj/item/storage/box/actionfigure + name = "box of action figures" + desc = "The latest set of collectable action figures." + icon_state = "box" + +/obj/item/storage/box/actionfigure/PopulateContents() + for(var/i in 1 to 4) + var/randomFigure = pick(subtypesof(/obj/item/toy/figure)) + new randomFigure(src) + +#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.is_sharp()) + if(!contents.len) + if(item_state == "paperbag_None") + user.show_message("You cut eyeholes into [src].", 1) + 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.", 1) + 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 randomely chosen version the chef spawns with, it shouldn't actually exist. + name = "ingredients box" + illustration = "fruit" + var/theme_name + +/obj/item/storage/box/ingredients/Initialize() + . = ..() + if(theme_name) + name = "[name] ([theme_name])" + desc = "A box containing supplementary ingredients for the aspiring chef. The box's theme is '[theme_name]'." + 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/meatball(src) //YOGS - bigotry rule + new /obj/item/reagent_containers/food/drinks/bottle/wine(src) + +/obj/item/storage/box/ingredients/vegetarian + theme_name = "vegetarian" + +/obj/item/storage/box/ingredients/vegetarian/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/carrot(src) + new /obj/item/reagent_containers/food/snacks/grown/eggplant(src) + new /obj/item/reagent_containers/food/snacks/grown/potato(src) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + +/obj/item/storage/box/ingredients/american + theme_name = "american" + +/obj/item/storage/box/ingredients/american/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/potato(src) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) //YOGS - bigotry rule + +/obj/item/storage/box/ingredients/fruity + theme_name = "fruity" + +/obj/item/storage/box/ingredients/fruity/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/orange(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/lime(src) + new /obj/item/reagent_containers/food/snacks/grown/watermelon(src) + +/obj/item/storage/box/ingredients/sweets + theme_name = "sweets" + +/obj/item/storage/box/ingredients/sweets/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/cherries(src) + new /obj/item/reagent_containers/food/snacks/grown/banana(src) + new /obj/item/reagent_containers/food/snacks/chocolatebar(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + +/obj/item/storage/box/ingredients/delights + theme_name = "delights" + +/obj/item/storage/box/ingredients/delights/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/potato/sweet(src) + new /obj/item/reagent_containers/food/snacks/grown/bluecherries(src) + new /obj/item/reagent_containers/food/snacks/grown/vanillapod(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/food/snacks/grown/berries(src) + +/obj/item/storage/box/ingredients/grains + theme_name = "grains" + +/obj/item/storage/box/ingredients/grains/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/grown/oat(src) + new /obj/item/reagent_containers/food/snacks/grown/wheat(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/honeycomb(src) + new /obj/item/seeds/poppy(src) + +/obj/item/storage/box/ingredients/carnivore + theme_name = "carnivore" + +/obj/item/storage/box/ingredients/carnivore/PopulateContents() + new /obj/item/reagent_containers/food/snacks/meat/slab/bear(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/spider(src) + new /obj/item/reagent_containers/food/snacks/spidereggs(src) + new /obj/item/reagent_containers/food/snacks/carpmeat(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/xeno(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) //YOGS - bigotry rule + +/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() + var/static/items_inside = list( + /obj/item/stock_parts/capacitor = 3, + /obj/item/stock_parts/scanning_module = 3, + /obj/item/stock_parts/manipulator = 3, + /obj/item/stock_parts/micro_laser = 3, + /obj/item/stock_parts/matter_bin = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/stockparts/deluxe + name = "box of deluxe stock parts" + desc = "Contains a variety of deluxe stock parts." + icon_state = "syndiebox" + +/obj/item/storage/box/stockparts/deluxe/PopulateContents() + var/static/items_inside = list( + /obj/item/stock_parts/capacitor/quadratic = 3, + /obj/item/stock_parts/scanning_module/triphasic = 3, + /obj/item/stock_parts/manipulator/femto = 3, + /obj/item/stock_parts/micro_laser/quadultra = 3, + /obj/item/stock_parts/matter_bin/bluespace = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/dishdrive + name = "DIY Dish Drive Kit" + desc = "Contains everything you need to build your own Dish Drive!" + custom_premium_price = 200 + +/obj/item/storage/box/dishdrive/PopulateContents() + var/static/items_inside = list( + /obj/item/stack/sheet/metal/five = 1, + /obj/item/stack/cable_coil/random/five = 1, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/matter_bin = 2, + /obj/item/screwdriver = 1) generate_items_inside(items_inside,src) \ 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 62d2227d7914..bc08ec556e1a 100644 --- a/code/game/objects/items/storage/briefcase.dm +++ b/code/game/objects/items/storage/briefcase.dm @@ -1,49 +1,49 @@ -/obj/item/storage/briefcase - name = "briefcase" - desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional." - icon_state = "briefcase" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - flags_1 = CONDUCT_1 - force = 8 - hitsound = "swing_hit" - throw_speed = 2 - throw_range = 4 - w_class = WEIGHT_CLASS_BULKY - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - resistance_flags = FLAMMABLE - max_integrity = 150 - var/folder_path = /obj/item/folder //this is the path of the folder that gets spawned in New() - -/obj/item/storage/briefcase/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 21 - -/obj/item/storage/briefcase/PopulateContents() - new /obj/item/pen(src) - var/obj/item/folder/folder = new folder_path(src) - for(var/i in 1 to 6) - new /obj/item/paper(folder) - -/obj/item/storage/briefcase/lawyer - folder_path = /obj/item/folder/blue - -/obj/item/storage/briefcase/lawyer/PopulateContents() - new /obj/item/stamp/law(src) - ..() - -/obj/item/storage/briefcase/sniperbundle - desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding. Smells like L'Air du Temps." - force = 10 - -/obj/item/storage/briefcase/sniperbundle/PopulateContents() - ..() // in case you need any paperwork done after your rampage - new /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate(src) - new /obj/item/clothing/neck/tie/red(src) - new /obj/item/clothing/under/syndicate/sniper(src) - new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) - new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) - new /obj/item/suppressor/specialoffer(src) - +/obj/item/storage/briefcase + name = "briefcase" + desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional." + icon_state = "briefcase" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + flags_1 = CONDUCT_1 + force = 8 + hitsound = "swing_hit" + throw_speed = 2 + throw_range = 4 + w_class = WEIGHT_CLASS_BULKY + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + resistance_flags = FLAMMABLE + max_integrity = 150 + var/folder_path = /obj/item/folder //this is the path of the folder that gets spawned in New() + +/obj/item/storage/briefcase/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 21 + +/obj/item/storage/briefcase/PopulateContents() + new /obj/item/pen(src) + var/obj/item/folder/folder = new folder_path(src) + for(var/i in 1 to 6) + new /obj/item/paper(folder) + +/obj/item/storage/briefcase/lawyer + folder_path = /obj/item/folder/blue + +/obj/item/storage/briefcase/lawyer/PopulateContents() + new /obj/item/stamp/law(src) + ..() + +/obj/item/storage/briefcase/sniperbundle + desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding. Smells like L'Air du Temps." + force = 10 + +/obj/item/storage/briefcase/sniperbundle/PopulateContents() + ..() // in case you need any paperwork done after your rampage + new /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate(src) + new /obj/item/clothing/neck/tie/red(src) + new /obj/item/clothing/under/syndicate/sniper(src) + new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) + new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) + new /obj/item/suppressor/specialoffer(src) + diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm index 47995683fb1f..f5388f47f7cc 100644 --- a/code/game/objects/items/storage/fancy.dm +++ b/code/game/objects/items/storage/fancy.dm @@ -1,352 +1,352 @@ -/* - * 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.set_holdable(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.set_holdable(list(/obj/item/reagent_containers/food/snacks/egg)) - -/* - * Candle Box - */ - -/obj/item/storage/fancy/candle_box - name = "candle pack" - desc = "A pack of red candles." - icon = 'icons/obj/candle.dmi' - icon_state = "candlebox5" - icon_type = "candle" - 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.set_holdable(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.") - -/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.set_holdable(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.set_holdable(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 = 1 //generate sprites for cigars in the box - for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) - var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]_[cigar_position]") - 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.set_holdable(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.set_holdable(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.set_holdable(list(/obj/item/reagent_containers/food/snacks/egg)) + +/* + * Candle Box + */ + +/obj/item/storage/fancy/candle_box + name = "candle pack" + desc = "A pack of red candles." + icon = 'icons/obj/candle.dmi' + icon_state = "candlebox5" + icon_type = "candle" + 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.set_holdable(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.") + +/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.set_holdable(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.set_holdable(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 = 1 //generate sprites for cigars in the box + for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) + var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]_[cigar_position]") + 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.set_holdable(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm index eceaa8409dd9..16c198d4df92 100644 --- a/code/game/objects/items/storage/firstaid.dm +++ b/code/game/objects/items/storage/firstaid.dm @@ -1,358 +1,358 @@ -/* First aid storage - * Contains: - * First Aid Kits - * Pill Bottles - * Dice Pack (in a pill bottle) - */ - -/* - * First Aid Kits - */ -/obj/item/storage/firstaid - name = "first-aid kit" - desc = "It's an emergency medical kit for those serious boo-boos." - icon_state = "firstaid" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 7 - var/empty = FALSE - -/obj/item/storage/firstaid/regular - icon_state = "firstaid" - desc = "A first aid kit with the ability to heal common types of injuries." - -/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/firstaid/regular/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/stack/medical/gauze = 1, - /obj/item/stack/medical/bruise_pack = 2, - /obj/item/stack/medical/ointment = 2, - /obj/item/reagent_containers/hypospray/medipen = 1, - /obj/item/healthanalyzer = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/ancient - icon_state = "firstaid" - desc = "A first aid kit with the ability to heal common types of injuries." - -/obj/item/storage/firstaid/ancient/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/stack/medical/gauze = 1, - /obj/item/stack/medical/bruise_pack = 3, - /obj/item/stack/medical/ointment= 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/fire - name = "burn treatment kit" - desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down." - icon_state = "ointment" - item_state = "firstaid-ointment" - -/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!") - return FIRELOSS - -/obj/item/storage/firstaid/fire/Initialize(mapload) - . = ..() - icon_state = pick("ointment","firefirstaid") - -/obj/item/storage/firstaid/fire/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/silver_sulf = 3, - /obj/item/reagent_containers/pill/oxandrolone = 2, - /obj/item/reagent_containers/hypospray/medipen = 1, - /obj/item/healthanalyzer = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/toxin - name = "toxin treatment kit" - desc = "Used to treat toxic blood content and radiation poisoning." - icon_state = "antitoxin" - item_state = "firstaid-toxin" - -/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return TOXLOSS - -/obj/item/storage/firstaid/toxin/Initialize(mapload) - . = ..() - icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3") - -/obj/item/storage/firstaid/toxin/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/syringe/charcoal = 4, - /obj/item/storage/pill_bottle/charcoal = 2, - /obj/item/healthanalyzer = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/o2 - name = "oxygen deprivation treatment kit" - desc = "A box full of oxygen goodies." - icon_state = "o2" - item_state = "firstaid-o2" - -/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/storage/firstaid/o2/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/syringe/perfluorodecalin = 5, - /obj/item/reagent_containers/hypospray/medipen = 1, - /obj/item/healthanalyzer = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/brute - name = "brute trauma treatment kit" - desc = "A first aid kit for when you get toolboxed." - icon_state = "brute" - item_state = "firstaid-brute" - -/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/firstaid/brute/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/styptic = 4, - /obj/item/stack/medical/gauze = 2, - /obj/item/healthanalyzer = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/advanced - name = "advanced first aid kit" - desc = "An advanced kit to help deal with advanced wounds." - icon_state = "radfirstaid" - item_state = "firstaid-rad" - custom_premium_price = 600 - -/obj/item/storage/firstaid/advanced/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/synthflesh = 3, - /obj/item/reagent_containers/hypospray/medipen/atropine = 2, - /obj/item/stack/medical/gauze = 1, - /obj/item/storage/pill_bottle/penacid = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/tactical - name = "combat medical kit" - desc = "I hope you've got insurance." - icon_state = "bezerk" - item_state = "firstaid-bezerk" - -/obj/item/storage/firstaid/tactical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - -/obj/item/storage/firstaid/tactical/PopulateContents() - if(empty) - return - new /obj/item/stack/medical/gauze(src) - new /obj/item/defibrillator/compact/combat/loaded(src) - new /obj/item/reagent_containers/hypospray/combat(src) - new /obj/item/reagent_containers/pill/patch/styptic(src) - new /obj/item/reagent_containers/pill/patch/styptic(src) - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - new /obj/item/clothing/glasses/hud/health/night(src) - -//medibot assembly -/obj/item/storage/firstaid/attackby(obj/item/bodypart/S, mob/user, params) - if((!istype(S, /obj/item/bodypart/l_arm/robot)) && (!istype(S, /obj/item/bodypart/r_arm/robot))) - return ..() - - //Making a medibot! - if(contents.len >= 1) - to_chat(user, "You need to empty [src] out first!") - return - - var/obj/item/bot_assembly/medbot/A = new - if(istype(src, /obj/item/storage/firstaid/fire)) - A.skin = "ointment" - else if(istype(src, /obj/item/storage/firstaid/toxin)) - A.skin = "tox" - else if(istype(src, /obj/item/storage/firstaid/o2)) - A.skin = "o2" - else if(istype(src, /obj/item/storage/firstaid/brute)) - A.skin = "brute" - user.put_in_hands(A) - to_chat(user, "You add [S] to [src].") - A.robot_arm = S.type - A.firstaid = type - qdel(S) - qdel(src) - -/* - * Pill Bottles - */ - -/obj/item/storage/pill_bottle - name = "pill bottle" - desc = "It's an airtight container for storing medication." - icon_state = "pill_canister" - icon = 'icons/obj/chemical.dmi' - item_state = "contsolid" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/pill_bottle/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.allow_quick_gather = TRUE - STR.click_gather = TRUE - STR.set_holdable(list(/obj/item/reagent_containers/pill, /obj/item/dice)) - -/obj/item/storage/pill_bottle/suicide_act(mob/user) - user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (TOXLOSS) - -/obj/item/storage/pill_bottle/charcoal - name = "bottle of charcoal pills" - desc = "Contains pills used to counter toxins." - -/obj/item/storage/pill_bottle/charcoal/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/charcoal(src) - -/obj/item/storage/pill_bottle/epinephrine - name = "bottle of epinephrine pills" - desc = "Contains pills used to stabilize patients." - -/obj/item/storage/pill_bottle/epinephrine/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/epinephrine(src) - -/obj/item/storage/pill_bottle/mutadone - name = "bottle of mutadone pills" - desc = "Contains pills used to treat genetic abnormalities." - -/obj/item/storage/pill_bottle/mutadone/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/mutadone(src) - -/obj/item/storage/pill_bottle/mannitol - name = "bottle of mannitol pills" - desc = "Contains pills used to treat brain damage." - -/obj/item/storage/pill_bottle/mannitol/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/mannitol(src) - -/obj/item/storage/pill_bottle/stimulant - name = "bottle of stimulant pills" - desc = "Guaranteed to give you that extra burst of energy during a long shift!" - -/obj/item/storage/pill_bottle/stimulant/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/stimulant(src) - -/obj/item/storage/pill_bottle/mining - name = "bottle of patches" - desc = "Contains patches used to treat brute and burn damage." - -/obj/item/storage/pill_bottle/mining/PopulateContents() - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/patch/styptic(src) - -/obj/item/storage/pill_bottle/zoom - name = "suspicious pill bottle" - desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds." - -/obj/item/storage/pill_bottle/zoom/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/zoom(src) - -/obj/item/storage/pill_bottle/happy - name = "suspicious pill bottle" - desc = "There is a smiley on the top." - -/obj/item/storage/pill_bottle/happy/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/happy(src) - -/obj/item/storage/pill_bottle/lsd - name = "suspicious pill bottle" - desc = "There is a crude drawing which could be either a mushroom, or a deformed moon." - -/obj/item/storage/pill_bottle/lsd/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/lsd(src) - -/obj/item/storage/pill_bottle/aranesp - name = "suspicious pill bottle" - desc = "The label has 'fuck disablers' hastily scrawled in black marker." - -/obj/item/storage/pill_bottle/aranesp/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/aranesp(src) - -/obj/item/storage/pill_bottle/psicodine - name = "bottle of psicodine pills" - desc = "Contains pills used to treat mental distress and traumas." - -/obj/item/storage/pill_bottle/psicodine/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/psicodine(src) - -/obj/item/storage/pill_bottle/happiness - name = "happiness pill bottle" - desc = "The label is long gone, in its place an 'H' written with a marker." - -/obj/item/storage/pill_bottle/happiness/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/happiness(src) - -/obj/item/storage/pill_bottle/penacid - name = "bottle of pentetic acid pills" - desc = "Contains pills to expunge radiation and toxins." - -/obj/item/storage/pill_bottle/penacid/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/penacid(src) - - -/obj/item/storage/pill_bottle/neurine - name = "bottle of neurine pills" - desc = "Contains pills to treat non-severe mental traumas." - -/obj/item/storage/pill_bottle/neurine/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/neurine(src) - -/obj/item/storage/pill_bottle/floorpill - name = "bottle of floorpills" - desc = "An old pill bottle. It smells musty." - -/obj/item/storage/pill_bottle/floorpill/Initialize() - . = ..() - var/obj/item/reagent_containers/pill/P = locate() in src - name = "bottle of [P.name]s" - -/obj/item/storage/pill_bottle/floorpill/PopulateContents() - for(var/i in 1 to rand(1,7)) - new /obj/item/reagent_containers/pill/floorpill(src) - -/obj/item/storage/pill_bottle/floorpill/full/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/floorpill(src) +/* First aid storage + * Contains: + * First Aid Kits + * Pill Bottles + * Dice Pack (in a pill bottle) + */ + +/* + * First Aid Kits + */ +/obj/item/storage/firstaid + name = "first-aid kit" + desc = "It's an emergency medical kit for those serious boo-boos." + icon_state = "firstaid" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 7 + var/empty = FALSE + +/obj/item/storage/firstaid/regular + icon_state = "firstaid" + desc = "A first aid kit with the ability to heal common types of injuries." + +/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/firstaid/regular/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/bruise_pack = 2, + /obj/item/stack/medical/ointment = 2, + /obj/item/reagent_containers/hypospray/medipen = 1, + /obj/item/healthanalyzer = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/ancient + icon_state = "firstaid" + desc = "A first aid kit with the ability to heal common types of injuries." + +/obj/item/storage/firstaid/ancient/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/bruise_pack = 3, + /obj/item/stack/medical/ointment= 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/fire + name = "burn treatment kit" + desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down." + icon_state = "ointment" + item_state = "firstaid-ointment" + +/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!") + return FIRELOSS + +/obj/item/storage/firstaid/fire/Initialize(mapload) + . = ..() + icon_state = pick("ointment","firefirstaid") + +/obj/item/storage/firstaid/fire/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/silver_sulf = 3, + /obj/item/reagent_containers/pill/oxandrolone = 2, + /obj/item/reagent_containers/hypospray/medipen = 1, + /obj/item/healthanalyzer = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/toxin + name = "toxin treatment kit" + desc = "Used to treat toxic blood content and radiation poisoning." + icon_state = "antitoxin" + item_state = "firstaid-toxin" + +/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return TOXLOSS + +/obj/item/storage/firstaid/toxin/Initialize(mapload) + . = ..() + icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3") + +/obj/item/storage/firstaid/toxin/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/syringe/charcoal = 4, + /obj/item/storage/pill_bottle/charcoal = 2, + /obj/item/healthanalyzer = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/o2 + name = "oxygen deprivation treatment kit" + desc = "A box full of oxygen goodies." + icon_state = "o2" + item_state = "firstaid-o2" + +/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/storage/firstaid/o2/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/syringe/perfluorodecalin = 5, + /obj/item/reagent_containers/hypospray/medipen = 1, + /obj/item/healthanalyzer = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/brute + name = "brute trauma treatment kit" + desc = "A first aid kit for when you get toolboxed." + icon_state = "brute" + item_state = "firstaid-brute" + +/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/firstaid/brute/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/styptic = 4, + /obj/item/stack/medical/gauze = 2, + /obj/item/healthanalyzer = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/advanced + name = "advanced first aid kit" + desc = "An advanced kit to help deal with advanced wounds." + icon_state = "radfirstaid" + item_state = "firstaid-rad" + custom_premium_price = 600 + +/obj/item/storage/firstaid/advanced/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/synthflesh = 3, + /obj/item/reagent_containers/hypospray/medipen/atropine = 2, + /obj/item/stack/medical/gauze = 1, + /obj/item/storage/pill_bottle/penacid = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/tactical + name = "combat medical kit" + desc = "I hope you've got insurance." + icon_state = "bezerk" + item_state = "firstaid-bezerk" + +/obj/item/storage/firstaid/tactical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + +/obj/item/storage/firstaid/tactical/PopulateContents() + if(empty) + return + new /obj/item/stack/medical/gauze(src) + new /obj/item/defibrillator/compact/combat/loaded(src) + new /obj/item/reagent_containers/hypospray/combat(src) + new /obj/item/reagent_containers/pill/patch/styptic(src) + new /obj/item/reagent_containers/pill/patch/styptic(src) + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + new /obj/item/clothing/glasses/hud/health/night(src) + +//medibot assembly +/obj/item/storage/firstaid/attackby(obj/item/bodypart/S, mob/user, params) + if((!istype(S, /obj/item/bodypart/l_arm/robot)) && (!istype(S, /obj/item/bodypart/r_arm/robot))) + return ..() + + //Making a medibot! + if(contents.len >= 1) + to_chat(user, "You need to empty [src] out first!") + return + + var/obj/item/bot_assembly/medbot/A = new + if(istype(src, /obj/item/storage/firstaid/fire)) + A.skin = "ointment" + else if(istype(src, /obj/item/storage/firstaid/toxin)) + A.skin = "tox" + else if(istype(src, /obj/item/storage/firstaid/o2)) + A.skin = "o2" + else if(istype(src, /obj/item/storage/firstaid/brute)) + A.skin = "brute" + user.put_in_hands(A) + to_chat(user, "You add [S] to [src].") + A.robot_arm = S.type + A.firstaid = type + qdel(S) + qdel(src) + +/* + * Pill Bottles + */ + +/obj/item/storage/pill_bottle + name = "pill bottle" + desc = "It's an airtight container for storing medication." + icon_state = "pill_canister" + icon = 'icons/obj/chemical.dmi' + item_state = "contsolid" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/pill_bottle/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.allow_quick_gather = TRUE + STR.click_gather = TRUE + STR.set_holdable(list(/obj/item/reagent_containers/pill, /obj/item/dice)) + +/obj/item/storage/pill_bottle/suicide_act(mob/user) + user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (TOXLOSS) + +/obj/item/storage/pill_bottle/charcoal + name = "bottle of charcoal pills" + desc = "Contains pills used to counter toxins." + +/obj/item/storage/pill_bottle/charcoal/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/charcoal(src) + +/obj/item/storage/pill_bottle/epinephrine + name = "bottle of epinephrine pills" + desc = "Contains pills used to stabilize patients." + +/obj/item/storage/pill_bottle/epinephrine/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/epinephrine(src) + +/obj/item/storage/pill_bottle/mutadone + name = "bottle of mutadone pills" + desc = "Contains pills used to treat genetic abnormalities." + +/obj/item/storage/pill_bottle/mutadone/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/mutadone(src) + +/obj/item/storage/pill_bottle/mannitol + name = "bottle of mannitol pills" + desc = "Contains pills used to treat brain damage." + +/obj/item/storage/pill_bottle/mannitol/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/mannitol(src) + +/obj/item/storage/pill_bottle/stimulant + name = "bottle of stimulant pills" + desc = "Guaranteed to give you that extra burst of energy during a long shift!" + +/obj/item/storage/pill_bottle/stimulant/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/stimulant(src) + +/obj/item/storage/pill_bottle/mining + name = "bottle of patches" + desc = "Contains patches used to treat brute and burn damage." + +/obj/item/storage/pill_bottle/mining/PopulateContents() + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/patch/styptic(src) + +/obj/item/storage/pill_bottle/zoom + name = "suspicious pill bottle" + desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds." + +/obj/item/storage/pill_bottle/zoom/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/zoom(src) + +/obj/item/storage/pill_bottle/happy + name = "suspicious pill bottle" + desc = "There is a smiley on the top." + +/obj/item/storage/pill_bottle/happy/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/happy(src) + +/obj/item/storage/pill_bottle/lsd + name = "suspicious pill bottle" + desc = "There is a crude drawing which could be either a mushroom, or a deformed moon." + +/obj/item/storage/pill_bottle/lsd/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/lsd(src) + +/obj/item/storage/pill_bottle/aranesp + name = "suspicious pill bottle" + desc = "The label has 'fuck disablers' hastily scrawled in black marker." + +/obj/item/storage/pill_bottle/aranesp/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/aranesp(src) + +/obj/item/storage/pill_bottle/psicodine + name = "bottle of psicodine pills" + desc = "Contains pills used to treat mental distress and traumas." + +/obj/item/storage/pill_bottle/psicodine/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/psicodine(src) + +/obj/item/storage/pill_bottle/happiness + name = "happiness pill bottle" + desc = "The label is long gone, in its place an 'H' written with a marker." + +/obj/item/storage/pill_bottle/happiness/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/happiness(src) + +/obj/item/storage/pill_bottle/penacid + name = "bottle of pentetic acid pills" + desc = "Contains pills to expunge radiation and toxins." + +/obj/item/storage/pill_bottle/penacid/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/penacid(src) + + +/obj/item/storage/pill_bottle/neurine + name = "bottle of neurine pills" + desc = "Contains pills to treat non-severe mental traumas." + +/obj/item/storage/pill_bottle/neurine/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/neurine(src) + +/obj/item/storage/pill_bottle/floorpill + name = "bottle of floorpills" + desc = "An old pill bottle. It smells musty." + +/obj/item/storage/pill_bottle/floorpill/Initialize() + . = ..() + var/obj/item/reagent_containers/pill/P = locate() in src + name = "bottle of [P.name]s" + +/obj/item/storage/pill_bottle/floorpill/PopulateContents() + for(var/i in 1 to rand(1,7)) + new /obj/item/reagent_containers/pill/floorpill(src) + +/obj/item/storage/pill_bottle/floorpill/full/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/floorpill(src) diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm index 3de85cf94c8a..1be9a1d75414 100644 --- a/code/game/objects/items/storage/lockbox.dm +++ b/code/game/objects/items/storage/lockbox.dm @@ -1,187 +1,187 @@ -/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) - 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 - -/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.set_holdable(list(/obj/item/clothing/accessory/medal)) - -/obj/item/storage/lockbox/medal/examine(mob/user) - . = ..() - if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - . += "Alt-click to [open ? "close":"open"] it." - -/obj/item/storage/lockbox/medal/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE)) - if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - open = (open ? FALSE : TRUE) - update_icon() - ..() - -/obj/item/storage/lockbox/medal/PopulateContents() - new /obj/item/clothing/accessory/medal/gold/captain(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/silver/security(src) - new /obj/item/clothing/accessory/medal/bronze_heart(src) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/conduct(src) - -/obj/item/storage/lockbox/medal/update_icon() - 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/service - name = "service award box" - desc = "A locked box used to store awards to be given to members of the service department." - req_access = list(ACCESS_HOP) - -/obj/item/storage/lockbox/medal/service/PopulateContents() - new /obj/item/clothing/accessory/medal/silver/excellence(src) - -/obj/item/storage/lockbox/medal/sci - name = "science medal box" - desc = "A locked box used to store medals to be given to members of the science department." - req_access = list(ACCESS_RD) - -/obj/item/storage/lockbox/medal/sci/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) +/obj/item/storage/lockbox + 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) + 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 + +/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.set_holdable(list(/obj/item/clothing/accessory/medal)) + +/obj/item/storage/lockbox/medal/examine(mob/user) + . = ..() + if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + . += "Alt-click to [open ? "close":"open"] it." + +/obj/item/storage/lockbox/medal/AltClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE)) + if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + open = (open ? FALSE : TRUE) + update_icon() + ..() + +/obj/item/storage/lockbox/medal/PopulateContents() + new /obj/item/clothing/accessory/medal/gold/captain(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/silver/security(src) + new /obj/item/clothing/accessory/medal/bronze_heart(src) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/conduct(src) + +/obj/item/storage/lockbox/medal/update_icon() + 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/service + name = "service award box" + desc = "A locked box used to store awards to be given to members of the service department." + req_access = list(ACCESS_HOP) + +/obj/item/storage/lockbox/medal/service/PopulateContents() + new /obj/item/clothing/accessory/medal/silver/excellence(src) + +/obj/item/storage/lockbox/medal/sci + name = "science medal box" + desc = "A locked box used to store medals to be given to members of the science department." + req_access = list(ACCESS_RD) + +/obj/item/storage/lockbox/medal/sci/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm index 2e91c68050b8..6bffc6c6eefc 100644 --- a/code/game/objects/items/storage/secure.dm +++ b/code/game/objects/items/storage/secure.dm @@ -1,188 +1,188 @@ -/* - * Absorbs /obj/item/secstorage. - * Reimplements it only slightly to use existing storage functionality. - * - * Contains: - * Secure Briefcase - * Wall Safe - */ - -// ----------------------------- -// Generic Item -// ----------------------------- -/obj/item/storage/secure - name = "secstorage" - var/icon_locking = "secureb" - var/icon_sparking = "securespark" - var/icon_opened = "secure0" - var/code = "" - var/l_code = null - var/l_set = 0 - var/l_setshort = 0 - var/l_hacking = 0 - var/open = FALSE - w_class = WEIGHT_CLASS_NORMAL - desc = "This shouldn't exist. If it does, create an issue report." - -/obj/item/storage/secure/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.max_combined_w_class = 14 - -/obj/item/storage/secure/examine(mob/user) - . = ..() - . += "The service panel is currently [open ? "unscrewed" : "screwed shut"]." - -/obj/item/storage/secure/attackby(obj/item/W, mob/user, params) - if(SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - if (W.tool_behaviour == TOOL_SCREWDRIVER) - if (W.use_tool(src, user, 20)) - open =! open - to_chat(user, "You [open ? "open" : "close"] the service panel.") - return - if (W.tool_behaviour == TOOL_WIRECUTTER) - to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.") - if ((W.tool_behaviour == TOOL_MULTITOOL) && (!l_hacking)) - if(open == 1) - to_chat(user, "Now attempting to reset internal memory, please hold.") - l_hacking = 1 - if (W.use_tool(src, user, 400)) - to_chat(user, "Internal memory reset - lock has been disengaged.") - l_set = 0 - l_hacking = 0 - else - l_hacking = 0 - else - to_chat(user, "You must unscrew the service panel before you can pulse the wiring.") - return - //At this point you have exhausted all the special things to do when locked - // ... but it's still locked. - return - - // -> storage/attackby() what with handle insertion, etc - return ..() - -/obj/item/storage/secure/attack_self(mob/user) - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - user.set_machine(src) - var/dat = text("[]
                \n\nLock Status: []",src, (locked ? "LOCKED" : "UNLOCKED")) - var/message = "Code" - if ((l_set == 0) && (!l_setshort)) - dat += text("

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

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


                \n>[]
                \n1-2-3
                \n4-5-6
                \n7-8-9
                \nR-0-E
                \n
                ", message) - user << browse(dat, "window=caselock;size=300x280") - -/obj/item/storage/secure/Topic(href, href_list) - ..() - if ((usr.stat || usr.restrained()) || (get_dist(src, usr) > 1)) - return - if (href_list["type"]) - if (href_list["type"] == "E") - if ((l_set == 0) && (length(code) == 5) && (!l_setshort) && (code != "ERROR")) - l_code = code - l_set = 1 - else if ((code == l_code) && (l_set == 1)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) - cut_overlays() - add_overlay(icon_opened) - code = null - else - code = "ERROR" - else - if ((href_list["type"] == "R") && (!l_setshort)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE) - cut_overlays() - code = null - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_FROM, usr) - else - code += text("[]", sanitize_text(href_list["type"])) - if (length(code) > 5) - code = "ERROR" - add_fingerprint(usr) - for(var/mob/M in viewers(1, loc)) - if ((M.client && M.machine == src)) - attack_self(M) - return - return - - -// ----------------------------- -// Secure Briefcase -// ----------------------------- -/obj/item/storage/secure/briefcase - name = "secure briefcase" - icon = 'icons/obj/storage.dmi' - icon_state = "secure" - 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.set_holdable(null, list(/obj/item/storage/secure/briefcase)) - STR.max_w_class = 8 //?? - -/obj/item/storage/secure/safe/PopulateContents() - new /obj/item/paper(src) - new /obj/item/pen(src) - -/obj/item/storage/secure/safe/attack_hand(mob/user) - . = ..() - if(.) - return - return attack_self(user) - -/obj/item/storage/secure/safe/HoS - name = "head of security's safe" +/* + * Absorbs /obj/item/secstorage. + * Reimplements it only slightly to use existing storage functionality. + * + * Contains: + * Secure Briefcase + * Wall Safe + */ + +// ----------------------------- +// Generic Item +// ----------------------------- +/obj/item/storage/secure + name = "secstorage" + var/icon_locking = "secureb" + var/icon_sparking = "securespark" + var/icon_opened = "secure0" + var/code = "" + var/l_code = null + var/l_set = 0 + var/l_setshort = 0 + var/l_hacking = 0 + var/open = FALSE + w_class = WEIGHT_CLASS_NORMAL + desc = "This shouldn't exist. If it does, create an issue report." + +/obj/item/storage/secure/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_combined_w_class = 14 + +/obj/item/storage/secure/examine(mob/user) + . = ..() + . += "The service panel is currently [open ? "unscrewed" : "screwed shut"]." + +/obj/item/storage/secure/attackby(obj/item/W, mob/user, params) + if(SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + if (W.tool_behaviour == TOOL_SCREWDRIVER) + if (W.use_tool(src, user, 20)) + open =! open + to_chat(user, "You [open ? "open" : "close"] the service panel.") + return + if (W.tool_behaviour == TOOL_WIRECUTTER) + to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.") + if ((W.tool_behaviour == TOOL_MULTITOOL) && (!l_hacking)) + if(open == 1) + to_chat(user, "Now attempting to reset internal memory, please hold.") + l_hacking = 1 + if (W.use_tool(src, user, 400)) + to_chat(user, "Internal memory reset - lock has been disengaged.") + l_set = 0 + l_hacking = 0 + else + l_hacking = 0 + else + to_chat(user, "You must unscrew the service panel before you can pulse the wiring.") + return + //At this point you have exhausted all the special things to do when locked + // ... but it's still locked. + return + + // -> storage/attackby() what with handle insertion, etc + return ..() + +/obj/item/storage/secure/attack_self(mob/user) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + user.set_machine(src) + var/dat = text("[]
                \n\nLock Status: []",src, (locked ? "LOCKED" : "UNLOCKED")) + var/message = "Code" + if ((l_set == 0) && (!l_setshort)) + dat += text("

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

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


                \n>[]
                \n1-2-3
                \n4-5-6
                \n7-8-9
                \nR-0-E
                \n
                ", message) + user << browse(dat, "window=caselock;size=300x280") + +/obj/item/storage/secure/Topic(href, href_list) + ..() + if ((usr.stat || usr.restrained()) || (get_dist(src, usr) > 1)) + return + if (href_list["type"]) + if (href_list["type"] == "E") + if ((l_set == 0) && (length(code) == 5) && (!l_setshort) && (code != "ERROR")) + l_code = code + l_set = 1 + else if ((code == l_code) && (l_set == 1)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) + cut_overlays() + add_overlay(icon_opened) + code = null + else + code = "ERROR" + else + if ((href_list["type"] == "R") && (!l_setshort)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE) + cut_overlays() + code = null + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_FROM, usr) + else + code += text("[]", sanitize_text(href_list["type"])) + if (length(code) > 5) + code = "ERROR" + add_fingerprint(usr) + for(var/mob/M in viewers(1, loc)) + if ((M.client && M.machine == src)) + attack_self(M) + return + return + + +// ----------------------------- +// Secure Briefcase +// ----------------------------- +/obj/item/storage/secure/briefcase + name = "secure briefcase" + icon = 'icons/obj/storage.dmi' + icon_state = "secure" + 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.set_holdable(null, list(/obj/item/storage/secure/briefcase)) + STR.max_w_class = 8 //?? + +/obj/item/storage/secure/safe/PopulateContents() + new /obj/item/paper(src) + new /obj/item/pen(src) + +/obj/item/storage/secure/safe/attack_hand(mob/user) + . = ..() + if(.) + return + return attack_self(user) + +/obj/item/storage/secure/safe/HoS + name = "head of security's safe" diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm index d39a58af8dba..b0aef5c94259 100644 --- a/code/game/objects/items/storage/storage.dm +++ b/code/game/objects/items/storage/storage.dm @@ -1,45 +1,45 @@ -/obj/item/storage - name = "storage" - icon = 'icons/obj/storage.dmi' - w_class = WEIGHT_CLASS_NORMAL - var/rummage_if_nodrop = TRUE - var/component_type = /datum/component/storage/concrete - -/obj/item/storage/get_dumping_location(obj/item/storage/source,mob/user) - return src - -/obj/item/storage/Initialize() - . = ..() - PopulateContents() - -/obj/item/storage/ComponentInitialize() - AddComponent(component_type) - -/obj/item/storage/AllowDrop() - return FALSE - -/obj/item/storage/contents_explosion(severity, target) - for(var/atom/A in contents) - A.ex_act(severity, target) - CHECK_TICK - -/obj/item/storage/canStrip(mob/who) - . = ..() - if(!. && rummage_if_nodrop) - return TRUE - -/obj/item/storage/doStrip(mob/who) - if(HAS_TRAIT(src, TRAIT_NODROP) && rummage_if_nodrop) - var/datum/component/storage/CP = GetComponent(/datum/component/storage) - CP.do_quick_empty() - return TRUE - return ..() - -/obj/item/storage/contents_explosion(severity, target) -//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW" - -/obj/item/storage/proc/PopulateContents() - -/obj/item/storage/proc/emptyStorage() - var/datum/component/storage/ST = GetComponent(/datum/component/storage) - ST.do_quick_empty() +/obj/item/storage + name = "storage" + icon = 'icons/obj/storage.dmi' + w_class = WEIGHT_CLASS_NORMAL + var/rummage_if_nodrop = TRUE + var/component_type = /datum/component/storage/concrete + +/obj/item/storage/get_dumping_location(obj/item/storage/source,mob/user) + return src + +/obj/item/storage/Initialize() + . = ..() + PopulateContents() + +/obj/item/storage/ComponentInitialize() + AddComponent(component_type) + +/obj/item/storage/AllowDrop() + return FALSE + +/obj/item/storage/contents_explosion(severity, target) + for(var/atom/A in contents) + A.ex_act(severity, target) + CHECK_TICK + +/obj/item/storage/canStrip(mob/who) + . = ..() + if(!. && rummage_if_nodrop) + return TRUE + +/obj/item/storage/doStrip(mob/who) + if(HAS_TRAIT(src, TRAIT_NODROP) && rummage_if_nodrop) + var/datum/component/storage/CP = GetComponent(/datum/component/storage) + CP.do_quick_empty() + return TRUE + return ..() + +/obj/item/storage/contents_explosion(severity, target) +//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW" + +/obj/item/storage/proc/PopulateContents() + +/obj/item/storage/proc/emptyStorage() + var/datum/component/storage/ST = GetComponent(/datum/component/storage) + ST.do_quick_empty() diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 7bb1fea4efe5..4b387c748bd6 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -1,303 +1,303 @@ -/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 - -/obj/item/storage/toolbox/Initialize() - . = ..() - if(has_latches) - if(prob(10)) - latches = "double_latch" - if(prob(1)) - latches = "triple_latch" - update_icon() - -/obj/item/storage/toolbox/update_icon() - ..() - cut_overlays() - if(has_latches) - add_overlay(latches) - - -/obj/item/storage/toolbox/suicide_act(mob/user) - user.visible_message("[user] robusts [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/storage/toolbox/emergency - name = "emergency toolbox" - icon_state = "red" - item_state = "toolbox_red" - -/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 - -/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 - -/obj/item/storage/toolbox/mechanical/old/heirloom - name = "toolbox" //this will be named "X family toolbox" - desc = "It's seen better days." - force = 5 - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() - return - -/obj/item/storage/toolbox/mechanical/old/clean - name = "toolbox" - desc = "A old, blue toolbox, it looks robust." - icon_state = "oldtoolboxclean" - item_state = "toolbox_blue" - has_latches = FALSE - force = 19 - throwforce = 22 - -/obj/item/storage/toolbox/mechanical/old/clean/proc/calc_damage() - var/power = 0 - for (var/obj/item/stack/telecrystal/TC in GetAllContents()) - power += TC.amount - force = 19 + power - throwforce = 22 + power - -/obj/item/storage/toolbox/mechanical/old/clean/attack(mob/target, mob/living/user) - calc_damage() - ..() - -/obj/item/storage/toolbox/mechanical/old/clean/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - calc_damage() - ..() - -/obj/item/storage/toolbox/mechanical/old/clean/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/clothing/gloves/color/yellow(src) - -/obj/item/storage/toolbox/electrical - name = "electrical toolbox" - icon_state = "yellow" - 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,MAXCOIL,pickedcolor) - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - if(prob(5)) - new /obj/item/clothing/gloves/color/yellow(src) - else - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - -/obj/item/storage/toolbox/syndicate - name = "suspicious looking toolbox" - icon_state = "syndicate" - item_state = "toolbox_syndi" - 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() - //YOGS start - toolspeed - var/obj/item/I - new /obj/item/screwdriver/nuke(src) - I = new /obj/item/wrench(src) - I.toolspeed = 0.5 - I = new /obj/item/weldingtool/largetank(src) - I.toolspeed = 0.5 - I = new /obj/item/crowbar/red(src) - I.toolspeed = 0.5 - I = new /obj/item/wirecutters(src, "red") - I.toolspeed = 0.5 - I = new /obj/item/multitool(src) - I.toolspeed = 0.5 - I = new /obj/item/clothing/gloves/combat(src) - I.toolspeed = 0.5 - -/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,MAXCOIL,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") - 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/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 = "artistic_toolbox" - 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) - -//floorbot assembly -/obj/item/storage/toolbox/attackby(obj/item/stack/tile/plasteel/T, mob/user, params) - var/list/allowed_toolbox = list(/obj/item/storage/toolbox/emergency, //which toolboxes can be made into floorbots - /obj/item/storage/toolbox/electrical, - /obj/item/storage/toolbox/mechanical, - /obj/item/storage/toolbox/artistic, - /obj/item/storage/toolbox/syndicate) - - if(!istype(T, /obj/item/stack/tile/plasteel)) - ..() - return - if(!is_type_in_list(src, allowed_toolbox) && (type != /obj/item/storage/toolbox)) - return - if(contents.len >= 1) - to_chat(user, "They won't fit in, as there is already stuff inside!") - return - if(T.use(10)) - var/obj/item/bot_assembly/floorbot/B = new - B.toolbox = type - switch(B.toolbox) - if(/obj/item/storage/toolbox) - B.toolbox_color = "r" - if(/obj/item/storage/toolbox/emergency) - B.toolbox_color = "r" - if(/obj/item/storage/toolbox/electrical) - B.toolbox_color = "y" - if(/obj/item/storage/toolbox/artistic) - B.toolbox_color = "g" - if(/obj/item/storage/toolbox/syndicate) - B.toolbox_color = "s" - user.put_in_hands(B) - B.update_icon() - to_chat(user, "You add the tiles into the empty [name]. They protrude from the top.") - qdel(src) - else - to_chat(user, "You need 10 floor tiles to start building a floorbot!") - return +/obj/item/storage/toolbox + name = "toolbox" + desc = "Danger. Very robust." + icon_state = "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 + +/obj/item/storage/toolbox/Initialize() + . = ..() + if(has_latches) + if(prob(10)) + latches = "double_latch" + if(prob(1)) + latches = "triple_latch" + update_icon() + +/obj/item/storage/toolbox/update_icon() + ..() + cut_overlays() + if(has_latches) + add_overlay(latches) + + +/obj/item/storage/toolbox/suicide_act(mob/user) + user.visible_message("[user] robusts [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/storage/toolbox/emergency + name = "emergency toolbox" + icon_state = "red" + item_state = "toolbox_red" + +/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 + +/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 + +/obj/item/storage/toolbox/mechanical/old/heirloom + name = "toolbox" //this will be named "X family toolbox" + desc = "It's seen better days." + force = 5 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() + return + +/obj/item/storage/toolbox/mechanical/old/clean + name = "toolbox" + desc = "A old, blue toolbox, it looks robust." + icon_state = "oldtoolboxclean" + item_state = "toolbox_blue" + has_latches = FALSE + force = 19 + throwforce = 22 + +/obj/item/storage/toolbox/mechanical/old/clean/proc/calc_damage() + var/power = 0 + for (var/obj/item/stack/telecrystal/TC in GetAllContents()) + power += TC.amount + force = 19 + power + throwforce = 22 + power + +/obj/item/storage/toolbox/mechanical/old/clean/attack(mob/target, mob/living/user) + calc_damage() + ..() + +/obj/item/storage/toolbox/mechanical/old/clean/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + calc_damage() + ..() + +/obj/item/storage/toolbox/mechanical/old/clean/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/clothing/gloves/color/yellow(src) + +/obj/item/storage/toolbox/electrical + name = "electrical toolbox" + icon_state = "yellow" + 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,MAXCOIL,pickedcolor) + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + if(prob(5)) + new /obj/item/clothing/gloves/color/yellow(src) + else + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + +/obj/item/storage/toolbox/syndicate + name = "suspicious looking toolbox" + icon_state = "syndicate" + item_state = "toolbox_syndi" + 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() + //YOGS start - toolspeed + var/obj/item/I + new /obj/item/screwdriver/nuke(src) + I = new /obj/item/wrench(src) + I.toolspeed = 0.5 + I = new /obj/item/weldingtool/largetank(src) + I.toolspeed = 0.5 + I = new /obj/item/crowbar/red(src) + I.toolspeed = 0.5 + I = new /obj/item/wirecutters(src, "red") + I.toolspeed = 0.5 + I = new /obj/item/multitool(src) + I.toolspeed = 0.5 + I = new /obj/item/clothing/gloves/combat(src) + I.toolspeed = 0.5 + +/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,MAXCOIL,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") + 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/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 = "artistic_toolbox" + 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) + +//floorbot assembly +/obj/item/storage/toolbox/attackby(obj/item/stack/tile/plasteel/T, mob/user, params) + var/list/allowed_toolbox = list(/obj/item/storage/toolbox/emergency, //which toolboxes can be made into floorbots + /obj/item/storage/toolbox/electrical, + /obj/item/storage/toolbox/mechanical, + /obj/item/storage/toolbox/artistic, + /obj/item/storage/toolbox/syndicate) + + if(!istype(T, /obj/item/stack/tile/plasteel)) + ..() + return + if(!is_type_in_list(src, allowed_toolbox) && (type != /obj/item/storage/toolbox)) + return + if(contents.len >= 1) + to_chat(user, "They won't fit in, as there is already stuff inside!") + return + if(T.use(10)) + var/obj/item/bot_assembly/floorbot/B = new + B.toolbox = type + switch(B.toolbox) + if(/obj/item/storage/toolbox) + B.toolbox_color = "r" + if(/obj/item/storage/toolbox/emergency) + B.toolbox_color = "r" + if(/obj/item/storage/toolbox/electrical) + B.toolbox_color = "y" + if(/obj/item/storage/toolbox/artistic) + B.toolbox_color = "g" + if(/obj/item/storage/toolbox/syndicate) + B.toolbox_color = "s" + user.put_in_hands(B) + B.update_icon() + to_chat(user, "You add the tiles into the empty [name]. They protrude from the top.") + qdel(src) + else + to_chat(user, "You need 10 floor tiles to start building a floorbot!") + return diff --git a/code/game/objects/items/storage/wallets.dm b/code/game/objects/items/storage/wallets.dm index 52f715850fa2..0827d214ff58 100644 --- a/code/game/objects/items/storage/wallets.dm +++ b/code/game/objects/items/storage/wallets.dm @@ -1,85 +1,85 @@ -/obj/item/storage/wallet - name = "wallet" - desc = "It can hold a few small and personal things." - icon_state = "wallet" - w_class = WEIGHT_CLASS_SMALL - resistance_flags = FLAMMABLE - slot_flags = ITEM_SLOT_ID - - var/obj/item/card/id/front_id = null - var/list/combined_access - -/obj/item/storage/wallet/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 4 - STR.set_holdable(list( - /obj/item/stack/spacecash, - /obj/item/holochip, - /obj/item/card, - /obj/item/clothing/mask/cigarette, - /obj/item/flashlight/pen, - /obj/item/seeds, - /obj/item/stack/medical, - /obj/item/toy/crayon, - /obj/item/coin, - /obj/item/dice, - /obj/item/disk, - /obj/item/implanter, - /obj/item/lighter, - /obj/item/lipstick, - /obj/item/match, - /obj/item/paper, - /obj/item/pen, - /obj/item/photo, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/syringe, - /obj/item/screwdriver, - /obj/item/stamp), - list(/obj/item/screwdriver/power)) - -/obj/item/storage/wallet/Exited(atom/movable/AM) - . = ..() - refreshID() - -/obj/item/storage/wallet/proc/refreshID() - LAZYCLEARLIST(combined_access) - if(!(front_id in src)) - front_id = null - for(var/obj/item/card/id/I in contents) - if(!front_id) - front_id = I - LAZYINITLIST(combined_access) - combined_access |= I.access - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() - update_icon() - -/obj/item/storage/wallet/Entered(atom/movable/AM) - . = ..() - refreshID() - -/obj/item/storage/wallet/update_icon() - var/new_state = "wallet" - if(front_id) - new_state = "wallet_[front_id.icon_state]" - if(new_state != icon_state) //avoid so many icon state changes. - icon_state = new_state - -/obj/item/storage/wallet/GetID() - return front_id - -/obj/item/storage/wallet/GetAccess() - if(LAZYLEN(combined_access)) - return combined_access - else - return ..() - -/obj/item/storage/wallet/random - icon_state = "random_wallet" - -/obj/item/storage/wallet/random/PopulateContents() - new /obj/item/holochip(src, rand(5,30)) - update_icon() +/obj/item/storage/wallet + name = "wallet" + desc = "It can hold a few small and personal things." + icon_state = "wallet" + w_class = WEIGHT_CLASS_SMALL + resistance_flags = FLAMMABLE + slot_flags = ITEM_SLOT_ID + + var/obj/item/card/id/front_id = null + var/list/combined_access + +/obj/item/storage/wallet/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 4 + STR.set_holdable(list( + /obj/item/stack/spacecash, + /obj/item/holochip, + /obj/item/card, + /obj/item/clothing/mask/cigarette, + /obj/item/flashlight/pen, + /obj/item/seeds, + /obj/item/stack/medical, + /obj/item/toy/crayon, + /obj/item/coin, + /obj/item/dice, + /obj/item/disk, + /obj/item/implanter, + /obj/item/lighter, + /obj/item/lipstick, + /obj/item/match, + /obj/item/paper, + /obj/item/pen, + /obj/item/photo, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/syringe, + /obj/item/screwdriver, + /obj/item/stamp), + list(/obj/item/screwdriver/power)) + +/obj/item/storage/wallet/Exited(atom/movable/AM) + . = ..() + refreshID() + +/obj/item/storage/wallet/proc/refreshID() + LAZYCLEARLIST(combined_access) + if(!(front_id in src)) + front_id = null + for(var/obj/item/card/id/I in contents) + if(!front_id) + front_id = I + LAZYINITLIST(combined_access) + combined_access |= I.access + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.wear_id == src) + H.sec_hud_set_ID() + update_icon() + +/obj/item/storage/wallet/Entered(atom/movable/AM) + . = ..() + refreshID() + +/obj/item/storage/wallet/update_icon() + var/new_state = "wallet" + if(front_id) + new_state = "wallet_[front_id.icon_state]" + if(new_state != icon_state) //avoid so many icon state changes. + icon_state = new_state + +/obj/item/storage/wallet/GetID() + return front_id + +/obj/item/storage/wallet/GetAccess() + if(LAZYLEN(combined_access)) + return combined_access + else + return ..() + +/obj/item/storage/wallet/random + icon_state = "random_wallet" + +/obj/item/storage/wallet/random/PopulateContents() + new /obj/item/holochip(src, rand(5,30)) + update_icon() diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index 4b265a277e1b..a09069fd11b7 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -1,214 +1,214 @@ -/obj/item/melee/baton - name = "stun baton" - 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 = 0 - 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() - return 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, datum/thrownthing/throwingdatum) - ..() - //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) - if(cell) - //Note this value returned is significant, as it will determine - //if a stun is applied or not - . = cell.use(chrgdeductamt) - if(status && cell.charge < hitcost) - //we're below minimum, turn off - status = 0 - update_icon() - playsound(loc, "sparks", 75, 1, -1) - - -/obj/item/melee/baton/update_icon() - if(status) - icon_state = "[initial(icon_state)]_active" - else if(!cell) - icon_state = "[initial(icon_state)]_nocell" - else - icon_state = "[initial(icon_state)]" - -/obj/item/melee/baton/examine(mob/user) - . = ..() - if(cell) - . += "\The [src] is [round(cell.percent())]% charged." - else - . += "\The [src] does not have a power source installed." - -/obj/item/melee/baton/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/cell)) - var/obj/item/stock_parts/cell/C = W - if(cell) - to_chat(user, "[src] already has a cell.") - else - if(C.maxcharge < hitcost) - to_chat(user, "[src] requires a higher capacity cell.") - return - if(!user.transferItemToLoc(W, src)) - return - cell = W - to_chat(user, "You install a cell in [src].") - update_icon() - - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(cell) - cell.update_icon() - cell.forceMove(get_turf(src)) - cell = null - to_chat(user, "You remove the cell from [src].") - status = 0 - update_icon() - else - return ..() - -/obj/item/melee/baton/attack_self(mob/user) - if(cell && cell.charge > hitcost) - status = !status - to_chat(user, "[src] is now [status ? "on" : "off"].") - playsound(loc, "sparks", 75, 1, -1) - else - status = 0 - if(!cell) - to_chat(user, "[src] does not have a power source!") - else - to_chat(user, "[src] is out of charge.") - update_icon() - add_fingerprint(user) - -/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) - if(status && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ - "You accidentally hit yourself with [src]!") - user.Paralyze(stunforce*3) - deductcharge(hitcost) - return - //yogs edit begin --------------------------------- - if(status && ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH) - if(istype(stomach)) - stomach.adjust_charge(20) - to_chat(M,"You get charged by [src].") - //yogs edit end ---------------------------------- - 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) - 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(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that - playsound(L, 'sound/weapons/genhit.ogg', 50, 1) - return 0 - if(iscyborg(loc)) - var/mob/living/silicon/robot/R = loc - if(!R || !R.cell || !R.cell.use(hitcost)) - return 0 - else - if(!deductcharge(hitcost)) - return 0 - - L.Paralyze(stunforce) - 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 1 - -/obj/item/melee/baton/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_SELF)) - deductcharge(1000 / severity) - -//Makeshift stun baton. Replacement for stun gloves. -/obj/item/melee/baton/cattleprod - name = "stunprod" - desc = "An improvised stun baton." - icon_state = "stunprod" - 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 = 0 - -/obj/item/melee/baton/cattleprod/Initialize() - . = ..() - sparkler = new (src) - -/obj/item/melee/baton/cattleprod/baton_stun() - if(sparkler.activate()) - ..() +/obj/item/melee/baton + name = "stun baton" + 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 = 0 + 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() + return 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, datum/thrownthing/throwingdatum) + ..() + //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) + if(cell) + //Note this value returned is significant, as it will determine + //if a stun is applied or not + . = cell.use(chrgdeductamt) + if(status && cell.charge < hitcost) + //we're below minimum, turn off + status = 0 + update_icon() + playsound(loc, "sparks", 75, 1, -1) + + +/obj/item/melee/baton/update_icon() + if(status) + icon_state = "[initial(icon_state)]_active" + else if(!cell) + icon_state = "[initial(icon_state)]_nocell" + else + icon_state = "[initial(icon_state)]" + +/obj/item/melee/baton/examine(mob/user) + . = ..() + if(cell) + . += "\The [src] is [round(cell.percent())]% charged." + else + . += "\The [src] does not have a power source installed." + +/obj/item/melee/baton/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/cell)) + var/obj/item/stock_parts/cell/C = W + if(cell) + to_chat(user, "[src] already has a cell.") + else + if(C.maxcharge < hitcost) + to_chat(user, "[src] requires a higher capacity cell.") + return + if(!user.transferItemToLoc(W, src)) + return + cell = W + to_chat(user, "You install a cell in [src].") + update_icon() + + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(cell) + cell.update_icon() + cell.forceMove(get_turf(src)) + cell = null + to_chat(user, "You remove the cell from [src].") + status = 0 + update_icon() + else + return ..() + +/obj/item/melee/baton/attack_self(mob/user) + if(cell && cell.charge > hitcost) + status = !status + to_chat(user, "[src] is now [status ? "on" : "off"].") + playsound(loc, "sparks", 75, 1, -1) + else + status = 0 + if(!cell) + to_chat(user, "[src] does not have a power source!") + else + to_chat(user, "[src] is out of charge.") + update_icon() + add_fingerprint(user) + +/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) + if(status && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ + "You accidentally hit yourself with [src]!") + user.Paralyze(stunforce*3) + deductcharge(hitcost) + return + //yogs edit begin --------------------------------- + if(status && ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH) + if(istype(stomach)) + stomach.adjust_charge(20) + to_chat(M,"You get charged by [src].") + //yogs edit end ---------------------------------- + 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) + 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(ishuman(L)) + var/mob/living/carbon/human/H = L + if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that + playsound(L, 'sound/weapons/genhit.ogg', 50, 1) + return 0 + if(iscyborg(loc)) + var/mob/living/silicon/robot/R = loc + if(!R || !R.cell || !R.cell.use(hitcost)) + return 0 + else + if(!deductcharge(hitcost)) + return 0 + + L.Paralyze(stunforce) + 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 1 + +/obj/item/melee/baton/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_SELF)) + deductcharge(1000 / severity) + +//Makeshift stun baton. Replacement for stun gloves. +/obj/item/melee/baton/cattleprod + name = "stunprod" + desc = "An improvised stun baton." + icon_state = "stunprod" + 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 = 0 + +/obj/item/melee/baton/cattleprod/Initialize() + . = ..() + sparkler = new (src) + +/obj/item/melee/baton/cattleprod/baton_stun() + if(sparkler.activate()) + ..() diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm index e0383aa68141..d5f99257684c 100644 --- a/code/game/objects/items/tanks/jetpack.dm +++ b/code/game/objects/items/tanks/jetpack.dm @@ -1,254 +1,254 @@ -/obj/item/tank/jetpack - name = "jetpack (empty)" - desc = "A tank of compressed gas for use as propulsion in zero-gravity areas. Use with caution." - icon_state = "jetpack" - item_state = "jetpack" - lefthand_file = 'icons/mob/inhands/equipment/jetpacks_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/jetpacks_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - distribute_pressure = ONE_ATMOSPHERE * O2STANDARD - actions_types = list(/datum/action/item_action/set_internals, /datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) - var/gas_type = /datum/gas/oxygen - var/on = FALSE - var/stabilizers = FALSE - var/full_speed = TRUE // If the jetpack will have a speedboost in space/nograv or not - var/datum/effect_system/trail_follow/ion/ion_trail - -/obj/item/tank/jetpack/Initialize() - . = ..() - ion_trail = new - ion_trail.set_up(src) - -/obj/item/tank/jetpack/populate_gas() - if(gas_type) - air_contents.assert_gas(gas_type) - air_contents.gases[gas_type][MOLES] = ((6 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C)) - -/obj/item/tank/jetpack/ui_action_click(mob/user, action) - if(istype(action, /datum/action/item_action/toggle_jetpack)) - cycle(user) - else if(istype(action, /datum/action/item_action/jetpack_stabilization)) - if(on) - stabilizers = !stabilizers - to_chat(user, "You turn the jetpack stabilization [stabilizers ? "on" : "off"].") - else - toggle_internals(user) - - -/obj/item/tank/jetpack/proc/cycle(mob/user) - if(user.incapacitated()) - return - - if(!on) - turn_on(user) - to_chat(user, "You turn the jetpack on.") - else - turn_off(user) - to_chat(user, "You turn the jetpack off.") - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - - -/obj/item/tank/jetpack/proc/turn_on(mob/user) - on = TRUE - icon_state = "[initial(icon_state)]-on" - ion_trail.start() - RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/move_react) - if(full_speed) - user.add_movespeed_modifier(MOVESPEED_ID_JETPACK, priority=100, multiplicative_slowdown=-2, movetypes=FLOATING, conflict=MOVE_CONFLICT_JETPACK) - -/obj/item/tank/jetpack/proc/turn_off(mob/user) - on = FALSE - stabilizers = FALSE - icon_state = initial(icon_state) - ion_trail.stop() - UnregisterSignal(user, COMSIG_MOVABLE_MOVED) - user.remove_movespeed_modifier(MOVESPEED_ID_JETPACK) - -/obj/item/tank/jetpack/proc/move_react(mob/user) - allow_thrust(0.01, user) - -/obj/item/tank/jetpack/proc/allow_thrust(num, mob/living/user) - if(!on) - return - if((num < 0.005 || air_contents.total_moles() < num)) - turn_off(user) - return - - var/datum/gas_mixture/removed = air_contents.remove(num) - if(removed.total_moles() < 0.005) - turn_off(user) - return - - var/turf/T = get_turf(user) - T.assume_air(removed) - - return TRUE - -/obj/item/tank/jetpack/suicide_act(mob/user) - if (istype(user, /mob/living/carbon/human/)) - var/mob/living/carbon/human/H = user - H.forcesay("WHAT THE FUCK IS CARBON DIOXIDE?") - H.visible_message("[user] is suffocating [user.p_them()]self with [src]! It looks like [user.p_they()] didn't read what that jetpack says!") - return (OXYLOSS) - else - ..() - -/obj/item/tank/jetpack/improvised - name = "improvised jetpack" - desc = "A jetpack made from two air tanks, a fire extinguisher and some atmospherics equipment. It doesn't look like it can hold much." - icon_state = "jetpack-improvised" - item_state = "jetpack-sec" - volume = 20 //normal jetpacks have 70 volume - gas_type = null //it starts empty - full_speed = FALSE //moves at hardsuit jetpack speeds - -/obj/item/tank/jetpack/improvised/allow_thrust(num, mob/living/user) - if(!on) - return - if((num < 0.005 || air_contents.total_moles() < num)) - turn_off(user) - return - if(rand(0,250) == 0) - to_chat(user, "You feel your jetpack's engines cut out.") - turn_off(user) - return - - var/datum/gas_mixture/removed = air_contents.remove(num) - if(removed.total_moles() < 0.005) - turn_off(user) - return - - var/turf/T = get_turf(user) - T.assume_air(removed) - - return TRUE - -/obj/item/tank/jetpack/void - name = "void jetpack (oxygen)" - desc = "It works well in a void." - icon_state = "jetpack-void" - item_state = "jetpack-void" - -/obj/item/tank/jetpack/oxygen - name = "jetpack (oxygen)" - desc = "A tank of compressed oxygen for use as propulsion in zero-gravity areas. Use with caution." - icon_state = "jetpack" - item_state = "jetpack" - -/obj/item/tank/jetpack/oxygen/harness - name = "jet harness (oxygen)" - desc = "A lightweight tactical harness, used by those who don't want to be weighed down by traditional jetpacks." - icon_state = "jetpack-mini" - item_state = "jetpack-mini" - volume = 40 - throw_range = 7 - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/tank/jetpack/oxygen/captain - name = "\improper Captain's jetpack" - desc = "A compact, lightweight jetpack containing a high amount of compressed oxygen." - icon_state = "jetpack-captain" - item_state = "jetpack-captain" - w_class = WEIGHT_CLASS_NORMAL - volume = 90 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF //steal objective items are hard to destroy. - -/obj/item/tank/jetpack/oxygen/security - name = "security jetpack (oxygen)" - desc = "A tank of compressed oxygen for use as propulsion in zero-gravity areas by security forces." - icon_state = "jetpack-sec" - item_state = "jetpack-sec" - - - -/obj/item/tank/jetpack/carbondioxide - name = "jetpack (carbon dioxide)" - desc = "A tank of compressed carbon dioxide for use as propulsion in zero-gravity areas. Painted black to indicate that it should not be used as a source for internals." - icon_state = "jetpack-black" - item_state = "jetpack-black" - distribute_pressure = 0 - gas_type = /datum/gas/carbon_dioxide - - -/obj/item/tank/jetpack/suit - name = "hardsuit jetpack upgrade" - desc = "A modular, compact set of thrusters designed to integrate with a hardsuit. It is fueled by a tank inserted into the suit's storage compartment." - icon_state = "jetpack-mining" - item_state = "jetpack-black" - w_class = WEIGHT_CLASS_NORMAL - actions_types = list(/datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) - volume = 1 - slot_flags = null - gas_type = null - full_speed = FALSE - var/datum/gas_mixture/temp_air_contents - var/obj/item/tank/internals/tank = null - var/mob/living/carbon/human/cur_user - -/obj/item/tank/jetpack/suit/Initialize() - . = ..() - STOP_PROCESSING(SSobj, src) - temp_air_contents = air_contents - -/obj/item/tank/jetpack/suit/attack_self() - return - -/obj/item/tank/jetpack/suit/cycle(mob/user) - if(!istype(loc, /obj/item/clothing/suit/space/hardsuit)) - to_chat(user, "\The [src] must be connected to a hardsuit!") - return - - var/mob/living/carbon/human/H = user - if(!istype(H.s_store, /obj/item/tank/internals)) - to_chat(user, "You need a tank in your suit storage!") - return - ..() - -/obj/item/tank/jetpack/suit/turn_on(mob/user) - if(!istype(loc, /obj/item/clothing/suit/space/hardsuit) || !ishuman(loc.loc) || loc.loc != user) - return - var/mob/living/carbon/human/H = user - tank = H.s_store - air_contents = tank.air_contents - START_PROCESSING(SSobj, src) - cur_user = user - ..() - -/obj/item/tank/jetpack/suit/turn_off(mob/user) - tank = null - air_contents = temp_air_contents - STOP_PROCESSING(SSobj, src) - cur_user = null - ..() - -/obj/item/tank/jetpack/suit/process() - if(!istype(loc, /obj/item/clothing/suit/space/hardsuit) || !ishuman(loc.loc)) - turn_off(cur_user) - return - var/mob/living/carbon/human/H = loc.loc - if(!tank || tank != H.s_store) - turn_off(cur_user) - return - ..() - - -//Return a jetpack that the mob can use -//Back worn jetpacks, hardsuit internal packs, and so on. -//Used in Process_Spacemove() and wherever you want to check for/get a jetpack - -/mob/proc/get_jetpack() - return - -/mob/living/carbon/get_jetpack() - var/obj/item/tank/jetpack/J = back - if(istype(J)) - return J - -/mob/living/carbon/human/get_jetpack() - var/obj/item/tank/jetpack/J = ..() - if(!istype(J) && istype(wear_suit, /obj/item/clothing/suit/space/hardsuit)) - var/obj/item/clothing/suit/space/hardsuit/C = wear_suit - J = C.jetpack - return J +/obj/item/tank/jetpack + name = "jetpack (empty)" + desc = "A tank of compressed gas for use as propulsion in zero-gravity areas. Use with caution." + icon_state = "jetpack" + item_state = "jetpack" + lefthand_file = 'icons/mob/inhands/equipment/jetpacks_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/jetpacks_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + distribute_pressure = ONE_ATMOSPHERE * O2STANDARD + actions_types = list(/datum/action/item_action/set_internals, /datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) + var/gas_type = /datum/gas/oxygen + var/on = FALSE + var/stabilizers = FALSE + var/full_speed = TRUE // If the jetpack will have a speedboost in space/nograv or not + var/datum/effect_system/trail_follow/ion/ion_trail + +/obj/item/tank/jetpack/Initialize() + . = ..() + ion_trail = new + ion_trail.set_up(src) + +/obj/item/tank/jetpack/populate_gas() + if(gas_type) + air_contents.assert_gas(gas_type) + air_contents.gases[gas_type][MOLES] = ((6 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C)) + +/obj/item/tank/jetpack/ui_action_click(mob/user, action) + if(istype(action, /datum/action/item_action/toggle_jetpack)) + cycle(user) + else if(istype(action, /datum/action/item_action/jetpack_stabilization)) + if(on) + stabilizers = !stabilizers + to_chat(user, "You turn the jetpack stabilization [stabilizers ? "on" : "off"].") + else + toggle_internals(user) + + +/obj/item/tank/jetpack/proc/cycle(mob/user) + if(user.incapacitated()) + return + + if(!on) + turn_on(user) + to_chat(user, "You turn the jetpack on.") + else + turn_off(user) + to_chat(user, "You turn the jetpack off.") + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + + +/obj/item/tank/jetpack/proc/turn_on(mob/user) + on = TRUE + icon_state = "[initial(icon_state)]-on" + ion_trail.start() + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/move_react) + if(full_speed) + user.add_movespeed_modifier(MOVESPEED_ID_JETPACK, priority=100, multiplicative_slowdown=-2, movetypes=FLOATING, conflict=MOVE_CONFLICT_JETPACK) + +/obj/item/tank/jetpack/proc/turn_off(mob/user) + on = FALSE + stabilizers = FALSE + icon_state = initial(icon_state) + ion_trail.stop() + UnregisterSignal(user, COMSIG_MOVABLE_MOVED) + user.remove_movespeed_modifier(MOVESPEED_ID_JETPACK) + +/obj/item/tank/jetpack/proc/move_react(mob/user) + allow_thrust(0.01, user) + +/obj/item/tank/jetpack/proc/allow_thrust(num, mob/living/user) + if(!on) + return + if((num < 0.005 || air_contents.total_moles() < num)) + turn_off(user) + return + + var/datum/gas_mixture/removed = air_contents.remove(num) + if(removed.total_moles() < 0.005) + turn_off(user) + return + + var/turf/T = get_turf(user) + T.assume_air(removed) + + return TRUE + +/obj/item/tank/jetpack/suicide_act(mob/user) + if (istype(user, /mob/living/carbon/human/)) + var/mob/living/carbon/human/H = user + H.forcesay("WHAT THE FUCK IS CARBON DIOXIDE?") + H.visible_message("[user] is suffocating [user.p_them()]self with [src]! It looks like [user.p_they()] didn't read what that jetpack says!") + return (OXYLOSS) + else + ..() + +/obj/item/tank/jetpack/improvised + name = "improvised jetpack" + desc = "A jetpack made from two air tanks, a fire extinguisher and some atmospherics equipment. It doesn't look like it can hold much." + icon_state = "jetpack-improvised" + item_state = "jetpack-sec" + volume = 20 //normal jetpacks have 70 volume + gas_type = null //it starts empty + full_speed = FALSE //moves at hardsuit jetpack speeds + +/obj/item/tank/jetpack/improvised/allow_thrust(num, mob/living/user) + if(!on) + return + if((num < 0.005 || air_contents.total_moles() < num)) + turn_off(user) + return + if(rand(0,250) == 0) + to_chat(user, "You feel your jetpack's engines cut out.") + turn_off(user) + return + + var/datum/gas_mixture/removed = air_contents.remove(num) + if(removed.total_moles() < 0.005) + turn_off(user) + return + + var/turf/T = get_turf(user) + T.assume_air(removed) + + return TRUE + +/obj/item/tank/jetpack/void + name = "void jetpack (oxygen)" + desc = "It works well in a void." + icon_state = "jetpack-void" + item_state = "jetpack-void" + +/obj/item/tank/jetpack/oxygen + name = "jetpack (oxygen)" + desc = "A tank of compressed oxygen for use as propulsion in zero-gravity areas. Use with caution." + icon_state = "jetpack" + item_state = "jetpack" + +/obj/item/tank/jetpack/oxygen/harness + name = "jet harness (oxygen)" + desc = "A lightweight tactical harness, used by those who don't want to be weighed down by traditional jetpacks." + icon_state = "jetpack-mini" + item_state = "jetpack-mini" + volume = 40 + throw_range = 7 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/tank/jetpack/oxygen/captain + name = "\improper Captain's jetpack" + desc = "A compact, lightweight jetpack containing a high amount of compressed oxygen." + icon_state = "jetpack-captain" + item_state = "jetpack-captain" + w_class = WEIGHT_CLASS_NORMAL + volume = 90 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF //steal objective items are hard to destroy. + +/obj/item/tank/jetpack/oxygen/security + name = "security jetpack (oxygen)" + desc = "A tank of compressed oxygen for use as propulsion in zero-gravity areas by security forces." + icon_state = "jetpack-sec" + item_state = "jetpack-sec" + + + +/obj/item/tank/jetpack/carbondioxide + name = "jetpack (carbon dioxide)" + desc = "A tank of compressed carbon dioxide for use as propulsion in zero-gravity areas. Painted black to indicate that it should not be used as a source for internals." + icon_state = "jetpack-black" + item_state = "jetpack-black" + distribute_pressure = 0 + gas_type = /datum/gas/carbon_dioxide + + +/obj/item/tank/jetpack/suit + name = "hardsuit jetpack upgrade" + desc = "A modular, compact set of thrusters designed to integrate with a hardsuit. It is fueled by a tank inserted into the suit's storage compartment." + icon_state = "jetpack-mining" + item_state = "jetpack-black" + w_class = WEIGHT_CLASS_NORMAL + actions_types = list(/datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) + volume = 1 + slot_flags = null + gas_type = null + full_speed = FALSE + var/datum/gas_mixture/temp_air_contents + var/obj/item/tank/internals/tank = null + var/mob/living/carbon/human/cur_user + +/obj/item/tank/jetpack/suit/Initialize() + . = ..() + STOP_PROCESSING(SSobj, src) + temp_air_contents = air_contents + +/obj/item/tank/jetpack/suit/attack_self() + return + +/obj/item/tank/jetpack/suit/cycle(mob/user) + if(!istype(loc, /obj/item/clothing/suit/space/hardsuit)) + to_chat(user, "\The [src] must be connected to a hardsuit!") + return + + var/mob/living/carbon/human/H = user + if(!istype(H.s_store, /obj/item/tank/internals)) + to_chat(user, "You need a tank in your suit storage!") + return + ..() + +/obj/item/tank/jetpack/suit/turn_on(mob/user) + if(!istype(loc, /obj/item/clothing/suit/space/hardsuit) || !ishuman(loc.loc) || loc.loc != user) + return + var/mob/living/carbon/human/H = user + tank = H.s_store + air_contents = tank.air_contents + START_PROCESSING(SSobj, src) + cur_user = user + ..() + +/obj/item/tank/jetpack/suit/turn_off(mob/user) + tank = null + air_contents = temp_air_contents + STOP_PROCESSING(SSobj, src) + cur_user = null + ..() + +/obj/item/tank/jetpack/suit/process() + if(!istype(loc, /obj/item/clothing/suit/space/hardsuit) || !ishuman(loc.loc)) + turn_off(cur_user) + return + var/mob/living/carbon/human/H = loc.loc + if(!tank || tank != H.s_store) + turn_off(cur_user) + return + ..() + + +//Return a jetpack that the mob can use +//Back worn jetpacks, hardsuit internal packs, and so on. +//Used in Process_Spacemove() and wherever you want to check for/get a jetpack + +/mob/proc/get_jetpack() + return + +/mob/living/carbon/get_jetpack() + var/obj/item/tank/jetpack/J = back + if(istype(J)) + return J + +/mob/living/carbon/human/get_jetpack() + var/obj/item/tank/jetpack/J = ..() + if(!istype(J) && istype(wear_suit, /obj/item/clothing/suit/space/hardsuit)) + var/obj/item/clothing/suit/space/hardsuit/C = wear_suit + J = C.jetpack + return J diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index da806908df87..b1374c8f3e3f 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -1,236 +1,236 @@ - -#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 (!W.imp_in || !isliving(W.loc)) - continue - else - var/mob/living/M = W.loc - if (M.stat == DEAD) - if (M.timeofdeath + W.lifespan_postmortem < world.time) - continue - - var/turf/tr = get_turf(W) - 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/area/A = get_area(T) - if(A.noteleport) - to_chat(user, "\The [src] is malfunctioning.") - return - current_location = get_turf(user) //Recheck. - current_area = current_location.loc - if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf - to_chat(user, "\The [src] is malfunctioning.") - return - user.show_message("Locked In.", 2) - 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 (!W.imp_in || !isliving(W.loc)) + continue + else + var/mob/living/M = W.loc + if (M.stat == DEAD) + if (M.timeofdeath + W.lifespan_postmortem < world.time) + continue + + var/turf/tr = get_turf(W) + 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/area/A = get_area(T) + if(A.noteleport) + to_chat(user, "\The [src] is malfunctioning.") + return + current_location = get_turf(user) //Recheck. + current_area = current_location.loc + if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf + to_chat(user, "\The [src] is malfunctioning.") + return + user.show_message("Locked In.", 2) + 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 ae0f7f7d31dd..3fd1adb0d8e6 100644 --- a/code/game/objects/items/tools/crowbar.dm +++ b/code/game/objects/items/tools/crowbar.dm @@ -1,88 +1,88 @@ -/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_brass" - toolspeed = 0.5 - -/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.7 - -/obj/item/crowbar/cyborg - name = "hydraulic crowbar" - desc = "A hydraulic prying tool, compact but powerful. Designed to replace crowbar in construction cyborgs." - 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.7 - -/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) +/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_brass" + toolspeed = 0.5 + +/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.7 + +/obj/item/crowbar/cyborg + name = "hydraulic crowbar" + desc = "A hydraulic prying tool, compact but powerful. Designed to replace crowbar in construction cyborgs." + 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.7 + +/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) \ 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 a1a4b6fcc346..72324870c132 100644 --- a/code/game/objects/items/tools/screwdriver.dm +++ b/code/game/objects/items/tools/screwdriver.dm @@ -1,141 +1,141 @@ -/obj/item/screwdriver - name = "screwdriver" - desc = "You can be totally screwy with this." - icon = 'icons/obj/tools.dmi' - icon_state = "screwdriver_map" - 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) - . = 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 ..() - 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 - 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_brass" - item_state = "screwdriver_brass" - toolspeed = 0.5 - 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.7 - 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 = "powered screwdriver" - desc = "An electrical screwdriver, designed to be both precise and quick." - usesound = 'sound/items/drill_use.ogg' - toolspeed = 0.5 +/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) + . = 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 ..() + 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 + 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_brass" + item_state = "screwdriver_brass" + toolspeed = 0.5 + 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.7 + 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 = "powered screwdriver" + desc = "An electrical screwdriver, designed to be both precise and quick." + usesound = 'sound/items/drill_use.ogg' + toolspeed = 0.5 diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm index c4de2bcb738c..82c5ea730a00 100644 --- a/code/game/objects/items/tools/wirecutters.dm +++ b/code/game/objects/items/tools/wirecutters.dm @@ -1,126 +1,126 @@ -/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 if(istype(C) && C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) - to_chat(C, "You attempt to remove the durathread strand from around your neck.") - if(do_after(user, 15, null, C)) - to_chat(C, "You succesfuly remove the durathread strand.") - C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) - else - ..() - -/obj/item/wirecutters/suicide_act(mob/user) - user.visible_message("[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, usesound, 50, 1, -1) - return (BRUTELOSS) - -/obj/item/wirecutters/brass - name = "brass wirecutters" - desc = "A pair of wirecutters made of brass. The handle feels freezing cold to the touch." - resistance_flags = FIRE_PROOF | ACID_PROOF - icon_state = "cutters_brass" - random_color = FALSE - toolspeed = 0.5 - -/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." - toolspeed = 0.5 - -/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.7 - 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) && C.handcuffed) - user.visible_message("[user] cuts [C]'s restraints with [src]!") - qdel(C.handcuffed) - return - else - ..() +/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 if(istype(C) && C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) + to_chat(C, "You attempt to remove the durathread strand from around your neck.") + if(do_after(user, 15, null, C)) + to_chat(C, "You succesfuly remove the durathread strand.") + C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + else + ..() + +/obj/item/wirecutters/suicide_act(mob/user) + user.visible_message("[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, usesound, 50, 1, -1) + return (BRUTELOSS) + +/obj/item/wirecutters/brass + name = "brass wirecutters" + desc = "A pair of wirecutters made of brass. The handle feels freezing cold to the touch." + resistance_flags = FIRE_PROOF | ACID_PROOF + icon_state = "cutters_brass" + random_color = FALSE + toolspeed = 0.5 + +/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." + toolspeed = 0.5 + +/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.7 + 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) && C.handcuffed) + user.visible_message("[user] cuts [C]'s restraints with [src]!") + qdel(C.handcuffed) + return + else + ..() diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm index c5bde43b2e83..49cda4259633 100644 --- a/code/game/objects/items/tools/wrench.dm +++ b/code/game/objects/items/tools/wrench.dm @@ -1,110 +1,110 @@ -/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." - 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_brass" - toolspeed = 0.5 - -/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.7 - -/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() - +/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." + 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_brass" + toolspeed = 0.5 + +/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.7 + +/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 \ No newline at end of file diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index ebebb8a4bb95..bc6e8ec1a06a 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -1,88 +1,88 @@ -//Added by Jack Rost -/obj/item/trash - icon = 'yogstation/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/energybar - name = "energybar wrapper" - icon_state = "energybar" - -/obj/item/trash/waffles - name = "waffles tray" - icon_state = "waffles" - -/obj/item/trash/plate - name = "plate" - icon_state = "plate" - resistance_flags = NONE - -/obj/item/trash/pistachios - name = "pistachios pack" - icon_state = "pistachios_pack" - -/obj/item/trash/semki - name = "semki pack" - icon_state = "semki_pack" - -/obj/item/trash/tray - name = "tray" - icon_state = "tray" - resistance_flags = NONE - -/obj/item/trash/candle - name = "candle" - icon = 'icons/obj/candle.dmi' - icon_state = "candle4" - -/obj/item/trash/can - name = "crushed can" - icon_state = "cola" - resistance_flags = NONE - grind_results = list(/datum/reagent/aluminium = 10) - -/obj/item/trash/attack(mob/M, mob/living/user) - return - -/obj/item/trash/coal - name = "lump of coal" - icon = 'icons/obj/mining.dmi' - icon_state = "slag" - desc = "Someone's gotten on the naughty list." - grind_results = list(/datum/reagent/carbon = 20) - -/obj/item/trash/coal/burn() - visible_message("[src] fuses into a diamond! Someone wasn't so naughty after all...") - new /obj/item/stack/ore/diamond(loc) - qdel(src) +//Added by Jack Rost +/obj/item/trash + icon = 'yogstation/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/energybar + name = "energybar wrapper" + icon_state = "energybar" + +/obj/item/trash/waffles + name = "waffles tray" + icon_state = "waffles" + +/obj/item/trash/plate + name = "plate" + icon_state = "plate" + resistance_flags = NONE + +/obj/item/trash/pistachios + name = "pistachios pack" + icon_state = "pistachios_pack" + +/obj/item/trash/semki + name = "semki pack" + icon_state = "semki_pack" + +/obj/item/trash/tray + name = "tray" + icon_state = "tray" + resistance_flags = NONE + +/obj/item/trash/candle + name = "candle" + icon = 'icons/obj/candle.dmi' + icon_state = "candle4" + +/obj/item/trash/can + name = "crushed can" + icon_state = "cola" + resistance_flags = NONE + grind_results = list(/datum/reagent/aluminium = 10) + +/obj/item/trash/attack(mob/M, mob/living/user) + return + +/obj/item/trash/coal + name = "lump of coal" + icon = 'icons/obj/mining.dmi' + icon_state = "slag" + desc = "Someone's gotten on the naughty list." + grind_results = list(/datum/reagent/carbon = 20) + +/obj/item/trash/coal/burn() + visible_message("[src] fuses into a diamond! Someone wasn't so naughty after all...") + new /obj/item/stack/ore/diamond(loc) + qdel(src) diff --git a/code/game/objects/items/vending_items.dm b/code/game/objects/items/vending_items.dm index af647550ea0f..2964d31259d9 100644 --- a/code/game/objects/items/vending_items.dm +++ b/code/game/objects/items/vending_items.dm @@ -1,51 +1,51 @@ -/* - Vending machine refills can be found at /code/modules/vending/ within each vending machine's respective file -*/ -/obj/item/vending_refill - name = "resupply canister" - var/machine_name = "Generic" - - icon = 'icons/obj/vending_restock.dmi' - icon_state = "refill_snack" - 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 4fd6b47973d7..a496eb09a281 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -1,679 +1,679 @@ -/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 - -/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 - 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_HUGE - 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 - -/obj/item/katana/cursed - slot_flags = null - -/obj/item/katana/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!") - return(BRUTELOSS) - -/obj/item/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 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding - 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/throwing_star/magspear - name = "magnetic spear" - desc = "A reusable spear that is typically loaded into kinetic spearguns." - icon = 'icons/obj/ammo.dmi' - icon_state = "magspear" - throwforce = 25 //kills regular carps in one hit - force = 10 - throw_range = 0 //throwing these invalidates the speargun - attack_verb = list("stabbed", "ripped", "gored", "impaled") - embedding = list("embedded_pain_multiplier" = 8, "embed_chance" = 100, "embedded_fall_chance" = 0, "embedded_impact_pain_multiplier" = 15) //55 damage+embed on hit - -/obj/item/switchblade - name = "switchblade" - icon_state = "switchblade" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A sharp, concealable, spring-loaded knife." - flags_1 = CONDUCT_1 - force = 3 - w_class = WEIGHT_CLASS_SMALL - throwforce = 5 - throw_speed = 3 - throw_range = 6 - 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/ectoplasm/angelic - icon = 'icons/obj/wizard.dmi' - icon_state = "angelplasm" - -/obj/item/mounted_chainsaw - name = "mounted chainsaw" - desc = "A chainsaw that has replaced your arm." - icon_state = "chainsaw_on" - 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_HUGE - 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' - -/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") - var/impressiveness = 45 - -/obj/item/statuebust/Initialize() - . = ..() - AddComponent(/datum/component/art, impressiveness) - -/obj/item/statuebust/hippocratic - name = "hippocrates bust" - desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine." - icon_state = "hippocratic" - impressiveness = 50 - -/obj/item/tailclub - name = "tail club" - desc = "For the beating to death of lizards with their own tails." - icon_state = "tailclub" - force = 14 - throwforce = 1 // why are you throwing a club do you even weapon - throw_speed = 1 - throw_range = 1 - attack_verb = list("clubbed", "bludgeoned") - -/obj/item/melee/chainofcommand/tailwhip - name = "liz o' nine tails" - desc = "A whip fashioned from the severed tails of lizards." - icon_state = "tailwhip" - 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_HUGE - var/homerun_ready = 0 - var/homerun_able = 0 - -/obj/item/melee/baseball_bat/homerun - name = "home run bat" - desc = "This thing looks dangerous... Dangerously good at baseball, that is." - homerun_able = 1 - -/obj/item/melee/baseball_bat/attack_self(mob/user) - if(!homerun_able) - ..() - return - if(homerun_ready) - to_chat(user, "You're already ready to do a home run!") - ..() - return - to_chat(user, "You begin gathering strength...") - playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, 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/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) - 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 -/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 - var/min_reach = 2 - -/obj/item/extendohand/acme - name = "\improper ACME Extendo-Hand" - desc = "A novelty extendo-hand produced by the ACME corporation. Originally designed to knock out roadrunners." - -/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user) - var/dist = get_dist(M, user) - if(dist < min_reach) - to_chat(user, "[M] is too close to use [src] on.") - return - M.attack_hand(user) +/obj/item/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 + +/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 + 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_HUGE + 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 + +/obj/item/katana/cursed + slot_flags = null + +/obj/item/katana/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!") + return(BRUTELOSS) + +/obj/item/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 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding + 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/throwing_star/magspear + name = "magnetic spear" + desc = "A reusable spear that is typically loaded into kinetic spearguns." + icon = 'icons/obj/ammo.dmi' + icon_state = "magspear" + throwforce = 25 //kills regular carps in one hit + force = 10 + throw_range = 0 //throwing these invalidates the speargun + attack_verb = list("stabbed", "ripped", "gored", "impaled") + embedding = list("embedded_pain_multiplier" = 8, "embed_chance" = 100, "embedded_fall_chance" = 0, "embedded_impact_pain_multiplier" = 15) //55 damage+embed on hit + +/obj/item/switchblade + name = "switchblade" + icon_state = "switchblade" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + desc = "A sharp, concealable, spring-loaded knife." + flags_1 = CONDUCT_1 + force = 3 + w_class = WEIGHT_CLASS_SMALL + throwforce = 5 + throw_speed = 3 + throw_range = 6 + 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/ectoplasm/angelic + icon = 'icons/obj/wizard.dmi' + icon_state = "angelplasm" + +/obj/item/mounted_chainsaw + name = "mounted chainsaw" + desc = "A chainsaw that has replaced your arm." + icon_state = "chainsaw_on" + 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_HUGE + 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' + +/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") + var/impressiveness = 45 + +/obj/item/statuebust/Initialize() + . = ..() + AddComponent(/datum/component/art, impressiveness) + +/obj/item/statuebust/hippocratic + name = "hippocrates bust" + desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine." + icon_state = "hippocratic" + impressiveness = 50 + +/obj/item/tailclub + name = "tail club" + desc = "For the beating to death of lizards with their own tails." + icon_state = "tailclub" + force = 14 + throwforce = 1 // why are you throwing a club do you even weapon + throw_speed = 1 + throw_range = 1 + attack_verb = list("clubbed", "bludgeoned") + +/obj/item/melee/chainofcommand/tailwhip + name = "liz o' nine tails" + desc = "A whip fashioned from the severed tails of lizards." + icon_state = "tailwhip" + 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_HUGE + var/homerun_ready = 0 + var/homerun_able = 0 + +/obj/item/melee/baseball_bat/homerun + name = "home run bat" + desc = "This thing looks dangerous... Dangerously good at baseball, that is." + homerun_able = 1 + +/obj/item/melee/baseball_bat/attack_self(mob/user) + if(!homerun_able) + ..() + return + if(homerun_ready) + to_chat(user, "You're already ready to do a home run!") + ..() + return + to_chat(user, "You begin gathering strength...") + playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, 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/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) + 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 +/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 + var/min_reach = 2 + +/obj/item/extendohand/acme + name = "\improper ACME Extendo-Hand" + desc = "A novelty extendo-hand produced by the ACME corporation. Originally designed to knock out roadrunners." + +/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user) + var/dist = get_dist(M, user) + if(dist < min_reach) + to_chat(user, "[M] is too close to use [src] on.") + return + M.attack_hand(user) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index ff88513e4891..91377e90e6b6 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -1,255 +1,255 @@ - -/obj - 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 //Has the item been reskinned? - var/list/unique_reskin //List of options to reskin. - - // Access levels, used in modules\jobs\access.dm - var/list/req_access - var/req_access_txt = "0" - var/list/req_one_access - var/req_one_access_txt = "0" - - 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, force) - ..() - if(obj_flags & FROZEN) - visible_message("[src] shatters into a million pieces!") - qdel(src) - - -/obj/assume_air(datum/gas_mixture/giver) - if(loc) - return loc.assume_air(giver) - else - return null - -/obj/remove_air(amount) - if(loc) - return loc.remove_air(amount) - else - return null - -/obj/return_air() - if(loc) - return loc.return_air() - else - return null - -/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request) - //Return: (NONSTANDARD) - // null if object handles breathing logic for lifeform - // datum/air_group to tell lifeform to process using that breath return - //DEFAULT: Take air from turf to give to have mob process - - if(breath_request>0) - var/datum/gas_mixture/environment = return_air() - var/breath_percentage = BREATH_VOLUME / environment.return_volume() - return remove_air(environment.total_moles() * breath_percentage) - else - return null - -/obj/proc/updateUsrDialog() - if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI)) - var/is_in_use = FALSE - var/list/nearby = viewers(1, src) - for(var/mob/M in nearby) - if ((M.client && M.machine == src)) - is_in_use = TRUE - ui_interact(M) - if(issilicon(usr) || IsAdminGhost(usr)) - if (!(usr in nearby)) - if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh. - is_in_use = TRUE - ui_interact(usr) - - // check for TK users - - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - if(!(usr in nearby)) - if(usr.client && usr.machine==src) - if(H.dna.check_mutation(TK)) - is_in_use = TRUE - ui_interact(usr) - if (is_in_use) - obj_flags |= IN_USE - else - obj_flags &= ~IN_USE - -/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE) - // Check that people are actually using the machine. If not, don't update anymore. - if(obj_flags & IN_USE) - var/is_in_use = FALSE - if(update_viewers) - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - is_in_use = TRUE - src.interact(M) - var/ai_in_use = FALSE - if(update_ais) - ai_in_use = AutoUpdateAI(src) - - if(update_viewers && update_ais) //State change is sure only if we check both - if(!ai_in_use && !is_in_use) - obj_flags &= ~IN_USE - - -/obj/attack_ghost(mob/user) - . = ..() - if(.) - return - ui_interact(user) - -/obj/proc/container_resist(mob/living/user) - return - -/obj/proc/update_icon() - 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/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) - . += "Alt-click it to reskin it." - -/obj/AltClick(mob/user) - . = ..() - if(unique_reskin && !current_skin && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - reskin_obj(user) - -/obj/proc/reskin_obj(mob/M) - if(!LAZYLEN(unique_reskin)) - return - to_chat(M, "Reskin options for [name]:") - for(var/V in unique_reskin) - var/output = icon2html(src, M, unique_reskin[V]) - to_chat(M, "[V]: [output]") - - var/choice = input(M,"Warning, you can only reskin [src] once!","Reskin Object") as null|anything in unique_reskin - if(!QDELETED(src) && choice && !current_skin && !M.incapacitated() && in_range(M,src)) - if(!unique_reskin[choice]) - return - current_skin = choice - icon_state = unique_reskin[choice] - to_chat(M, "[src] is now skinned as '[choice].'") - -/obj/analyzer_act(mob/living/user, obj/item/I) - if(atmosanalyzer_scan(user, src)) - return TRUE - return ..() - -/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) - return + +/obj + 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 //Has the item been reskinned? + var/list/unique_reskin //List of options to reskin. + + // Access levels, used in modules\jobs\access.dm + var/list/req_access + var/req_access_txt = "0" + var/list/req_one_access + var/req_one_access_txt = "0" + + 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, force) + ..() + if(obj_flags & FROZEN) + visible_message("[src] shatters into a million pieces!") + qdel(src) + + +/obj/assume_air(datum/gas_mixture/giver) + if(loc) + return loc.assume_air(giver) + else + return null + +/obj/remove_air(amount) + if(loc) + return loc.remove_air(amount) + else + return null + +/obj/return_air() + if(loc) + return loc.return_air() + else + return null + +/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request) + //Return: (NONSTANDARD) + // null if object handles breathing logic for lifeform + // datum/air_group to tell lifeform to process using that breath return + //DEFAULT: Take air from turf to give to have mob process + + if(breath_request>0) + var/datum/gas_mixture/environment = return_air() + var/breath_percentage = BREATH_VOLUME / environment.return_volume() + return remove_air(environment.total_moles() * breath_percentage) + else + return null + +/obj/proc/updateUsrDialog() + if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI)) + var/is_in_use = FALSE + var/list/nearby = viewers(1, src) + for(var/mob/M in nearby) + if ((M.client && M.machine == src)) + is_in_use = TRUE + ui_interact(M) + if(issilicon(usr) || IsAdminGhost(usr)) + if (!(usr in nearby)) + if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh. + is_in_use = TRUE + ui_interact(usr) + + // check for TK users + + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + if(!(usr in nearby)) + if(usr.client && usr.machine==src) + if(H.dna.check_mutation(TK)) + is_in_use = TRUE + ui_interact(usr) + if (is_in_use) + obj_flags |= IN_USE + else + obj_flags &= ~IN_USE + +/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE) + // Check that people are actually using the machine. If not, don't update anymore. + if(obj_flags & IN_USE) + var/is_in_use = FALSE + if(update_viewers) + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + is_in_use = TRUE + src.interact(M) + var/ai_in_use = FALSE + if(update_ais) + ai_in_use = AutoUpdateAI(src) + + if(update_viewers && update_ais) //State change is sure only if we check both + if(!ai_in_use && !is_in_use) + obj_flags &= ~IN_USE + + +/obj/attack_ghost(mob/user) + . = ..() + if(.) + return + ui_interact(user) + +/obj/proc/container_resist(mob/living/user) + return + +/obj/proc/update_icon() + 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/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) + . += "Alt-click it to reskin it." + +/obj/AltClick(mob/user) + . = ..() + if(unique_reskin && !current_skin && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + reskin_obj(user) + +/obj/proc/reskin_obj(mob/M) + if(!LAZYLEN(unique_reskin)) + return + to_chat(M, "Reskin options for [name]:") + for(var/V in unique_reskin) + var/output = icon2html(src, M, unique_reskin[V]) + to_chat(M, "[V]: [output]") + + var/choice = input(M,"Warning, you can only reskin [src] once!","Reskin Object") as null|anything in unique_reskin + if(!QDELETED(src) && choice && !current_skin && !M.incapacitated() && in_range(M,src)) + if(!unique_reskin[choice]) + return + current_skin = choice + icon_state = unique_reskin[choice] + to_chat(M, "[src] is now skinned as '[choice].'") + +/obj/analyzer_act(mob/living/user, obj/item/I) + if(atmosanalyzer_scan(user, src)) + return TRUE + return ..() + +/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) + return diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index ba779538e17e..bf445ab6f8f4 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -1,113 +1,113 @@ -/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.Paralyze(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)) - var/mob/living/carbon/C = O - if(C.mobility_flags & MOBILITY_MOVE) - climb_structure(user) - return - if(!istype(O, /obj/item) || user.get_active_held_item() != O) - return - if(iscyborg(user)) - return - if(!user.dropItemToGround(O)) - return - if (O.loc != src.loc) - step(O, get_dir(O, src)) - -/obj/structure/proc/do_climb(atom/movable/A) - if(climbable) - 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.Paralyze(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)) + var/mob/living/carbon/C = O + if(C.mobility_flags & MOBILITY_MOVE) + climb_structure(user) + return + if(!istype(O, /obj/item) || user.get_active_held_item() != O) + return + if(iscyborg(user)) + return + if(!user.dropItemToGround(O)) + return + if (O.loc != src.loc) + step(O, get_dir(O, src)) + +/obj/structure/proc/do_climb(atom/movable/A) + if(climbable) + 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 69ce261e1d4d..d36ab31e2af3 100644 --- a/code/game/objects/structures/ai_core.dm +++ b/code/game/objects/structures/ai_core.dm @@ -1,343 +1,343 @@ -/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 = EMPTY_CORE - var/datum/ai_laws/laws - var/obj/item/circuitboard/aicore/circuit - var/obj/item/mmi/brain - var/can_deconstruct = TRUE - -/obj/structure/AIcore/Initialize() - . = ..() - laws = new - laws.set_laws_config() - -/obj/structure/AIcore/handle_atom_del(atom/A) - if(A == circuit) - circuit = null - if((state != GLASS_CORE) && (state != AI_READY_CORE)) - state = EMPTY_CORE - update_icon() - if(A == brain) - brain = null - . = ..() - - -/obj/structure/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"]." - . += "You could [active? "deactivate" : "activate"] it with a multitool." - -/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(P.tool_behaviour == TOOL_MULTITOOL) - active = !active - to_chat(user, "You [active? "activate" : "deactivate"] \the [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(P.tool_behaviour == TOOL_WRENCH) - return default_unfasten_wrench(user, P, 20) - if(!anchored) - if(P.tool_behaviour == TOOL_WELDER && can_deconstruct) - if(state != EMPTY_CORE) - to_chat(user, "The core must be empty to deconstruct it!") - return - - if(!P.tool_start_check(user, amount=0)) - return - - to_chat(user, "You start to deconstruct the frame...") - if(P.use_tool(src, user, 20, volume=50) && state == EMPTY_CORE) - to_chat(user, "You deconstruct the frame.") - deconstruct(TRUE) - return - else - switch(state) - if(EMPTY_CORE) - if(istype(P, /obj/item/circuitboard/aicore)) - if(!user.transferItemToLoc(P, src)) - return - playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) - to_chat(user, "You place the circuit board inside the frame.") - update_icon() - state = CIRCUIT_CORE - circuit = P - return - if(CIRCUIT_CORE) - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You screw the circuit board into place.") - state = SCREWED_CORE - update_icon() - return - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the circuit board.") - state = EMPTY_CORE - update_icon() - circuit.forceMove(loc) - circuit = null - return - if(SCREWED_CORE) - if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You unfasten the circuit board.") - state = CIRCUIT_CORE - update_icon() - return - if(istype(P, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = P - if(C.get_amount() >= 5) - playsound(loc, 'sound/items/deconstruct.ogg', 50, 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(P.tool_behaviour == TOOL_WIRECUTTER) - if(brain) - to_chat(user, "Get that [brain.name] out of there first!") - else - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = SCREWED_CORE - update_icon() - new /obj/item/stack/cable_coil(drop_location(), 5) - return - - if(istype(P, /obj/item/stack/sheet/rglass)) - var/obj/item/stack/sheet/rglass/G = P - if(G.get_amount() >= 2) - playsound(loc, 'sound/items/deconstruct.ogg', 50, 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) || (is_banned_from(M.brainmob.ckey, "AI") && !QDELETED(src) && !QDELETED(user) && !QDELETED(M) && !QDELETED(user) && Adjacent(user))) - if(!QDELETED(M)) - to_chat(user, "This [M.name] does not seem to fit!") - return - - if(!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(P.tool_behaviour == TOOL_CROWBAR && brain) - P.play_tool_sound(src) - to_chat(user, "You remove the brain.") - brain.forceMove(loc) - brain = null - update_icon() - return - - if(GLASS_CORE) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the glass panel.") - state = CABLED_CORE - update_icon() - new /obj/item/stack/sheet/rglass(loc, 2) - return - - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You connect the monitor.") - if(brain) - 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(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You disconnect the monitor.") - state = GLASS_CORE - update_icon() - return - return ..() - -/obj/structure/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/Initialize() - . = ..() - circuit = new(src) - - -/* -This is a good place for AI-related object verbs so I'm sticking it here. -If adding stuff to this, don't forget that an AI need to cancel_camera() whenever it physically moves to a different location. -That prevents a few funky behaviors. -*/ -//The type of interaction, the player performing the operation, the AI itself, and the card object, if any. - - -/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(istype(card)) - if(card.flush) - to_chat(user, "ERROR: AI flush is in progress, cannot execute transfer protocol.") - return FALSE - return TRUE - -/obj/structure/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 = FALSE - AI.radio_enabled = TRUE - AI.forceMove(loc) // to replace the terminal. - to_chat(AI, "You have been uploaded to a stationary terminal. Remote device connection restored.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - card.AI = null - AI.battery = circuit.battery - qdel(src) - else //If for some reason you use an empty card on an empty AI terminal. - to_chat(user, "There is no AI loaded on this terminal!") - -/obj/item/circuitboard/aicore - name = "AI core (AI Core Board)" //Well, duh, but best to be consistent - var/battery = 200 //backup battery for when the AI loses power. Copied to/from AI mobs when carding, and placed here to avoid recharge via deconning the core +/obj/structure/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 = EMPTY_CORE + var/datum/ai_laws/laws + var/obj/item/circuitboard/aicore/circuit + var/obj/item/mmi/brain + var/can_deconstruct = TRUE + +/obj/structure/AIcore/Initialize() + . = ..() + laws = new + laws.set_laws_config() + +/obj/structure/AIcore/handle_atom_del(atom/A) + if(A == circuit) + circuit = null + if((state != GLASS_CORE) && (state != AI_READY_CORE)) + state = EMPTY_CORE + update_icon() + if(A == brain) + brain = null + . = ..() + + +/obj/structure/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"]." + . += "You could [active? "deactivate" : "activate"] it with a multitool." + +/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(P.tool_behaviour == TOOL_MULTITOOL) + active = !active + to_chat(user, "You [active? "activate" : "deactivate"] \the [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(P.tool_behaviour == TOOL_WRENCH) + return default_unfasten_wrench(user, P, 20) + if(!anchored) + if(P.tool_behaviour == TOOL_WELDER && can_deconstruct) + if(state != EMPTY_CORE) + to_chat(user, "The core must be empty to deconstruct it!") + return + + if(!P.tool_start_check(user, amount=0)) + return + + to_chat(user, "You start to deconstruct the frame...") + if(P.use_tool(src, user, 20, volume=50) && state == EMPTY_CORE) + to_chat(user, "You deconstruct the frame.") + deconstruct(TRUE) + return + else + switch(state) + if(EMPTY_CORE) + if(istype(P, /obj/item/circuitboard/aicore)) + if(!user.transferItemToLoc(P, src)) + return + playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You place the circuit board inside the frame.") + update_icon() + state = CIRCUIT_CORE + circuit = P + return + if(CIRCUIT_CORE) + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You screw the circuit board into place.") + state = SCREWED_CORE + update_icon() + return + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the circuit board.") + state = EMPTY_CORE + update_icon() + circuit.forceMove(loc) + circuit = null + return + if(SCREWED_CORE) + if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You unfasten the circuit board.") + state = CIRCUIT_CORE + update_icon() + return + if(istype(P, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = P + if(C.get_amount() >= 5) + playsound(loc, 'sound/items/deconstruct.ogg', 50, 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(P.tool_behaviour == TOOL_WIRECUTTER) + if(brain) + to_chat(user, "Get that [brain.name] out of there first!") + else + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = SCREWED_CORE + update_icon() + new /obj/item/stack/cable_coil(drop_location(), 5) + return + + if(istype(P, /obj/item/stack/sheet/rglass)) + var/obj/item/stack/sheet/rglass/G = P + if(G.get_amount() >= 2) + playsound(loc, 'sound/items/deconstruct.ogg', 50, 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) || (is_banned_from(M.brainmob.ckey, "AI") && !QDELETED(src) && !QDELETED(user) && !QDELETED(M) && !QDELETED(user) && Adjacent(user))) + if(!QDELETED(M)) + to_chat(user, "This [M.name] does not seem to fit!") + return + + if(!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(P.tool_behaviour == TOOL_CROWBAR && brain) + P.play_tool_sound(src) + to_chat(user, "You remove the brain.") + brain.forceMove(loc) + brain = null + update_icon() + return + + if(GLASS_CORE) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the glass panel.") + state = CABLED_CORE + update_icon() + new /obj/item/stack/sheet/rglass(loc, 2) + return + + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You connect the monitor.") + if(brain) + 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(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You disconnect the monitor.") + state = GLASS_CORE + update_icon() + return + return ..() + +/obj/structure/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/Initialize() + . = ..() + circuit = new(src) + + +/* +This is a good place for AI-related object verbs so I'm sticking it here. +If adding stuff to this, don't forget that an AI need to cancel_camera() whenever it physically moves to a different location. +That prevents a few funky behaviors. +*/ +//The type of interaction, the player performing the operation, the AI itself, and the card object, if any. + + +/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(istype(card)) + if(card.flush) + to_chat(user, "ERROR: AI flush is in progress, cannot execute transfer protocol.") + return FALSE + return TRUE + +/obj/structure/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 = FALSE + AI.radio_enabled = TRUE + AI.forceMove(loc) // to replace the terminal. + to_chat(AI, "You have been uploaded to a stationary terminal. Remote device connection restored.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + card.AI = null + AI.battery = circuit.battery + qdel(src) + else //If for some reason you use an empty card on an empty AI terminal. + to_chat(user, "There is no AI loaded on this terminal!") + +/obj/item/circuitboard/aicore + name = "AI core (AI Core Board)" //Well, duh, but best to be consistent + var/battery = 200 //backup battery for when the AI loses power. Copied to/from AI mobs when carding, and placed here to avoid recharge via deconning the core diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm index d47c940f678a..7b4d8ace7dfe 100644 --- a/code/game/objects/structures/bedsheet_bin.dm +++ b/code/game/objects/structures/bedsheet_bin.dm @@ -1,413 +1,413 @@ -/* -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(I.tool_behaviour == TOOL_WIRECUTTER || I.is_sharp()) - // yogs start - disable infinite holocloth - to_chat(user, "You tear [src] up.") - if(flags_1 & HOLOGRAM_1) - qdel(src) - return - var/obj/item/stack/sheet/cloth/C = new (get_turf(src), 3) - transfer_fingerprints_to(C) - C.add_fingerprint(user) - qdel(src) - // yogs end - 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/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/item/bedsheet/dorms - icon_state = "random_bedsheet" - item_color = "rainbow" - name = "random dorms bedsheet" - desc = "If you're reading this description ingame, something has gone wrong! Honk!" - -/obj/item/bedsheet/dorms/Initialize() - ..() - var/type = pickweight(list("Colors" = 80, "Special" = 20)) - switch(type) - if("Colors") - type = pick(list(/obj/item/bedsheet, - /obj/item/bedsheet/blue, - /obj/item/bedsheet/green, - /obj/item/bedsheet/grey, - /obj/item/bedsheet/orange, - /obj/item/bedsheet/purple, - /obj/item/bedsheet/red, - /obj/item/bedsheet/yellow, - /obj/item/bedsheet/brown, - /obj/item/bedsheet/black)) - if("Special") - type = pick(list(/obj/item/bedsheet/patriot, - /obj/item/bedsheet/rainbow, - /obj/item/bedsheet/ian, - /obj/item/bedsheet/cosmos, - /obj/item/bedsheet/nanotrasen)) - new type(loc) - return INITIALIZE_HINT_QDEL - -/obj/structure/bedsheetbin - name = "linen bin" - desc = "It looks rather cosy." - icon = 'icons/obj/structures.dmi' - icon_state = "linenbin-full" - anchored = TRUE - resistance_flags = FLAMMABLE - max_integrity = 70 - var/amount = 10 - var/list/sheets = list() - var/obj/item/hidden = null - -/obj/structure/bedsheetbin/empty - amount = 0 - icon_state = "linenbin-empty" - anchored = FALSE - - -/obj/structure/bedsheetbin/examine(mob/user) - . = ..() - if(amount < 1) - . += "There are no bed sheets in the bin." - else if(amount == 1) - . += "There is one bed sheet in the bin." - else - . += "There are [amount] bed sheets in the bin." - - -/obj/structure/bedsheetbin/update_icon() - switch(amount) - if(0) - icon_state = "linenbin-empty" - if(1 to 5) - icon_state = "linenbin-half" - else - icon_state = "linenbin-full" - -/obj/structure/bedsheetbin/fire_act(exposed_temperature, exposed_volume) - if(amount) - amount = 0 - update_icon() - ..() - -/obj/structure/bedsheetbin/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/bedsheet)) - if(!user.transferItemToLoc(I, src)) - return - sheets.Add(I) - amount++ - to_chat(user, "You put [I] in [src].") - update_icon() - - else if(default_unfasten_wrench(user, I, 5)) - return - - else if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(flags_1 & NODECONSTRUCT_1) - return - if(amount) - to_chat(user, "The [src] must be empty first!") - return - if(I.use_tool(src, user, 5, volume=50)) - to_chat(user, "You disassemble the [src].") - new /obj/item/stack/rods(loc, 2) - qdel(src) - - else if(amount && !hidden && I.w_class < WEIGHT_CLASS_BULKY) //make sure there's sheets to hide it among, make sure nothing else is hidden in there. - if(!user.transferItemToLoc(I, src)) - to_chat(user, "\The [I] is stuck to your hand, you cannot hide it among the sheets!") - return - hidden = I - to_chat(user, "You hide [I] among the sheets.") - - -/obj/structure/bedsheetbin/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/bedsheetbin/attack_hand(mob/user) - . = ..() - if(.) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - if(amount >= 1) - amount-- - - var/obj/item/bedsheet/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - B = new /obj/item/bedsheet(loc) - - B.forceMove(drop_location()) - user.put_in_hands(B) - to_chat(user, "You take [B] out of [src].") - update_icon() - - if(hidden) - hidden.forceMove(drop_location()) - to_chat(user, "[hidden] falls out of [B]!") - hidden = null - - - add_fingerprint(user) -/obj/structure/bedsheetbin/attack_tk(mob/user) - if(amount >= 1) - amount-- - - var/obj/item/bedsheet/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - B = new /obj/item/bedsheet(loc) - - B.forceMove(drop_location()) - to_chat(user, "You telekinetically remove [B] from [src].") - update_icon() - - if(hidden) - hidden.forceMove(drop_location()) - hidden = null - - - add_fingerprint(user) +/* +CONTAINS: +BEDSHEETS +LINEN BINS +*/ + +/obj/item/bedsheet + name = "bedsheet" + desc = "A surprisingly soft linen bedsheet." + icon = 'icons/obj/bedsheets.dmi' + 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(I.tool_behaviour == TOOL_WIRECUTTER || I.is_sharp()) + // yogs start - disable infinite holocloth + to_chat(user, "You tear [src] up.") + if(flags_1 & HOLOGRAM_1) + qdel(src) + return + var/obj/item/stack/sheet/cloth/C = new (get_turf(src), 3) + transfer_fingerprints_to(C) + C.add_fingerprint(user) + qdel(src) + // yogs end + 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/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/item/bedsheet/dorms + icon_state = "random_bedsheet" + item_color = "rainbow" + name = "random dorms bedsheet" + desc = "If you're reading this description ingame, something has gone wrong! Honk!" + +/obj/item/bedsheet/dorms/Initialize() + ..() + var/type = pickweight(list("Colors" = 80, "Special" = 20)) + switch(type) + if("Colors") + type = pick(list(/obj/item/bedsheet, + /obj/item/bedsheet/blue, + /obj/item/bedsheet/green, + /obj/item/bedsheet/grey, + /obj/item/bedsheet/orange, + /obj/item/bedsheet/purple, + /obj/item/bedsheet/red, + /obj/item/bedsheet/yellow, + /obj/item/bedsheet/brown, + /obj/item/bedsheet/black)) + if("Special") + type = pick(list(/obj/item/bedsheet/patriot, + /obj/item/bedsheet/rainbow, + /obj/item/bedsheet/ian, + /obj/item/bedsheet/cosmos, + /obj/item/bedsheet/nanotrasen)) + new type(loc) + return INITIALIZE_HINT_QDEL + +/obj/structure/bedsheetbin + name = "linen bin" + desc = "It looks rather cosy." + icon = 'icons/obj/structures.dmi' + icon_state = "linenbin-full" + anchored = TRUE + resistance_flags = FLAMMABLE + max_integrity = 70 + var/amount = 10 + var/list/sheets = list() + var/obj/item/hidden = null + +/obj/structure/bedsheetbin/empty + amount = 0 + icon_state = "linenbin-empty" + anchored = FALSE + + +/obj/structure/bedsheetbin/examine(mob/user) + . = ..() + if(amount < 1) + . += "There are no bed sheets in the bin." + else if(amount == 1) + . += "There is one bed sheet in the bin." + else + . += "There are [amount] bed sheets in the bin." + + +/obj/structure/bedsheetbin/update_icon() + switch(amount) + if(0) + icon_state = "linenbin-empty" + if(1 to 5) + icon_state = "linenbin-half" + else + icon_state = "linenbin-full" + +/obj/structure/bedsheetbin/fire_act(exposed_temperature, exposed_volume) + if(amount) + amount = 0 + update_icon() + ..() + +/obj/structure/bedsheetbin/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/bedsheet)) + if(!user.transferItemToLoc(I, src)) + return + sheets.Add(I) + amount++ + to_chat(user, "You put [I] in [src].") + update_icon() + + else if(default_unfasten_wrench(user, I, 5)) + return + + else if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(flags_1 & NODECONSTRUCT_1) + return + if(amount) + to_chat(user, "The [src] must be empty first!") + return + if(I.use_tool(src, user, 5, volume=50)) + to_chat(user, "You disassemble the [src].") + new /obj/item/stack/rods(loc, 2) + qdel(src) + + else if(amount && !hidden && I.w_class < WEIGHT_CLASS_BULKY) //make sure there's sheets to hide it among, make sure nothing else is hidden in there. + if(!user.transferItemToLoc(I, src)) + to_chat(user, "\The [I] is stuck to your hand, you cannot hide it among the sheets!") + return + hidden = I + to_chat(user, "You hide [I] among the sheets.") + + +/obj/structure/bedsheetbin/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/bedsheetbin/attack_hand(mob/user) + . = ..() + if(.) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + if(amount >= 1) + amount-- + + var/obj/item/bedsheet/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + B = new /obj/item/bedsheet(loc) + + B.forceMove(drop_location()) + user.put_in_hands(B) + to_chat(user, "You take [B] out of [src].") + update_icon() + + if(hidden) + hidden.forceMove(drop_location()) + to_chat(user, "[hidden] falls out of [B]!") + hidden = null + + + add_fingerprint(user) +/obj/structure/bedsheetbin/attack_tk(mob/user) + if(amount >= 1) + amount-- + + var/obj/item/bedsheet/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + B = new /obj/item/bedsheet(loc) + + B.forceMove(drop_location()) + to_chat(user, "You telekinetically remove [B] from [src].") + update_icon() + + if(hidden) + hidden.forceMove(drop_location()) + hidden = null + + + add_fingerprint(user) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 37ad16b7ab0c..9105d016f6c3 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1,506 +1,506 @@ -/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" - - -/obj/structure/closet/Initialize(mapload) - if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents - addtimer(CALLBACK(src, .proc/take_contents), 0) - . = ..() - update_icon() - PopulateContents() - -//USE THIS TO FILL IT, NOT INITIALIZE OR NEW -/obj/structure/closet/proc/PopulateContents() - return - -/obj/structure/closet/Destroy() - dump_contents() - return ..() - -/obj/structure/closet/update_icon() - cut_overlays() - if(!opened) - layer = OBJ_LAYER - if(icon_door) - add_overlay("[icon_door]_door") - else - add_overlay("[icon_state]_door") - if(welded) - add_overlay(icon_welded) - if(secure && !broken) - if(locked) - add_overlay("locked") - else - add_overlay("unlocked") - - else - layer = BELOW_OBJ_LAYER - if(icon_door_override) - add_overlay("[icon_door]_open") - else - add_overlay("[icon_state]_open") - -/obj/structure/closet/examine(mob/user) - .=..() - if(notreallyacloset) // Yogs -- Fixes bodybags complaining they can be welded together - return . // Yogs - if(welded) - . += "It's welded shut." - if(anchored) - . += "It is bolted to the ground." - if(opened) - . += "The parts are welded together." - else if(secure && !opened) - . += "Alt-click to [locked ? "unlock" : "lock"]." - if(isliving(user)) - var/mob/living/L = user - if(HAS_TRAIT(L, TRAIT_SKITTISH)) - . += "Ctrl-Shift-click [src] to jump inside." - -/obj/structure/closet/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/dump_contents() - var/atom/L = drop_location() - for(var/atom/movable/AM in src) - AM.forceMove(L) - if(throwing) // you keep some momentum when getting out of a thrown closet - step(AM, dir) - if(throwing) - throwing.finalize(FALSE) - -/obj/structure/closet/proc/take_contents() - var/atom/L = drop_location() - for(var/atom/movable/AM in L) - if(AM != src && insert(AM) == -1) // limit reached - break - -/obj/structure/closet/proc/open(mob/living/user) - 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)) - AM.forceMove(src) - return TRUE - else - return FALSE - -/obj/structure/closet/proc/insertion_allowed(atom/movable/AM) - if(ismob(AM)) - if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... - return FALSE - var/mob/living/L = AM - if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) - return FALSE - if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. - if(horizontal && L.density) - return FALSE - if(L.mob_size > max_mob_size) - return FALSE - var/mobs_stored = 0 - for(var/mob/living/M in contents) - if(++mobs_stored >= mob_storage_capacity) - return FALSE - L.stop_pulling() - - else if(istype(AM, /obj/structure/closet)) - return FALSE - else if(isobj(AM)) - if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) - return FALSE - else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) - return TRUE - else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) - return FALSE - else - return FALSE - - return TRUE - -/obj/structure/closet/proc/close(mob/living/user) - if(!opened || !can_close(user)) - return FALSE - take_contents() - playsound(loc, close_sound, 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/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)) - if(W.tool_behaviour == TOOL_WELDER) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin cutting \the [src] apart...") - if(W.use_tool(src, user, 40, volume=50)) - if(!opened) - return - user.visible_message("[user] slices apart \the [src].", - "You cut \the [src] apart with \the [W].", - "You hear welding.") - deconstruct(TRUE) - return - else // for example cardboard box is cut with wirecutters - user.visible_message("[user] cut apart \the [src].", \ - "You cut \the [src] apart with \the [W].") - deconstruct(TRUE) - return - if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too - return - else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") - if(W.use_tool(src, user, 40, volume=50)) - if(opened) - return - welded = !welded - after_weld(welded) - user.visible_message("[user] [welded ? "welds shut" : "unwelded"] \the [src].", - "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", - "You hear welding.") - update_icon() - else if(W.tool_behaviour == TOOL_WRENCH && anchorable) - if(isinspace() && !anchored) - return - setAnchored(!anchored) - W.play_tool_sound(src, 75) - user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You hear a ratchet.") - else if(user.a_intent != INTENT_HARM) - var/item_is_id = W.GetID() - if(!item_is_id && !(W.item_flags & NOBLUDGEON)) - return FALSE - if(item_is_id || !toggle(user)) - togglelock(user) - else - return FALSE - -/obj/structure/closet/proc/after_weld(weld_state) - return - -/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) - if(!istype(O) || O.anchored || istype(O, /obj/screen)) - return - if(!istype(user) || user.incapacitated() || !(user.mobility_flags & MOBILITY_STAND)) - return - if(!Adjacent(user) || !user.Adjacent(O)) - return - if(user == O) //try to climb onto it - return ..() - if(!opened) - return - if(!isturf(O.loc)) - return - - var/actuallyismob = 0 - if(isliving(O)) - actuallyismob = 1 - else if(!isitem(O)) - return - var/turf/T = get_turf(src) - var/list/targets = list(O, src) - add_fingerprint(user) - user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ - "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ - "You hear clanging.") - if(actuallyismob) - if(do_after_mob(user, targets, 40)) - user.visible_message("[user] stuffs [O] into [src].", \ - "You stuff [O] into [src].", \ - "You hear a loud metal bang.") - var/mob/living/L = O - if(!issilicon(L)) - L.Paralyze(40) - O.forceMove(T) - close() - else - O.forceMove(T) - return 1 - -/obj/structure/closet/relaymove(mob/user) - if(user.stat || !isturf(loc) || !isliving(user)) - return - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - container_resist(user) - -/obj/structure/closet/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!(user.mobility_flags & MOBILITY_STAND) && get_dist(src, user) > 0) - return - - if(!toggle(user)) - togglelock(user) - -/obj/structure/closet/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/closet/attack_robot(mob/user) - if(user.Adjacent(src)) - return attack_hand(user) - -// tk grab then use on self -/obj/structure/closet/attack_self_tk(mob/user) - return attack_hand(user) - -/obj/structure/closet/verb/verb_toggleopen() - set src in view(1) - set category = "Object" - set name = "Toggle Open" - - if(!usr.canUseTopic(src, BE_CLOSE) || !isturf(loc)) - return - - if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) - return toggle(usr) - else - to_chat(usr, "This mob type can't use this verb.") - -// Objects that try to exit a locker by stepping were doing so successfully, -// and due to an oversight in turf/Enter() were going through walls. That -// should be independently resolved, but this is also an interesting twist. -/obj/structure/closet/Exit(atom/movable/AM) - open() - if(AM.loc == src) - return 0 - return 1 - -/obj/structure/closet/container_resist(mob/living/user) - if(opened) - return - if(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/proc/bust_open() - welded = FALSE //applies to all lockers - locked = FALSE //applies to critter crates and secure lockers only - broken = TRUE //applies to secure lockers only - open() - -/obj/structure/closet/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc)) - return - if(opened || !secure) - return - else - togglelock(user) - -/obj/structure/closet/CtrlShiftClick(mob/living/user) - if(!HAS_TRAIT(user, TRAIT_SKITTISH)) - return ..() - if(!user.canUseTopic(src, BE_CLOSE) || !isturf(user.loc)) - return - dive_into(user) - -/obj/structure/closet/proc/togglelock(mob/living/user, silent) - if(secure && !broken) - if(allowed(user)) - if(iscarbon(user)) - add_fingerprint(user) - locked = !locked - user.visible_message("[user] [locked ? null : "un"]locks [src].", - "You [locked ? null : "un"]lock [src].") - update_icon() - else if(!silent) - to_chat(user, "Access Denied") - else if(secure && broken) - to_chat(user, "\The [src] is broken!") - -/obj/structure/closet/emag_act(mob/user) - if(secure && !broken) - 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 - update_icon() - -/obj/structure/closet/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/structure/closet/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if (!(. & EMP_PROTECT_CONTENTS)) - for(var/obj/O in src) - O.emp_act(severity) - if(secure && !broken && !(. & EMP_PROTECT_SELF)) - if(prob(50 / severity)) - locked = !locked - update_icon() - if(prob(20 / severity) && !opened) - if(!locked) - open() - else - req_access = list() - req_access += pick(get_all_accesses()) - -/obj/structure/closet/contents_explosion(severity, target) - for(var/atom/A in contents) - 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 + 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" + + +/obj/structure/closet/Initialize(mapload) + if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents + addtimer(CALLBACK(src, .proc/take_contents), 0) + . = ..() + update_icon() + PopulateContents() + +//USE THIS TO FILL IT, NOT INITIALIZE OR NEW +/obj/structure/closet/proc/PopulateContents() + return + +/obj/structure/closet/Destroy() + dump_contents() + return ..() + +/obj/structure/closet/update_icon() + cut_overlays() + if(!opened) + layer = OBJ_LAYER + if(icon_door) + add_overlay("[icon_door]_door") + else + add_overlay("[icon_state]_door") + if(welded) + add_overlay(icon_welded) + if(secure && !broken) + if(locked) + add_overlay("locked") + else + add_overlay("unlocked") + + else + layer = BELOW_OBJ_LAYER + if(icon_door_override) + add_overlay("[icon_door]_open") + else + add_overlay("[icon_state]_open") + +/obj/structure/closet/examine(mob/user) + .=..() + if(notreallyacloset) // Yogs -- Fixes bodybags complaining they can be welded together + return . // Yogs + if(welded) + . += "It's welded shut." + if(anchored) + . += "It is bolted to the ground." + if(opened) + . += "The parts are welded together." + else if(secure && !opened) + . += "Alt-click to [locked ? "unlock" : "lock"]." + if(isliving(user)) + var/mob/living/L = user + if(HAS_TRAIT(L, TRAIT_SKITTISH)) + . += "Ctrl-Shift-click [src] to jump inside." + +/obj/structure/closet/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/dump_contents() + var/atom/L = drop_location() + for(var/atom/movable/AM in src) + AM.forceMove(L) + if(throwing) // you keep some momentum when getting out of a thrown closet + step(AM, dir) + if(throwing) + throwing.finalize(FALSE) + +/obj/structure/closet/proc/take_contents() + var/atom/L = drop_location() + for(var/atom/movable/AM in L) + if(AM != src && insert(AM) == -1) // limit reached + break + +/obj/structure/closet/proc/open(mob/living/user) + 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)) + AM.forceMove(src) + return TRUE + else + return FALSE + +/obj/structure/closet/proc/insertion_allowed(atom/movable/AM) + if(ismob(AM)) + if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... + return FALSE + var/mob/living/L = AM + if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) + return FALSE + if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. + if(horizontal && L.density) + return FALSE + if(L.mob_size > max_mob_size) + return FALSE + var/mobs_stored = 0 + for(var/mob/living/M in contents) + if(++mobs_stored >= mob_storage_capacity) + return FALSE + L.stop_pulling() + + else if(istype(AM, /obj/structure/closet)) + return FALSE + else if(isobj(AM)) + if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) + return FALSE + else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) + return TRUE + else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) + return FALSE + else + return FALSE + + return TRUE + +/obj/structure/closet/proc/close(mob/living/user) + if(!opened || !can_close(user)) + return FALSE + take_contents() + playsound(loc, close_sound, 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/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)) + if(W.tool_behaviour == TOOL_WELDER) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin cutting \the [src] apart...") + if(W.use_tool(src, user, 40, volume=50)) + if(!opened) + return + user.visible_message("[user] slices apart \the [src].", + "You cut \the [src] apart with \the [W].", + "You hear welding.") + deconstruct(TRUE) + return + else // for example cardboard box is cut with wirecutters + user.visible_message("[user] cut apart \the [src].", \ + "You cut \the [src] apart with \the [W].") + deconstruct(TRUE) + return + if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too + return + else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") + if(W.use_tool(src, user, 40, volume=50)) + if(opened) + return + welded = !welded + after_weld(welded) + user.visible_message("[user] [welded ? "welds shut" : "unwelded"] \the [src].", + "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", + "You hear welding.") + update_icon() + else if(W.tool_behaviour == TOOL_WRENCH && anchorable) + if(isinspace() && !anchored) + return + setAnchored(!anchored) + W.play_tool_sound(src, 75) + user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You hear a ratchet.") + else if(user.a_intent != INTENT_HARM) + var/item_is_id = W.GetID() + if(!item_is_id && !(W.item_flags & NOBLUDGEON)) + return FALSE + if(item_is_id || !toggle(user)) + togglelock(user) + else + return FALSE + +/obj/structure/closet/proc/after_weld(weld_state) + return + +/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) + if(!istype(O) || O.anchored || istype(O, /obj/screen)) + return + if(!istype(user) || user.incapacitated() || !(user.mobility_flags & MOBILITY_STAND)) + return + if(!Adjacent(user) || !user.Adjacent(O)) + return + if(user == O) //try to climb onto it + return ..() + if(!opened) + return + if(!isturf(O.loc)) + return + + var/actuallyismob = 0 + if(isliving(O)) + actuallyismob = 1 + else if(!isitem(O)) + return + var/turf/T = get_turf(src) + var/list/targets = list(O, src) + add_fingerprint(user) + user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ + "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ + "You hear clanging.") + if(actuallyismob) + if(do_after_mob(user, targets, 40)) + user.visible_message("[user] stuffs [O] into [src].", \ + "You stuff [O] into [src].", \ + "You hear a loud metal bang.") + var/mob/living/L = O + if(!issilicon(L)) + L.Paralyze(40) + O.forceMove(T) + close() + else + O.forceMove(T) + return 1 + +/obj/structure/closet/relaymove(mob/user) + if(user.stat || !isturf(loc) || !isliving(user)) + return + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + container_resist(user) + +/obj/structure/closet/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!(user.mobility_flags & MOBILITY_STAND) && get_dist(src, user) > 0) + return + + if(!toggle(user)) + togglelock(user) + +/obj/structure/closet/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/closet/attack_robot(mob/user) + if(user.Adjacent(src)) + return attack_hand(user) + +// tk grab then use on self +/obj/structure/closet/attack_self_tk(mob/user) + return attack_hand(user) + +/obj/structure/closet/verb/verb_toggleopen() + set src in view(1) + set category = "Object" + set name = "Toggle Open" + + if(!usr.canUseTopic(src, BE_CLOSE) || !isturf(loc)) + return + + if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) + return toggle(usr) + else + to_chat(usr, "This mob type can't use this verb.") + +// Objects that try to exit a locker by stepping were doing so successfully, +// and due to an oversight in turf/Enter() were going through walls. That +// should be independently resolved, but this is also an interesting twist. +/obj/structure/closet/Exit(atom/movable/AM) + open() + if(AM.loc == src) + return 0 + return 1 + +/obj/structure/closet/container_resist(mob/living/user) + if(opened) + return + if(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/proc/bust_open() + welded = FALSE //applies to all lockers + locked = FALSE //applies to critter crates and secure lockers only + broken = TRUE //applies to secure lockers only + open() + +/obj/structure/closet/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc)) + return + if(opened || !secure) + return + else + togglelock(user) + +/obj/structure/closet/CtrlShiftClick(mob/living/user) + if(!HAS_TRAIT(user, TRAIT_SKITTISH)) + return ..() + if(!user.canUseTopic(src, BE_CLOSE) || !isturf(user.loc)) + return + dive_into(user) + +/obj/structure/closet/proc/togglelock(mob/living/user, silent) + if(secure && !broken) + if(allowed(user)) + if(iscarbon(user)) + add_fingerprint(user) + locked = !locked + user.visible_message("[user] [locked ? null : "un"]locks [src].", + "You [locked ? null : "un"]lock [src].") + update_icon() + else if(!silent) + to_chat(user, "Access Denied") + else if(secure && broken) + to_chat(user, "\The [src] is broken!") + +/obj/structure/closet/emag_act(mob/user) + if(secure && !broken) + 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 + update_icon() + +/obj/structure/closet/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/structure/closet/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if (!(. & EMP_PROTECT_CONTENTS)) + for(var/obj/O in src) + O.emp_act(severity) + if(secure && !broken && !(. & EMP_PROTECT_SELF)) + if(prob(50 / severity)) + locked = !locked + update_icon() + if(prob(20 / severity) && !opened) + if(!locked) + open() + else + req_access = list() + req_access += pick(get_all_accesses()) + +/obj/structure/closet/contents_explosion(severity, target) + for(var/atom/A in contents) + 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]!") diff --git a/code/game/objects/structures/crates_lockers/closets/fitness.dm b/code/game/objects/structures/crates_lockers/closets/fitness.dm index de479566d05b..ad493dd6f501 100644 --- a/code/game/objects/structures/crates_lockers/closets/fitness.dm +++ b/code/game/objects/structures/crates_lockers/closets/fitness.dm @@ -1,65 +1,65 @@ -/obj/structure/closet/athletic_mixed - name = "athletic wardrobe" - desc = "It's a storage unit for athletic wear." - icon_door = "mixed" - -/obj/structure/closet/athletic_mixed/PopulateContents() - ..() - new /obj/item/clothing/under/shorts/purple(src) - new /obj/item/clothing/under/shorts/grey(src) - new /obj/item/clothing/under/shorts/black(src) - new /obj/item/clothing/under/shorts/red(src) - new /obj/item/clothing/under/shorts/blue(src) - new /obj/item/clothing/under/shorts/green(src) - new /obj/item/clothing/under/jabroni(src) - - -/obj/structure/closet/boxinggloves - name = "boxing gloves" - desc = "It's a storage unit for gloves for use in the boxing ring." - -/obj/structure/closet/boxinggloves/PopulateContents() - ..() - new /obj/item/clothing/gloves/boxing/blue(src) - new /obj/item/clothing/gloves/boxing/green(src) - new /obj/item/clothing/gloves/boxing/yellow(src) - new /obj/item/clothing/gloves/boxing(src) - - -/obj/structure/closet/masks - name = "mask closet" - desc = "IT'S A STORAGE UNIT FOR FIGHTER MASKS OLE!" - -/obj/structure/closet/masks/PopulateContents() - ..() - new /obj/item/clothing/mask/luchador(src) - new /obj/item/clothing/mask/luchador/rudos(src) - new /obj/item/clothing/mask/luchador/tecnicos(src) - - -/obj/structure/closet/lasertag/red - name = "red laser tag equipment" - desc = "It's a storage unit for laser tag equipment." - icon_door = "red" - -/obj/structure/closet/lasertag/red/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser/redtag(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/redtag(src) - new /obj/item/clothing/head/helmet/redtaghelm(src) - - -/obj/structure/closet/lasertag/blue - name = "blue laser tag equipment" - desc = "It's a storage unit for laser tag equipment." - icon_door = "blue" - -/obj/structure/closet/lasertag/blue/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser/bluetag(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/bluetag(src) - new /obj/item/clothing/head/helmet/bluetaghelm(src) +/obj/structure/closet/athletic_mixed + name = "athletic wardrobe" + desc = "It's a storage unit for athletic wear." + icon_door = "mixed" + +/obj/structure/closet/athletic_mixed/PopulateContents() + ..() + new /obj/item/clothing/under/shorts/purple(src) + new /obj/item/clothing/under/shorts/grey(src) + new /obj/item/clothing/under/shorts/black(src) + new /obj/item/clothing/under/shorts/red(src) + new /obj/item/clothing/under/shorts/blue(src) + new /obj/item/clothing/under/shorts/green(src) + new /obj/item/clothing/under/jabroni(src) + + +/obj/structure/closet/boxinggloves + name = "boxing gloves" + desc = "It's a storage unit for gloves for use in the boxing ring." + +/obj/structure/closet/boxinggloves/PopulateContents() + ..() + new /obj/item/clothing/gloves/boxing/blue(src) + new /obj/item/clothing/gloves/boxing/green(src) + new /obj/item/clothing/gloves/boxing/yellow(src) + new /obj/item/clothing/gloves/boxing(src) + + +/obj/structure/closet/masks + name = "mask closet" + desc = "IT'S A STORAGE UNIT FOR FIGHTER MASKS OLE!" + +/obj/structure/closet/masks/PopulateContents() + ..() + new /obj/item/clothing/mask/luchador(src) + new /obj/item/clothing/mask/luchador/rudos(src) + new /obj/item/clothing/mask/luchador/tecnicos(src) + + +/obj/structure/closet/lasertag/red + name = "red laser tag equipment" + desc = "It's a storage unit for laser tag equipment." + icon_door = "red" + +/obj/structure/closet/lasertag/red/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser/redtag(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/redtag(src) + new /obj/item/clothing/head/helmet/redtaghelm(src) + + +/obj/structure/closet/lasertag/blue + name = "blue laser tag equipment" + desc = "It's a storage unit for laser tag equipment." + icon_door = "blue" + +/obj/structure/closet/lasertag/blue/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser/bluetag(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/bluetag(src) + new /obj/item/clothing/head/helmet/bluetaghelm(src) diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm index c9a2ad54ffef..52f351508129 100644 --- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm +++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm @@ -1,107 +1,107 @@ -/obj/structure/closet/cabinet - name = "cabinet" - desc = "Old will forever be in fashion." - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - -/obj/structure/closet/acloset - name = "strange closet" - desc = "It looks alien!" - icon_state = "alien" - - -/obj/structure/closet/gimmick - name = "administrative supply closet" - desc = "It's a storage unit for things that have no right being here." - icon_state = "syndicate" - -/obj/structure/closet/gimmick/russian - name = "\improper Russian surplus closet" - desc = "It's a storage unit for Russian standard-issue surplus." - -/obj/structure/closet/gimmick/russian/PopulateContents() - ..() - for(var/i in 1 to 5) - new /obj/item/clothing/head/ushanka(src) - for(var/i in 1 to 5) - new /obj/item/clothing/under/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 + +/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 b87a44de3c42..a07172d9301c 100644 --- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm @@ -1,311 +1,311 @@ -// Closets for specific jobs - -/obj/structure/closet/gmcloset - name = "formal closet" - desc = "It's a storage unit for formal clothing." - icon_door = "black" - -/obj/structure/closet/gmcloset/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/head/that = 2, - /obj/item/radio/headset/headset_srv = 2, - /obj/item/clothing/under/sl_suit = 2, - /obj/item/clothing/under/rank/bartender = 2, - /obj/item/clothing/accessory/waistcoat = 2, - /obj/item/clothing/head/soft/black = 2, - /obj/item/clothing/shoes/sneakers/black = 2, - /obj/item/reagent_containers/glass/rag = 2, - /obj/item/storage/box/beanbag = 1, - /obj/item/clothing/suit/armor/vest/alt = 1, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/clothing/glasses/sunglasses/reagent = 1, - /obj/item/clothing/neck/petcollar = 1, - /obj/item/storage/belt/bandolier = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/chefcloset - name = "\proper chef's closet" - desc = "It's a storage unit for foodservice garments and mouse traps." - icon_door = "black" - -/obj/structure/closet/chefcloset/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/under/waiter = 2, - /obj/item/radio/headset/headset_srv = 2, - /obj/item/clothing/accessory/waistcoat = 2, - /obj/item/clothing/suit/apron/chef = 3, - /obj/item/clothing/head/soft/mime = 2, - /obj/item/storage/box/mousetraps = 2, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/clothing/suit/toggle/chef = 1, - /obj/item/clothing/under/rank/chef = 1, - /obj/item/clothing/head/chefhat = 1, - /obj/item/reagent_containers/glass/rag = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/jcloset - name = "custodial closet" - desc = "It's a storage unit for janitorial clothes and gear." - icon_door = "mixed" - -/obj/structure/closet/jcloset/PopulateContents() - ..() - new /obj/item/clothing/under/rank/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 4) // yogs - Makes there be 4 caution signs instead of 3 - new /obj/item/clothing/suit/caution(src) - new /obj/item/holosign_creator/janibarrier(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/choice_beacon/holy(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/chaplainsuit/nun(src) - new /obj/item/clothing/head/nun_hood(src) - new /obj/item/clothing/suit/chaplainsuit/holidaypriest(src) - new /obj/item/storage/backpack/cultpack(src) - new /obj/item/storage/fancy/candle_box(src) - new /obj/item/storage/fancy/candle_box(src) - return - -/obj/structure/closet/wardrobe/red - name = "security wardrobe" - icon_door = "red" - -/obj/structure/closet/wardrobe/red/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/suit/hooded/wintercoat/security = 1, - /obj/item/storage/backpack/security = 1, - /obj/item/storage/backpack/satchel/sec = 1, - /obj/item/storage/backpack/duffelbag/sec = 2, - /obj/item/clothing/under/rank/security = 3, - /obj/item/clothing/under/rank/security/skirt = 2, - /obj/item/clothing/shoes/jackboots = 3, - /obj/item/clothing/head/beret/sec = 3, - /obj/item/clothing/head/soft/sec = 3, - /obj/item/clothing/mask/bandana/red = 2) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/cargotech - name = "cargo wardrobe" - icon_door = "orange" - -/obj/structure/closet/wardrobe/cargotech/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/suit/hooded/wintercoat/cargo = 1, - /obj/item/clothing/under/rank/cargotech = 3, - /obj/item/clothing/shoes/sneakers/black = 3, - /obj/item/clothing/gloves/fingerless = 3, - /obj/item/clothing/head/soft = 3, - /obj/item/radio/headset/headset_cargo = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/wardrobe/atmospherics_yellow - name = "atmospherics wardrobe" - icon_door = "atmos_wardrobe" - -/obj/structure/closet/wardrobe/atmospherics_yellow/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/engineering = 1, - /obj/item/storage/backpack/satchel/eng = 1, - /obj/item/storage/backpack/industrial = 1, - /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3, - /obj/item/clothing/under/rank/atmospheric_technician = 3, - /obj/item/clothing/shoes/sneakers/black = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/engineering_yellow - name = "engineering wardrobe" - icon_door = "yellow" - -/obj/structure/closet/wardrobe/engineering_yellow/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/engineering = 1, - /obj/item/storage/backpack/industrial = 1, - /obj/item/storage/backpack/satchel/eng = 1, - /obj/item/clothing/suit/hooded/wintercoat/engineering = 1, - /obj/item/clothing/under/rank/engineer = 3, - /obj/item/clothing/suit/hazardvest = 3, - /obj/item/clothing/shoes/workboots = 3, - /obj/item/clothing/head/hardhat = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/white/medical - name = "medical doctor's wardrobe" - -/obj/structure/closet/wardrobe/white/medical/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/med = 1, - /obj/item/storage/backpack/medic = 1, - /obj/item/storage/backpack/satchel/med = 1, - /obj/item/clothing/suit/hooded/wintercoat/medical = 1, - /obj/item/clothing/under/rank/nursesuit = 1, - /obj/item/clothing/head/nursehat = 1, - /obj/item/clothing/under/rank/medical/blue = 1, - /obj/item/clothing/under/rank/medical/green = 1, - /obj/item/clothing/under/rank/medical/purple = 1, - /obj/item/clothing/under/rank/medical = 3, - /obj/item/clothing/suit/toggle/labcoat = 3, - /obj/item/clothing/suit/toggle/labcoat/emt = 3, - /obj/item/clothing/shoes/sneakers/white = 3, - /obj/item/clothing/head/soft/emt = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/robotics_black - name = "robotics wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/robotics_black/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/glasses/hud/diagnostic = 2, - /obj/item/clothing/under/rank/roboticist = 2, - /obj/item/clothing/suit/toggle/labcoat = 2, - /obj/item/clothing/shoes/sneakers/black = 2, - /obj/item/clothing/gloves/fingerless = 2, - /obj/item/clothing/head/soft/black = 2) - generate_items_inside(items_inside,src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - return - - -/obj/structure/closet/wardrobe/chemistry_white - name = "chemistry wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/chemistry_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/chemist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/chemist = 2, - /obj/item/storage/backpack/chemistry = 2, - /obj/item/storage/backpack/satchel/chem = 2, - /obj/item/storage/bag/chemistry = 2) - generate_items_inside(items_inside,src) - return - - -/obj/structure/closet/wardrobe/genetics_white - name = "genetics wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/genetics_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/geneticist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/genetics = 2, - /obj/item/storage/backpack/genetics = 2, - /obj/item/storage/backpack/satchel/gen = 2) - generate_items_inside(items_inside,src) - return - - -/obj/structure/closet/wardrobe/virology_white - name = "virology wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/virology_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/virologist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/virologist = 2, - /obj/item/clothing/mask/surgical = 2, - /obj/item/storage/backpack/virology = 2, - /obj/item/storage/backpack/satchel/vir = 2) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/science_white - name = "science wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/science_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/science = 2, - /obj/item/storage/backpack/satchel/tox = 2, - /obj/item/clothing/suit/hooded/wintercoat/science = 1, - /obj/item/clothing/under/rank/scientist = 3, - /obj/item/clothing/suit/toggle/labcoat/science = 3, - /obj/item/clothing/shoes/sneakers/white = 3, - /obj/item/radio/headset/headset_sci = 2, - /obj/item/clothing/mask/gas = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/botanist - name = "botanist wardrobe" - icon_door = "green" - -/obj/structure/closet/wardrobe/botanist/PopulateContents() - var/static/items_inside = list( - /obj/item/storage/backpack/botany = 2, - /obj/item/storage/backpack/satchel/hyd = 2, - /obj/item/clothing/suit/hooded/wintercoat/hydro = 1, - /obj/item/clothing/suit/apron = 2, - /obj/item/clothing/suit/apron/overalls = 2, - /obj/item/clothing/under/rank/hydroponics = 3, - /obj/item/clothing/mask/bandana = 3) - generate_items_inside(items_inside,src) - -/obj/structure/closet/wardrobe/curator - name = "treasure hunting wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/curator/PopulateContents() - new /obj/item/clothing/head/fedora/curator(src) - new /obj/item/clothing/suit/curator(src) - new /obj/item/clothing/under/rank/curator/treasure_hunter(src) - new /obj/item/clothing/shoes/workboots/mining(src) - new /obj/item/storage/backpack/satchel/explorer(src) - +// Closets for specific jobs + +/obj/structure/closet/gmcloset + name = "formal closet" + desc = "It's a storage unit for formal clothing." + icon_door = "black" + +/obj/structure/closet/gmcloset/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/head/that = 2, + /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/under/sl_suit = 2, + /obj/item/clothing/under/rank/bartender = 2, + /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/head/soft/black = 2, + /obj/item/clothing/shoes/sneakers/black = 2, + /obj/item/reagent_containers/glass/rag = 2, + /obj/item/storage/box/beanbag = 1, + /obj/item/clothing/suit/armor/vest/alt = 1, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/clothing/glasses/sunglasses/reagent = 1, + /obj/item/clothing/neck/petcollar = 1, + /obj/item/storage/belt/bandolier = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/chefcloset + name = "\proper chef's closet" + desc = "It's a storage unit for foodservice garments and mouse traps." + icon_door = "black" + +/obj/structure/closet/chefcloset/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/under/waiter = 2, + /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/suit/apron/chef = 3, + /obj/item/clothing/head/soft/mime = 2, + /obj/item/storage/box/mousetraps = 2, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/clothing/suit/toggle/chef = 1, + /obj/item/clothing/under/rank/chef = 1, + /obj/item/clothing/head/chefhat = 1, + /obj/item/reagent_containers/glass/rag = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/jcloset + name = "custodial closet" + desc = "It's a storage unit for janitorial clothes and gear." + icon_door = "mixed" + +/obj/structure/closet/jcloset/PopulateContents() + ..() + new /obj/item/clothing/under/rank/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 4) // yogs - Makes there be 4 caution signs instead of 3 + new /obj/item/clothing/suit/caution(src) + new /obj/item/holosign_creator/janibarrier(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/choice_beacon/holy(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/chaplainsuit/nun(src) + new /obj/item/clothing/head/nun_hood(src) + new /obj/item/clothing/suit/chaplainsuit/holidaypriest(src) + new /obj/item/storage/backpack/cultpack(src) + new /obj/item/storage/fancy/candle_box(src) + new /obj/item/storage/fancy/candle_box(src) + return + +/obj/structure/closet/wardrobe/red + name = "security wardrobe" + icon_door = "red" + +/obj/structure/closet/wardrobe/red/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/suit/hooded/wintercoat/security = 1, + /obj/item/storage/backpack/security = 1, + /obj/item/storage/backpack/satchel/sec = 1, + /obj/item/storage/backpack/duffelbag/sec = 2, + /obj/item/clothing/under/rank/security = 3, + /obj/item/clothing/under/rank/security/skirt = 2, + /obj/item/clothing/shoes/jackboots = 3, + /obj/item/clothing/head/beret/sec = 3, + /obj/item/clothing/head/soft/sec = 3, + /obj/item/clothing/mask/bandana/red = 2) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/cargotech + name = "cargo wardrobe" + icon_door = "orange" + +/obj/structure/closet/wardrobe/cargotech/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/suit/hooded/wintercoat/cargo = 1, + /obj/item/clothing/under/rank/cargotech = 3, + /obj/item/clothing/shoes/sneakers/black = 3, + /obj/item/clothing/gloves/fingerless = 3, + /obj/item/clothing/head/soft = 3, + /obj/item/radio/headset/headset_cargo = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/wardrobe/atmospherics_yellow + name = "atmospherics wardrobe" + icon_door = "atmos_wardrobe" + +/obj/structure/closet/wardrobe/atmospherics_yellow/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/engineering = 1, + /obj/item/storage/backpack/satchel/eng = 1, + /obj/item/storage/backpack/industrial = 1, + /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3, + /obj/item/clothing/under/rank/atmospheric_technician = 3, + /obj/item/clothing/shoes/sneakers/black = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/engineering_yellow + name = "engineering wardrobe" + icon_door = "yellow" + +/obj/structure/closet/wardrobe/engineering_yellow/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/engineering = 1, + /obj/item/storage/backpack/industrial = 1, + /obj/item/storage/backpack/satchel/eng = 1, + /obj/item/clothing/suit/hooded/wintercoat/engineering = 1, + /obj/item/clothing/under/rank/engineer = 3, + /obj/item/clothing/suit/hazardvest = 3, + /obj/item/clothing/shoes/workboots = 3, + /obj/item/clothing/head/hardhat = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/white/medical + name = "medical doctor's wardrobe" + +/obj/structure/closet/wardrobe/white/medical/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/med = 1, + /obj/item/storage/backpack/medic = 1, + /obj/item/storage/backpack/satchel/med = 1, + /obj/item/clothing/suit/hooded/wintercoat/medical = 1, + /obj/item/clothing/under/rank/nursesuit = 1, + /obj/item/clothing/head/nursehat = 1, + /obj/item/clothing/under/rank/medical/blue = 1, + /obj/item/clothing/under/rank/medical/green = 1, + /obj/item/clothing/under/rank/medical/purple = 1, + /obj/item/clothing/under/rank/medical = 3, + /obj/item/clothing/suit/toggle/labcoat = 3, + /obj/item/clothing/suit/toggle/labcoat/emt = 3, + /obj/item/clothing/shoes/sneakers/white = 3, + /obj/item/clothing/head/soft/emt = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/robotics_black + name = "robotics wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/robotics_black/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/glasses/hud/diagnostic = 2, + /obj/item/clothing/under/rank/roboticist = 2, + /obj/item/clothing/suit/toggle/labcoat = 2, + /obj/item/clothing/shoes/sneakers/black = 2, + /obj/item/clothing/gloves/fingerless = 2, + /obj/item/clothing/head/soft/black = 2) + generate_items_inside(items_inside,src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + return + + +/obj/structure/closet/wardrobe/chemistry_white + name = "chemistry wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/chemistry_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/chemist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/chemist = 2, + /obj/item/storage/backpack/chemistry = 2, + /obj/item/storage/backpack/satchel/chem = 2, + /obj/item/storage/bag/chemistry = 2) + generate_items_inside(items_inside,src) + return + + +/obj/structure/closet/wardrobe/genetics_white + name = "genetics wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/genetics_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/geneticist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/genetics = 2, + /obj/item/storage/backpack/genetics = 2, + /obj/item/storage/backpack/satchel/gen = 2) + generate_items_inside(items_inside,src) + return + + +/obj/structure/closet/wardrobe/virology_white + name = "virology wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/virology_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/virologist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/virologist = 2, + /obj/item/clothing/mask/surgical = 2, + /obj/item/storage/backpack/virology = 2, + /obj/item/storage/backpack/satchel/vir = 2) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/science_white + name = "science wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/science_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/science = 2, + /obj/item/storage/backpack/satchel/tox = 2, + /obj/item/clothing/suit/hooded/wintercoat/science = 1, + /obj/item/clothing/under/rank/scientist = 3, + /obj/item/clothing/suit/toggle/labcoat/science = 3, + /obj/item/clothing/shoes/sneakers/white = 3, + /obj/item/radio/headset/headset_sci = 2, + /obj/item/clothing/mask/gas = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/botanist + name = "botanist wardrobe" + icon_door = "green" + +/obj/structure/closet/wardrobe/botanist/PopulateContents() + var/static/items_inside = list( + /obj/item/storage/backpack/botany = 2, + /obj/item/storage/backpack/satchel/hyd = 2, + /obj/item/clothing/suit/hooded/wintercoat/hydro = 1, + /obj/item/clothing/suit/apron = 2, + /obj/item/clothing/suit/apron/overalls = 2, + /obj/item/clothing/under/rank/hydroponics = 3, + /obj/item/clothing/mask/bandana = 3) + generate_items_inside(items_inside,src) + +/obj/structure/closet/wardrobe/curator + name = "treasure hunting wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/curator/PopulateContents() + new /obj/item/clothing/head/fedora/curator(src) + new /obj/item/clothing/suit/curator(src) + new /obj/item/clothing/under/rank/curator/treasure_hunter(src) + new /obj/item/clothing/shoes/workboots/mining(src) + new /obj/item/storage/backpack/satchel/explorer(src) + diff --git a/code/game/objects/structures/crates_lockers/closets/l3closet.dm b/code/game/objects/structures/crates_lockers/closets/l3closet.dm index f53398e67850..22d996c5ef76 100644 --- a/code/game/objects/structures/crates_lockers/closets/l3closet.dm +++ b/code/game/objects/structures/crates_lockers/closets/l3closet.dm @@ -1,54 +1,54 @@ -/obj/structure/closet/l3closet - name = "level 3 biohazard gear closet" - desc = "It's a storage unit for level 3 biohazard gear." - icon_state = "bio" - -/obj/structure/closet/l3closet/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/general(src) - new /obj/item/clothing/head/bio_hood/general(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/virology - icon_state = "bio_viro" - -/obj/structure/closet/l3closet/virology/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/virology(src) - new /obj/item/clothing/head/bio_hood/virology(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/security - icon_state = "bio_sec" - -/obj/structure/closet/l3closet/security/PopulateContents() - new /obj/item/clothing/suit/bio_suit/security(src) - new /obj/item/clothing/head/bio_hood/security(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/janitor - icon_state = "bio_jan" - -/obj/structure/closet/l3closet/janitor/PopulateContents() - new /obj/item/clothing/suit/bio_suit/janitor(src) - new /obj/item/clothing/head/bio_hood/janitor(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/scientist - icon_state = "bio_viro" - -/obj/structure/closet/l3closet/scientist/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/scientist(src) - new /obj/item/clothing/head/bio_hood/scientist(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - +/obj/structure/closet/l3closet + name = "level 3 biohazard gear closet" + desc = "It's a storage unit for level 3 biohazard gear." + icon_state = "bio" + +/obj/structure/closet/l3closet/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/general(src) + new /obj/item/clothing/head/bio_hood/general(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/virology + icon_state = "bio_viro" + +/obj/structure/closet/l3closet/virology/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/virology(src) + new /obj/item/clothing/head/bio_hood/virology(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/security + icon_state = "bio_sec" + +/obj/structure/closet/l3closet/security/PopulateContents() + new /obj/item/clothing/suit/bio_suit/security(src) + new /obj/item/clothing/head/bio_hood/security(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/janitor + icon_state = "bio_jan" + +/obj/structure/closet/l3closet/janitor/PopulateContents() + new /obj/item/clothing/suit/bio_suit/janitor(src) + new /obj/item/clothing/head/bio_hood/janitor(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/scientist + icon_state = "bio_viro" + +/obj/structure/closet/l3closet/scientist/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/scientist(src) + new /obj/item/clothing/head/bio_hood/scientist(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + diff --git a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm index 7a25e906654f..bec0dd3dd740 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm @@ -1,12 +1,12 @@ -/obj/structure/closet/secure_closet/bar - name = "booze storage" - req_access = list(ACCESS_BAR) - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - -/obj/structure/closet/secure_closet/bar/PopulateContents() - ..() - for(var/i in 1 to 10) - new /obj/item/reagent_containers/food/drinks/beer( src ) - new /obj/item/etherealballdeployer(src) +/obj/structure/closet/secure_closet/bar + name = "booze storage" + req_access = list(ACCESS_BAR) + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + +/obj/structure/closet/secure_closet/bar/PopulateContents() + ..() + for(var/i in 1 to 10) + new /obj/item/reagent_containers/food/drinks/beer( src ) + new /obj/item/etherealballdeployer(src) 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 a55b80cc6ce3..ff05844ebf6f 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm @@ -1,24 +1,24 @@ -/obj/structure/closet/secure_closet/quartermaster - name = "\proper quartermaster's locker" - req_access = list(ACCESS_QM) - icon_state = "qm" - -/obj/structure/closet/secure_closet/quartermaster/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/qm(src) - new /obj/item/storage/lockbox/medal/cargo(src) - new /obj/item/clothing/under/rank/cargo(src) - new /obj/item/clothing/under/rank/cargo/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/radio/headset/headset_cargo(src) - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/gloves/fingerless(src) - new /obj/item/megaphone/cargo(src) - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/head/soft(src) - new /obj/item/export_scanner(src) - new /obj/item/door_remote/quartermaster(src) - new /obj/item/circuitboard/machine/techfab/department/cargo(src) - new /obj/item/storage/photo_album/QM(src) - new /obj/item/circuitboard/machine/ore_silo(src) +/obj/structure/closet/secure_closet/quartermaster + name = "\proper quartermaster's locker" + req_access = list(ACCESS_QM) + icon_state = "qm" + +/obj/structure/closet/secure_closet/quartermaster/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/qm(src) + new /obj/item/storage/lockbox/medal/cargo(src) + new /obj/item/clothing/under/rank/cargo(src) + new /obj/item/clothing/under/rank/cargo/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/radio/headset/headset_cargo(src) + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/gloves/fingerless(src) + new /obj/item/megaphone/cargo(src) + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/head/soft(src) + new /obj/item/export_scanner(src) + new /obj/item/door_remote/quartermaster(src) + new /obj/item/circuitboard/machine/techfab/department/cargo(src) + new /obj/item/storage/photo_album/QM(src) + new /obj/item/circuitboard/machine/ore_silo(src) 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 a4d6770d8a0b..c2eadc1d84af 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -1,99 +1,99 @@ -/obj/structure/closet/secure_closet/engineering_chief - name = "\proper chief engineer's locker" - req_access = list(ACCESS_CE) - icon_state = "ce" - -/obj/structure/closet/secure_closet/engineering_chief/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/ce(src) - new /obj/item/clothing/under/rank/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/head/welding(src) - new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/tank/jetpack/suit(src) - new /obj/item/cartridge/ce(src) - new /obj/item/radio/headset/heads/ce(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/clothing/suit/hazardvest(src) - new /obj/item/megaphone/command(src) - new /obj/item/areaeditor/blueprints(src) - new /obj/item/airlock_painter(src) - new /obj/item/holosign_creator/engineering(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/multitool(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) - -/obj/structure/closet/secure_closet/engineering_electrical - name = "electrical supplies locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng" - icon_door = "eng_elec" - -/obj/structure/closet/secure_closet/engineering_electrical/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/gloves/color/yellow = 2, - /obj/item/inducer = 2, - /obj/item/storage/toolbox/electrical = 3, - /obj/item/electronics/apc = 3, - /obj/item/multitool = 3) - generate_items_inside(items_inside,src) - -/obj/structure/closet/secure_closet/engineering_welding - name = "welding supplies locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng" - icon_door = "eng_weld" - -/obj/structure/closet/secure_closet/engineering_welding/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/head/welding(src) - for(var/i in 1 to 3) - new /obj/item/weldingtool(src) - -/obj/structure/closet/secure_closet/engineering_personal - name = "engineer's locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng_secure" - -/obj/structure/closet/secure_closet/engineering_personal/PopulateContents() - ..() - new /obj/item/radio/headset/headset_eng(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - new /obj/item/holosign_creator/engineering(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/glasses/meson/engine(src) - new /obj/item/storage/box/emptysandbags(src) - new /obj/item/storage/bag/construction(src) - - -/obj/structure/closet/secure_closet/atmospherics - name = "\proper atmospheric technician's locker" - req_access = list(ACCESS_ATMOSPHERICS) - icon_state = "atmos" - -/obj/structure/closet/secure_closet/atmospherics/PopulateContents() - ..() - new /obj/item/radio/headset/headset_eng(src) - new /obj/item/pipe_dispenser(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - new /obj/item/analyzer(src) - new /obj/item/holosign_creator/atmos(src) - new /obj/item/watertank/atmos(src) - new /obj/item/clothing/suit/fire/atmos(src) - new /obj/item/clothing/head/hardhat/atmos(src) - new /obj/item/clothing/glasses/meson/engine/tray(src) - new /obj/item/extinguisher/advanced(src) +/obj/structure/closet/secure_closet/engineering_chief + name = "\proper chief engineer's locker" + req_access = list(ACCESS_CE) + icon_state = "ce" + +/obj/structure/closet/secure_closet/engineering_chief/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/ce(src) + new /obj/item/clothing/under/rank/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/head/welding(src) + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/tank/jetpack/suit(src) + new /obj/item/cartridge/ce(src) + new /obj/item/radio/headset/heads/ce(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/clothing/suit/hazardvest(src) + new /obj/item/megaphone/command(src) + new /obj/item/areaeditor/blueprints(src) + new /obj/item/airlock_painter(src) + new /obj/item/holosign_creator/engineering(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/multitool(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) + +/obj/structure/closet/secure_closet/engineering_electrical + name = "electrical supplies locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng" + icon_door = "eng_elec" + +/obj/structure/closet/secure_closet/engineering_electrical/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/gloves/color/yellow = 2, + /obj/item/inducer = 2, + /obj/item/storage/toolbox/electrical = 3, + /obj/item/electronics/apc = 3, + /obj/item/multitool = 3) + generate_items_inside(items_inside,src) + +/obj/structure/closet/secure_closet/engineering_welding + name = "welding supplies locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng" + icon_door = "eng_weld" + +/obj/structure/closet/secure_closet/engineering_welding/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/head/welding(src) + for(var/i in 1 to 3) + new /obj/item/weldingtool(src) + +/obj/structure/closet/secure_closet/engineering_personal + name = "engineer's locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng_secure" + +/obj/structure/closet/secure_closet/engineering_personal/PopulateContents() + ..() + new /obj/item/radio/headset/headset_eng(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + new /obj/item/holosign_creator/engineering(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/glasses/meson/engine(src) + new /obj/item/storage/box/emptysandbags(src) + new /obj/item/storage/bag/construction(src) + + +/obj/structure/closet/secure_closet/atmospherics + name = "\proper atmospheric technician's locker" + req_access = list(ACCESS_ATMOSPHERICS) + icon_state = "atmos" + +/obj/structure/closet/secure_closet/atmospherics/PopulateContents() + ..() + new /obj/item/radio/headset/headset_eng(src) + new /obj/item/pipe_dispenser(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + new /obj/item/analyzer(src) + new /obj/item/holosign_creator/atmos(src) + new /obj/item/watertank/atmos(src) + new /obj/item/clothing/suit/fire/atmos(src) + new /obj/item/clothing/head/hardhat/atmos(src) + new /obj/item/clothing/glasses/meson/engine/tray(src) + new /obj/item/extinguisher/advanced(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm index d06e0fc1f31e..97a28e12d59b 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm @@ -1,110 +1,110 @@ -/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" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/meat/PopulateContents() - ..() - for(var/i = 0, i < 4, i++) - new /obj/item/reagent_containers/food/snacks/meat/slab/monkey(src) - new /obj/item/storage/box/goatcubes(src) // yogs change added goat cubes to meat freezer - -/obj/structure/closet/secure_closet/freezer/meat/open - req_access = null - locked = FALSE - - -/obj/structure/closet/secure_closet/freezer/fridge - name = "refrigerator" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/fridge/PopulateContents() - ..() - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/milk(src) - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/soymilk(src) - for(var/i = 0, i < 2, i++) - new /obj/item/storage/fancy/egg_box(src) - -/obj/structure/closet/secure_closet/freezer/fridge/open - req_access = null - locked = FALSE - -/obj/structure/closet/secure_closet/freezer/money - name = "freezer" - desc = "This contains cold hard cash." - req_access = list(ACCESS_VAULT) - -/obj/structure/closet/secure_closet/freezer/money/PopulateContents() - ..() - for(var/i = 0, i < 3, i++) - new /obj/item/stack/spacecash/c1000(src) - for(var/i = 0, i < 5, i++) - new /obj/item/stack/spacecash/c500(src) - for(var/i = 0, i < 6, i++) - new /obj/item/stack/spacecash/c200(src) - -/obj/structure/closet/secure_closet/freezer/cream_pie - name = "cream pie closet" - desc = "Contains pies filled with cream and/or custard, you sickos." - req_access = list(ACCESS_THEATRE) - -/obj/structure/closet/secure_closet/freezer/cream_pie/PopulateContents() - ..() - new /obj/item/reagent_containers/food/snacks/pie/cream(src) +/obj/structure/closet/secure_closet/freezer + icon_state = "freezer" + var/jones = FALSE + +/obj/structure/closet/secure_closet/freezer/Destroy() + recursive_organ_check(src) + ..() + +/obj/structure/closet/secure_closet/freezer/Initialize() + ..() + recursive_organ_check(src) + +/obj/structure/closet/secure_closet/freezer/open(mob/living/user) + 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" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/meat/PopulateContents() + ..() + for(var/i = 0, i < 4, i++) + new /obj/item/reagent_containers/food/snacks/meat/slab/monkey(src) + new /obj/item/storage/box/goatcubes(src) // yogs change added goat cubes to meat freezer + +/obj/structure/closet/secure_closet/freezer/meat/open + req_access = null + locked = FALSE + + +/obj/structure/closet/secure_closet/freezer/fridge + name = "refrigerator" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/fridge/PopulateContents() + ..() + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/milk(src) + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/soymilk(src) + for(var/i = 0, i < 2, i++) + new /obj/item/storage/fancy/egg_box(src) + +/obj/structure/closet/secure_closet/freezer/fridge/open + req_access = null + locked = FALSE + +/obj/structure/closet/secure_closet/freezer/money + name = "freezer" + desc = "This contains cold hard cash." + req_access = list(ACCESS_VAULT) + +/obj/structure/closet/secure_closet/freezer/money/PopulateContents() + ..() + for(var/i = 0, i < 3, i++) + new /obj/item/stack/spacecash/c1000(src) + for(var/i = 0, i < 5, i++) + new /obj/item/stack/spacecash/c500(src) + for(var/i = 0, i < 6, i++) + new /obj/item/stack/spacecash/c200(src) + +/obj/structure/closet/secure_closet/freezer/cream_pie + name = "cream pie closet" + desc = "Contains pies filled with cream and/or custard, you sickos." + req_access = list(ACCESS_THEATRE) + +/obj/structure/closet/secure_closet/freezer/cream_pie/PopulateContents() + ..() + new /obj/item/reagent_containers/food/snacks/pie/cream(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm index ac246d5053ca..a9e62430603c 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 853d6a07b910..d82e5035afbd 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -1,111 +1,111 @@ -/obj/structure/closet/secure_closet/medical1 - name = "medicine closet" - desc = "Filled to the brim with medical junk." - icon_state = "med" - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/medical1/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/reagent_containers/glass/beaker = 2, - /obj/item/reagent_containers/dropper = 2, - /obj/item/storage/belt/medical = 1, - /obj/item/storage/box/syringes = 1, - /obj/item/reagent_containers/glass/bottle/toxin = 1, - /obj/item/reagent_containers/glass/bottle/morphine = 2, - /obj/item/reagent_containers/glass/bottle/epinephrine= 3, - /obj/item/reagent_containers/glass/bottle/charcoal = 3, - /obj/item/storage/box/rxglasses = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/secure_closet/medical2 - name = "anesthetic closet" - desc = "Used to knock people out." - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/medical2/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/tank/internals/anesthetic(src) - for(var/i in 1 to 3) - new /obj/item/clothing/mask/breath/medical(src) - -/obj/structure/closet/secure_closet/medical3 - name = "medical doctor's locker" - req_access = list(ACCESS_SURGERY) - icon_state = "med_secure" - -/obj/structure/closet/secure_closet/medical3/PopulateContents() - ..() - new /obj/item/radio/headset/headset_med(src) - new /obj/item/defibrillator/loaded(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/clothing/glasses/hud/health(src) - return - -/obj/structure/closet/secure_closet/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/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/reagent_containers/hypospray/CMO(src) - new /obj/item/autosurgeon/cmo(src) - new /obj/item/door_remote/chief_medical_officer(src) - new /obj/item/clothing/neck/petcollar(src) - new /obj/item/pet_carrier(src) - new /obj/item/wallframe/defib_mount(src) - new /obj/item/circuitboard/machine/techfab/department/medical(src) - new /obj/item/storage/photo_album/CMO(src) - -/obj/structure/closet/secure_closet/animal - name = "animal control" - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/animal/PopulateContents() - ..() - new /obj/item/assembly/signaler(src) - for(var/i in 1 to 3) - new /obj/item/electropack(src) - -/obj/structure/closet/secure_closet/chemical - name = "chemical closet" - desc = "Store dangerous chemicals in here." - req_access = list(ACCESS_CHEMISTRY) - icon_door = "chemical" - -/obj/structure/closet/secure_closet/chemical/PopulateContents() - ..() - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/medsprays(src) - new /obj/item/storage/box/medsprays(src) - -/obj/structure/closet/secure_closet/chemical/heisenberg //contains one of each beaker, syringe etc. - name = "advanced chemical closet" - -/obj/structure/closet/secure_closet/chemical/heisenberg/PopulateContents() - ..() - new /obj/item/reagent_containers/dropper(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/storage/box/syringes/variety(src) - new /obj/item/storage/box/beakers/variety(src) - new /obj/item/clothing/glasses/science(src) +/obj/structure/closet/secure_closet/medical1 + name = "medicine closet" + desc = "Filled to the brim with medical junk." + icon_state = "med" + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/medical1/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/reagent_containers/glass/beaker = 2, + /obj/item/reagent_containers/dropper = 2, + /obj/item/storage/belt/medical = 1, + /obj/item/storage/box/syringes = 1, + /obj/item/reagent_containers/glass/bottle/toxin = 1, + /obj/item/reagent_containers/glass/bottle/morphine = 2, + /obj/item/reagent_containers/glass/bottle/epinephrine= 3, + /obj/item/reagent_containers/glass/bottle/charcoal = 3, + /obj/item/storage/box/rxglasses = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/secure_closet/medical2 + name = "anesthetic closet" + desc = "Used to knock people out." + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/medical2/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/tank/internals/anesthetic(src) + for(var/i in 1 to 3) + new /obj/item/clothing/mask/breath/medical(src) + +/obj/structure/closet/secure_closet/medical3 + name = "medical doctor's locker" + req_access = list(ACCESS_SURGERY) + icon_state = "med_secure" + +/obj/structure/closet/secure_closet/medical3/PopulateContents() + ..() + new /obj/item/radio/headset/headset_med(src) + new /obj/item/defibrillator/loaded(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/clothing/glasses/hud/health(src) + return + +/obj/structure/closet/secure_closet/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/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/reagent_containers/hypospray/CMO(src) + new /obj/item/autosurgeon/cmo(src) + new /obj/item/door_remote/chief_medical_officer(src) + new /obj/item/clothing/neck/petcollar(src) + new /obj/item/pet_carrier(src) + new /obj/item/wallframe/defib_mount(src) + new /obj/item/circuitboard/machine/techfab/department/medical(src) + new /obj/item/storage/photo_album/CMO(src) + +/obj/structure/closet/secure_closet/animal + name = "animal control" + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/animal/PopulateContents() + ..() + new /obj/item/assembly/signaler(src) + for(var/i in 1 to 3) + new /obj/item/electropack(src) + +/obj/structure/closet/secure_closet/chemical + name = "chemical closet" + desc = "Store dangerous chemicals in here." + req_access = list(ACCESS_CHEMISTRY) + icon_door = "chemical" + +/obj/structure/closet/secure_closet/chemical/PopulateContents() + ..() + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/medsprays(src) + new /obj/item/storage/box/medsprays(src) + +/obj/structure/closet/secure_closet/chemical/heisenberg //contains one of each beaker, syringe etc. + name = "advanced chemical closet" + +/obj/structure/closet/secure_closet/chemical/heisenberg/PopulateContents() + ..() + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/storage/box/syringes/variety(src) + new /obj/item/storage/box/beakers/variety(src) + new /obj/item/clothing/glasses/science(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm index eb764fc23073..8a8f13a37451 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm @@ -1,53 +1,53 @@ -/obj/structure/closet/secure_closet/personal - desc = "It's a secure locker for personnel. The first card swiped gains control." - name = "personal closet" - req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) - var/registered_name = null - -/obj/structure/closet/secure_closet/personal/PopulateContents() - ..() - if(prob(50)) - new /obj/item/storage/backpack/duffelbag(src) - if(prob(50)) - new /obj/item/storage/backpack(src) - else - new /obj/item/storage/backpack/satchel(src) - new /obj/item/radio/headset( src ) - -/obj/structure/closet/secure_closet/personal/patient - name = "patient's closet" - -/obj/structure/closet/secure_closet/personal/patient/PopulateContents() - new /obj/item/clothing/under/color/white( src ) - new /obj/item/clothing/shoes/sneakers/white( src ) - -/obj/structure/closet/secure_closet/personal/cabinet - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - -/obj/structure/closet/secure_closet/personal/cabinet/PopulateContents() - new /obj/item/storage/backpack/satchel/leather/withwallet( src ) - new /obj/item/instrument/piano_synth(src) - new /obj/item/radio/headset( src ) - -/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params) - var/obj/item/card/id/I = W.GetID() - if(istype(I)) - if(broken) - to_chat(user, "It appears to be broken.") - return - if(!I || !I.registered_name) - return - if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name))) - //they can open all lockers, or nobody owns this, or they own this locker - locked = !locked - update_icon() - - if(!registered_name) - registered_name = I.registered_name - desc = "Owned by [I.registered_name]." - else - to_chat(user, "Access Denied.") - else - return ..() +/obj/structure/closet/secure_closet/personal + desc = "It's a secure locker for personnel. The first card swiped gains control." + name = "personal closet" + req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) + var/registered_name = null + +/obj/structure/closet/secure_closet/personal/PopulateContents() + ..() + if(prob(50)) + new /obj/item/storage/backpack/duffelbag(src) + if(prob(50)) + new /obj/item/storage/backpack(src) + else + new /obj/item/storage/backpack/satchel(src) + new /obj/item/radio/headset( src ) + +/obj/structure/closet/secure_closet/personal/patient + name = "patient's closet" + +/obj/structure/closet/secure_closet/personal/patient/PopulateContents() + new /obj/item/clothing/under/color/white( src ) + new /obj/item/clothing/shoes/sneakers/white( src ) + +/obj/structure/closet/secure_closet/personal/cabinet + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + +/obj/structure/closet/secure_closet/personal/cabinet/PopulateContents() + new /obj/item/storage/backpack/satchel/leather/withwallet( src ) + new /obj/item/instrument/piano_synth(src) + new /obj/item/radio/headset( src ) + +/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params) + var/obj/item/card/id/I = W.GetID() + if(istype(I)) + if(broken) + to_chat(user, "It appears to be broken.") + return + if(!I || !I.registered_name) + return + if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name))) + //they can open all lockers, or nobody owns this, or they own this locker + locked = !locked + update_icon() + + if(!registered_name) + registered_name = I.registered_name + desc = "Owned by [I.registered_name]." + else + to_chat(user, "Access Denied.") + else + return ..() diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm index 54226a39ee04..d583425db218 100755 --- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm @@ -1,31 +1,31 @@ -/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/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) +/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/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) 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 dd503d76f706..57520f6f407b 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 64a4608261f4..f2b48eb5ddb1 100755 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -1,303 +1,303 @@ -/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/neck/petcollar(src) - new /obj/item/pet_carrier(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/restraints/handcuffs/cable/zipties(src) - new /obj/item/storage/belt/sabre(src) - // new /obj/item/gun/energy/e_gun(src) - yogs - new /obj/item/door_remote/captain(src) - new /obj/item/card/id/captains_spare(src) - new /obj/item/storage/photo_album/Captain(src) - -/obj/structure/closet/secure_closet/hop - name = "\proper head of personnel's locker" - req_access = list(ACCESS_HOP) - icon_state = "hop" - -/obj/structure/closet/secure_closet/hop/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/hop(src) - new /obj/item/storage/lockbox/medal/service(src) - new /obj/item/clothing/under/rank/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/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/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) - -/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) - -/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) - -/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 - -/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/suit/det_suit/noir(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) - new /obj/item/twohanded/binoculars(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/neck/petcollar(src) + new /obj/item/pet_carrier(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/restraints/handcuffs/cable/zipties(src) + new /obj/item/storage/belt/sabre(src) + // new /obj/item/gun/energy/e_gun(src) - yogs + new /obj/item/door_remote/captain(src) + new /obj/item/card/id/captains_spare(src) + new /obj/item/storage/photo_album/Captain(src) + +/obj/structure/closet/secure_closet/hop + name = "\proper head of personnel's locker" + req_access = list(ACCESS_HOP) + icon_state = "hop" + +/obj/structure/closet/secure_closet/hop/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/hop(src) + new /obj/item/storage/lockbox/medal/service(src) + new /obj/item/clothing/under/rank/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/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/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) + +/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) + +/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) + +/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 + +/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/suit/det_suit/noir(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) + new /obj/item/twohanded/binoculars(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 f2d32b773ed1..a86492222b41 100644 --- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm +++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm @@ -1,119 +1,119 @@ -/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/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/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 7ee0f4dc24c4..6e6ca861fd35 100644 --- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm @@ -1,175 +1,175 @@ -/* Utility Closets - * Contains: - * Emergency Closet - * Fire Closet - * Tool Closet - * Radiation Closet - * Bombsuit Closet - * Hydrant - * First Aid - */ - -/* - * Emergency Closet - */ -/obj/structure/closet/emcloset - name = "emergency closet" - desc = "It's a storage unit for emergency breath masks and O2 tanks." - icon_state = "emergency" - -/obj/structure/closet/emcloset/anchored - anchored = TRUE - -/obj/structure/closet/emcloset/PopulateContents() - ..() - - if (prob(40)) - new /obj/item/storage/toolbox/emergency(src) - - switch (pickweight(list("small" = 40, "aid" = 25, "tank" = 20, "both" = 10, "nothing" = 4, "delete" = 1))) - if ("small") - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/clothing/mask/breath(src) - - if ("aid") - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/storage/firstaid/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 - - // teehee - if ("delete") - qdel(src) - -/* - * Fire Closet - */ -/obj/structure/closet/firecloset - name = "fire-safety closet" - desc = "It's a storage unit for fire-fighting supplies." - icon_state = "fire" - -/obj/structure/closet/firecloset/PopulateContents() - ..() - - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/tank/internals/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - -/obj/structure/closet/firecloset/full/PopulateContents() - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/flashlight(src) - new /obj/item/tank/internals/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - -/* - * Tool Closet - */ -/obj/structure/closet/toolcloset - name = "tool closet" - desc = "It's a storage unit for tools." - icon_state = "eng" - icon_door = "eng_tool" - -/obj/structure/closet/toolcloset/PopulateContents() - ..() - if(prob(40)) - new /obj/item/clothing/suit/hazardvest(src) - if(prob(70)) - new /obj/item/flashlight(src) - if(prob(70)) - new /obj/item/screwdriver(src) - if(prob(70)) - new /obj/item/wrench(src) - if(prob(70)) - new /obj/item/weldingtool(src) - if(prob(70)) - new /obj/item/crowbar(src) - if(prob(70)) - new /obj/item/wirecutters(src) - if(prob(70)) - new /obj/item/t_scanner(src) - if(prob(20)) - new /obj/item/storage/belt/utility(src) - if(prob(30)) - new /obj/item/stack/cable_coil/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() - ..() - new /obj/item/geiger_counter(src) - new /obj/item/clothing/suit/radiation(src) - new /obj/item/clothing/head/radiation(src) - -/* - * Bombsuit closet - */ -/obj/structure/closet/bombcloset - name = "\improper EOD closet" - desc = "It's a storage unit for explosion-protective suits." - icon_state = "bomb" - -/obj/structure/closet/bombcloset/PopulateContents() - ..() - new /obj/item/clothing/suit/bomb_suit(src) - new /obj/item/clothing/under/color/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/head/bomb_hood(src) - -/obj/structure/closet/bombcloset/security/PopulateContents() - new /obj/item/clothing/suit/bomb_suit/security(src) - new /obj/item/clothing/under/rank/security(src) - new /obj/item/clothing/shoes/jackboots(src) - new /obj/item/clothing/head/bomb_hood/security(src) - -/obj/structure/closet/bombcloset/white/PopulateContents() - new /obj/item/clothing/suit/bomb_suit/white(src) - new /obj/item/clothing/under/color/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/head/bomb_hood/white(src) - -/* - * Ammunition - */ -/obj/structure/closet/ammunitionlocker - name = "ammunition locker" - -/obj/structure/closet/ammunitionlocker/PopulateContents() - ..() - for(var/i in 1 to 8) - new /obj/item/ammo_casing/shotgun/beanbag(src) +/* Utility Closets + * Contains: + * Emergency Closet + * Fire Closet + * Tool Closet + * Radiation Closet + * Bombsuit Closet + * Hydrant + * First Aid + */ + +/* + * Emergency Closet + */ +/obj/structure/closet/emcloset + name = "emergency closet" + desc = "It's a storage unit for emergency breath masks and O2 tanks." + icon_state = "emergency" + +/obj/structure/closet/emcloset/anchored + anchored = TRUE + +/obj/structure/closet/emcloset/PopulateContents() + ..() + + if (prob(40)) + new /obj/item/storage/toolbox/emergency(src) + + switch (pickweight(list("small" = 40, "aid" = 25, "tank" = 20, "both" = 10, "nothing" = 4, "delete" = 1))) + if ("small") + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/clothing/mask/breath(src) + + if ("aid") + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/storage/firstaid/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 + + // teehee + if ("delete") + qdel(src) + +/* + * Fire Closet + */ +/obj/structure/closet/firecloset + name = "fire-safety closet" + desc = "It's a storage unit for fire-fighting supplies." + icon_state = "fire" + +/obj/structure/closet/firecloset/PopulateContents() + ..() + + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/tank/internals/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + +/obj/structure/closet/firecloset/full/PopulateContents() + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/flashlight(src) + new /obj/item/tank/internals/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + +/* + * Tool Closet + */ +/obj/structure/closet/toolcloset + name = "tool closet" + desc = "It's a storage unit for tools." + icon_state = "eng" + icon_door = "eng_tool" + +/obj/structure/closet/toolcloset/PopulateContents() + ..() + if(prob(40)) + new /obj/item/clothing/suit/hazardvest(src) + if(prob(70)) + new /obj/item/flashlight(src) + if(prob(70)) + new /obj/item/screwdriver(src) + if(prob(70)) + new /obj/item/wrench(src) + if(prob(70)) + new /obj/item/weldingtool(src) + if(prob(70)) + new /obj/item/crowbar(src) + if(prob(70)) + new /obj/item/wirecutters(src) + if(prob(70)) + new /obj/item/t_scanner(src) + if(prob(20)) + new /obj/item/storage/belt/utility(src) + if(prob(30)) + new /obj/item/stack/cable_coil/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() + ..() + new /obj/item/geiger_counter(src) + new /obj/item/clothing/suit/radiation(src) + new /obj/item/clothing/head/radiation(src) + +/* + * Bombsuit closet + */ +/obj/structure/closet/bombcloset + name = "\improper EOD closet" + desc = "It's a storage unit for explosion-protective suits." + icon_state = "bomb" + +/obj/structure/closet/bombcloset/PopulateContents() + ..() + new /obj/item/clothing/suit/bomb_suit(src) + new /obj/item/clothing/under/color/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/head/bomb_hood(src) + +/obj/structure/closet/bombcloset/security/PopulateContents() + new /obj/item/clothing/suit/bomb_suit/security(src) + new /obj/item/clothing/under/rank/security(src) + new /obj/item/clothing/shoes/jackboots(src) + new /obj/item/clothing/head/bomb_hood/security(src) + +/obj/structure/closet/bombcloset/white/PopulateContents() + new /obj/item/clothing/suit/bomb_suit/white(src) + new /obj/item/clothing/under/color/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/head/bomb_hood/white(src) + +/* + * Ammunition + */ +/obj/structure/closet/ammunitionlocker + name = "ammunition locker" + +/obj/structure/closet/ammunitionlocker/PopulateContents() + ..() + for(var/i in 1 to 8) + new /obj/item/ammo_casing/shotgun/beanbag(src) diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm index 897f4958ecac..f464695475ae 100644 --- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm +++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm @@ -1,204 +1,204 @@ -/obj/structure/closet/wardrobe - name = "wardrobe" - desc = "It's a storage unit for standard-issue Nanotrasen attire." - icon_door = "blue" - -/obj/structure/closet/wardrobe/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/blue(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/skirt/color/blue(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/brown(src) - return - -/obj/structure/closet/wardrobe/pink - name = "pink wardrobe" - icon_door = "pink" - -/obj/structure/closet/wardrobe/pink/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/pink(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/skirt/color/pink(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/brown(src) - return - -/obj/structure/closet/wardrobe/black - name = "black wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/black/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/skirt/color/black(src) - if(prob(25)) - new /obj/item/clothing/suit/jacket/leather(src) - if(prob(20)) - new /obj/item/clothing/suit/jacket/leather/overcoat(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/that(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/mask/bandana/black(src) - new /obj/item/clothing/mask/bandana/black(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - return - - -/obj/structure/closet/wardrobe/green - name = "green wardrobe" - icon_door = "green" - -/obj/structure/closet/wardrobe/green/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/green(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/skirt/color/green(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/mask/bandana/green(src) - new /obj/item/clothing/mask/bandana/green(src) - return - - -/obj/structure/closet/wardrobe/orange - name = "prison wardrobe" - desc = "It's a storage unit for Nanotrasen-regulation prisoner attire." - icon_door = "orange" - -/obj/structure/closet/wardrobe/orange/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/prisoner(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/prisoner/skirt(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/orange(src) - return - - -/obj/structure/closet/wardrobe/yellow - name = "yellow wardrobe" - icon_door = "yellow" - -/obj/structure/closet/wardrobe/yellow/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/yellow(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/skirt/color/yellow(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/orange(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/mask/bandana/gold(src) - return - - -/obj/structure/closet/wardrobe/white - name = "white wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/white/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/skirt/color/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/mime(src) - return - -/obj/structure/closet/wardrobe/pjs - name = "pajama wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/pjs/PopulateContents() - new /obj/item/clothing/under/pj/red(src) - new /obj/item/clothing/under/pj/red(src) - new /obj/item/clothing/under/pj/blue(src) - new /obj/item/clothing/under/pj/blue(src) - for(var/i in 1 to 4) - new /obj/item/clothing/shoes/sneakers/white(src) - return - - -/obj/structure/closet/wardrobe/grey - name = "grey wardrobe" - icon_door = "grey" - -/obj/structure/closet/wardrobe/grey/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/grey(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/skirt/color/grey(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/grey(src) - if(prob(50)) - new /obj/item/storage/backpack/duffelbag(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/black(src) - new /obj/item/clothing/mask/bandana/black(src) - if(prob(40)) - new /obj/item/clothing/under/assistantformal(src) - if(prob(40)) - new /obj/item/clothing/under/assistantformal(src) - if(prob(30)) - new /obj/item/clothing/suit/hooded/wintercoat(src) - new /obj/item/clothing/shoes/winterboots(src) - if(prob(30)) - new /obj/item/clothing/accessory/pocketprotector(src) - return - - -/obj/structure/closet/wardrobe/mixed - name = "mixed wardrobe" - icon_door = "mixed" - -/obj/structure/closet/wardrobe/mixed/PopulateContents() - if(prob(40)) - new /obj/item/clothing/suit/jacket(src) - if(prob(40)) - new /obj/item/clothing/suit/jacket(src) - new /obj/item/clothing/under/color/white(src) - new /obj/item/clothing/under/skirt/color/white(src) - new /obj/item/clothing/under/color/blue(src) - new /obj/item/clothing/under/skirt/color/blue(src) - new /obj/item/clothing/under/color/yellow(src) - new /obj/item/clothing/under/skirt/color/yellow(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/skirt/color/green(src) - new /obj/item/clothing/under/color/orange(src) - new /obj/item/clothing/under/skirt/color/orange(src) - new /obj/item/clothing/under/color/pink(src) - new /obj/item/clothing/under/skirt/color/pink(src) - new /obj/item/clothing/under/color/red(src) - new /obj/item/clothing/under/skirt/color/red(src) - new /obj/item/clothing/under/color/darkblue(src) - new /obj/item/clothing/under/skirt/color/darkblue(src) - new /obj/item/clothing/under/color/teal(src) - new /obj/item/clothing/under/skirt/color/teal(src) - new /obj/item/clothing/under/color/lightpurple(src) - new /obj/item/clothing/under/skirt/color/lightpurple(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/skirt/color/green(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/clothing/shoes/sneakers/white(src) - if(prob(30)) - new /obj/item/clothing/suit/hooded/wintercoat(src) - new /obj/item/clothing/shoes/winterboots(src) - return +/obj/structure/closet/wardrobe + name = "wardrobe" + desc = "It's a storage unit for standard-issue Nanotrasen attire." + icon_door = "blue" + +/obj/structure/closet/wardrobe/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/blue(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/skirt/color/blue(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/brown(src) + return + +/obj/structure/closet/wardrobe/pink + name = "pink wardrobe" + icon_door = "pink" + +/obj/structure/closet/wardrobe/pink/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/pink(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/skirt/color/pink(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/brown(src) + return + +/obj/structure/closet/wardrobe/black + name = "black wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/black/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/skirt/color/black(src) + if(prob(25)) + new /obj/item/clothing/suit/jacket/leather(src) + if(prob(20)) + new /obj/item/clothing/suit/jacket/leather/overcoat(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/that(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/mask/bandana/black(src) + new /obj/item/clothing/mask/bandana/black(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + return + + +/obj/structure/closet/wardrobe/green + name = "green wardrobe" + icon_door = "green" + +/obj/structure/closet/wardrobe/green/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/green(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/skirt/color/green(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/mask/bandana/green(src) + new /obj/item/clothing/mask/bandana/green(src) + return + + +/obj/structure/closet/wardrobe/orange + name = "prison wardrobe" + desc = "It's a storage unit for Nanotrasen-regulation prisoner attire." + icon_door = "orange" + +/obj/structure/closet/wardrobe/orange/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/prisoner(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/prisoner/skirt(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/orange(src) + return + + +/obj/structure/closet/wardrobe/yellow + name = "yellow wardrobe" + icon_door = "yellow" + +/obj/structure/closet/wardrobe/yellow/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/yellow(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/skirt/color/yellow(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/orange(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/mask/bandana/gold(src) + return + + +/obj/structure/closet/wardrobe/white + name = "white wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/white/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/skirt/color/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/mime(src) + return + +/obj/structure/closet/wardrobe/pjs + name = "pajama wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/pjs/PopulateContents() + new /obj/item/clothing/under/pj/red(src) + new /obj/item/clothing/under/pj/red(src) + new /obj/item/clothing/under/pj/blue(src) + new /obj/item/clothing/under/pj/blue(src) + for(var/i in 1 to 4) + new /obj/item/clothing/shoes/sneakers/white(src) + return + + +/obj/structure/closet/wardrobe/grey + name = "grey wardrobe" + icon_door = "grey" + +/obj/structure/closet/wardrobe/grey/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/grey(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/skirt/color/grey(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/grey(src) + if(prob(50)) + new /obj/item/storage/backpack/duffelbag(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/black(src) + new /obj/item/clothing/mask/bandana/black(src) + if(prob(40)) + new /obj/item/clothing/under/assistantformal(src) + if(prob(40)) + new /obj/item/clothing/under/assistantformal(src) + if(prob(30)) + new /obj/item/clothing/suit/hooded/wintercoat(src) + new /obj/item/clothing/shoes/winterboots(src) + if(prob(30)) + new /obj/item/clothing/accessory/pocketprotector(src) + return + + +/obj/structure/closet/wardrobe/mixed + name = "mixed wardrobe" + icon_door = "mixed" + +/obj/structure/closet/wardrobe/mixed/PopulateContents() + if(prob(40)) + new /obj/item/clothing/suit/jacket(src) + if(prob(40)) + new /obj/item/clothing/suit/jacket(src) + new /obj/item/clothing/under/color/white(src) + new /obj/item/clothing/under/skirt/color/white(src) + new /obj/item/clothing/under/color/blue(src) + new /obj/item/clothing/under/skirt/color/blue(src) + new /obj/item/clothing/under/color/yellow(src) + new /obj/item/clothing/under/skirt/color/yellow(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/skirt/color/green(src) + new /obj/item/clothing/under/color/orange(src) + new /obj/item/clothing/under/skirt/color/orange(src) + new /obj/item/clothing/under/color/pink(src) + new /obj/item/clothing/under/skirt/color/pink(src) + new /obj/item/clothing/under/color/red(src) + new /obj/item/clothing/under/skirt/color/red(src) + new /obj/item/clothing/under/color/darkblue(src) + new /obj/item/clothing/under/skirt/color/darkblue(src) + new /obj/item/clothing/under/color/teal(src) + new /obj/item/clothing/under/skirt/color/teal(src) + new /obj/item/clothing/under/color/lightpurple(src) + new /obj/item/clothing/under/skirt/color/lightpurple(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/skirt/color/green(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/clothing/shoes/sneakers/white(src) + if(prob(30)) + new /obj/item/clothing/suit/hooded/wintercoat(src) + new /obj/item/clothing/shoes/winterboots(src) + return diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index 5cc3aeacead4..0d5abe0ebb53 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -1,211 +1,211 @@ -/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/Initialize() - . = ..() - 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/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/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." - -/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) - new /obj/item/reagent_containers/blood/gorilla(src) // yogs -- gorilla people - new /obj/item/reagent_containers/blood/ethereal(src) - for(var/i in 1 to 3) - new /obj/item/reagent_containers/blood/random(src) - -/obj/structure/closet/crate/freezer/surplus_limbs - name = "surplus prosthetic limbs" - desc = "A crate containing an assortment of cheap prosthetic limbs." - -/obj/structure/closet/crate/freezer/surplus_limbs/PopulateContents() - . = ..() - new /obj/item/bodypart/l_arm/robot/surplus(src) - new /obj/item/bodypart/l_arm/robot/surplus(src) - new /obj/item/bodypart/r_arm/robot/surplus(src) - new /obj/item/bodypart/r_arm/robot/surplus(src) - new /obj/item/bodypart/l_leg/robot/surplus(src) - new /obj/item/bodypart/l_leg/robot/surplus(src) - new /obj/item/bodypart/r_leg/robot/surplus(src) - new /obj/item/bodypart/r_leg/robot/surplus(src) - -/obj/structure/closet/crate/radiation - desc = "A crate with a radiation sign on it." - name = "radiation crate" - icon_state = "radiation" - -/obj/structure/closet/crate/hydroponics - name = "hydroponics crate" - desc = "All you need to destroy those pesky weeds and pests." - icon_state = "hydrocrate" - -/obj/structure/closet/crate/engineering - name = "engineering crate" - icon_state = "engi_crate" - -/obj/structure/closet/crate/engineering/electrical - icon_state = "engi_e_crate" - -/obj/structure/closet/crate/rcd - desc = "A crate for the storage of an RCD." - name = "\improper RCD crate" - icon_state = "engi_crate" - -/obj/structure/closet/crate/rcd/PopulateContents() - ..() - for(var/i in 1 to 4) - new /obj/item/rcd_ammo(src) - new /obj/item/construction/rcd(src) - -/obj/structure/closet/crate/science - name = "science crate" - desc = "A science crate." - icon_state = "scicrate" - -/obj/structure/closet/crate/solarpanel_small - name = "budget solar panel crate" - icon_state = "engi_e_crate" - -/obj/structure/closet/crate/solarpanel_small/PopulateContents() - ..() - for(var/i in 1 to 13) - new /obj/item/solar_assembly(src) - new /obj/item/circuitboard/computer/solar_control(src) - new /obj/item/paper/guides/jobs/engi/solars(src) - new /obj/item/electronics/tracker(src) - -/obj/structure/closet/crate/goldcrate - name = "gold crate" - -/obj/structure/closet/crate/goldcrate/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/stack/sheet/mineral/gold(src, 1, FALSE) - new /obj/item/storage/belt/champion(src) - -/obj/structure/closet/crate/silvercrate - name = "silver crate" - -/obj/structure/closet/crate/silvercrate/PopulateContents() - ..() - for(var/i in 1 to 5) - new /obj/item/coin/silver(src) +/obj/structure/closet/crate + name = "crate" + desc = "A rectangular steel crate." + icon = 'icons/obj/crates.dmi' + icon_state = "crate" + req_access = null + can_weld_shut = FALSE + horizontal = TRUE + allow_objects = TRUE + allow_dense = TRUE + dense_when_open = TRUE + climbable = TRUE + climb_time = 10 //real fast, because let's be honest stepping into or onto a crate is easy + climb_stun = 0 //climbing onto crates isn't hard, guys + delivery_icon = "deliverycrate" + var/obj/item/paper/fluff/jobs/cargo/manifest/manifest + +/obj/structure/closet/crate/Initialize() + . = ..() + if(icon_state == "[initial(icon_state)]open") + opened = TRUE + update_icon() + +/obj/structure/closet/crate/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/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/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." + +/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) + new /obj/item/reagent_containers/blood/gorilla(src) // yogs -- gorilla people + new /obj/item/reagent_containers/blood/ethereal(src) + for(var/i in 1 to 3) + new /obj/item/reagent_containers/blood/random(src) + +/obj/structure/closet/crate/freezer/surplus_limbs + name = "surplus prosthetic limbs" + desc = "A crate containing an assortment of cheap prosthetic limbs." + +/obj/structure/closet/crate/freezer/surplus_limbs/PopulateContents() + . = ..() + new /obj/item/bodypart/l_arm/robot/surplus(src) + new /obj/item/bodypart/l_arm/robot/surplus(src) + new /obj/item/bodypart/r_arm/robot/surplus(src) + new /obj/item/bodypart/r_arm/robot/surplus(src) + new /obj/item/bodypart/l_leg/robot/surplus(src) + new /obj/item/bodypart/l_leg/robot/surplus(src) + new /obj/item/bodypart/r_leg/robot/surplus(src) + new /obj/item/bodypart/r_leg/robot/surplus(src) + +/obj/structure/closet/crate/radiation + desc = "A crate with a radiation sign on it." + name = "radiation crate" + icon_state = "radiation" + +/obj/structure/closet/crate/hydroponics + name = "hydroponics crate" + desc = "All you need to destroy those pesky weeds and pests." + icon_state = "hydrocrate" + +/obj/structure/closet/crate/engineering + name = "engineering crate" + icon_state = "engi_crate" + +/obj/structure/closet/crate/engineering/electrical + icon_state = "engi_e_crate" + +/obj/structure/closet/crate/rcd + desc = "A crate for the storage of an RCD." + name = "\improper RCD crate" + icon_state = "engi_crate" + +/obj/structure/closet/crate/rcd/PopulateContents() + ..() + for(var/i in 1 to 4) + new /obj/item/rcd_ammo(src) + new /obj/item/construction/rcd(src) + +/obj/structure/closet/crate/science + name = "science crate" + desc = "A science crate." + icon_state = "scicrate" + +/obj/structure/closet/crate/solarpanel_small + name = "budget solar panel crate" + icon_state = "engi_e_crate" + +/obj/structure/closet/crate/solarpanel_small/PopulateContents() + ..() + for(var/i in 1 to 13) + new /obj/item/solar_assembly(src) + new /obj/item/circuitboard/computer/solar_control(src) + new /obj/item/paper/guides/jobs/engi/solars(src) + new /obj/item/electronics/tracker(src) + +/obj/structure/closet/crate/goldcrate + name = "gold crate" + +/obj/structure/closet/crate/goldcrate/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/stack/sheet/mineral/gold(src, 1, FALSE) + new /obj/item/storage/belt/champion(src) + +/obj/structure/closet/crate/silvercrate + name = "silver crate" + +/obj/structure/closet/crate/silvercrate/PopulateContents() + ..() + for(var/i in 1 to 5) + new /obj/item/coin/silver(src) diff --git a/code/game/objects/structures/crates_lockers/crates/bins.dm b/code/game/objects/structures/crates_lockers/crates/bins.dm index 48e8d48cb11e..b0044a284a57 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' - anchored = TRUE - horizontal = FALSE - delivery_icon = null - -/obj/structure/closet/crate/bin/Initialize() - . = ..() - 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() - else if(istype(W, /obj/item/wrench)) - anchored = !anchored - W.play_tool_sound(src, 75) - 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' + anchored = TRUE + horizontal = FALSE + delivery_icon = null + +/obj/structure/closet/crate/bin/Initialize() + . = ..() + 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() + else if(istype(W, /obj/item/wrench)) + anchored = !anchored + W.play_tool_sound(src, 75) + 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 b94da08322b4..e122075e5ea7 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/Initialize() - . = ..() - tank = new - -/obj/structure/closet/crate/critter/Destroy() - var/turf/T = get_turf(src) - if(tank) - tank.forceMove(T) - tank = null - - return ..() - -/obj/structure/closet/crate/critter/update_icon() - 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/Initialize() + . = ..() + tank = new + +/obj/structure/closet/crate/critter/Destroy() + var/turf/T = get_turf(src) + if(tank) + tank.forceMove(T) + tank = null + + return ..() + +/obj/structure/closet/crate/critter/update_icon() + 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 cee5e477fe0f..28cbd39e7ec9 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(W.tool_behaviour == TOOL_CROWBAR) - if(manifest) - tear_manifest(user) - - user.visible_message("[user] pries \the [src] open.", \ - "You pry open \the [src].", \ - "You hear splitting wood.") - playsound(src.loc, 'sound/weapons/slashmiss.ogg', 75, 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(W.tool_behaviour == TOOL_CROWBAR) + if(manifest) + tear_manifest(user) + + user.visible_message("[user] pries \the [src] open.", \ + "You pry open \the [src].", \ + "You hear splitting wood.") + playsound(src.loc, 'sound/weapons/slashmiss.ogg', 75, 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 725e4b2e5e93..e5356b613ee3 100644 --- a/code/game/objects/structures/crates_lockers/crates/secure.dm +++ b/code/game/objects/structures/crates_lockers/crates/secure.dm @@ -1,108 +1,108 @@ -/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 - return ..() - - -/obj/structure/closet/crate/secure/proc/boom(mob/user) - if(user) - to_chat(user, "The crate's anti-tamper system activates!") - log_bomber(user, "has detonated a", src) - for(var/atom/movable/AM in src) - qdel(AM) - explosion(get_turf(src), 0, 1, 5, 5) - qdel(src) - -/obj/structure/closet/crate/secure/weapon - desc = "A secure weapons crate." - name = "weapons crate" - icon_state = "weaponcrate" - -/obj/structure/closet/crate/secure/plasma - desc = "A secure plasma crate." - name = "plasma crate" - icon_state = "plasmacrate" - -/obj/structure/closet/crate/secure/gear - desc = "A secure gear crate." - name = "gear crate" - icon_state = "secgearcrate" - -/obj/structure/closet/crate/secure/hydroponics - desc = "A crate with a lock on it, painted in the scheme of the station's botanists." - name = "secure hydroponics crate" - icon_state = "hydrosecurecrate" - -/obj/structure/closet/crate/secure/engineering - desc = "A crate with a lock on it, painted in the scheme of the station's engineers." - name = "secure engineering crate" - icon_state = "engi_secure_crate" - -/obj/structure/closet/crate/secure/science - name = "secure science crate" - desc = "A crate with a lock on it, painted in the scheme of the station's scientists." - icon_state = "scisecurecrate" - -/obj/structure/closet/crate/secure/owned - name = "private crate" - desc = "A crate cover designed to only open for who purchased its contents." - icon_state = "privatecrate" - var/datum/bank_account/buyer_account - var/privacy_lock = TRUE - -/obj/structure/closet/crate/secure/owned/examine(mob/user) - . = ..() - . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID." - -/obj/structure/closet/crate/secure/owned/Initialize(mapload, datum/bank_account/_buyer_account) - . = ..() - buyer_account = _buyer_account - -/obj/structure/closet/crate/secure/owned/togglelock(mob/living/user, silent) - if(privacy_lock) - if(!broken) - var/obj/item/card/id/id_card = user.get_idcard(TRUE) - if(id_card) - if(id_card.registered_account) - if(id_card.registered_account == buyer_account) - if(iscarbon(user)) - add_fingerprint(user) - locked = !locked - user.visible_message("[user] unlocks [src]'s privacy lock.", - "You unlock [src]'s privacy lock.") - privacy_lock = FALSE - update_icon() - else if(!silent) - to_chat(user, "Bank account does not match with buyer!") - else if(!silent) - to_chat(user, "No linked bank account detected!") - else if(!silent) - to_chat(user, "No ID detected!") - else if(!silent) - to_chat(user, "[src] is broken!") - else ..() +/obj/structure/closet/crate/secure + desc = "A secure crate." + name = "secure crate" + icon_state = "securecrate" + secure = TRUE + locked = TRUE + max_integrity = 500 + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + var/tamperproof = 0 + +/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 + return ..() + + +/obj/structure/closet/crate/secure/proc/boom(mob/user) + if(user) + to_chat(user, "The crate's anti-tamper system activates!") + log_bomber(user, "has detonated a", src) + for(var/atom/movable/AM in src) + qdel(AM) + explosion(get_turf(src), 0, 1, 5, 5) + qdel(src) + +/obj/structure/closet/crate/secure/weapon + desc = "A secure weapons crate." + name = "weapons crate" + icon_state = "weaponcrate" + +/obj/structure/closet/crate/secure/plasma + desc = "A secure plasma crate." + name = "plasma crate" + icon_state = "plasmacrate" + +/obj/structure/closet/crate/secure/gear + desc = "A secure gear crate." + name = "gear crate" + icon_state = "secgearcrate" + +/obj/structure/closet/crate/secure/hydroponics + desc = "A crate with a lock on it, painted in the scheme of the station's botanists." + name = "secure hydroponics crate" + icon_state = "hydrosecurecrate" + +/obj/structure/closet/crate/secure/engineering + desc = "A crate with a lock on it, painted in the scheme of the station's engineers." + name = "secure engineering crate" + icon_state = "engi_secure_crate" + +/obj/structure/closet/crate/secure/science + name = "secure science crate" + desc = "A crate with a lock on it, painted in the scheme of the station's scientists." + icon_state = "scisecurecrate" + +/obj/structure/closet/crate/secure/owned + name = "private crate" + desc = "A crate cover designed to only open for who purchased its contents." + icon_state = "privatecrate" + var/datum/bank_account/buyer_account + var/privacy_lock = TRUE + +/obj/structure/closet/crate/secure/owned/examine(mob/user) + . = ..() + . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID." + +/obj/structure/closet/crate/secure/owned/Initialize(mapload, datum/bank_account/_buyer_account) + . = ..() + buyer_account = _buyer_account + +/obj/structure/closet/crate/secure/owned/togglelock(mob/living/user, silent) + if(privacy_lock) + if(!broken) + var/obj/item/card/id/id_card = user.get_idcard(TRUE) + if(id_card) + if(id_card.registered_account) + if(id_card.registered_account == buyer_account) + if(iscarbon(user)) + add_fingerprint(user) + locked = !locked + user.visible_message("[user] unlocks [src]'s privacy lock.", + "You unlock [src]'s privacy lock.") + privacy_lock = FALSE + update_icon() + else if(!silent) + to_chat(user, "Bank account does not match with buyer!") + else if(!silent) + to_chat(user, "No linked bank account detected!") + else if(!silent) + to_chat(user, "No ID detected!") + else if(!silent) + to_chat(user, "[src] is broken!") + else ..() diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index fcdc1df4f219..5260238eade4 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -1,346 +1,346 @@ -/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:\n [trophy_message]" - - -/obj/structure/displaycase/proc/dump() - if (showpiece) - showpiece.forceMove(loc) - showpiece = null - -/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 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(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP && !broken) - if(obj_integrity < max_integrity) - if(!W.tool_start_check(user, amount=5)) - return - - to_chat(user, "You begin repairing [src].") - if(W.use_tool(src, user, 40, amount=5, volume=50)) - obj_integrity = max_integrity - update_icon() - to_chat(user, "You repair [src].") - else - to_chat(user, "[src] is already in good condition!") - return - else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases - if(broken) - if(showpiece) - to_chat(user, "Remove the displayed object first.") - else - to_chat(user, "You remove the destroyed case") - qdel(src) - else - to_chat(user, "You start to [open ? "close":"open"] [src].") - if(W.use_tool(src, user, 20)) - to_chat(user, "You [open ? "close":"open"] [src].") - toggle_lock(user) - else if(open && !showpiece) - if(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(I.tool_behaviour == TOOL_WRENCH) //The player can only deconstruct the wooden frame - to_chat(user, "You start disassembling [src]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 30)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 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/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)) - if(isnotpretty(chosen_plaque)) // Yogs -- Adds the pretty filter to plaques - to_chat(user, "That's not a terribly good plaque. See rule 0.1.1.") - message_admins("[key_name(user)] just tripped a pretty filter while enscribing a plaque: '[chosen_plaque]'.") - else// Yogs end I guess - 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:\n [trophy_message]" + + +/obj/structure/displaycase/proc/dump() + if (showpiece) + showpiece.forceMove(loc) + showpiece = null + +/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 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(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP && !broken) + if(obj_integrity < max_integrity) + if(!W.tool_start_check(user, amount=5)) + return + + to_chat(user, "You begin repairing [src].") + if(W.use_tool(src, user, 40, amount=5, volume=50)) + obj_integrity = max_integrity + update_icon() + to_chat(user, "You repair [src].") + else + to_chat(user, "[src] is already in good condition!") + return + else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases + if(broken) + if(showpiece) + to_chat(user, "Remove the displayed object first.") + else + to_chat(user, "You remove the destroyed case") + qdel(src) + else + to_chat(user, "You start to [open ? "close":"open"] [src].") + if(W.use_tool(src, user, 20)) + to_chat(user, "You [open ? "close":"open"] [src].") + toggle_lock(user) + else if(open && !showpiece) + if(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(I.tool_behaviour == TOOL_WRENCH) //The player can only deconstruct the wooden frame + to_chat(user, "You start disassembling [src]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 30)) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 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/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)) + if(isnotpretty(chosen_plaque)) // Yogs -- Adds the pretty filter to plaques + to_chat(user, "That's not a terribly good plaque. See rule 0.1.1.") + message_admins("[key_name(user)] just tripped a pretty filter while enscribing a plaque: '[chosen_plaque]'.") + else// Yogs end I guess + 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 890efe66fc0b..7df5c724d154 100644 --- a/code/game/objects/structures/dresser.dm +++ b/code/game/objects/structures/dresser.dm @@ -1,55 +1,55 @@ -/obj/structure/dresser - name = "dresser" - desc = "A nicely-crafted wooden dresser. It's filled with lots of undies." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "dresser" - density = TRUE - anchored = TRUE - -/obj/structure/dresser/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") - setAnchored(!anchored) - else - return ..() - -/obj/structure/dresser/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood(drop_location(), 10) - qdel(src) - -/obj/structure/dresser/attack_hand(mob/user) - . = ..() - if(.) - return - if(!Adjacent(user))//no tele-grooming - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - - if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits)) - to_chat(user, "You are not capable of wearing underwear.") - return - - var/choice = input(user, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in list("Underwear","Undershirt","Socks") - - if(!Adjacent(user)) - return - switch(choice) - if("Underwear") - var/new_undies = input(user, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list - if(new_undies) - H.underwear = new_undies - - if("Undershirt") - var/new_undershirt = input(user, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list - if(new_undershirt) - H.undershirt = new_undershirt - if("Socks") - var/new_socks = input(user, "Select your socks", "Changing") as null|anything in GLOB.socks_list - if(new_socks) - H.socks= new_socks - - add_fingerprint(H) - H.update_body() +/obj/structure/dresser + name = "dresser" + desc = "A nicely-crafted wooden dresser. It's filled with lots of undies." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "dresser" + density = TRUE + anchored = TRUE + +/obj/structure/dresser/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") + setAnchored(!anchored) + else + return ..() + +/obj/structure/dresser/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood(drop_location(), 10) + qdel(src) + +/obj/structure/dresser/attack_hand(mob/user) + . = ..() + if(.) + return + if(!Adjacent(user))//no tele-grooming + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + + if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits)) + to_chat(user, "You are not capable of wearing underwear.") + return + + var/choice = input(user, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in list("Underwear","Undershirt","Socks") + + if(!Adjacent(user)) + return + switch(choice) + if("Underwear") + var/new_undies = input(user, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list + if(new_undies) + H.underwear = new_undies + + if("Undershirt") + var/new_undershirt = input(user, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list + if(new_undershirt) + H.undershirt = new_undershirt + if("Socks") + var/new_socks = input(user, "Select your socks", "Changing") as null|anything in GLOB.socks_list + if(new_socks) + H.socks= new_socks + + add_fingerprint(H) + H.update_body() diff --git a/code/game/objects/structures/electricchair.dm b/code/game/objects/structures/electricchair.dm index 1fcafb9a3859..271d3c943f70 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/Initialize() - . = ..() - add_overlay(mutable_appearance('icons/obj/chairs.dmi', "echair_over", MOB_LAYER + 1)) - -/obj/structure/chair/e_chair/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WRENCH) - var/obj/structure/chair/C = new /obj/structure/chair(loc) - W.play_tool_sound(src) - C.setDir(dir) - part.forceMove(loc) - part.master = null - part = null - qdel(src) - -/obj/structure/chair/e_chair/proc/shock() - if(last_time + 50 > world.time) - return - last_time = world.time - - // special power handling - var/area/A = get_area(src) - if(!isarea(A)) - return - if(!A.powered(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/Initialize() + . = ..() + add_overlay(mutable_appearance('icons/obj/chairs.dmi', "echair_over", MOB_LAYER + 1)) + +/obj/structure/chair/e_chair/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WRENCH) + var/obj/structure/chair/C = new /obj/structure/chair(loc) + W.play_tool_sound(src) + C.setDir(dir) + part.forceMove(loc) + part.master = null + part = null + qdel(src) + +/obj/structure/chair/e_chair/proc/shock() + if(last_time + 50 > world.time) + return + last_time = world.time + + // special power handling + var/area/A = get_area(src) + if(!isarea(A)) + return + if(!A.powered(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 762a4b108378..c2de86e7b8f2 100644 --- a/code/game/objects/structures/extinguisher.dm +++ b/code/game/objects/structures/extinguisher.dm @@ -1,154 +1,154 @@ -/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(I.tool_behaviour == TOOL_WRENCH && !stored_extinguisher) - to_chat(user, "You start unsecuring [name]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 60)) - playsound(loc, 'sound/items/deconstruct.ogg', 50, 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) - -/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(I.tool_behaviour == TOOL_WRENCH && !stored_extinguisher) + to_chat(user, "You start unsecuring [name]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 60)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, 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) + +/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 8201fd6948de..fdee37a35ff2 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -1,440 +1,440 @@ -/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.is_sharp() && 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 //yogs - christmas presents shouldn't be pulse rifles. - var/unlimited = FALSE - var/static/list/took_presents //shared between all xmas trees - -/obj/structure/flora/tree/pine/xmas/presents/Initialize() - . = ..() - if(!took_presents) - took_presents = list() - -/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!user.ckey) - return - - if(took_presents[user.ckey] && !unlimited) - to_chat(user, "There are no presents with your name on.") - return - to_chat(user, "After a bit of rummaging, you locate a gift with your name on it!") - - if(!unlimited) - took_presents[user.ckey] = TRUE - - var/obj/item/G = new gift_type(src) - user.put_in_hands(G) - -/obj/structure/flora/tree/pine/xmas/presents/unlimited - desc = "A wonderous decorated Christmas tree. It has a seemly endless supply of presents!" - unlimited = TRUE - -/obj/structure/flora/tree/dead - icon = 'icons/obj/flora/deadtrees.dmi' - desc = "A dead tree. How it died, you know not." - icon_state = "tree_1" - -/obj/structure/flora/tree/palm - icon = 'icons/misc/beach2.dmi' - desc = "A tree straight from the tropics." - icon_state = "palm1" - -/obj/structure/flora/tree/palm/Initialize() - . = ..() - icon_state = pick("palm1","palm2") - pixel_x = 0 - -/obj/structure/festivus - name = "festivus pole" - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "festivus_pole" - desc = "During last year's Feats of Strength the Research Director was able to suplex this passing immobile rod into a planter." - -/obj/structure/festivus/anchored - name = "suplexed rod" - desc = "A true feat of strength, almost as good as last year." - icon_state = "anchored_rod" - anchored = TRUE - -/obj/structure/flora/tree/dead/Initialize() - icon_state = "tree_[rand(1, 6)]" - . = ..() - -/obj/structure/flora/tree/jungle - name = "tree" - icon_state = "tree" - desc = "It's seriously hampering your view of the jungle." - icon = 'icons/obj/flora/jungletrees.dmi' - pixel_x = -48 - pixel_y = -20 - -/obj/structure/flora/tree/jungle/Initialize() - icon_state = "[icon_state][rand(1, 6)]" - . = ..() - -/obj/structure/flora/tree/jungle/small - pixel_y = 0 - pixel_x = -32 - icon = 'icons/obj/flora/jungletreesmall.dmi' - -//grass -/obj/structure/flora/grass - name = "grass" - desc = "A patch of overgrown grass." - icon = 'icons/obj/flora/snowflora.dmi' - gender = PLURAL //"this is grass" not "this is a grass" - -/obj/structure/flora/grass/brown - icon_state = "snowgrass1bb" - -/obj/structure/flora/grass/brown/Initialize() - icon_state = "snowgrass[rand(1, 3)]bb" - . = ..() - - -/obj/structure/flora/grass/green - icon_state = "snowgrass1gb" - -/obj/structure/flora/grass/green/Initialize() - icon_state = "snowgrass[rand(1, 3)]gb" - . = ..() - -/obj/structure/flora/grass/both - icon_state = "snowgrassall1" - -/obj/structure/flora/grass/both/Initialize() - icon_state = "snowgrassall[rand(1, 3)]" - . = ..() - - -//bushes -/obj/structure/flora/bush - name = "bush" - desc = "Some type of shrub." - icon = 'icons/obj/flora/snowflora.dmi' - icon_state = "snowbush1" - anchored = TRUE - -/obj/structure/flora/bush/Initialize() - icon_state = "snowbush[rand(1, 6)]" - . = ..() - -//newbushes - -/obj/structure/flora/ausbushes - name = "bush" - desc = "Some kind of plant." - icon = 'icons/obj/flora/ausflora.dmi' - icon_state = "firstbush_1" - -/obj/structure/flora/ausbushes/Initialize() - if(icon_state == "firstbush_1") - icon_state = "firstbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/reedbush - icon_state = "reedbush_1" - -/obj/structure/flora/ausbushes/reedbush/Initialize() - icon_state = "reedbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/leafybush - icon_state = "leafybush_1" - -/obj/structure/flora/ausbushes/leafybush/Initialize() - icon_state = "leafybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/palebush - icon_state = "palebush_1" - -/obj/structure/flora/ausbushes/palebush/Initialize() - icon_state = "palebush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/stalkybush - icon_state = "stalkybush_1" - -/obj/structure/flora/ausbushes/stalkybush/Initialize() - icon_state = "stalkybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/grassybush - icon_state = "grassybush_1" - -/obj/structure/flora/ausbushes/grassybush/Initialize() - icon_state = "grassybush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/fernybush - icon_state = "fernybush_1" - -/obj/structure/flora/ausbushes/fernybush/Initialize() - icon_state = "fernybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/sunnybush - icon_state = "sunnybush_1" - -/obj/structure/flora/ausbushes/sunnybush/Initialize() - icon_state = "sunnybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/genericbush - icon_state = "genericbush_1" - -/obj/structure/flora/ausbushes/genericbush/Initialize() - icon_state = "genericbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/pointybush - icon_state = "pointybush_1" - -/obj/structure/flora/ausbushes/pointybush/Initialize() - icon_state = "pointybush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/lavendergrass - icon_state = "lavendergrass_1" - -/obj/structure/flora/ausbushes/lavendergrass/Initialize() - icon_state = "lavendergrass_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/ywflowers - icon_state = "ywflowers_1" - -/obj/structure/flora/ausbushes/ywflowers/Initialize() - icon_state = "ywflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/brflowers - icon_state = "brflowers_1" - -/obj/structure/flora/ausbushes/brflowers/Initialize() - icon_state = "brflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/ppflowers - icon_state = "ppflowers_1" - -/obj/structure/flora/ausbushes/ppflowers/Initialize() - icon_state = "ppflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/sparsegrass - icon_state = "sparsegrass_1" - -/obj/structure/flora/ausbushes/sparsegrass/Initialize() - icon_state = "sparsegrass_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/fullgrass - icon_state = "fullgrass_1" - -/obj/structure/flora/ausbushes/fullgrass/Initialize() - icon_state = "fullgrass_[rand(1, 3)]" - . = ..() - -/obj/item/twohanded/required/kirbyplants - name = "potted plant" - icon = 'yogstation/icons/obj/flora/plants.dmi' //yogs changed path - 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 = 'yogstation/icons/obj/flora/plants.dmi' //yogs changed icon path - 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 34) //yogs changed 25 plants to 34 - 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.is_sharp() && 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 //yogs - christmas presents shouldn't be pulse rifles. + var/unlimited = FALSE + var/static/list/took_presents //shared between all xmas trees + +/obj/structure/flora/tree/pine/xmas/presents/Initialize() + . = ..() + if(!took_presents) + took_presents = list() + +/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!user.ckey) + return + + if(took_presents[user.ckey] && !unlimited) + to_chat(user, "There are no presents with your name on.") + return + to_chat(user, "After a bit of rummaging, you locate a gift with your name on it!") + + if(!unlimited) + took_presents[user.ckey] = TRUE + + var/obj/item/G = new gift_type(src) + user.put_in_hands(G) + +/obj/structure/flora/tree/pine/xmas/presents/unlimited + desc = "A wonderous decorated Christmas tree. It has a seemly endless supply of presents!" + unlimited = TRUE + +/obj/structure/flora/tree/dead + icon = 'icons/obj/flora/deadtrees.dmi' + desc = "A dead tree. How it died, you know not." + icon_state = "tree_1" + +/obj/structure/flora/tree/palm + icon = 'icons/misc/beach2.dmi' + desc = "A tree straight from the tropics." + icon_state = "palm1" + +/obj/structure/flora/tree/palm/Initialize() + . = ..() + icon_state = pick("palm1","palm2") + pixel_x = 0 + +/obj/structure/festivus + name = "festivus pole" + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "festivus_pole" + desc = "During last year's Feats of Strength the Research Director was able to suplex this passing immobile rod into a planter." + +/obj/structure/festivus/anchored + name = "suplexed rod" + desc = "A true feat of strength, almost as good as last year." + icon_state = "anchored_rod" + anchored = TRUE + +/obj/structure/flora/tree/dead/Initialize() + icon_state = "tree_[rand(1, 6)]" + . = ..() + +/obj/structure/flora/tree/jungle + name = "tree" + icon_state = "tree" + desc = "It's seriously hampering your view of the jungle." + icon = 'icons/obj/flora/jungletrees.dmi' + pixel_x = -48 + pixel_y = -20 + +/obj/structure/flora/tree/jungle/Initialize() + icon_state = "[icon_state][rand(1, 6)]" + . = ..() + +/obj/structure/flora/tree/jungle/small + pixel_y = 0 + pixel_x = -32 + icon = 'icons/obj/flora/jungletreesmall.dmi' + +//grass +/obj/structure/flora/grass + name = "grass" + desc = "A patch of overgrown grass." + icon = 'icons/obj/flora/snowflora.dmi' + gender = PLURAL //"this is grass" not "this is a grass" + +/obj/structure/flora/grass/brown + icon_state = "snowgrass1bb" + +/obj/structure/flora/grass/brown/Initialize() + icon_state = "snowgrass[rand(1, 3)]bb" + . = ..() + + +/obj/structure/flora/grass/green + icon_state = "snowgrass1gb" + +/obj/structure/flora/grass/green/Initialize() + icon_state = "snowgrass[rand(1, 3)]gb" + . = ..() + +/obj/structure/flora/grass/both + icon_state = "snowgrassall1" + +/obj/structure/flora/grass/both/Initialize() + icon_state = "snowgrassall[rand(1, 3)]" + . = ..() + + +//bushes +/obj/structure/flora/bush + name = "bush" + desc = "Some type of shrub." + icon = 'icons/obj/flora/snowflora.dmi' + icon_state = "snowbush1" + anchored = TRUE + +/obj/structure/flora/bush/Initialize() + icon_state = "snowbush[rand(1, 6)]" + . = ..() + +//newbushes + +/obj/structure/flora/ausbushes + name = "bush" + desc = "Some kind of plant." + icon = 'icons/obj/flora/ausflora.dmi' + icon_state = "firstbush_1" + +/obj/structure/flora/ausbushes/Initialize() + if(icon_state == "firstbush_1") + icon_state = "firstbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/reedbush + icon_state = "reedbush_1" + +/obj/structure/flora/ausbushes/reedbush/Initialize() + icon_state = "reedbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/leafybush + icon_state = "leafybush_1" + +/obj/structure/flora/ausbushes/leafybush/Initialize() + icon_state = "leafybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/palebush + icon_state = "palebush_1" + +/obj/structure/flora/ausbushes/palebush/Initialize() + icon_state = "palebush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/stalkybush + icon_state = "stalkybush_1" + +/obj/structure/flora/ausbushes/stalkybush/Initialize() + icon_state = "stalkybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/grassybush + icon_state = "grassybush_1" + +/obj/structure/flora/ausbushes/grassybush/Initialize() + icon_state = "grassybush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/fernybush + icon_state = "fernybush_1" + +/obj/structure/flora/ausbushes/fernybush/Initialize() + icon_state = "fernybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/sunnybush + icon_state = "sunnybush_1" + +/obj/structure/flora/ausbushes/sunnybush/Initialize() + icon_state = "sunnybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/genericbush + icon_state = "genericbush_1" + +/obj/structure/flora/ausbushes/genericbush/Initialize() + icon_state = "genericbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/pointybush + icon_state = "pointybush_1" + +/obj/structure/flora/ausbushes/pointybush/Initialize() + icon_state = "pointybush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/lavendergrass + icon_state = "lavendergrass_1" + +/obj/structure/flora/ausbushes/lavendergrass/Initialize() + icon_state = "lavendergrass_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/ywflowers + icon_state = "ywflowers_1" + +/obj/structure/flora/ausbushes/ywflowers/Initialize() + icon_state = "ywflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/brflowers + icon_state = "brflowers_1" + +/obj/structure/flora/ausbushes/brflowers/Initialize() + icon_state = "brflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/ppflowers + icon_state = "ppflowers_1" + +/obj/structure/flora/ausbushes/ppflowers/Initialize() + icon_state = "ppflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/sparsegrass + icon_state = "sparsegrass_1" + +/obj/structure/flora/ausbushes/sparsegrass/Initialize() + icon_state = "sparsegrass_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/fullgrass + icon_state = "fullgrass_1" + +/obj/structure/flora/ausbushes/fullgrass/Initialize() + icon_state = "fullgrass_[rand(1, 3)]" + . = ..() + +/obj/item/twohanded/required/kirbyplants + name = "potted plant" + icon = 'yogstation/icons/obj/flora/plants.dmi' //yogs changed path + 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 = 'yogstation/icons/obj/flora/plants.dmi' //yogs changed icon path + 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 34) //yogs changed 25 plants to 34 + 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 741b24b6e979..7d19ef98c899 100644 --- a/code/game/objects/structures/hivebot.dm +++ b/code/game/objects/structures/hivebot.dm @@ -1,36 +1,36 @@ -/obj/structure/hivebot_beacon - name = "beacon" - desc = "Some odd beacon thing." - icon = 'icons/mob/hivebot.dmi' - icon_state = "def_radar-off" - anchored = TRUE - density = TRUE - var/bot_type = "norm" - var/bot_amt = 10 - -/obj/structure/hivebot_beacon/Initialize() - . = ..() - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(2, loc) - smoke.start() - visible_message("[src] warps in!") - playsound(src.loc, 'sound/effects/empulse.ogg', 25, 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/Initialize() + . = ..() + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(2, loc) + smoke.start() + visible_message("[src] warps in!") + playsound(src.loc, 'sound/effects/empulse.ogg', 25, 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 e4573320785c..ad95b6c60955 100644 --- a/code/game/objects/structures/kitchen_spike.dm +++ b/code/game/objects/structures/kitchen_spike.dm @@ -1,152 +1,152 @@ -//////Kitchen Spike -#define VIABLE_MOB_CHECK(X) (isliving(X) && !issilicon(X) && !isbot(X)) - -/obj/structure/kitchenspike_frame - name = "meatspike frame" - icon = 'icons/obj/kitchen.dmi' - icon_state = "spikeframe" - desc = "The frame of a meat spike." - density = TRUE - anchored = FALSE - max_integrity = 200 - -/obj/structure/kitchenspike_frame/attackby(obj/item/I, mob/user, params) - add_fingerprint(user) - if(default_unfasten_wrench(user, I)) - return - else if(istype(I, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = I - if(R.get_amount() >= 4) - R.use(4) - to_chat(user, "You add spikes to the frame.") - var/obj/F = new /obj/structure/kitchenspike(src.loc) - transfer_fingerprints_to(F) - qdel(src) - else if(I.tool_behaviour == TOOL_WELDER) - if(!I.tool_start_check(user, amount=0)) - return - to_chat(user, "You begin cutting \the [src] apart...") - if(I.use_tool(src, user, 50, volume=50)) - visible_message("[user] slices apart \the [src].", - "You cut \the [src] apart with \the [I].", - "You hear welding.") - new /obj/item/stack/sheet/metal(src.loc, 4) - qdel(src) - return - else - return ..() - -/obj/structure/kitchenspike - name = "meat spike" - icon = 'icons/obj/kitchen.dmi' - icon_state = "spike" - desc = "A spike for collecting meat from animals." - density = TRUE - anchored = TRUE - buckle_lying = 0 - can_buckle = 1 - max_integrity = 250 - -/obj/structure/kitchenspike/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/kitchenspike/crowbar_act(mob/living/user, obj/item/I) - if(has_buckled_mobs()) - to_chat(user, "You can't do that while something's on the spike!") - return TRUE - - if(I.use_tool(src, user, 20, volume=100)) - to_chat(user, "You pry the spikes out of the frame.") - deconstruct(TRUE) - return TRUE - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/structure/kitchenspike/attack_hand(mob/user) - if(VIABLE_MOB_CHECK(user.pulling) && user.a_intent == INTENT_GRAB && !has_buckled_mobs()) - var/mob/living/L = user.pulling - if(do_mob(user, src, 120)) - if(has_buckled_mobs()) //to prevent spam/queing up attacks - return - if(L.buckled) - return - if(user.pulling != L) - return - playsound(src.loc, 'sound/effects/splat.ogg', 25, 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") - L.add_splatter_floor() - L.adjustBruteLoss(30) - L.setDir(2) - buckle_mob(L, force=1) - var/matrix/m180 = matrix(L.transform) - m180.Turn(180) - animate(L, transform = m180, time = 3) - L.pixel_y = L.get_standard_pixel_y_offset(180) - else if (has_buckled_mobs()) - for(var/mob/living/L in buckled_mobs) - user_unbuckle_mob(L, user) - else - ..() - - - -/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking - return - -/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user) - if(buckled_mob) - var/mob/living/M = buckled_mob - if(M != user) - M.visible_message(\ - "[user] tries to pull [M] free of [src]!",\ - "[user] is trying to pull you off [src], opening up fresh wounds!",\ - "You hear a squishy wet noise.") - if(!do_after(user, 300, target = src)) - if(M && M.buckled) - M.visible_message(\ - "[user] fails to free [M]!",\ - "[user] fails to pull you off of [src].") - return - - else - M.visible_message(\ - "[M] struggles to break free from [src]!",\ - "You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\ - "You hear a wet squishing noise..") - M.adjustBruteLoss(30) - if(!do_after(M, 1200, target = src)) - if(M && M.buckled) - to_chat(M, "You fail to free yourself!") - return - if(!M.buckled) - return - release_mob(M) - -/obj/structure/kitchenspike/proc/release_mob(mob/living/M) - var/matrix/m180 = matrix(M.transform) - m180.Turn(180) - animate(M, transform = m180, time = 3) - M.pixel_y = M.get_standard_pixel_y_offset(180) - M.adjustBruteLoss(30) - src.visible_message(text("[M] falls free of [src]!")) - unbuckle_mob(M,force=1) - M.emote("scream") - M.AdjustParalyzed(20) - -/obj/structure/kitchenspike/Destroy() - if(has_buckled_mobs()) - for(var/mob/living/L in buckled_mobs) - release_mob(L) - return ..() - -/obj/structure/kitchenspike/deconstruct(disassembled = TRUE) - if(disassembled) - var/obj/F = new /obj/structure/kitchenspike_frame(src.loc) - transfer_fingerprints_to(F) - else - new /obj/item/stack/sheet/metal(src.loc, 4) - new /obj/item/stack/rods(loc, 4) - qdel(src) - -#undef VIABLE_MOB_CHECK +//////Kitchen Spike +#define VIABLE_MOB_CHECK(X) (isliving(X) && !issilicon(X) && !isbot(X)) + +/obj/structure/kitchenspike_frame + name = "meatspike frame" + icon = 'icons/obj/kitchen.dmi' + icon_state = "spikeframe" + desc = "The frame of a meat spike." + density = TRUE + anchored = FALSE + max_integrity = 200 + +/obj/structure/kitchenspike_frame/attackby(obj/item/I, mob/user, params) + add_fingerprint(user) + if(default_unfasten_wrench(user, I)) + return + else if(istype(I, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = I + if(R.get_amount() >= 4) + R.use(4) + to_chat(user, "You add spikes to the frame.") + var/obj/F = new /obj/structure/kitchenspike(src.loc) + transfer_fingerprints_to(F) + qdel(src) + else if(I.tool_behaviour == TOOL_WELDER) + if(!I.tool_start_check(user, amount=0)) + return + to_chat(user, "You begin cutting \the [src] apart...") + if(I.use_tool(src, user, 50, volume=50)) + visible_message("[user] slices apart \the [src].", + "You cut \the [src] apart with \the [I].", + "You hear welding.") + new /obj/item/stack/sheet/metal(src.loc, 4) + qdel(src) + return + else + return ..() + +/obj/structure/kitchenspike + name = "meat spike" + icon = 'icons/obj/kitchen.dmi' + icon_state = "spike" + desc = "A spike for collecting meat from animals." + density = TRUE + anchored = TRUE + buckle_lying = 0 + can_buckle = 1 + max_integrity = 250 + +/obj/structure/kitchenspike/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/kitchenspike/crowbar_act(mob/living/user, obj/item/I) + if(has_buckled_mobs()) + to_chat(user, "You can't do that while something's on the spike!") + return TRUE + + if(I.use_tool(src, user, 20, volume=100)) + to_chat(user, "You pry the spikes out of the frame.") + deconstruct(TRUE) + return TRUE + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/structure/kitchenspike/attack_hand(mob/user) + if(VIABLE_MOB_CHECK(user.pulling) && user.a_intent == INTENT_GRAB && !has_buckled_mobs()) + var/mob/living/L = user.pulling + if(do_mob(user, src, 120)) + if(has_buckled_mobs()) //to prevent spam/queing up attacks + return + if(L.buckled) + return + if(user.pulling != L) + return + playsound(src.loc, 'sound/effects/splat.ogg', 25, 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") + L.add_splatter_floor() + L.adjustBruteLoss(30) + L.setDir(2) + buckle_mob(L, force=1) + var/matrix/m180 = matrix(L.transform) + m180.Turn(180) + animate(L, transform = m180, time = 3) + L.pixel_y = L.get_standard_pixel_y_offset(180) + else if (has_buckled_mobs()) + for(var/mob/living/L in buckled_mobs) + user_unbuckle_mob(L, user) + else + ..() + + + +/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking + return + +/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user) + if(buckled_mob) + var/mob/living/M = buckled_mob + if(M != user) + M.visible_message(\ + "[user] tries to pull [M] free of [src]!",\ + "[user] is trying to pull you off [src], opening up fresh wounds!",\ + "You hear a squishy wet noise.") + if(!do_after(user, 300, target = src)) + if(M && M.buckled) + M.visible_message(\ + "[user] fails to free [M]!",\ + "[user] fails to pull you off of [src].") + return + + else + M.visible_message(\ + "[M] struggles to break free from [src]!",\ + "You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\ + "You hear a wet squishing noise..") + M.adjustBruteLoss(30) + if(!do_after(M, 1200, target = src)) + if(M && M.buckled) + to_chat(M, "You fail to free yourself!") + return + if(!M.buckled) + return + release_mob(M) + +/obj/structure/kitchenspike/proc/release_mob(mob/living/M) + var/matrix/m180 = matrix(M.transform) + m180.Turn(180) + animate(M, transform = m180, time = 3) + M.pixel_y = M.get_standard_pixel_y_offset(180) + M.adjustBruteLoss(30) + src.visible_message(text("[M] falls free of [src]!")) + unbuckle_mob(M,force=1) + M.emote("scream") + M.AdjustParalyzed(20) + +/obj/structure/kitchenspike/Destroy() + if(has_buckled_mobs()) + for(var/mob/living/L in buckled_mobs) + release_mob(L) + return ..() + +/obj/structure/kitchenspike/deconstruct(disassembled = TRUE) + if(disassembled) + var/obj/F = new /obj/structure/kitchenspike_frame(src.loc) + transfer_fingerprints_to(F) + else + new /obj/item/stack/sheet/metal(src.loc, 4) + new /obj/item/stack/rods(loc, 4) + qdel(src) + +#undef VIABLE_MOB_CHECK diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm index d7a5a55ec743..b3f415bafe64 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/manned_turret.dm b/code/game/objects/structures/manned_turret.dm index 80ffced5a2b2..79489e4ae639 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 dc0c17789637..be1dec4f21cb 100644 --- a/code/game/objects/structures/mineral_doors.dm +++ b/code/game/objects/structures/mineral_doors.dm @@ -1,348 +1,348 @@ -//NOT using the existing /obj/machinery/door type, since that has some complications on its own, mainly based on its -//machineryness - -/obj/structure/mineral_door - name = "metal door" - density = TRUE - anchored = TRUE - opacity = TRUE - layer = CLOSED_DOOR_LAYER - - icon = 'icons/obj/doors/mineral_doors.dmi' - icon_state = "metal" - max_integrity = 200 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) - CanAtmosPass = ATMOS_PASS_DENSITY - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - rad_insulation = RAD_MEDIUM_INSULATION - - var/door_opened = FALSE //if it's open or not. - var/isSwitchingStates = FALSE //don't try to change stats if we're already opening - - var/close_delay = -1 //-1 if does not auto close. - var/openSound = 'sound/effects/stonedoor_openclose.ogg' - var/closeSound = 'sound/effects/stonedoor_openclose.ogg' - - var/sheetType = /obj/item/stack/sheet/metal //what we're made of - var/sheetAmount = 7 //how much we drop when deconstructed - -/obj/structure/mineral_door/Initialize() - . = ..() - - air_update_turf(TRUE) - -/obj/structure/mineral_door/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/structure/mineral_door/Bumped(atom/movable/AM) - ..() - if(!door_opened) - return TryToSwitchState(AM) - -/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral - if(isAI(user)) //so the AI can't open it - return - else if(iscyborg(user)) //but cyborgs can - if(get_dist(user,src) <= 1) //not remotely though - return TryToSwitchState(user) - -/obj/structure/mineral_door/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/mineral_door/attack_hand(mob/user) - . = ..() - if(.) - return - return TryToSwitchState(user) - -/obj/structure/mineral_door/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 || !anchored) - return - if(isliving(user)) - var/mob/living/M = user - if(world.time - M.last_bumped <= 60) - return //NOTE do we really need that? - if(M.client) - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!C.handcuffed) - SwitchState() - else - SwitchState() - else if(ismecha(user)) - SwitchState() - -/obj/structure/mineral_door/proc/SwitchState() - if(door_opened) - Close() - else - Open() - -/obj/structure/mineral_door/proc/Open() - isSwitchingStates = TRUE - playsound(src, openSound, 100, 1) - set_opacity(FALSE) - flick("[initial(icon_state)]opening",src) - sleep(10) - density = FALSE - door_opened = TRUE - layer = OPEN_DOOR_LAYER - air_update_turf(1) - update_icon() - isSwitchingStates = FALSE - - if(close_delay != -1) - addtimer(CALLBACK(src, .proc/Close), close_delay) - -/obj/structure/mineral_door/proc/Close() - if(isSwitchingStates || !door_opened) - return - var/turf/T = get_turf(src) - for(var/mob/living/L in T) - return - isSwitchingStates = TRUE - playsound(src, closeSound, 100, 1) - flick("[initial(icon_state)]closing",src) - sleep(10) - density = TRUE - set_opacity(TRUE) - door_opened = FALSE - layer = initial(layer) - air_update_turf(1) - update_icon() - isSwitchingStates = FALSE - -/obj/structure/mineral_door/update_icon() - icon_state = "[initial(icon_state)][door_opened ? "open":""]" - -/obj/structure/mineral_door/attackby(obj/item/I, mob/user) - if(pickaxe_door(user, I)) - return - else if(user.a_intent != INTENT_HARM) - return attack_hand(user) - else - return ..() - -/obj/structure/mineral_door/setAnchored(anchorvalue) //called in default_unfasten_wrench() chain - . = ..() - set_opacity(anchored ? !door_opened : FALSE) - air_update_turf(TRUE) - -/obj/structure/mineral_door/wrench_act(mob/living/user, obj/item/I) - default_unfasten_wrench(user, I, 40) - return TRUE - - -/////////////////////// TOOL OVERRIDES /////////////////////// - - -/obj/structure/mineral_door/proc/pickaxe_door(mob/living/user, obj/item/I) //override if the door isn't supposed to be a minable mineral. - if(!istype(user)) - return - if(I.tool_behaviour != TOOL_MINING) - return - . = TRUE - to_chat(user, "You start digging [src]...") - if(I.use_tool(src, user, 40, volume=50)) - to_chat(user, "You finish digging.") - deconstruct(TRUE) - -/obj/structure/mineral_door/welder_act(mob/living/user, obj/item/I) //override if the door is supposed to be flammable. - . = TRUE - if(anchored) - to_chat(user, "[src] is still firmly secured to the ground!") - return - - user.visible_message("[user] starts to weld apart [src]!", "You start welding apart [src].") - if(!I.use_tool(src, user, 60, 5, 50)) - to_chat(user, "You failed to weld apart [src]!/span>") - return - - user.visible_message("[user] welded [src] into pieces!", "You welded apart [src]!") - deconstruct(TRUE) - -/obj/structure/mineral_door/proc/crowbar_door(mob/living/user, obj/item/I) //if the door is flammable, call this in crowbar_act() so we can still decon it - . = TRUE - if(anchored) - to_chat(user, "[src] is still firmly secured to the ground!") - return - - user.visible_message("[user] starts to pry apart [src]!", "You start prying apart [src].") - if(!I.use_tool(src, user, 60, volume = 50)) - to_chat(user, "You failed to pry apart [src]!/span>") - return - - user.visible_message("[user] pried [src] into pieces!", "You pried apart [src]!") - deconstruct(TRUE) - - -/////////////////////// END TOOL OVERRIDES /////////////////////// - - -/obj/structure/mineral_door/deconstruct(disassembled = TRUE) - var/turf/T = get_turf(src) - if(disassembled) - new sheetType(T, sheetAmount) - else - new sheetType(T, max(sheetAmount - 2, 1)) - qdel(src) - - -/obj/structure/mineral_door/iron - name = "iron door" - max_integrity = 300 - -/obj/structure/mineral_door/silver - name = "silver door" - icon_state = "silver" - sheetType = /obj/item/stack/sheet/mineral/silver - max_integrity = 300 - rad_insulation = RAD_HEAVY_INSULATION - -/obj/structure/mineral_door/gold - name = "gold door" - icon_state = "gold" - sheetType = /obj/item/stack/sheet/mineral/gold - rad_insulation = RAD_HEAVY_INSULATION - -/obj/structure/mineral_door/uranium - name = "uranium door" - icon_state = "uranium" - sheetType = /obj/item/stack/sheet/mineral/uranium - max_integrity = 300 - light_range = 2 - -/obj/structure/mineral_door/uranium/ComponentInitialize() - return - -/obj/structure/mineral_door/sandstone - name = "sandstone door" - icon_state = "sandstone" - sheetType = /obj/item/stack/sheet/mineral/sandstone - max_integrity = 100 - -/obj/structure/mineral_door/transparent - opacity = FALSE - rad_insulation = RAD_VERY_LIGHT_INSULATION - -/obj/structure/mineral_door/transparent/Close() - ..() - set_opacity(FALSE) - -/obj/structure/mineral_door/transparent/plasma - name = "plasma door" - icon_state = "plasma" - sheetType = /obj/item/stack/sheet/mineral/plasma - -/obj/structure/mineral_door/transparent/plasma/ComponentInitialize() - return - -/obj/structure/mineral_door/transparent/plasma/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/transparent/plasma/attackby(obj/item/W, mob/user, params) - if(W.is_hot()) - var/turf/T = get_turf(src) - message_admins("Plasma mineral door ignited by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]") - log_game("Plasma mineral door ignited by [key_name(user)] in [AREACOORD(T)]") - TemperatureAct() - else - return ..() - -/obj/structure/mineral_door/transparent/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature > 300) - TemperatureAct() - -/obj/structure/mineral_door/transparent/plasma/proc/TemperatureAct() - atmos_spawn_air("plasma=500;TEMP=1000") - deconstruct(FALSE) - -/obj/structure/mineral_door/transparent/diamond - name = "diamond door" - icon_state = "diamond" - sheetType = /obj/item/stack/sheet/mineral/diamond - max_integrity = 1000 - rad_insulation = RAD_EXTREME_INSULATION - -/obj/structure/mineral_door/wood - name = "wood door" - icon_state = "wood" - openSound = 'sound/effects/doorcreaky.ogg' - closeSound = 'sound/effects/doorcreaky.ogg' - sheetType = /obj/item/stack/sheet/mineral/wood - resistance_flags = FLAMMABLE - max_integrity = 200 - rad_insulation = RAD_VERY_LIGHT_INSULATION - -/obj/structure/mineral_door/wood/pickaxe_door(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/wood/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/wood/crowbar_act(mob/living/user, obj/item/I) - return crowbar_door(user, I) - -/obj/structure/mineral_door/wood/attackby(obj/item/I, mob/living/user) - if(I.is_hot()) - fire_act(I.is_hot()) - return - - return ..() - -/obj/structure/mineral_door/paperframe - name = "paper frame door" - icon_state = "paperframe" - openSound = 'sound/effects/doorcreaky.ogg' - closeSound = 'sound/effects/doorcreaky.ogg' - sheetType = /obj/item/stack/sheet/paperframes - sheetAmount = 3 - resistance_flags = FLAMMABLE - max_integrity = 20 - -/obj/structure/mineral_door/paperframe/Initialize() - . = ..() - queue_smooth_neighbors(src) - -/obj/structure/mineral_door/paperframe/examine(mob/user) - . = ..() - if(obj_integrity < max_integrity) - . += "It looks a bit damaged, you may be able to fix it with some paper." - -/obj/structure/mineral_door/paperframe/pickaxe_door(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/paperframe/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/paperframe/crowbar_act(mob/living/user, obj/item/I) - return crowbar_door(user, I) - -/obj/structure/mineral_door/paperframe/attackby(obj/item/I, mob/living/user) - if(I.is_hot()) //BURN IT ALL DOWN JIM - fire_act(I.is_hot()) - return - - if((user.a_intent != INTENT_HARM) && istype(I, /obj/item/paper) && (obj_integrity < max_integrity)) - user.visible_message("[user] starts to patch the holes in [src].", "You start patching some of the holes in [src]!") - if(do_after(user, 20, TRUE, src)) - obj_integrity = min(obj_integrity+4,max_integrity) - qdel(I) - user.visible_message("[user] patches some of the holes in [src].", "You patch some of the holes in [src]!") - return TRUE - - return ..() - -/obj/structure/mineral_door/paperframe/ComponentInitialize() - return - -/obj/structure/mineral_door/paperframe/Destroy() - queue_smooth_neighbors(src) - return ..() +//NOT using the existing /obj/machinery/door type, since that has some complications on its own, mainly based on its +//machineryness + +/obj/structure/mineral_door + name = "metal door" + density = TRUE + anchored = TRUE + opacity = TRUE + layer = CLOSED_DOOR_LAYER + + icon = 'icons/obj/doors/mineral_doors.dmi' + icon_state = "metal" + max_integrity = 200 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) + CanAtmosPass = ATMOS_PASS_DENSITY + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + rad_insulation = RAD_MEDIUM_INSULATION + + var/door_opened = FALSE //if it's open or not. + var/isSwitchingStates = FALSE //don't try to change stats if we're already opening + + var/close_delay = -1 //-1 if does not auto close. + var/openSound = 'sound/effects/stonedoor_openclose.ogg' + var/closeSound = 'sound/effects/stonedoor_openclose.ogg' + + var/sheetType = /obj/item/stack/sheet/metal //what we're made of + var/sheetAmount = 7 //how much we drop when deconstructed + +/obj/structure/mineral_door/Initialize() + . = ..() + + air_update_turf(TRUE) + +/obj/structure/mineral_door/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/structure/mineral_door/Bumped(atom/movable/AM) + ..() + if(!door_opened) + return TryToSwitchState(AM) + +/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral + if(isAI(user)) //so the AI can't open it + return + else if(iscyborg(user)) //but cyborgs can + if(get_dist(user,src) <= 1) //not remotely though + return TryToSwitchState(user) + +/obj/structure/mineral_door/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/mineral_door/attack_hand(mob/user) + . = ..() + if(.) + return + return TryToSwitchState(user) + +/obj/structure/mineral_door/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 || !anchored) + return + if(isliving(user)) + var/mob/living/M = user + if(world.time - M.last_bumped <= 60) + return //NOTE do we really need that? + if(M.client) + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(!C.handcuffed) + SwitchState() + else + SwitchState() + else if(ismecha(user)) + SwitchState() + +/obj/structure/mineral_door/proc/SwitchState() + if(door_opened) + Close() + else + Open() + +/obj/structure/mineral_door/proc/Open() + isSwitchingStates = TRUE + playsound(src, openSound, 100, 1) + set_opacity(FALSE) + flick("[initial(icon_state)]opening",src) + sleep(10) + density = FALSE + door_opened = TRUE + layer = OPEN_DOOR_LAYER + air_update_turf(1) + update_icon() + isSwitchingStates = FALSE + + if(close_delay != -1) + addtimer(CALLBACK(src, .proc/Close), close_delay) + +/obj/structure/mineral_door/proc/Close() + if(isSwitchingStates || !door_opened) + return + var/turf/T = get_turf(src) + for(var/mob/living/L in T) + return + isSwitchingStates = TRUE + playsound(src, closeSound, 100, 1) + flick("[initial(icon_state)]closing",src) + sleep(10) + density = TRUE + set_opacity(TRUE) + door_opened = FALSE + layer = initial(layer) + air_update_turf(1) + update_icon() + isSwitchingStates = FALSE + +/obj/structure/mineral_door/update_icon() + icon_state = "[initial(icon_state)][door_opened ? "open":""]" + +/obj/structure/mineral_door/attackby(obj/item/I, mob/user) + if(pickaxe_door(user, I)) + return + else if(user.a_intent != INTENT_HARM) + return attack_hand(user) + else + return ..() + +/obj/structure/mineral_door/setAnchored(anchorvalue) //called in default_unfasten_wrench() chain + . = ..() + set_opacity(anchored ? !door_opened : FALSE) + air_update_turf(TRUE) + +/obj/structure/mineral_door/wrench_act(mob/living/user, obj/item/I) + default_unfasten_wrench(user, I, 40) + return TRUE + + +/////////////////////// TOOL OVERRIDES /////////////////////// + + +/obj/structure/mineral_door/proc/pickaxe_door(mob/living/user, obj/item/I) //override if the door isn't supposed to be a minable mineral. + if(!istype(user)) + return + if(I.tool_behaviour != TOOL_MINING) + return + . = TRUE + to_chat(user, "You start digging [src]...") + if(I.use_tool(src, user, 40, volume=50)) + to_chat(user, "You finish digging.") + deconstruct(TRUE) + +/obj/structure/mineral_door/welder_act(mob/living/user, obj/item/I) //override if the door is supposed to be flammable. + . = TRUE + if(anchored) + to_chat(user, "[src] is still firmly secured to the ground!") + return + + user.visible_message("[user] starts to weld apart [src]!", "You start welding apart [src].") + if(!I.use_tool(src, user, 60, 5, 50)) + to_chat(user, "You failed to weld apart [src]!/span>") + return + + user.visible_message("[user] welded [src] into pieces!", "You welded apart [src]!") + deconstruct(TRUE) + +/obj/structure/mineral_door/proc/crowbar_door(mob/living/user, obj/item/I) //if the door is flammable, call this in crowbar_act() so we can still decon it + . = TRUE + if(anchored) + to_chat(user, "[src] is still firmly secured to the ground!") + return + + user.visible_message("[user] starts to pry apart [src]!", "You start prying apart [src].") + if(!I.use_tool(src, user, 60, volume = 50)) + to_chat(user, "You failed to pry apart [src]!/span>") + return + + user.visible_message("[user] pried [src] into pieces!", "You pried apart [src]!") + deconstruct(TRUE) + + +/////////////////////// END TOOL OVERRIDES /////////////////////// + + +/obj/structure/mineral_door/deconstruct(disassembled = TRUE) + var/turf/T = get_turf(src) + if(disassembled) + new sheetType(T, sheetAmount) + else + new sheetType(T, max(sheetAmount - 2, 1)) + qdel(src) + + +/obj/structure/mineral_door/iron + name = "iron door" + max_integrity = 300 + +/obj/structure/mineral_door/silver + name = "silver door" + icon_state = "silver" + sheetType = /obj/item/stack/sheet/mineral/silver + max_integrity = 300 + rad_insulation = RAD_HEAVY_INSULATION + +/obj/structure/mineral_door/gold + name = "gold door" + icon_state = "gold" + sheetType = /obj/item/stack/sheet/mineral/gold + rad_insulation = RAD_HEAVY_INSULATION + +/obj/structure/mineral_door/uranium + name = "uranium door" + icon_state = "uranium" + sheetType = /obj/item/stack/sheet/mineral/uranium + max_integrity = 300 + light_range = 2 + +/obj/structure/mineral_door/uranium/ComponentInitialize() + return + +/obj/structure/mineral_door/sandstone + name = "sandstone door" + icon_state = "sandstone" + sheetType = /obj/item/stack/sheet/mineral/sandstone + max_integrity = 100 + +/obj/structure/mineral_door/transparent + opacity = FALSE + rad_insulation = RAD_VERY_LIGHT_INSULATION + +/obj/structure/mineral_door/transparent/Close() + ..() + set_opacity(FALSE) + +/obj/structure/mineral_door/transparent/plasma + name = "plasma door" + icon_state = "plasma" + sheetType = /obj/item/stack/sheet/mineral/plasma + +/obj/structure/mineral_door/transparent/plasma/ComponentInitialize() + return + +/obj/structure/mineral_door/transparent/plasma/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/transparent/plasma/attackby(obj/item/W, mob/user, params) + if(W.is_hot()) + var/turf/T = get_turf(src) + message_admins("Plasma mineral door ignited by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]") + log_game("Plasma mineral door ignited by [key_name(user)] in [AREACOORD(T)]") + TemperatureAct() + else + return ..() + +/obj/structure/mineral_door/transparent/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature > 300) + TemperatureAct() + +/obj/structure/mineral_door/transparent/plasma/proc/TemperatureAct() + atmos_spawn_air("plasma=500;TEMP=1000") + deconstruct(FALSE) + +/obj/structure/mineral_door/transparent/diamond + name = "diamond door" + icon_state = "diamond" + sheetType = /obj/item/stack/sheet/mineral/diamond + max_integrity = 1000 + rad_insulation = RAD_EXTREME_INSULATION + +/obj/structure/mineral_door/wood + name = "wood door" + icon_state = "wood" + openSound = 'sound/effects/doorcreaky.ogg' + closeSound = 'sound/effects/doorcreaky.ogg' + sheetType = /obj/item/stack/sheet/mineral/wood + resistance_flags = FLAMMABLE + max_integrity = 200 + rad_insulation = RAD_VERY_LIGHT_INSULATION + +/obj/structure/mineral_door/wood/pickaxe_door(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/wood/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/wood/crowbar_act(mob/living/user, obj/item/I) + return crowbar_door(user, I) + +/obj/structure/mineral_door/wood/attackby(obj/item/I, mob/living/user) + if(I.is_hot()) + fire_act(I.is_hot()) + return + + return ..() + +/obj/structure/mineral_door/paperframe + name = "paper frame door" + icon_state = "paperframe" + openSound = 'sound/effects/doorcreaky.ogg' + closeSound = 'sound/effects/doorcreaky.ogg' + sheetType = /obj/item/stack/sheet/paperframes + sheetAmount = 3 + resistance_flags = FLAMMABLE + max_integrity = 20 + +/obj/structure/mineral_door/paperframe/Initialize() + . = ..() + queue_smooth_neighbors(src) + +/obj/structure/mineral_door/paperframe/examine(mob/user) + . = ..() + if(obj_integrity < max_integrity) + . += "It looks a bit damaged, you may be able to fix it with some paper." + +/obj/structure/mineral_door/paperframe/pickaxe_door(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/paperframe/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/paperframe/crowbar_act(mob/living/user, obj/item/I) + return crowbar_door(user, I) + +/obj/structure/mineral_door/paperframe/attackby(obj/item/I, mob/living/user) + if(I.is_hot()) //BURN IT ALL DOWN JIM + fire_act(I.is_hot()) + return + + if((user.a_intent != INTENT_HARM) && istype(I, /obj/item/paper) && (obj_integrity < max_integrity)) + user.visible_message("[user] starts to patch the holes in [src].", "You start patching some of the holes in [src]!") + if(do_after(user, 20, TRUE, src)) + obj_integrity = min(obj_integrity+4,max_integrity) + qdel(I) + user.visible_message("[user] patches some of the holes in [src].", "You patch some of the holes in [src]!") + return TRUE + + return ..() + +/obj/structure/mineral_door/paperframe/ComponentInitialize() + return + +/obj/structure/mineral_door/paperframe/Destroy() + queue_smooth_neighbors(src) + return ..() diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 154dc9382b9b..9ca6f7f7fc7e 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -1,244 +1,244 @@ -//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 != FEMALE) - 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 list()// no message spam - return ..() - -/obj/structure/mirror/obj_break(damage_flag, mapload) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - icon_state = "mirror_broke" - if(!mapload) - playsound(src, "shatter", 70, 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/choosable_races = list() - -/obj/structure/mirror/magic/New() - if(!choosable_races.len) - for(var/speciestype in subtypesof(/datum/species)) - var/datum/species/S = speciestype - if(initial(S.changesource_flags) & MIRROR_MAGIC) - choosable_races += initial(S.id) - ..() - -/obj/structure/mirror/magic/lesser/New() - choosable_races = GLOB.roundstart_races.Copy() - ..() - -/obj/structure/mirror/magic/badmin/New() - for(var/speciestype in subtypesof(/datum/species)) - var/datum/species/S = speciestype - if(initial(S.changesource_flags) & MIRROR_BADMIN) - choosable_races += initial(S.id) - ..() - -/obj/structure/mirror/magic/attack_hand(mob/user) - . = ..() - if(.) - return - if(!ishuman(user)) - return - - var/mob/living/carbon/human/H = user - - var/choice = input(user, "Something to change?", "Magical Grooming") as null|anything in list("name", "race", "gender", "hair", "eyes") - - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - switch(choice) - if("name") - var/newname = 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(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - if(new_s_tone) - H.skin_tone = new_s_tone - H.dna.update_ui_block(DNA_SKIN_TONE_BLOCK) - - if(MUTCOLORS in H.dna.species.species_traits) - var/new_mutantcolor = input(user, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_mutantcolor) - var/temp_hsv = RGBtoHSV(new_mutantcolor) - - if(ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright - H.dna.features["mcolor"] = sanitize_hexcolor(new_mutantcolor) - - else - to_chat(H, "Invalid color. Your color is not bright enough.") - - H.update_body() - H.update_hair() - H.update_body_parts() - H.update_mutations_overlay() // no hulk lizard - - if("gender") - if(!(H.gender in list("male", "female"))) //blame the patriarchy - return - if(H.gender == "male") - if(alert(H, "Become a Witch?", "Confirmation", "Yes", "No") == "Yes") - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.gender = "female" - to_chat(H, "Man, you feel like a woman!") - else - return - - else - if(alert(H, "Become a Warlock?", "Confirmation", "Yes", "No") == "Yes") - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.gender = "male" - 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(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_hair_color) - H.hair_color = sanitize_hexcolor(new_hair_color) - H.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK) - if(H.gender == "male") - var/new_face_color = input(H, "Choose your facial hair color", "Hair Color","#"+H.facial_hair_color) as color|null - if(new_face_color) - H.facial_hair_color = sanitize_hexcolor(new_face_color) - H.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK) - H.update_hair() - - if(BODY_ZONE_PRECISE_EYES) - var/new_eye_color = input(H, "Choose your eye color", "Eye Color","#"+H.eye_color) as color|null - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_eye_color) - H.eye_color = sanitize_hexcolor(new_eye_color) - H.dna.update_ui_block(DNA_EYE_COLOR_BLOCK) - H.update_body() - if(choice) - curse(user) - -/obj/structure/mirror/magic/proc/curse(mob/living/user) - return +//wip wip wup +/obj/structure/mirror + name = "mirror" + desc = "Mirror mirror on the wall, who's the most robust of them all?" + icon = 'icons/obj/watercloset.dmi' + icon_state = "mirror" + density = FALSE + anchored = TRUE + max_integrity = 200 + integrity_failure = 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 != FEMALE) + 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 list()// no message spam + return ..() + +/obj/structure/mirror/obj_break(damage_flag, mapload) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + icon_state = "mirror_broke" + if(!mapload) + playsound(src, "shatter", 70, 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/choosable_races = list() + +/obj/structure/mirror/magic/New() + if(!choosable_races.len) + for(var/speciestype in subtypesof(/datum/species)) + var/datum/species/S = speciestype + if(initial(S.changesource_flags) & MIRROR_MAGIC) + choosable_races += initial(S.id) + ..() + +/obj/structure/mirror/magic/lesser/New() + choosable_races = GLOB.roundstart_races.Copy() + ..() + +/obj/structure/mirror/magic/badmin/New() + for(var/speciestype in subtypesof(/datum/species)) + var/datum/species/S = speciestype + if(initial(S.changesource_flags) & MIRROR_BADMIN) + choosable_races += initial(S.id) + ..() + +/obj/structure/mirror/magic/attack_hand(mob/user) + . = ..() + if(.) + return + if(!ishuman(user)) + return + + var/mob/living/carbon/human/H = user + + var/choice = input(user, "Something to change?", "Magical Grooming") as null|anything in list("name", "race", "gender", "hair", "eyes") + + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + switch(choice) + if("name") + var/newname = 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(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + if(new_s_tone) + H.skin_tone = new_s_tone + H.dna.update_ui_block(DNA_SKIN_TONE_BLOCK) + + if(MUTCOLORS in H.dna.species.species_traits) + var/new_mutantcolor = input(user, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_mutantcolor) + var/temp_hsv = RGBtoHSV(new_mutantcolor) + + if(ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright + H.dna.features["mcolor"] = sanitize_hexcolor(new_mutantcolor) + + else + to_chat(H, "Invalid color. Your color is not bright enough.") + + H.update_body() + H.update_hair() + H.update_body_parts() + H.update_mutations_overlay() // no hulk lizard + + if("gender") + if(!(H.gender in list("male", "female"))) //blame the patriarchy + return + if(H.gender == "male") + if(alert(H, "Become a Witch?", "Confirmation", "Yes", "No") == "Yes") + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.gender = "female" + to_chat(H, "Man, you feel like a woman!") + else + return + + else + if(alert(H, "Become a Warlock?", "Confirmation", "Yes", "No") == "Yes") + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.gender = "male" + 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(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_hair_color) + H.hair_color = sanitize_hexcolor(new_hair_color) + H.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK) + if(H.gender == "male") + var/new_face_color = input(H, "Choose your facial hair color", "Hair Color","#"+H.facial_hair_color) as color|null + if(new_face_color) + H.facial_hair_color = sanitize_hexcolor(new_face_color) + H.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK) + H.update_hair() + + if(BODY_ZONE_PRECISE_EYES) + var/new_eye_color = input(H, "Choose your eye color", "Eye Color","#"+H.eye_color) as color|null + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_eye_color) + H.eye_color = sanitize_hexcolor(new_eye_color) + H.dna.update_ui_block(DNA_EYE_COLOR_BLOCK) + H.update_body() + if(choice) + curse(user) + +/obj/structure/mirror/magic/proc/curse(mob/living/user) + return diff --git a/code/game/objects/structures/mop_bucket.dm b/code/game/objects/structures/mop_bucket.dm index bb34ecfec7dc..59260696e2f5 100644 --- a/code/game/objects/structures/mop_bucket.dm +++ b/code/game/objects/structures/mop_bucket.dm @@ -1,30 +1,30 @@ -/obj/structure/mopbucket - name = "mop bucket" - desc = "Fill it with water, but don't forget a mop!" - icon = 'icons/obj/janitor.dmi' - icon_state = "mopbucket" - density = TRUE - var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite - - -/obj/structure/mopbucket/Initialize() - . = ..() - create_reagents(100, OPENCONTAINER) - -/obj/structure/mopbucket/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/mop)) - if(reagents.total_volume < 1) - to_chat(user, "[src] is out of water!") - else - reagents.trans_to(I, 5, transfered_by = user) - to_chat(user, "You wet [I] in [src].") - playsound(loc, 'sound/effects/slosh.ogg', 25, 1) - update_icon() - else - . = ..() - update_icon() - -/obj/structure/mopbucket/update_icon() - cut_overlays() - if(reagents.total_volume > 0) - add_overlay("mopbucket_water") +/obj/structure/mopbucket + name = "mop bucket" + desc = "Fill it with water, but don't forget a mop!" + icon = 'icons/obj/janitor.dmi' + icon_state = "mopbucket" + density = TRUE + var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite + + +/obj/structure/mopbucket/Initialize() + . = ..() + create_reagents(100, OPENCONTAINER) + +/obj/structure/mopbucket/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/mop)) + if(reagents.total_volume < 1) + to_chat(user, "[src] is out of water!") + else + reagents.trans_to(I, 5, transfered_by = user) + to_chat(user, "You wet [I] in [src].") + playsound(loc, 'sound/effects/slosh.ogg', 25, 1) + update_icon() + else + . = ..() + update_icon() + +/obj/structure/mopbucket/update_icon() + cut_overlays() + if(reagents.total_volume > 0) + add_overlay("mopbucket_water") diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index 99a4a6260eaf..e50fa5f82d2d 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -1,390 +1,390 @@ -/* 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) - recursive_organ_check(src) - 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) - update_icon() - -/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) -/* - * Morgue - */ -/obj/structure/bodycontainer/morgue - name = "morgue" - desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system." - icon_state = "morgue1" - dir = EAST - var/beeper = TRUE - var/beep_cooldown = 50 - var/next_beep = 0 - -/obj/structure/bodycontainer/morgue/Initialize() - . = ..() - connected = new/obj/structure/tray/m_tray(src) - connected.connected = src - -/obj/structure/bodycontainer/morgue/examine(mob/user) - . = ..() - . += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it." - -/obj/structure/bodycontainer/morgue/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, !issilicon(user))) - return - beeper = !beeper - to_chat(user, "You turn the speaker function [beeper ? "on" : "off"].") - -/obj/structure/bodycontainer/morgue/update_icon() - if (!connected || connected.loc != src) // Open or tray is gone. - icon_state = "morgue0" - else - if(contents.len == 1) // Empty - icon_state = "morgue1" - else - icon_state = "morgue2" // Dead, brainded mob. - var/list/compiled = 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_BADDNA)) && !mob_occupant.hellbound) - icon_state = "morgue4" // Cloneable - if(mob_occupant.stat == DEAD && beeper) - if(world.time > next_beep) - playsound(src, 'sound/weapons/smg_empty_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() - GLOB.crematoriums.Add(src) - ..() - -/obj/structure/bodycontainer/crematorium/Initialize() - . = ..() - connected = new /obj/structure/tray/c_tray(src) - connected.connected = 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/i_scream in GetAllContents(/mob/living)) - var/obj/item/reagent_containers/food/snacks/icecream/IC = new() - IC.set_cone_type("waffle") - IC.add_mob_flavor(i_scream) - icecreams += IC - . = ..() - for(var/obj/IC in icecreams) - IC.forceMove(src) - -/* - * Generic Tray - * Parent class for morguetray and crematoriumtray - * For overriding only - */ -/obj/structure/tray - icon = 'icons/obj/stationobjs.dmi' - density = TRUE - var/obj/structure/bodycontainer/connected = null - anchored = TRUE - pass_flags = LETPASSTHROW - max_integrity = 350 - -/obj/structure/tray/Destroy() - if(connected) - connected.connected = null - connected.update_icon() - connected = null - return ..() - -/obj/structure/tray/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/structure/tray/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/tray/attack_hand(mob/user) - . = ..() - if(.) - return - if (src.connected) - connected.close() - add_fingerprint(user) - else - to_chat(user, "That's not connected to anything!") - -/obj/structure/tray/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.incapacitated()) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - O.forceMove(src.loc) - if (user != O) - visible_message("[user] stuffs [O] into [src].") - return - -/* - * Crematorium tray - */ -/obj/structure/tray/c_tray - name = "crematorium tray" - desc = "Apply body before burning." - icon_state = "cremat" - -/* - * Morgue tray - */ -/obj/structure/tray/m_tray - name = "morgue tray" - desc = "Apply corpse before closing." - icon_state = "morguet" - -/obj/structure/tray/m_tray/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) + recursive_organ_check(src) + 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) + update_icon() + +/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) +/* + * Morgue + */ +/obj/structure/bodycontainer/morgue + name = "morgue" + desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system." + icon_state = "morgue1" + dir = EAST + var/beeper = TRUE + var/beep_cooldown = 50 + var/next_beep = 0 + +/obj/structure/bodycontainer/morgue/Initialize() + . = ..() + connected = new/obj/structure/tray/m_tray(src) + connected.connected = src + +/obj/structure/bodycontainer/morgue/examine(mob/user) + . = ..() + . += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it." + +/obj/structure/bodycontainer/morgue/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, !issilicon(user))) + return + beeper = !beeper + to_chat(user, "You turn the speaker function [beeper ? "on" : "off"].") + +/obj/structure/bodycontainer/morgue/update_icon() + if (!connected || connected.loc != src) // Open or tray is gone. + icon_state = "morgue0" + else + if(contents.len == 1) // Empty + icon_state = "morgue1" + else + icon_state = "morgue2" // Dead, brainded mob. + var/list/compiled = 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_BADDNA)) && !mob_occupant.hellbound) + icon_state = "morgue4" // Cloneable + if(mob_occupant.stat == DEAD && beeper) + if(world.time > next_beep) + playsound(src, 'sound/weapons/smg_empty_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() + GLOB.crematoriums.Add(src) + ..() + +/obj/structure/bodycontainer/crematorium/Initialize() + . = ..() + connected = new /obj/structure/tray/c_tray(src) + connected.connected = 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/i_scream in GetAllContents(/mob/living)) + var/obj/item/reagent_containers/food/snacks/icecream/IC = new() + IC.set_cone_type("waffle") + IC.add_mob_flavor(i_scream) + icecreams += IC + . = ..() + for(var/obj/IC in icecreams) + IC.forceMove(src) + +/* + * Generic Tray + * Parent class for morguetray and crematoriumtray + * For overriding only + */ +/obj/structure/tray + icon = 'icons/obj/stationobjs.dmi' + density = TRUE + var/obj/structure/bodycontainer/connected = null + anchored = TRUE + pass_flags = LETPASSTHROW + max_integrity = 350 + +/obj/structure/tray/Destroy() + if(connected) + connected.connected = null + connected.update_icon() + connected = null + return ..() + +/obj/structure/tray/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/structure/tray/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/tray/attack_hand(mob/user) + . = ..() + if(.) + return + if (src.connected) + connected.close() + add_fingerprint(user) + else + to_chat(user, "That's not connected to anything!") + +/obj/structure/tray/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.incapacitated()) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + O.forceMove(src.loc) + if (user != O) + visible_message("[user] stuffs [O] into [src].") + return + +/* + * Crematorium tray + */ +/obj/structure/tray/c_tray + name = "crematorium tray" + desc = "Apply body before burning." + icon_state = "cremat" + +/* + * Morgue tray + */ +/obj/structure/tray/m_tray + name = "morgue tray" + desc = "Apply corpse before closing." + icon_state = "morguet" + +/obj/structure/tray/m_tray/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 7de9375f9200..3b5e2aec65f1 100644 --- a/code/game/objects/structures/musician.dm +++ b/code/game/objects/structures/musician.dm @@ -1,386 +1,386 @@ - -#define MUSICIAN_HEARCHECK_MINDELAY 4 -#define MUSIC_MAXLINES 300 -#define MUSIC_MAXLINECHARS 50 - -/datum/song - var/name = "Untitled" - var/list/lines = new() - var/tempo = 5 // delay between notes - - var/playing = 0 // if we're playing - var/help = 0 // if help is open - var/edit = 1 // if we're in editing mode - var/repeat = 0 // number of times remaining to repeat - var/max_repeats = 10 // maximum times we can repeat - - var/instrumentDir = "piano" // the folder with the sounds - var/instrumentExt = "ogg" // the file extension - var/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(mob/user, note, acc as text, oct) - // handle accidental -> B<>C of E<>F - if(acc == "b" && (note == 3 || note == 6)) // C or F - if(note == 3) - oct-- - note-- - acc = "n" - else if(acc == "#" && (note == 2 || note == 5)) // B or E - if(note == 2) - oct++ - note++ - acc = "n" - else if(acc == "#" && (note == 7)) //G# - note = 1 - acc = "b" - else if(acc == "#") // mass convert all sharps to flats, octave jump already handled - acc = "b" - note++ - - // check octave, C is allowed to go to 9 - if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) - return - - // now generate name - var/soundfile = "sound/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)) - LAZYADD(hearing_mobs, M) - last_hearcheck = world.time - - var/sound/music_played = sound(soundfile) - for(var/i in hearing_mobs) - var/mob/M = i - if(HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M)) - var/mob/living/L = M - L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC) - if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) - continue - M.playsound_local(source, null, 100, falloff = 5, S = music_played) - -/datum/song/proc/updateDialog(mob/user) - instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise - -/datum/song/proc/shouldStopPlaying(mob/user) - if(instrumentObj) - if(!user.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return TRUE - return !instrumentObj.anchored // add special cases to stop in subclasses - else - return TRUE - -/datum/song/proc/playsong(mob/user) - while(repeat >= 0) - var/cur_oct[7] - var/cur_acc[7] - for(var/i = 1 to 7) - cur_oct[i] = 3 - cur_acc[i] = "n" - - for(var/line in lines) - for(var/beat in splittext(lowertext(line), ",")) - var/list/notes = splittext(beat, "/") - for(var/note in splittext(notes[1], "-")) - if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case - playing = FALSE - hearing_mobs = null - return - if(!lentext(note)) - continue - var/cur_note = text2ascii(note) - 96 - if(cur_note < 1 || cur_note > 7) - continue - for(var/i=2 to lentext(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(user, cur_note, cur_acc[cur_note], cur_oct[cur_note]) - if(notes.len >= 2 && text2num(notes[2])) - sleep(sanitize_tempo(tempo / text2num(notes[2]))) - else - sleep(tempo) - repeat-- - 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(lentext(l) > MUSIC_MAXLINECHARS) - to_chat(usr, "Line [linenum] too long!") - lines.Remove(l) - else - linenum++ - updateDialog(usr) // make sure updates when complete - -/datum/song/Topic(href, href_list) - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - usr << browse(null, "window=instrument") - usr.unset_machine() - return - - instrumentObj.add_fingerprint(usr) - - if(href_list["newsong"]) - lines = new() - tempo = sanitize_tempo(5) // default 120 BPM - name = "" - - else if(href_list["import"]) - var/t = "" - do - t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - - if(lentext(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(cont == "no") - break - while(lentext(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 || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(lines.len > MUSIC_MAXLINES) - return - if(lentext(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 || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(lentext(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/Initialize() - . = ..() - song = new("piano", src) - - if(prob(50) && icon_state == initial(icon_state)) - name = "space minimoog" - desc = "This is a minimoog, like a space piano, but more spacey!" - icon_state = "minimoog" - else - name = "space piano" - desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." - icon_state = "piano" - -/obj/structure/piano/Destroy() - qdel(song) - song = null - return ..() - -/obj/structure/piano/Initialize(mapload) - . = ..() - if(mapload) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - -/obj/structure/piano/attack_hand(mob/user) - . = ..() - if(.) - return - interact(user) - -/obj/structure/piano/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/piano/interact(mob/user) - ui_interact(user) - -/obj/structure/piano/ui_interact(mob/user) - if(!user || !anchored) - return - - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return 1 - user.set_machine(src) - song.interact(user) - -/obj/structure/piano/wrench_act(mob/living/user, obj/item/I) - default_unfasten_wrench(user, I, 40) - return TRUE + +#define MUSICIAN_HEARCHECK_MINDELAY 4 +#define MUSIC_MAXLINES 300 +#define MUSIC_MAXLINECHARS 50 + +/datum/song + var/name = "Untitled" + var/list/lines = new() + var/tempo = 5 // delay between notes + + var/playing = 0 // if we're playing + var/help = 0 // if help is open + var/edit = 1 // if we're in editing mode + var/repeat = 0 // number of times remaining to repeat + var/max_repeats = 10 // maximum times we can repeat + + var/instrumentDir = "piano" // the folder with the sounds + var/instrumentExt = "ogg" // the file extension + var/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(mob/user, note, acc as text, oct) + // handle accidental -> B<>C of E<>F + if(acc == "b" && (note == 3 || note == 6)) // C or F + if(note == 3) + oct-- + note-- + acc = "n" + else if(acc == "#" && (note == 2 || note == 5)) // B or E + if(note == 2) + oct++ + note++ + acc = "n" + else if(acc == "#" && (note == 7)) //G# + note = 1 + acc = "b" + else if(acc == "#") // mass convert all sharps to flats, octave jump already handled + acc = "b" + note++ + + // check octave, C is allowed to go to 9 + if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) + return + + // now generate name + var/soundfile = "sound/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)) + LAZYADD(hearing_mobs, M) + last_hearcheck = world.time + + var/sound/music_played = sound(soundfile) + for(var/i in hearing_mobs) + var/mob/M = i + if(HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M)) + var/mob/living/L = M + L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC) + if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) + continue + M.playsound_local(source, null, 100, falloff = 5, S = music_played) + +/datum/song/proc/updateDialog(mob/user) + instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise + +/datum/song/proc/shouldStopPlaying(mob/user) + if(instrumentObj) + if(!user.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return TRUE + return !instrumentObj.anchored // add special cases to stop in subclasses + else + return TRUE + +/datum/song/proc/playsong(mob/user) + while(repeat >= 0) + var/cur_oct[7] + var/cur_acc[7] + for(var/i = 1 to 7) + cur_oct[i] = 3 + cur_acc[i] = "n" + + for(var/line in lines) + for(var/beat in splittext(lowertext(line), ",")) + var/list/notes = splittext(beat, "/") + for(var/note in splittext(notes[1], "-")) + if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case + playing = FALSE + hearing_mobs = null + return + if(!lentext(note)) + continue + var/cur_note = text2ascii(note) - 96 + if(cur_note < 1 || cur_note > 7) + continue + for(var/i=2 to lentext(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(user, cur_note, cur_acc[cur_note], cur_oct[cur_note]) + if(notes.len >= 2 && text2num(notes[2])) + sleep(sanitize_tempo(tempo / text2num(notes[2]))) + else + sleep(tempo) + repeat-- + 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(lentext(l) > MUSIC_MAXLINECHARS) + to_chat(usr, "Line [linenum] too long!") + lines.Remove(l) + else + linenum++ + updateDialog(usr) // make sure updates when complete + +/datum/song/Topic(href, href_list) + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + usr << browse(null, "window=instrument") + usr.unset_machine() + return + + instrumentObj.add_fingerprint(usr) + + if(href_list["newsong"]) + lines = new() + tempo = sanitize_tempo(5) // default 120 BPM + name = "" + + else if(href_list["import"]) + var/t = "" + do + t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + + if(lentext(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(cont == "no") + break + while(lentext(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 || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(lines.len > MUSIC_MAXLINES) + return + if(lentext(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 || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(lentext(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/Initialize() + . = ..() + song = new("piano", src) + + if(prob(50) && icon_state == initial(icon_state)) + name = "space minimoog" + desc = "This is a minimoog, like a space piano, but more spacey!" + icon_state = "minimoog" + else + name = "space piano" + desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." + icon_state = "piano" + +/obj/structure/piano/Destroy() + qdel(song) + song = null + return ..() + +/obj/structure/piano/Initialize(mapload) + . = ..() + if(mapload) + song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded + +/obj/structure/piano/attack_hand(mob/user) + . = ..() + if(.) + return + interact(user) + +/obj/structure/piano/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/piano/interact(mob/user) + ui_interact(user) + +/obj/structure/piano/ui_interact(mob/user) + if(!user || !anchored) + return + + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return 1 + user.set_machine(src) + song.interact(user) + +/obj/structure/piano/wrench_act(mob/living/user, obj/item/I) + default_unfasten_wrench(user, I, 40) + return TRUE diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm index b43b7292f2a0..f4bca1f900e1 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 9fd097f6ad23..8e9475118638 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/Initialize() - . = ..() - tumbler_1_pos = rand(0, 71) - tumbler_1_open = rand(0, 71) - - tumbler_2_pos = rand(0, 71) - tumbler_2_open = rand(0, 71) - - -/obj/structure/safe/Initialize(mapload) - . = ..() - - if(!mapload) - return - - for(var/obj/item/I in loc) - if(space >= maxspace) - return - if(I.w_class + space <= maxspace) - space += I.w_class - I.forceMove(src) - - -/obj/structure/safe/proc/check_unlocked(mob/user, canhear) - if(explosion_count > 2) - return 1 - if(user && canhear) - if(tumbler_1_pos == tumbler_1_open) - to_chat(user, "You hear a [pick("tonk", "krunk", "plunk")] from [src].") - if(tumbler_2_pos == tumbler_2_open) - to_chat(user, "You hear a [pick("tink", "krink", "plink")] from [src].") - if(tumbler_1_pos == tumbler_1_open && tumbler_2_pos == tumbler_2_open) - if(user) - visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!") - return TRUE - return FALSE - -/obj/structure/safe/proc/decrement(num) - num -= 1 - if(num < 0) - num = 71 - return num - -/obj/structure/safe/proc/increment(num) - num += 1 - if(num > 71) - num = 0 - return num - -/obj/structure/safe/update_icon() - 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 += "
                [entry][functions]
                " - for(var/i = contents.len, i>=1, i--) - var/obj/item/P = contents[i] - dat += "" - dat += "
                [P.name]
                " - user << browse("[name][dat]", "window=safe;size=350x300") - -/obj/structure/safe/Topic(href, href_list) - if(!ishuman(usr)) - return - var/mob/living/carbon/human/user = usr - - if(!user.canUseTopic(src, BE_CLOSE)) - return - - var/canhear = FALSE - if(user.is_holding_item_of_type(/obj/item/clothing/neck/stethoscope)) - canhear = TRUE - - if(href_list["open"]) - if(check_unlocked()) - to_chat(user, "You [open ? "close" : "open"] [src].") - open = !open - update_icon() - updateUsrDialog() - return - else - to_chat(user, "You can't [open ? "close" : "open"] [src], the lock is engaged!") - return - - if(href_list["decrement"]) - dial = decrement(dial) - if(dial == tumbler_1_pos + 1 || dial == tumbler_1_pos - 71) - tumbler_1_pos = decrement(tumbler_1_pos) - if(canhear) - to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") - if(tumbler_1_pos == tumbler_2_pos + 37 || tumbler_1_pos == tumbler_2_pos - 35) - tumbler_2_pos = decrement(tumbler_2_pos) - if(canhear) - to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") - check_unlocked(user, canhear) - updateUsrDialog() - return - - if(href_list["increment"]) - dial = increment(dial) - if(dial == tumbler_1_pos - 1 || dial == tumbler_1_pos + 71) - tumbler_1_pos = increment(tumbler_1_pos) - if(canhear) - to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") - if(tumbler_1_pos == tumbler_2_pos - 37 || tumbler_1_pos == tumbler_2_pos + 35) - tumbler_2_pos = increment(tumbler_2_pos) - if(canhear) - to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") - check_unlocked(user, canhear) - updateUsrDialog() - return - - if(href_list["retrieve"]) - user << browse("", "window=safe") // Close the menu - - var/obj/item/P = locate(href_list["retrieve"]) in src - if(open) - if(P && in_range(src, user)) - user.put_in_hands(P) - space -= P.w_class - updateUsrDialog() - - -/obj/structure/safe/attackby(obj/item/I, mob/user, params) - if(open) - . = 1 //no afterattack - if(I.w_class + space <= maxspace) - space += I.w_class - if(!user.transferItemToLoc(I, src)) - to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the safe!") - return - to_chat(user, "You put [I] in [src].") - updateUsrDialog() - return - else - to_chat(user, "[I] won't fit in [src].") - return - else if(istype(I, /obj/item/clothing/neck/stethoscope)) - to_chat(user, "Hold [I] in one of your hands while you manipulate the dial!") - else - return ..() - - -/obj/structure/safe/handle_atom_del(atom/A) - updateUsrDialog() - -/obj/structure/safe/blob_act(obj/structure/blob/B) - return - -/obj/structure/safe/ex_act(severity, target) - if(((severity == 2 && target == src) || severity == 1) && explosion_count < 3) - explosion_count++ - switch(explosion_count) - if(1) - desc = initial(desc) + "\nIt looks a little banged up." - if(2) - desc = initial(desc) + "\nIt's pretty heavily damaged." - if(3) - desc = initial(desc) + "\nThe lock seems to be broken." - - -//FLOOR SAFES -/obj/structure/safe/floor - name = "floor safe" - icon_state = "floorsafe" - density = FALSE - 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/Initialize() + . = ..() + tumbler_1_pos = rand(0, 71) + tumbler_1_open = rand(0, 71) + + tumbler_2_pos = rand(0, 71) + tumbler_2_open = rand(0, 71) + + +/obj/structure/safe/Initialize(mapload) + . = ..() + + if(!mapload) + return + + for(var/obj/item/I in loc) + if(space >= maxspace) + return + if(I.w_class + space <= maxspace) + space += I.w_class + I.forceMove(src) + + +/obj/structure/safe/proc/check_unlocked(mob/user, canhear) + if(explosion_count > 2) + return 1 + if(user && canhear) + if(tumbler_1_pos == tumbler_1_open) + to_chat(user, "You hear a [pick("tonk", "krunk", "plunk")] from [src].") + if(tumbler_2_pos == tumbler_2_open) + to_chat(user, "You hear a [pick("tink", "krink", "plink")] from [src].") + if(tumbler_1_pos == tumbler_1_open && tumbler_2_pos == tumbler_2_open) + if(user) + visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!") + return TRUE + return FALSE + +/obj/structure/safe/proc/decrement(num) + num -= 1 + if(num < 0) + num = 71 + return num + +/obj/structure/safe/proc/increment(num) + num += 1 + if(num > 71) + num = 0 + return num + +/obj/structure/safe/update_icon() + if(open) + icon_state = "[initial(icon_state)]-open" + else + icon_state = initial(icon_state) + +/obj/structure/safe/ui_interact(mob/user) + user.set_machine(src) + var/dat = "
                " + dat += "[open ? "Close" : "Open"] [src] | - [dial] +" + if(open) + dat += "" + for(var/i = contents.len, i>=1, i--) + var/obj/item/P = contents[i] + dat += "" + dat += "
                [P.name]
                " + user << browse("[name][dat]", "window=safe;size=350x300") + +/obj/structure/safe/Topic(href, href_list) + if(!ishuman(usr)) + return + var/mob/living/carbon/human/user = usr + + if(!user.canUseTopic(src, BE_CLOSE)) + return + + var/canhear = FALSE + if(user.is_holding_item_of_type(/obj/item/clothing/neck/stethoscope)) + canhear = TRUE + + if(href_list["open"]) + if(check_unlocked()) + to_chat(user, "You [open ? "close" : "open"] [src].") + open = !open + update_icon() + updateUsrDialog() + return + else + to_chat(user, "You can't [open ? "close" : "open"] [src], the lock is engaged!") + return + + if(href_list["decrement"]) + dial = decrement(dial) + if(dial == tumbler_1_pos + 1 || dial == tumbler_1_pos - 71) + tumbler_1_pos = decrement(tumbler_1_pos) + if(canhear) + to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") + if(tumbler_1_pos == tumbler_2_pos + 37 || tumbler_1_pos == tumbler_2_pos - 35) + tumbler_2_pos = decrement(tumbler_2_pos) + if(canhear) + to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") + check_unlocked(user, canhear) + updateUsrDialog() + return + + if(href_list["increment"]) + dial = increment(dial) + if(dial == tumbler_1_pos - 1 || dial == tumbler_1_pos + 71) + tumbler_1_pos = increment(tumbler_1_pos) + if(canhear) + to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") + if(tumbler_1_pos == tumbler_2_pos - 37 || tumbler_1_pos == tumbler_2_pos + 35) + tumbler_2_pos = increment(tumbler_2_pos) + if(canhear) + to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") + check_unlocked(user, canhear) + updateUsrDialog() + return + + if(href_list["retrieve"]) + user << browse("", "window=safe") // Close the menu + + var/obj/item/P = locate(href_list["retrieve"]) in src + if(open) + if(P && in_range(src, user)) + user.put_in_hands(P) + space -= P.w_class + updateUsrDialog() + + +/obj/structure/safe/attackby(obj/item/I, mob/user, params) + if(open) + . = 1 //no afterattack + if(I.w_class + space <= maxspace) + space += I.w_class + if(!user.transferItemToLoc(I, src)) + to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the safe!") + return + to_chat(user, "You put [I] in [src].") + updateUsrDialog() + return + else + to_chat(user, "[I] won't fit in [src].") + return + else if(istype(I, /obj/item/clothing/neck/stethoscope)) + to_chat(user, "Hold [I] in one of your hands while you manipulate the dial!") + else + return ..() + + +/obj/structure/safe/handle_atom_del(atom/A) + updateUsrDialog() + +/obj/structure/safe/blob_act(obj/structure/blob/B) + return + +/obj/structure/safe/ex_act(severity, target) + if(((severity == 2 && target == src) || severity == 1) && explosion_count < 3) + explosion_count++ + switch(explosion_count) + if(1) + desc = initial(desc) + "\nIt looks a little banged up." + if(2) + desc = initial(desc) + "\nIt's pretty heavily damaged." + if(3) + desc = initial(desc) + "\nThe lock seems to be broken." + + +//FLOOR SAFES +/obj/structure/safe/floor + name = "floor safe" + icon_state = "floorsafe" + density = FALSE + 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 1f53138abca9..abe0133d9db4 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -1,575 +1,575 @@ -/* Tables and Racks - * Contains: - * Tables - * Glass Tables - * Wooden Tables - * Reinforced Tables - * Racks - * Rack Parts - */ - -/* - * Tables - */ - -/obj/structure/table - name = "table" - desc = "A square piece of metal standing on four metal legs. It can not move." - icon = 'icons/obj/smooth_structures/table.dmi' - icon_state = "table" - density = TRUE - anchored = TRUE - layer = TABLE_LAYER - climbable = TRUE - pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.") - var/frame = /obj/structure/table_frame - var/framestack = /obj/item/stack/rods - var/buildstack = /obj/item/stack/sheet/metal - var/busy = FALSE - var/buildstackamount = 1 - var/framestackamount = 2 - var/deconstruction_ready = 1 - 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/attack_tk() - return FALSE - -/obj/structure/table/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return 1 - //YOGS start - flying over tables in nograv - if(iscarbon(mover)) - var/mob/living/carbon/C = mover - var/obj/item/tank/jetpack/jetpacktable = C.get_jetpack() - if(jetpacktable && jetpacktable.on && !has_gravity(C)) - return 1 - //YOGS end - flying over tables in nograv - 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(loc) - pushed_mob.set_resting(TRUE, TRUE) - pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \ - "[user] places [pushed_mob] onto [src].") - log_combat(user, pushed_mob, "places", null, "onto [src]") - -/obj/structure/table/proc/tablepush(mob/living/user, mob/living/pushed_mob) - 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.Paralyze(40) - pushed_mob.visible_message("[user] pushes [pushed_mob] onto [src].", \ - "[user] pushes [pushed_mob] 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/attackby(obj/item/I, mob/user, params) - if(!(flags_1 & NODECONSTRUCT_1)) - if(I.tool_behaviour == TOOL_SCREWDRIVER && deconstruction_ready) - to_chat(user, "You start disassembling [src]...") - if(I.use_tool(src, user, 20, volume=50)) - deconstruct(TRUE) - return - - if(I.tool_behaviour == TOOL_WRENCH && deconstruction_ready) - to_chat(user, "You start deconstructing [src]...") - if(I.use_tool(src, user, 40, volume=50)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 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/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/Initialize() - . = ..() - debris += new frame - debris += new /obj/item/shard - -/obj/structure/table/glass/Destroy() - QDEL_LIST(debris) - . = ..() - -/obj/structure/table/glass/Crossed(atom/movable/AM) - . = ..() - if(flags_1 & NODECONSTRUCT_1) - return - if(!isliving(AM)) - return - // Don't break if they're just flying past - if(AM.throwing) - addtimer(CALLBACK(src, .proc/throw_check, AM), 5) - else - check_break(AM) - -/obj/structure/table/glass/proc/throw_check(mob/living/M) - if(M.loc == get_turf(src)) - check_break(M) - -/obj/structure/table/glass/proc/check_break(mob/living/M) - if(M.has_gravity() && M.mob_size > MOB_SIZE_SMALL && !(M.movement_type & FLYING)) - table_shatter(M) - -/obj/structure/table/glass/proc/table_shatter(mob/living/L) - visible_message("[src] breaks!", - "You hear breaking glass.") - var/turf/T = get_turf(src) - playsound(T, "shatter", 50, 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.Paralyze(100) - qdel(src) - -/obj/structure/table/glass/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - ..() - return - else - var/turf/T = get_turf(src) - playsound(T, "shatter", 50, 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 - -/* - * 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) - 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' - -/* - * 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." - else - return "The top cover is firmly welded on." - -/obj/structure/table/reinforced/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WELDER) - 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/Initialize() - . = ..() - 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/Initialize() - . = ..() - 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(loc) - pushed_mob.set_resting(TRUE, TRUE) - visible_message("[user] has laid [pushed_mob] on [src].") - check_patient() - -/obj/structure/table/optable/proc/check_patient() - var/mob/living/carbon/human/M = locate(/mob/living/carbon/human, loc) - if(M) - if(M.resting) - patient = M - return TRUE - else - patient = null - return FALSE - -/* - * Racks - */ -/obj/structure/rack - name = "rack" - desc = "Different from the Middle Ages version." - icon = 'icons/obj/objects.dmi' - icon_state = "rack" - layer = TABLE_LAYER - density = TRUE - anchored = TRUE - pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density. - max_integrity = 20 - -/obj/structure/rack/examine(mob/user) - . = ..() - . += "It's held together by a couple of bolts." - -/obj/structure/rack/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 (W.tool_behaviour == TOOL_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.mobility_flags & MOBILITY_STAND) || user.get_num_legs() < 2) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src, ATTACK_EFFECT_KICK) - user.visible_message("[user] kicks [src].", null, null, COMBAT_MESSAGE_RANGE) - take_damage(rand(4,8), BRUTE, "melee", 1) - -/obj/structure/rack/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(loc, 'sound/items/dodgeball.ogg', 80, 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 (W.tool_behaviour == TOOL_WRENCH) - new /obj/item/stack/sheet/metal(user.loc) - qdel(src) - else - . = ..() - -/obj/item/rack_parts/attack_self(mob/user) - if(building) - return - building = TRUE - to_chat(user, "You start constructing a rack...") - if(do_after(user, 50, target = user, progress=TRUE)) - if(!user.temporarilyRemoveItemFromInventory(src)) - return - var/obj/structure/rack/R = new /obj/structure/rack(user.loc) - user.visible_message("[user] assembles \a [R].\ - ", "You assemble \a [R].") - R.add_fingerprint(user) - qdel(src) - building = FALSE +/* Tables and Racks + * Contains: + * Tables + * Glass Tables + * Wooden Tables + * Reinforced Tables + * Racks + * Rack Parts + */ + +/* + * Tables + */ + +/obj/structure/table + name = "table" + desc = "A square piece of metal standing on four metal legs. It can not move." + icon = 'icons/obj/smooth_structures/table.dmi' + icon_state = "table" + density = TRUE + anchored = TRUE + layer = TABLE_LAYER + climbable = TRUE + pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.") + var/frame = /obj/structure/table_frame + var/framestack = /obj/item/stack/rods + var/buildstack = /obj/item/stack/sheet/metal + var/busy = FALSE + var/buildstackamount = 1 + var/framestackamount = 2 + var/deconstruction_ready = 1 + 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/attack_tk() + return FALSE + +/obj/structure/table/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return 1 + //YOGS start - flying over tables in nograv + if(iscarbon(mover)) + var/mob/living/carbon/C = mover + var/obj/item/tank/jetpack/jetpacktable = C.get_jetpack() + if(jetpacktable && jetpacktable.on && !has_gravity(C)) + return 1 + //YOGS end - flying over tables in nograv + 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(loc) + pushed_mob.set_resting(TRUE, TRUE) + pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \ + "[user] places [pushed_mob] onto [src].") + log_combat(user, pushed_mob, "places", null, "onto [src]") + +/obj/structure/table/proc/tablepush(mob/living/user, mob/living/pushed_mob) + 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.Paralyze(40) + pushed_mob.visible_message("[user] pushes [pushed_mob] onto [src].", \ + "[user] pushes [pushed_mob] 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/attackby(obj/item/I, mob/user, params) + if(!(flags_1 & NODECONSTRUCT_1)) + if(I.tool_behaviour == TOOL_SCREWDRIVER && deconstruction_ready) + to_chat(user, "You start disassembling [src]...") + if(I.use_tool(src, user, 20, volume=50)) + deconstruct(TRUE) + return + + if(I.tool_behaviour == TOOL_WRENCH && deconstruction_ready) + to_chat(user, "You start deconstructing [src]...") + if(I.use_tool(src, user, 40, volume=50)) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 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/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/Initialize() + . = ..() + debris += new frame + debris += new /obj/item/shard + +/obj/structure/table/glass/Destroy() + QDEL_LIST(debris) + . = ..() + +/obj/structure/table/glass/Crossed(atom/movable/AM) + . = ..() + if(flags_1 & NODECONSTRUCT_1) + return + if(!isliving(AM)) + return + // Don't break if they're just flying past + if(AM.throwing) + addtimer(CALLBACK(src, .proc/throw_check, AM), 5) + else + check_break(AM) + +/obj/structure/table/glass/proc/throw_check(mob/living/M) + if(M.loc == get_turf(src)) + check_break(M) + +/obj/structure/table/glass/proc/check_break(mob/living/M) + if(M.has_gravity() && M.mob_size > MOB_SIZE_SMALL && !(M.movement_type & FLYING)) + table_shatter(M) + +/obj/structure/table/glass/proc/table_shatter(mob/living/L) + visible_message("[src] breaks!", + "You hear breaking glass.") + var/turf/T = get_turf(src) + playsound(T, "shatter", 50, 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.Paralyze(100) + qdel(src) + +/obj/structure/table/glass/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags_1 & NODECONSTRUCT_1)) + if(disassembled) + ..() + return + else + var/turf/T = get_turf(src) + playsound(T, "shatter", 50, 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 + +/* + * 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) + 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' + +/* + * 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." + else + return "The top cover is firmly welded on." + +/obj/structure/table/reinforced/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WELDER) + 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/Initialize() + . = ..() + 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/Initialize() + . = ..() + 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(loc) + pushed_mob.set_resting(TRUE, TRUE) + visible_message("[user] has laid [pushed_mob] on [src].") + check_patient() + +/obj/structure/table/optable/proc/check_patient() + var/mob/living/carbon/human/M = locate(/mob/living/carbon/human, loc) + if(M) + if(M.resting) + patient = M + return TRUE + else + patient = null + return FALSE + +/* + * Racks + */ +/obj/structure/rack + name = "rack" + desc = "Different from the Middle Ages version." + icon = 'icons/obj/objects.dmi' + icon_state = "rack" + layer = TABLE_LAYER + density = TRUE + anchored = TRUE + pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density. + max_integrity = 20 + +/obj/structure/rack/examine(mob/user) + . = ..() + . += "It's held together by a couple of bolts." + +/obj/structure/rack/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 (W.tool_behaviour == TOOL_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.mobility_flags & MOBILITY_STAND) || user.get_num_legs() < 2) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src, ATTACK_EFFECT_KICK) + user.visible_message("[user] kicks [src].", null, null, COMBAT_MESSAGE_RANGE) + take_damage(rand(4,8), BRUTE, "melee", 1) + +/obj/structure/rack/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(loc, 'sound/items/dodgeball.ogg', 80, 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 (W.tool_behaviour == TOOL_WRENCH) + new /obj/item/stack/sheet/metal(user.loc) + qdel(src) + else + . = ..() + +/obj/item/rack_parts/attack_self(mob/user) + if(building) + return + building = TRUE + to_chat(user, "You start constructing a rack...") + if(do_after(user, 50, target = user, progress=TRUE)) + if(!user.temporarilyRemoveItemFromInventory(src)) + return + var/obj/structure/rack/R = new /obj/structure/rack(user.loc) + user.visible_message("[user] assembles \a [R].\ + ", "You assemble \a [R].") + R.add_fingerprint(user) + qdel(src) + building = FALSE diff --git a/code/game/objects/structures/tank_dispenser.dm b/code/game/objects/structures/tank_dispenser.dm index ba437ed7b005..e828f55f7ab1 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(I.tool_behaviour == TOOL_WRENCH) - default_unfasten_wrench(user, I, time = 20) - return - else if(user.a_intent != INTENT_HARM) - to_chat(user, "[I] does not fit into [src].") - return - else - return ..() - if(full) - to_chat(user, "[src] can't hold any more of [I].") - return - - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You put [I] in [src].") - update_icon() - -/obj/structure/tank_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "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(I.tool_behaviour == TOOL_WRENCH) + default_unfasten_wrench(user, I, time = 20) + return + else if(user.a_intent != INTENT_HARM) + to_chat(user, "[I] does not fit into [src].") + return + else + return ..() + if(full) + to_chat(user, "[src] can't hold any more of [I].") + return + + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You put [I] in [src].") + update_icon() + +/obj/structure/tank_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "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 8ce0ffc91c98..86a3840665c2 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 9a2898f2d89f..8d5c2e31bcad 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 +=1 - if(existing_tubes >= 2) - to_chat(user, "You cannot wrench anymore 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 +=1 + if(existing_tubes >= 2) + to_chat(user, "You cannot wrench anymore 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 bd5688038444..6c0bc7ee8931 100644 --- a/code/game/objects/structures/windoor_assembly.dm +++ b/code/game/objects/structures/windoor_assembly.dm @@ -1,362 +1,362 @@ -/* 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(W.tool_behaviour == TOOL_WELDER && !anchored) - if(!W.tool_start_check(user, amount=0)) - return - - user.visible_message("[user] disassembles the windoor assembly.", - "You start to disassemble the windoor assembly...") - - if(W.use_tool(src, user, 40, volume=50)) - to_chat(user, "You disassemble the windoor assembly.") - var/obj/item/stack/sheet/rglass/RG = new (get_turf(src), 5) - RG.add_fingerprint(user) - if(secure) - var/obj/item/stack/rods/R = new (get_turf(src), 4) - R.add_fingerprint(user) - qdel(src) - return - - //Wrenching an unsecure assembly anchors it in place. Step 4 complete - if(W.tool_behaviour == TOOL_WRENCH && !anchored) - for(var/obj/machinery/door/window/WD in loc) - if(WD.dir == dir) - to_chat(user, "There is already a windoor in that location!") - return - user.visible_message("[user] secures the windoor assembly to the floor.", - "You start to secure the windoor assembly to the floor...") - - if(W.use_tool(src, user, 40, volume=100)) - if(anchored) - return - for(var/obj/machinery/door/window/WD in loc) - if(WD.dir == dir) - to_chat(user, "There is already a windoor in that location!") - return - to_chat(user, "You secure the windoor assembly.") - setAnchored(TRUE) - if(secure) - name = "secure anchored windoor assembly" - else - name = "anchored windoor assembly" - - //Unwrenching an unsecure assembly un-anchors it. Step 4 undone - else if(W.tool_behaviour == TOOL_WRENCH && anchored) - user.visible_message("[user] unsecures the windoor assembly to the floor.", - "You start to unsecure the windoor assembly to the floor...") - - if(W.use_tool(src, user, 40, volume=100)) - if(!anchored) - return - to_chat(user, "You unsecure the windoor assembly.") - setAnchored(FALSE) - if(secure) - name = "secure windoor assembly" - else - name = "windoor assembly" - - //Adding plasteel makes the assembly a secure windoor assembly. Step 2 (optional) complete. - else if(istype(W, /obj/item/stack/sheet/plasteel) && !secure) - var/obj/item/stack/sheet/plasteel/P = W - if(P.get_amount() < 2) - to_chat(user, "You need more plasteel to do this!") - return - to_chat(user, "You start to reinforce the windoor with plasteel...") - - if(do_after(user,40, target = src)) - if(!src || secure || P.get_amount() < 2) - return - - P.use(2) - to_chat(user, "You reinforce the windoor.") - secure = TRUE - if(anchored) - name = "secure anchored windoor assembly" - else - name = "secure windoor assembly" - - //Adding cable to the assembly. Step 5 complete. - else if(istype(W, /obj/item/stack/cable_coil) && anchored) - user.visible_message("[user] wires the windoor assembly.", "You start to wire the windoor assembly...") - - if(do_after(user, 40, target = src)) - if(!src || !anchored || src.state != "01") - return - var/obj/item/stack/cable_coil/CC = W - if(!CC.use(1)) - to_chat(user, "You need more cable to do this!") - return - to_chat(user, "You wire the windoor.") - state = "02" - if(secure) - name = "secure wired windoor assembly" - else - name = "wired windoor assembly" - else - return ..() - - if("02") - - //Removing wire from the assembly. Step 5 undone. - if(W.tool_behaviour == TOOL_WIRECUTTER) - user.visible_message("[user] cuts the wires from the airlock assembly.", "You start to cut the wires from airlock assembly...") - - if(W.use_tool(src, user, 40, volume=100)) - if(state != "02") - return - - to_chat(user, "You cut the windoor wires.") - new/obj/item/stack/cable_coil(get_turf(user), 1) - state = "01" - if(secure) - name = "secure anchored windoor assembly" - else - name = "anchored windoor assembly" - - //Adding airlock electronics for access. Step 6 complete. - else if(istype(W, /obj/item/electronics/airlock)) - if(!user.transferItemToLoc(W, src)) - return - W.play_tool_sound(src, 100) - user.visible_message("[user] installs the electronics into the airlock assembly.", - "You start to install electronics into the airlock assembly...") - - if(do_after(user, 40, target = src)) - if(!src || electronics) - W.forceMove(drop_location()) - return - to_chat(user, "You install the airlock electronics.") - name = "near finished windoor assembly" - electronics = W - else - W.forceMove(drop_location()) - - //Screwdriver to remove airlock electronics. Step 6 undone. - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(!electronics) - return - - user.visible_message("[user] removes the electronics from the airlock assembly.", - "You start to uninstall electronics from the airlock assembly...") - - if(W.use_tool(src, user, 40, volume=100) && electronics) - to_chat(user, "You remove the airlock electronics.") - name = "wired windoor assembly" - var/obj/item/electronics/airlock/ae - ae = electronics - electronics = null - ae.forceMove(drop_location()) - - else if(istype(W, /obj/item/pen)) - var/t = stripped_input(user, "Enter the name for the door.", name, created_name,MAX_NAME_LEN) - if(!t) - return - if(!in_range(src, usr) && loc != usr) - return - created_name = t - return - - - - //Crowbar to complete the assembly, Step 7 complete. - else if(W.tool_behaviour == TOOL_CROWBAR) - if(!electronics) - to_chat(usr, "The assembly is missing electronics!") - return - user << browse(null, "window=windoor_access") - user.visible_message("[user] pries the windoor into the frame.", - "You start prying the windoor into the frame...") - - if(W.use_tool(src, user, 40, volume=100) && electronics) - - density = TRUE //Shouldn't matter but just incase - to_chat(user, "You finish the windoor.") - - if(secure) - var/obj/machinery/door/window/brigdoor/windoor = new /obj/machinery/door/window/brigdoor(loc) - if(facing == "l") - windoor.icon_state = "leftsecureopen" - windoor.base_state = "leftsecure" - else - windoor.icon_state = "rightsecureopen" - windoor.base_state = "rightsecure" - windoor.setDir(dir) - windoor.density = FALSE - - if(electronics.one_access) - windoor.req_one_access = electronics.accesses - else - windoor.req_access = electronics.accesses - windoor.electronics = electronics - electronics.forceMove(windoor) - if(created_name) - windoor.name = created_name - qdel(src) - windoor.close() - - - else - var/obj/machinery/door/window/windoor = new /obj/machinery/door/window(loc) - if(facing == "l") - windoor.icon_state = "leftopen" - windoor.base_state = "left" - else - windoor.icon_state = "rightopen" - windoor.base_state = "right" - windoor.setDir(dir) - windoor.density = FALSE - - if(electronics.one_access) - windoor.req_one_access = electronics.accesses - else - windoor.req_access = electronics.accesses - windoor.electronics = electronics - electronics.loc = windoor - if(created_name) - windoor.name = created_name - qdel(src) - windoor.close() - - - else - return ..() - - //Update to reflect changes(if applicable) - update_icon() - - - -/obj/structure/windoor_assembly/ComponentInitialize() - . = ..() - 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.restrained()) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_USE)) - return - - if(facing == "l") - to_chat(usr, "The windoor will now slide to the right.") - facing = "r" - else - facing = "l" - to_chat(usr, "The windoor will now slide to the left.") - - update_icon() - return +/* Windoor (window door) assembly -Nodrak + * Step 1: Create a windoor out of rglass + * Step 2: Add r-glass to the assembly to make a secure windoor (Optional) + * Step 3: Rotate or Flip the assembly to face and open the way you want + * Step 4: Wrench the assembly in place + * Step 5: Add cables to the assembly + * Step 6: Set access for the door. + * Step 7: Screwdriver the door to complete + */ + + +/obj/structure/windoor_assembly + icon = 'icons/obj/doors/windoor.dmi' + + name = "windoor Assembly" + icon_state = "l_windoor_assembly01" + desc = "A small glass and wire assembly for windoors." + anchored = FALSE + density = FALSE + dir = NORTH + + var/ini_dir + var/obj/item/electronics/airlock/electronics = null + var/created_name = null + + //Vars to help with the icon's name + var/facing = "l" //Does the windoor open to the left or right? + var/secure = FALSE //Whether or not this creates a secure windoor + var/state = "01" //How far the door assembly has progressed + CanAtmosPass = ATMOS_PASS_PROC + +/obj/structure/windoor_assembly/New(loc, set_dir) + ..() + if(set_dir) + setDir(set_dir) + ini_dir = dir + air_update_turf(1) + +/obj/structure/windoor_assembly/Destroy() + density = FALSE + air_update_turf(1) + return ..() + +/obj/structure/windoor_assembly/Move() + var/turf/T = loc + . = ..() + setDir(ini_dir) + move_update_air(T) + +/obj/structure/windoor_assembly/update_icon() + 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(W.tool_behaviour == TOOL_WELDER && !anchored) + if(!W.tool_start_check(user, amount=0)) + return + + user.visible_message("[user] disassembles the windoor assembly.", + "You start to disassemble the windoor assembly...") + + if(W.use_tool(src, user, 40, volume=50)) + to_chat(user, "You disassemble the windoor assembly.") + var/obj/item/stack/sheet/rglass/RG = new (get_turf(src), 5) + RG.add_fingerprint(user) + if(secure) + var/obj/item/stack/rods/R = new (get_turf(src), 4) + R.add_fingerprint(user) + qdel(src) + return + + //Wrenching an unsecure assembly anchors it in place. Step 4 complete + if(W.tool_behaviour == TOOL_WRENCH && !anchored) + for(var/obj/machinery/door/window/WD in loc) + if(WD.dir == dir) + to_chat(user, "There is already a windoor in that location!") + return + user.visible_message("[user] secures the windoor assembly to the floor.", + "You start to secure the windoor assembly to the floor...") + + if(W.use_tool(src, user, 40, volume=100)) + if(anchored) + return + for(var/obj/machinery/door/window/WD in loc) + if(WD.dir == dir) + to_chat(user, "There is already a windoor in that location!") + return + to_chat(user, "You secure the windoor assembly.") + setAnchored(TRUE) + if(secure) + name = "secure anchored windoor assembly" + else + name = "anchored windoor assembly" + + //Unwrenching an unsecure assembly un-anchors it. Step 4 undone + else if(W.tool_behaviour == TOOL_WRENCH && anchored) + user.visible_message("[user] unsecures the windoor assembly to the floor.", + "You start to unsecure the windoor assembly to the floor...") + + if(W.use_tool(src, user, 40, volume=100)) + if(!anchored) + return + to_chat(user, "You unsecure the windoor assembly.") + setAnchored(FALSE) + if(secure) + name = "secure windoor assembly" + else + name = "windoor assembly" + + //Adding plasteel makes the assembly a secure windoor assembly. Step 2 (optional) complete. + else if(istype(W, /obj/item/stack/sheet/plasteel) && !secure) + var/obj/item/stack/sheet/plasteel/P = W + if(P.get_amount() < 2) + to_chat(user, "You need more plasteel to do this!") + return + to_chat(user, "You start to reinforce the windoor with plasteel...") + + if(do_after(user,40, target = src)) + if(!src || secure || P.get_amount() < 2) + return + + P.use(2) + to_chat(user, "You reinforce the windoor.") + secure = TRUE + if(anchored) + name = "secure anchored windoor assembly" + else + name = "secure windoor assembly" + + //Adding cable to the assembly. Step 5 complete. + else if(istype(W, /obj/item/stack/cable_coil) && anchored) + user.visible_message("[user] wires the windoor assembly.", "You start to wire the windoor assembly...") + + if(do_after(user, 40, target = src)) + if(!src || !anchored || src.state != "01") + return + var/obj/item/stack/cable_coil/CC = W + if(!CC.use(1)) + to_chat(user, "You need more cable to do this!") + return + to_chat(user, "You wire the windoor.") + state = "02" + if(secure) + name = "secure wired windoor assembly" + else + name = "wired windoor assembly" + else + return ..() + + if("02") + + //Removing wire from the assembly. Step 5 undone. + if(W.tool_behaviour == TOOL_WIRECUTTER) + user.visible_message("[user] cuts the wires from the airlock assembly.", "You start to cut the wires from airlock assembly...") + + if(W.use_tool(src, user, 40, volume=100)) + if(state != "02") + return + + to_chat(user, "You cut the windoor wires.") + new/obj/item/stack/cable_coil(get_turf(user), 1) + state = "01" + if(secure) + name = "secure anchored windoor assembly" + else + name = "anchored windoor assembly" + + //Adding airlock electronics for access. Step 6 complete. + else if(istype(W, /obj/item/electronics/airlock)) + if(!user.transferItemToLoc(W, src)) + return + W.play_tool_sound(src, 100) + user.visible_message("[user] installs the electronics into the airlock assembly.", + "You start to install electronics into the airlock assembly...") + + if(do_after(user, 40, target = src)) + if(!src || electronics) + W.forceMove(drop_location()) + return + to_chat(user, "You install the airlock electronics.") + name = "near finished windoor assembly" + electronics = W + else + W.forceMove(drop_location()) + + //Screwdriver to remove airlock electronics. Step 6 undone. + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(!electronics) + return + + user.visible_message("[user] removes the electronics from the airlock assembly.", + "You start to uninstall electronics from the airlock assembly...") + + if(W.use_tool(src, user, 40, volume=100) && electronics) + to_chat(user, "You remove the airlock electronics.") + name = "wired windoor assembly" + var/obj/item/electronics/airlock/ae + ae = electronics + electronics = null + ae.forceMove(drop_location()) + + else if(istype(W, /obj/item/pen)) + var/t = stripped_input(user, "Enter the name for the door.", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + created_name = t + return + + + + //Crowbar to complete the assembly, Step 7 complete. + else if(W.tool_behaviour == TOOL_CROWBAR) + if(!electronics) + to_chat(usr, "The assembly is missing electronics!") + return + user << browse(null, "window=windoor_access") + user.visible_message("[user] pries the windoor into the frame.", + "You start prying the windoor into the frame...") + + if(W.use_tool(src, user, 40, volume=100) && electronics) + + density = TRUE //Shouldn't matter but just incase + to_chat(user, "You finish the windoor.") + + if(secure) + var/obj/machinery/door/window/brigdoor/windoor = new /obj/machinery/door/window/brigdoor(loc) + if(facing == "l") + windoor.icon_state = "leftsecureopen" + windoor.base_state = "leftsecure" + else + windoor.icon_state = "rightsecureopen" + windoor.base_state = "rightsecure" + windoor.setDir(dir) + windoor.density = FALSE + + if(electronics.one_access) + windoor.req_one_access = electronics.accesses + else + windoor.req_access = electronics.accesses + windoor.electronics = electronics + electronics.forceMove(windoor) + if(created_name) + windoor.name = created_name + qdel(src) + windoor.close() + + + else + var/obj/machinery/door/window/windoor = new /obj/machinery/door/window(loc) + if(facing == "l") + windoor.icon_state = "leftopen" + windoor.base_state = "left" + else + windoor.icon_state = "rightopen" + windoor.base_state = "right" + windoor.setDir(dir) + windoor.density = FALSE + + if(electronics.one_access) + windoor.req_one_access = electronics.accesses + else + windoor.req_access = electronics.accesses + windoor.electronics = electronics + electronics.loc = windoor + if(created_name) + windoor.name = created_name + qdel(src) + windoor.close() + + + else + return ..() + + //Update to reflect changes(if applicable) + update_icon() + + + +/obj/structure/windoor_assembly/ComponentInitialize() + . = ..() + 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.restrained()) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_USE)) + return + + if(facing == "l") + to_chat(usr, "The windoor will now slide to the right.") + facing = "r" + else + facing = "l" + to_chat(usr, "The windoor will now slide to the left.") + + update_icon() + return diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm index 0f68e68b7b50..e5d58c3e1bfe 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/turfs/openspace/openspace.dm b/code/game/turfs/openspace/openspace.dm index 0f408b83d21d..c3f4850bd859 100644 --- a/code/game/turfs/openspace/openspace.dm +++ b/code/game/turfs/openspace/openspace.dm @@ -1,108 +1,108 @@ -/turf/open/openspace - name = "open space" - desc = "Watch your step!" - icon_state = "grey" - baseturfs = /turf/open/openspace - CanAtmosPassVertical = ATMOS_PASS_YES - plane = FLOOR_OPENSPACE_PLANE - layer = OPENSPACE_LAYER - //mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/can_cover_up = TRUE - var/can_build_on = TRUE - -/turf/open/openspace/debug/update_multiz() - ..() - return TRUE - -/turf/open/openspace/Initialize() - . = ..() - return INITIALIZE_HINT_LATELOAD - -/turf/open/openspace/LateInitialize() - update_multiz(TRUE, TRUE) - -/turf/open/openspace/Destroy() - vis_contents.len = 0 - return ..() - -/turf/open/openspace/update_multiz(prune_on_fail = FALSE, init = FALSE) - . = ..() - var/turf/T = below() - if(!T) - vis_contents.len = 0 - if(prune_on_fail) - ChangeTurf(/turf/open/floor/plating) - return FALSE - if(init) - vis_contents += T - return TRUE - -/turf/open/openspace/multiz_turf_del(turf/T, dir) - if(dir != DOWN) - return - update_multiz() - -/turf/open/openspace/multiz_turf_new(turf/T, dir) - if(dir != DOWN) - return - update_multiz() - -/turf/open/openspace/zAirIn() - return TRUE - -/turf/open/openspace/zAirOut() - return TRUE - -/turf/open/openspace/zPassIn(atom/movable/A, direction, turf/source) - return TRUE - -/turf/open/openspace/zPassOut(atom/movable/A, direction, turf/destination) - return TRUE - -/turf/open/openspace/proc/CanCoverUp() - return can_cover_up - -/turf/open/openspace/proc/CanBuildHere() - return can_build_on - -/turf/open/openspace/attackby(obj/item/C, mob/user, params) - ..() - if(!CanBuildHere()) - return - if(istype(C, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = C - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src) - if(W) - to_chat(user, "There is already a catwalk here!") - return - if(L) - if(R.use(1)) - to_chat(user, "You construct a catwalk.") - playsound(src, 'sound/weapons/genhit.ogg', 50, 1) - new/obj/structure/lattice/catwalk(src) - else - to_chat(user, "You need two rods to build a catwalk!") - return - if(R.use(1)) - to_chat(user, "You construct a lattice.") - playsound(src, 'sound/weapons/genhit.ogg', 50, 1) - ReplaceWithLattice() - else - to_chat(user, "You need one rod to build a lattice.") - return - if(istype(C, /obj/item/stack/tile/plasteel)) - if(!CanCoverUp()) - return - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - if(L) - var/obj/item/stack/tile/plasteel/S = C - if(S.use(1)) - qdel(L) - playsound(src, 'sound/weapons/genhit.ogg', 50, 1) - to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating) - else - to_chat(user, "You need one floor tile to build a floor!") - else - to_chat(user, "The plating is going to need some support! Place metal rods first.") +/turf/open/openspace + name = "open space" + desc = "Watch your step!" + icon_state = "grey" + baseturfs = /turf/open/openspace + CanAtmosPassVertical = ATMOS_PASS_YES + plane = FLOOR_OPENSPACE_PLANE + layer = OPENSPACE_LAYER + //mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/can_cover_up = TRUE + var/can_build_on = TRUE + +/turf/open/openspace/debug/update_multiz() + ..() + return TRUE + +/turf/open/openspace/Initialize() + . = ..() + return INITIALIZE_HINT_LATELOAD + +/turf/open/openspace/LateInitialize() + update_multiz(TRUE, TRUE) + +/turf/open/openspace/Destroy() + vis_contents.len = 0 + return ..() + +/turf/open/openspace/update_multiz(prune_on_fail = FALSE, init = FALSE) + . = ..() + var/turf/T = below() + if(!T) + vis_contents.len = 0 + if(prune_on_fail) + ChangeTurf(/turf/open/floor/plating) + return FALSE + if(init) + vis_contents += T + return TRUE + +/turf/open/openspace/multiz_turf_del(turf/T, dir) + if(dir != DOWN) + return + update_multiz() + +/turf/open/openspace/multiz_turf_new(turf/T, dir) + if(dir != DOWN) + return + update_multiz() + +/turf/open/openspace/zAirIn() + return TRUE + +/turf/open/openspace/zAirOut() + return TRUE + +/turf/open/openspace/zPassIn(atom/movable/A, direction, turf/source) + return TRUE + +/turf/open/openspace/zPassOut(atom/movable/A, direction, turf/destination) + return TRUE + +/turf/open/openspace/proc/CanCoverUp() + return can_cover_up + +/turf/open/openspace/proc/CanBuildHere() + return can_build_on + +/turf/open/openspace/attackby(obj/item/C, mob/user, params) + ..() + if(!CanBuildHere()) + return + if(istype(C, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = C + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src) + if(W) + to_chat(user, "There is already a catwalk here!") + return + if(L) + if(R.use(1)) + to_chat(user, "You construct a catwalk.") + playsound(src, 'sound/weapons/genhit.ogg', 50, 1) + new/obj/structure/lattice/catwalk(src) + else + to_chat(user, "You need two rods to build a catwalk!") + return + if(R.use(1)) + to_chat(user, "You construct a lattice.") + playsound(src, 'sound/weapons/genhit.ogg', 50, 1) + ReplaceWithLattice() + else + to_chat(user, "You need one rod to build a lattice.") + return + if(istype(C, /obj/item/stack/tile/plasteel)) + if(!CanCoverUp()) + return + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + if(L) + var/obj/item/stack/tile/plasteel/S = C + if(S.use(1)) + qdel(L) + playsound(src, 'sound/weapons/genhit.ogg', 50, 1) + to_chat(user, "You build a floor.") + PlaceOnTop(/turf/open/floor/plating) + else + to_chat(user, "You need one floor tile to build a floor!") + else + to_chat(user, "The plating is going to need some support! Place metal rods first.") diff --git a/code/game/world.dm b/code/game/world.dm index 8690a9314410..b6c6ad2309b0 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,339 +1,339 @@ -#define RESTART_COUNTER_PATH "data/round_counter.txt" - -GLOBAL_VAR(restart_counter) - -//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()]!") - - 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(minimum_required_security_level = TGS_SECURITY_TRUSTED) - - GLOB.revdata = new - - config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - - load_admins() - - //SetupLogs depends on the RoundID, so lets check - //DB schema and set RoundID if we can - SSdbcore.CheckSchemaVersion() - SSdbcore.SetRoundID() - SetupLogs() - -#ifndef USE_CUSTOM_ERROR_HANDLER - world.log = file("[GLOB.log_directory]/dd.log") -#else - if (TgsAvailable()) - world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. -#endif - - - load_yogs_stuff() // yogs - Donators - refresh_admin_files() //yogs - DB support - load_admins() - - LoadVerbs(/datum/verbs/menu) - if(CONFIG_GET(flag/usewhitelist)) - load_whitelist() - - setup_pretty_filter() //yogs - - GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 - - if(fexists(RESTART_COUNTER_PATH)) - GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) - fdel(RESTART_COUNTER_PATH) - - if(NO_INIT_PARAMETER in params) - return - - Master.Initialize(10, FALSE, TRUE) - - if(TEST_RUN_PARAMETER in params) - HandleTestRun() - -/world/proc/HandleTestRun() - //trigger things to run the whole process - Master.sleep_offline_after_initializations = FALSE - SSticker.start_immediately = TRUE - CONFIG_SET(number/round_end_countdown, 0) - var/datum/callback/cb -#ifdef UNIT_TESTS - cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests) -#else - cb = VARSET_CALLBACK(SSticker, force_ending, TRUE) -#endif - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS)) - -/world/proc/SetupExternalRSC() -#if (PRELOAD_RSC == 0) - GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") - var/i=1 - while(i<=GLOB.external_rsc_urls.len) - if(GLOB.external_rsc_urls[i]) - i++ - else - GLOB.external_rsc_urls.Cut(i,i+1) -#endif - -/world/proc/SetupLogs() - var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] - if(!override_dir) - var/realtime = world.realtime - var/texttime = time2text(realtime, "YYYY/MM/DD") - GLOB.log_directory = "data/logs/[texttime]/round-" - GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" - GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" - if(GLOB.round_id) - GLOB.log_directory += "[GLOB.round_id]" - GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" - GLOB.picture_log_directory += "[GLOB.round_id]" - else - var/timestamp = replacetext(time_stamp(), ":", ".") - GLOB.log_directory += "[timestamp]" - GLOB.picture_log_directory += "[timestamp]" - GLOB.picture_logging_prefix += "T_[timestamp]_" - else - GLOB.log_directory = "data/logs/[override_dir]" - GLOB.picture_logging_prefix = "O_[override_dir]_" - GLOB.picture_log_directory = "data/picture_logs/[override_dir]" - - GLOB.world_game_log = "[GLOB.log_directory]/game.log" - GLOB.world_mecha_log = "[GLOB.log_directory]/mecha.log" - GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" - GLOB.world_cloning_log = "[GLOB.log_directory]/cloning.log" - GLOB.world_asset_log = "[GLOB.log_directory]/asset.log" - GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" - GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" - GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" - GLOB.world_ntsl_log = "[GLOB.log_directory]/ntsl.log" - GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log" - GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" - GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" - GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" - GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log" - GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" - GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" - GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" - GLOB.world_paper_log = "[GLOB.log_directory]/paper.log" - -#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) - - GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently - if(fexists(GLOB.config_error_log)) - fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") - fdel(GLOB.config_error_log) - - if(GLOB.round_id) - log_game("Round ID: [GLOB.round_id]") - - // This was printed early in startup to the world log and config_error.log, - // but those are both private, so let's put the commit info in the runtime - // log which is ultimately public. - log_runtime(GLOB.revdata.get_log_message()) - -/world/Topic(T, addr, master, key) - TGS_TOPIC //redirect to server tools if necessary - - var/static/list/topic_handlers = TopicHandlers() - - var/list/input = params2list(T) - var/datum/world_topic/handler - for(var/I in topic_handlers) - if(I in input) - handler = topic_handlers[I] - break - - if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) - log_topic("\"[T]\", from:[addr], master:[master], key:[key]") - - if(!handler) - return - - handler = new handler() - return handler.TryRun(input) - -/world/proc/AnnouncePR(announcement, list/payload) - var/static/list/PRcounts = list() //PR id -> number of times announced this round - var/id = "[payload["pull_request"]["id"]]" - if(!PRcounts[id]) - PRcounts[id] = 1 - else - ++PRcounts[id] - if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) - return - - var/final_composed = "PR: [announcement]" - for(var/client/C in GLOB.clients) - C.AnnouncePR(final_composed) - -/world/proc/FinishTestRun() - set waitfor = FALSE - var/list/fail_reasons - if(GLOB) - if(GLOB.total_runtimes != 0) - fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") -#ifdef UNIT_TESTS - if(GLOB.failed_any_test) - LAZYADD(fail_reasons, "Unit Tests failed!") -#endif - if(!GLOB.log_directory) - LAZYADD(fail_reasons, "Missing GLOB.log_directory!") - else - fail_reasons = list("Missing GLOB!") - if(!fail_reasons) - text2file("Success!", "[GLOB.log_directory]/clean_run.lk") - else - log_world("Test run failed!\n[fail_reasons.Join("\n")]") - sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes - qdel(src) //shut it down - -/world/Reboot(reason = 0, fast_track = FALSE) - if (reason || fast_track) //special reboot, do none of the normal stuff - if (usr) - log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") - message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") - to_chat(world, "Rebooting World immediately due to host request") - else - to_chat(world, "Rebooting world...") - Master.Shutdown() //run SS shutdowns - - TgsReboot() - - if(TEST_RUN_PARAMETER in params) - FinishTestRun() - return - - if(TgsAvailable()) - var/do_hard_reboot - // check the hard reboot counter - var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) - switch(ruhr) - if(-1) - do_hard_reboot = FALSE - if(0) - do_hard_reboot = TRUE - else - if(GLOB.restart_counter >= ruhr) - do_hard_reboot = TRUE - else - text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) - do_hard_reboot = FALSE - - if(do_hard_reboot) - log_world("World hard rebooted at [time_stamp()]") - shutdown_logging() // See comment below. - TgsEndProcess() - - log_world("World rebooted at [time_stamp()]") - shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. - ..() - -/world/proc/update_status() //yogs -- Mirrored in the Yogs folder in March 2019. Do not edit, swallow, or submerge in acid - - var/list/features = list() - - if(GLOB.master_mode) - features += GLOB.master_mode - - if (!GLOB.enter_allowed) - features += "closed" - - var/s = "" - var/hostedby - if(config) - var/server_name = CONFIG_GET(string/servername) - if (server_name) - s += "[server_name] — " - features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" - if(CONFIG_GET(flag/allow_vote_mode)) - features += "vote" - if(CONFIG_GET(flag/allow_ai)) - features += "AI allowed" - hostedby = CONFIG_GET(string/hostedby) - - s += "[station_name()]"; - s += " (" - s += "" //Change this to wherever you want the hub to link to. - s += "Default" //Replace this with something else. Or ever better, delete it and uncomment the game version. - s += "" - s += ")" - - var/players = GLOB.clients.len - - var/popcaptext = "" - var/popcap = max(CONFIG_GET(number/extreme_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/soft_popcap)) - if (popcap) - popcaptext = "/[popcap]" - - if (players > 1) - features += "[players][popcaptext] players" - else if (players > 0) - features += "[players][popcaptext] player" - - game_state = (CONFIG_GET(number/extreme_popcap) && players >= CONFIG_GET(number/extreme_popcap)) //tells the hub if we are full - - if (!host && hostedby) - features += "hosted by [hostedby]" - - if (features) - s += ": [jointext(features, ", ")]" - - status = s - -/world/proc/update_hub_visibility(new_visibility) - if(new_visibility == GLOB.hub_visibility) - return - GLOB.hub_visibility = new_visibility - if(GLOB.hub_visibility) - hub_password = "kMZy3U5jJHSiBQjr" - else - hub_password = "SORRYNOPASSWORD" - -/world/proc/incrementMaxZ() - maxz++ - SSmobs.MaxZChanged() - SSidlenpcpool.MaxZChanged() - -/world/proc/change_fps(new_value = 20) - if(new_value <= 0) - CRASH("change_fps() called with [new_value] new_value.") - if(fps == new_value) - return //No change required. - - fps = new_value - on_tickrate_change() - - -/world/proc/change_tick_lag(new_value = 0.5) - if(new_value <= 0) - CRASH("change_tick_lag() called with [new_value] new_value.") - if(tick_lag == new_value) - return //No change required. - - tick_lag = new_value - on_tickrate_change() - - -/world/proc/on_tickrate_change() - SStimer?.reset_buckets() +#define RESTART_COUNTER_PATH "data/round_counter.txt" + +GLOBAL_VAR(restart_counter) + +//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()]!") + + 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(minimum_required_security_level = TGS_SECURITY_TRUSTED) + + GLOB.revdata = new + + config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + + load_admins() + + //SetupLogs depends on the RoundID, so lets check + //DB schema and set RoundID if we can + SSdbcore.CheckSchemaVersion() + SSdbcore.SetRoundID() + SetupLogs() + +#ifndef USE_CUSTOM_ERROR_HANDLER + world.log = file("[GLOB.log_directory]/dd.log") +#else + if (TgsAvailable()) + world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. +#endif + + + load_yogs_stuff() // yogs - Donators + refresh_admin_files() //yogs - DB support + load_admins() + + LoadVerbs(/datum/verbs/menu) + if(CONFIG_GET(flag/usewhitelist)) + load_whitelist() + + setup_pretty_filter() //yogs + + GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 + + if(fexists(RESTART_COUNTER_PATH)) + GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) + fdel(RESTART_COUNTER_PATH) + + if(NO_INIT_PARAMETER in params) + return + + Master.Initialize(10, FALSE, TRUE) + + if(TEST_RUN_PARAMETER in params) + HandleTestRun() + +/world/proc/HandleTestRun() + //trigger things to run the whole process + Master.sleep_offline_after_initializations = FALSE + SSticker.start_immediately = TRUE + CONFIG_SET(number/round_end_countdown, 0) + var/datum/callback/cb +#ifdef UNIT_TESTS + cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests) +#else + cb = VARSET_CALLBACK(SSticker, force_ending, TRUE) +#endif + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS)) + +/world/proc/SetupExternalRSC() +#if (PRELOAD_RSC == 0) + GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") + var/i=1 + while(i<=GLOB.external_rsc_urls.len) + if(GLOB.external_rsc_urls[i]) + i++ + else + GLOB.external_rsc_urls.Cut(i,i+1) +#endif + +/world/proc/SetupLogs() + var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] + if(!override_dir) + var/realtime = world.realtime + var/texttime = time2text(realtime, "YYYY/MM/DD") + GLOB.log_directory = "data/logs/[texttime]/round-" + GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" + GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" + if(GLOB.round_id) + GLOB.log_directory += "[GLOB.round_id]" + GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" + GLOB.picture_log_directory += "[GLOB.round_id]" + else + var/timestamp = replacetext(time_stamp(), ":", ".") + GLOB.log_directory += "[timestamp]" + GLOB.picture_log_directory += "[timestamp]" + GLOB.picture_logging_prefix += "T_[timestamp]_" + else + GLOB.log_directory = "data/logs/[override_dir]" + GLOB.picture_logging_prefix = "O_[override_dir]_" + GLOB.picture_log_directory = "data/picture_logs/[override_dir]" + + GLOB.world_game_log = "[GLOB.log_directory]/game.log" + GLOB.world_mecha_log = "[GLOB.log_directory]/mecha.log" + GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" + GLOB.world_cloning_log = "[GLOB.log_directory]/cloning.log" + GLOB.world_asset_log = "[GLOB.log_directory]/asset.log" + GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" + GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" + GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" + GLOB.world_ntsl_log = "[GLOB.log_directory]/ntsl.log" + GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log" + GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" + GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" + GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" + GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log" + GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" + GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" + GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" + GLOB.world_paper_log = "[GLOB.log_directory]/paper.log" + +#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) + + GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently + if(fexists(GLOB.config_error_log)) + fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") + fdel(GLOB.config_error_log) + + if(GLOB.round_id) + log_game("Round ID: [GLOB.round_id]") + + // This was printed early in startup to the world log and config_error.log, + // but those are both private, so let's put the commit info in the runtime + // log which is ultimately public. + log_runtime(GLOB.revdata.get_log_message()) + +/world/Topic(T, addr, master, key) + TGS_TOPIC //redirect to server tools if necessary + + var/static/list/topic_handlers = TopicHandlers() + + var/list/input = params2list(T) + var/datum/world_topic/handler + for(var/I in topic_handlers) + if(I in input) + handler = topic_handlers[I] + break + + if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) + log_topic("\"[T]\", from:[addr], master:[master], key:[key]") + + if(!handler) + return + + handler = new handler() + return handler.TryRun(input) + +/world/proc/AnnouncePR(announcement, list/payload) + var/static/list/PRcounts = list() //PR id -> number of times announced this round + var/id = "[payload["pull_request"]["id"]]" + if(!PRcounts[id]) + PRcounts[id] = 1 + else + ++PRcounts[id] + if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) + return + + var/final_composed = "PR: [announcement]" + for(var/client/C in GLOB.clients) + C.AnnouncePR(final_composed) + +/world/proc/FinishTestRun() + set waitfor = FALSE + var/list/fail_reasons + if(GLOB) + if(GLOB.total_runtimes != 0) + fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") +#ifdef UNIT_TESTS + if(GLOB.failed_any_test) + LAZYADD(fail_reasons, "Unit Tests failed!") +#endif + if(!GLOB.log_directory) + LAZYADD(fail_reasons, "Missing GLOB.log_directory!") + else + fail_reasons = list("Missing GLOB!") + if(!fail_reasons) + text2file("Success!", "[GLOB.log_directory]/clean_run.lk") + else + log_world("Test run failed!\n[fail_reasons.Join("\n")]") + sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes + qdel(src) //shut it down + +/world/Reboot(reason = 0, fast_track = FALSE) + if (reason || fast_track) //special reboot, do none of the normal stuff + if (usr) + log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") + message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") + to_chat(world, "Rebooting World immediately due to host request") + else + to_chat(world, "Rebooting world...") + Master.Shutdown() //run SS shutdowns + + TgsReboot() + + if(TEST_RUN_PARAMETER in params) + FinishTestRun() + return + + if(TgsAvailable()) + var/do_hard_reboot + // check the hard reboot counter + var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) + switch(ruhr) + if(-1) + do_hard_reboot = FALSE + if(0) + do_hard_reboot = TRUE + else + if(GLOB.restart_counter >= ruhr) + do_hard_reboot = TRUE + else + text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) + do_hard_reboot = FALSE + + if(do_hard_reboot) + log_world("World hard rebooted at [time_stamp()]") + shutdown_logging() // See comment below. + TgsEndProcess() + + log_world("World rebooted at [time_stamp()]") + shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. + ..() + +/world/proc/update_status() //yogs -- Mirrored in the Yogs folder in March 2019. Do not edit, swallow, or submerge in acid + + var/list/features = list() + + if(GLOB.master_mode) + features += GLOB.master_mode + + if (!GLOB.enter_allowed) + features += "closed" + + var/s = "" + var/hostedby + if(config) + var/server_name = CONFIG_GET(string/servername) + if (server_name) + s += "[server_name] — " + features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" + if(CONFIG_GET(flag/allow_vote_mode)) + features += "vote" + if(CONFIG_GET(flag/allow_ai)) + features += "AI allowed" + hostedby = CONFIG_GET(string/hostedby) + + s += "[station_name()]"; + s += " (" + s += "" //Change this to wherever you want the hub to link to. + s += "Default" //Replace this with something else. Or ever better, delete it and uncomment the game version. + s += "" + s += ")" + + var/players = GLOB.clients.len + + var/popcaptext = "" + var/popcap = max(CONFIG_GET(number/extreme_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/soft_popcap)) + if (popcap) + popcaptext = "/[popcap]" + + if (players > 1) + features += "[players][popcaptext] players" + else if (players > 0) + features += "[players][popcaptext] player" + + game_state = (CONFIG_GET(number/extreme_popcap) && players >= CONFIG_GET(number/extreme_popcap)) //tells the hub if we are full + + if (!host && hostedby) + features += "hosted by [hostedby]" + + if (features) + s += ": [jointext(features, ", ")]" + + status = s + +/world/proc/update_hub_visibility(new_visibility) + if(new_visibility == GLOB.hub_visibility) + return + GLOB.hub_visibility = new_visibility + if(GLOB.hub_visibility) + hub_password = "kMZy3U5jJHSiBQjr" + else + hub_password = "SORRYNOPASSWORD" + +/world/proc/incrementMaxZ() + maxz++ + SSmobs.MaxZChanged() + SSidlenpcpool.MaxZChanged() + +/world/proc/change_fps(new_value = 20) + if(new_value <= 0) + CRASH("change_fps() called with [new_value] new_value.") + if(fps == new_value) + return //No change required. + + fps = new_value + on_tickrate_change() + + +/world/proc/change_tick_lag(new_value = 0.5) + if(new_value <= 0) + CRASH("change_tick_lag() called with [new_value] new_value.") + if(tick_lag == new_value) + return //No change required. + + tick_lag = new_value + on_tickrate_change() + + +/world/proc/on_tickrate_change() + SStimer?.reset_buckets() diff --git a/code/modules/NTNet/services/_service.dm b/code/modules/NTNet/services/_service.dm index 3622dc38810c..75059d9992bd 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/admin.dm b/code/modules/admin/admin.dm index 2e5fc2546ed2..a439995e1a83 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1,900 +1,900 @@ - -//////////////////////////////// -/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 - - if(M.oobe_client) //yogs start - if(M.oobe_client.mob) - .(M.oobe_client.mob) //using . because show_player_panel(M.oobe_client.mob) caused "Runtime in admin.dm,30: undefined proc or verb /client/Show Player Panel()." - else - to_chat(usr, "Cannot open player panel because [key_name(M)] has (a)ghosted, but does not appear to have a mob.") - return //yogs end - - 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 | " - if(M.client) - body += "Ban | " - else - body += "Ban | " - - body += "Notes | Messages | Watchlist | " - if(M.client) - body += "| Prison | " - body += "\ Send back to Lobby | " - var/muted = M.client.prefs.muted - body += "
                Mute: " - body += "\[IC | " - body += "OOC | " - body += "PRAY | " - body += "ADMINHELP | " - body += "DEADCHAT\]" - body += "(toggle all)" - body += "FREEZE" //yogs - adminfreezing - - 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.YogsPPoptions(M) // YOGS - Player panel stuff, big PP - 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)
                " - - 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/toggleoocdead() - set category = "Server" - set desc="Toggle dis bitch" - set name="Toggle Dead OOC" - toggle_dooc() - - log_admin("[key_name(usr)] toggled OOC.") - message_admins("[key_name_admin(usr)] toggled Dead OOC.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dead OOC", "[GLOB.dooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/startnow() - set category = "Server" - set desc="Start the round RIGHT NOW" - set name="Start Now" - if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP) - 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/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('sound/ai/attention.ogg')) - log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/unprison(mob/M in GLOB.mob_list) - set category = "Admin" - set name = "Unprison" - if (is_centcom_level(M.z)) - SSjob.SendToLateJoin(M) - message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]") - log_admin("[key_name(usr)] has unprisoned [key_name(M)]") - else - alert("[M.name] is not prisoned.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Unprison") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS - -/datum/admins/proc/spawn_atom(object as text) - set category = "Debug" - set desc = "(atom path) Spawn an atom" - set name = "Spawn" - - if(!check_rights(R_SPAWN)) - 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/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/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/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 + + if(M.oobe_client) //yogs start + if(M.oobe_client.mob) + .(M.oobe_client.mob) //using . because show_player_panel(M.oobe_client.mob) caused "Runtime in admin.dm,30: undefined proc or verb /client/Show Player Panel()." + else + to_chat(usr, "Cannot open player panel because [key_name(M)] has (a)ghosted, but does not appear to have a mob.") + return //yogs end + + 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 | " + if(M.client) + body += "Ban | " + else + body += "Ban | " + + body += "Notes | Messages | Watchlist | " + if(M.client) + body += "| Prison | " + body += "\ Send back to Lobby | " + var/muted = M.client.prefs.muted + body += "
                Mute: " + body += "\[IC | " + body += "OOC | " + body += "PRAY | " + body += "ADMINHELP | " + body += "DEADCHAT\]" + body += "(toggle all)" + body += "FREEZE" //yogs - adminfreezing + + 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.YogsPPoptions(M) // YOGS - Player panel stuff, big PP + 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)
                " + + 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/toggleoocdead() + set category = "Server" + set desc="Toggle dis bitch" + set name="Toggle Dead OOC" + toggle_dooc() + + log_admin("[key_name(usr)] toggled OOC.") + message_admins("[key_name_admin(usr)] toggled Dead OOC.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dead OOC", "[GLOB.dooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/startnow() + set category = "Server" + set desc="Start the round RIGHT NOW" + set name="Start Now" + if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP) + 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/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('sound/ai/attention.ogg')) + log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/unprison(mob/M in GLOB.mob_list) + set category = "Admin" + set name = "Unprison" + if (is_centcom_level(M.z)) + SSjob.SendToLateJoin(M) + message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]") + log_admin("[key_name(usr)] has unprisoned [key_name(M)]") + else + alert("[M.name] is not prisoned.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Unprison") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS + +/datum/admins/proc/spawn_atom(object as text) + set category = "Debug" + set desc = "(atom path) Spawn an atom" + set name = "Spawn" + + if(!check_rights(R_SPAWN)) + 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/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/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/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_ranks.dm b/code/modules/admin/admin_ranks.dm index bf806c5dced3..69d5acf179ba 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) - CRASH("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) || line == " ") //YOGS - added our DB support - 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) || line == " ") //yogs - added our DB support - 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) + CRASH("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) || line == " ") //YOGS - added our DB support + 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) || line == " ") //yogs - added our DB support + 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/chat_commands.dm b/code/modules/admin/chat_commands.dm index d37480539672..b3e127dab516 100644 --- a/code/modules/admin/chat_commands.dm +++ b/code/modules/admin/chat_commands.dm @@ -1,120 +1,120 @@ -#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], Mode: [GLOB.master_mode]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]" - -/datum/tgs_chat_command/ahelp - name = "ahelp" - help_text = " |list>>" - admin_only = TRUE - -/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params) - var/list/all_params = splittext(params, " ") - if(all_params.len < 2) - return "Insufficient parameters" - var/target = all_params[1] - all_params.Cut(1, 2) - var/id = text2num(target) - if(id != null) - var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id) - if(AH) - target = AH.initiator_ckey - else - return "Ticket #[id] not found!" - var/res = 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 = TRUE - -/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 - . = "[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 - refresh_admin_files() //yogs - DB support - load_admins() +#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], Mode: [GLOB.master_mode]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]" + +/datum/tgs_chat_command/ahelp + name = "ahelp" + help_text = " |list>>" + admin_only = TRUE + +/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params) + var/list/all_params = splittext(params, " ") + if(all_params.len < 2) + return "Insufficient parameters" + var/target = all_params[1] + all_params.Cut(1, 2) + var/id = text2num(target) + if(id != null) + var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id) + if(AH) + target = AH.initiator_ckey + else + return "Ticket #[id] not found!" + var/res = 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 = TRUE + +/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 + . = "[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 + refresh_admin_files() //yogs - DB support + load_admins() diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index 90749af01e1c..8dc85cf85cfb 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -1,39 +1,39 @@ - -/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.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() - - // Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant. - H.dna.features["mcolor"] = random_short_color() - H.dna.features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] - H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard) - H.dna.features["snout"] = pick(GLOB.snouts_list) - H.dna.features["horns"] = pick(GLOB.horns_list) - H.dna.features["frills"] = pick(GLOB.frills_list) - H.dna.features["spines"] = pick(GLOB.spines_list) - H.dna.features["body_markings"] = pick(GLOB.body_markings_list) - H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list) - - H.update_body() - H.update_hair() + +/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.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() + + // Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant. + H.dna.features["mcolor"] = random_short_color() + H.dna.features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] + H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard) + H.dna.features["snout"] = pick(GLOB.snouts_list) + H.dna.features["horns"] = pick(GLOB.horns_list) + H.dna.features["frills"] = pick(GLOB.frills_list) + H.dna.features["spines"] = pick(GLOB.spines_list) + H.dna.features["body_markings"] = pick(GLOB.body_markings_list) + H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list) + + H.update_body() + H.update_hair() H.update_body_parts() \ No newline at end of file diff --git a/code/modules/admin/create_object.dm b/code/modules/admin/create_object.dm index ff49944dc4e3..a0d7e4148591 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 4742cac4420e..86e83f38aee0 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 048840cbeb76..d37040ac2b49 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) - CRASH("Admin datum created without a ckey") - return - if(!istype(R)) - QDEL_IN(src, 0) - CRASH("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) + CRASH("Admin datum created without a ckey") + return + if(!istype(R)) + QDEL_IN(src, 0) + CRASH("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 73bbe3e7d2b6..f9ad5e6fe213 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -1,310 +1,310 @@ -/datum/admins/proc/player_panel_new()//The new one - if(!check_rights()) - return - log_admin("[key_name(usr)] checked the player panel.") - var/dat = "Player Panel" - - //javascript, the part that does most of the work~ - dat += {" - - - - - - - "} - - //body tag start + onload and onkeypress (onkeyup) javascript event calls - dat += "" - - //title + search bar - dat += {" - -
                [J_title]: [J_opPos]/[job.total_positions < 0 ? " (unlimited)" : J_totPos]" + + dat += "" + if(job.total_positions >= 0) + dat += "Custom | " + dat += "Add 1 | " + if(job.total_positions > job.current_positions) + dat += "Remove | " + else + dat += "Remove | " + dat += "Unlimit
                - - - - - - -
                - Player panel
                - Hover over a line to see more information - Check antagonists - Kick everyone/AFKers in lobby -

                -

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

                +

                + Search: +
                + + "} + + //player table header + dat += {" + + "} + + var/list/mobs = sortmobs() + var/i = 1 + for(var/mob/M in mobs) + if(M.ckey) + + var/color = "#e6e6e6" + if(i%2 == 0) + color = "#f2f2f2" + var/is_antagonist = is_special_character(M) + + var/M_job = "" + + if(isliving(M)) + + if(iscarbon(M)) //Carbon stuff + if(ishuman(M)) + M_job = M.job + else if(ismonkey(M)) + M_job = "Monkey" + else if(isalien(M)) //aliens + if(islarva(M)) + M_job = "Alien larva" + else + M_job = ROLE_ALIEN + else + M_job = "Carbon-based" + + else if(issilicon(M)) //silicon + if(isAI(M)) + M_job = "AI" + else if(ispAI(M)) + M_job = ROLE_PAI + else if(iscyborg(M)) + M_job = "Cyborg" + else + M_job = "Silicon-based" + + else if(isanimal(M)) //simple animals + if(iscorgi(M)) + M_job = "Corgi" + else if(isslime(M)) + M_job = "slime" + else + M_job = "Animal" + + else + M_job = "Living" + + else if(isnewplayer(M)) + M_job = "New player" + + else if(isobserver(M)) + var/mob/dead/observer/O = M + if(O.started_as_observer)//Did they get BTFO or are they just not trying? + M_job = "Observer" + else + M_job = "Ghost" + + var/M_name = html_encode(M.name) + var/M_rname = html_encode(M.real_name) + var/M_key = html_encode(M.key) + var/previous_names = "" + if(M_key) + var/datum/player_details/P = GLOB.player_details[ckey(M_key)] + if(P) + previous_names = P.played_names.Join(",") + previous_names = html_encode(previous_names) + + //output for each mob + dat += {" + + + + + + "} + + i++ + + + //player table ending + dat += {" +
                + + + [M_name] - [M_rname] - [M_key] ([M_job]) + + +
                +
                +
                + + + + "} + + usr << browse(dat, "window=players;size=600x480") diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index ac5a04cb9a57..e54dee8d5248 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1,1980 +1,1980 @@ -/datum/admins/proc/CheckAdminHref(href, href_list) - var/auth = href_list["admin_token"] - . = auth && (auth == href_token || auth == GLOB.href_token) - if(.) - return - var/msg = !auth ? "no" : "a bad" - message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") - if(CONFIG_GET(flag/debug_admin_hrefs)) - message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") - log_world("UAH: [href]") - return TRUE - log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") - -/datum/admins/Topic(href, href_list) - ..() - - if(usr.client != src.owner || !check_rights(0)) - message_admins("[usr.key] has attempted to override the admin panel!") - log_admin("[key_name(usr)] tried to use the admin panel without authorization.") - return - - if(!CheckAdminHref(href, href_list)) - return - - if(href_list["afreeze"]) //yogs start - afreeze - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["afreeze"]) in GLOB.mob_list - if(!M || !M.client) - return - var/message - if(M.client.prefs.afreeze) - to_chat(M, "You are no longer frozen.") - M.client.prefs.afreeze = FALSE - M.client.show_popup_menus = TRUE - M.client.show_verb_panel = TRUE - M.notransform = FALSE - M.verbs += M.client.afreeze_stored_verbs - message = "[key_name(usr)] has unfrozen [key_name(M)]." - else - to_chat(M, "You have been frozen by an administrator.") - M.client.prefs.afreeze = TRUE - M.client.show_popup_menus = FALSE - M.client.show_verb_panel = FALSE - M.notransform = TRUE - M.client.afreeze_stored_verbs = M.verbs.Copy() - M.verbs.Cut() - message = "[key_name(usr)] has frozen [key_name(M)]." - log_admin(message) - message_admins(message) //yogs end - - 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.") - if("shadowling") - if(makeShadowling()) - message_admins("[key_name(usr)] created a shadowling.") - log_admin("[key_name(usr)] created a shadowling.") - else - message_admins("[key_name_admin(usr)] tried to create a shadowling. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a shadowling.") - 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["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["call_shuttle"]) - if(!check_rights(R_ADMIN)) - return - - - switch(href_list["call_shuttle"]) - if("1") - if(EMERGENCY_AT_LEAST_DOCKED) - return - SSshuttle.emergency.request() - log_admin("[key_name(usr)] called the Emergency Shuttle.") - message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") - - if("2") - if(EMERGENCY_AT_LEAST_DOCKED) - return - switch(SSshuttle.emergency.mode) - if(SHUTTLE_CALL) - SSshuttle.emergency.cancel() - log_admin("[key_name(usr)] sent the Emergency Shuttle back.") - message_admins("[key_name_admin(usr)] sent the Emergency Shuttle back.") - else - SSshuttle.emergency.cancel() - log_admin("[key_name(usr)] called the Emergency Shuttle.") - message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") - - - - else if(href_list["edit_shuttle_time"]) - if(!check_rights(R_SERVER)) - return - - var/timer = input("Enter new shuttle duration (seconds):","Edit Shuttle Timeleft", SSshuttle.emergency.timeLeft() ) as num|null - if(!timer) - return - SSshuttle.emergency.setTimer(timer*10) - log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") - minor_announce("The emergency shuttle will reach its destination in [round(SSshuttle.emergency.timeLeft(600))] minutes.") - message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") - else if(href_list["trigger_centcom_recall"]) - if(!check_rights(R_ADMIN)) - return - - usr.client.trigger_centcom_recall() - - else if(href_list["toggle_continuous"]) - if(!check_rights(R_ADMIN)) - return - var/list/continuous = CONFIG_GET(keyed_list/continuous) - if(!continuous[SSticker.mode.config_tag]) - continuous[SSticker.mode.config_tag] = TRUE - else - continuous[SSticker.mode.config_tag] = FALSE - - message_admins("[key_name_admin(usr)] toggled the round to [continuous[SSticker.mode.config_tag] ? "continue if all antagonists die" : "end with the antagonists"].") - check_antagonists() - - else if(href_list["toggle_midround_antag"]) - if(!check_rights(R_ADMIN)) - return - - var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) - if(!midround_antag[SSticker.mode.config_tag]) - midround_antag[SSticker.mode.config_tag] = TRUE - else - midround_antag[SSticker.mode.config_tag] = FALSE - - message_admins("[key_name_admin(usr)] toggled the round to [midround_antag[SSticker.mode.config_tag] ? "use" : "skip"] the midround antag system.") - check_antagonists() - - else if(href_list["alter_midround_time_limit"]) - if(!check_rights(R_ADMIN)) - return - - var/timer = input("Enter new maximum time",, CONFIG_GET(number/midround_antag_time_check)) as num|null - if(!timer) - return - CONFIG_SET(number/midround_antag_time_check, timer) - message_admins("[key_name_admin(usr)] edited the maximum midround antagonist time to [timer] minutes.") - check_antagonists() - - else if(href_list["alter_midround_life_limit"]) - if(!check_rights(R_ADMIN)) - return - - var/ratio = input("Enter new life ratio",, CONFIG_GET(number/midround_antag_life_check) * 100) as num - 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_ADMIN)) //YOGS - R_SERVER -> R_ADMIN - 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]") - 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 = FALSE - switch(alert("Delete old mob?","Message","Yes","No","Cancel")) - if("Cancel") - return - if("Yes") - delmob = TRUE - - log_admin("[key_name(usr)] has used rudimentary transformation on [key_name(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") - message_admins("[key_name_admin(usr)] has used rudimentary transformation on [key_name_admin(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") - switch(href_list["simplemake"]) - if("observer") - M.change_mob_type( /mob/dead/observer , null, null, delmob ) - if("drone") - M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob ) - if("hunter") - M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob ) - if("queen") - M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/queen , null, null, delmob ) - if("praetorian") - M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/praetorian , null, null, delmob ) - if("sentinel") - M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob ) - if("larva") - M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) - if("human") - var/posttransformoutfit = usr.client.robust_dress_shop() - if (!posttransformoutfit) - return - var/mob/living/carbon/human/newmob = M.change_mob_type( /mob/living/carbon/human , null, null, delmob ) - if(posttransformoutfit && istype(newmob)) - newmob.equipOutfit(posttransformoutfit) - if("slime") - M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) - if("monkey") - M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob ) - if("robot") - M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) - if("cat") - M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob ) - if("runtime") - M.change_mob_type( /mob/living/simple_animal/pet/cat/Runtime , null, null, delmob ) - if("corgi") - M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob ) - if("ian") - M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi/Ian , null, null, delmob ) - if("pug") - M.change_mob_type( /mob/living/simple_animal/pet/dog/pug , null, null, delmob ) - if("crab") - M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) - if("coffee") - M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob ) - if("parrot") - M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) - if("polyparrot") - M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob ) - if("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 ) - - 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 - /* yogs - admins don't need handholding if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") - return yogs - admins don't need handholding */ - 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)*/ //yogs - remove severity - - 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()*/ //yogs - simple fast interface thanks - usr << browse(edit_log,"window=noteedits") //yogs - qdel(query_get_message_edits) - - else if(href_list["mute"]) - if(!check_rights(R_ADMIN)) - return - cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) - - else if(href_list["c_mode"]) - return HandleCMode() - - else if(href_list["f_secret"]) - return HandleFSecret() - - else if(href_list["c_mode2"]) - if(!check_rights(R_ADMIN|R_SERVER)) - return - - if (SSticker.HasRoundStarted()) - if (askuser(usr, "The game has already started. Would you like to save this as the default mode effective next round?", "Save mode", "Yes", "Cancel", Timeout = null) == 1) - SSticker.save_mode(href_list["c_mode2"]) - HandleCMode() - return - GLOB.master_mode = href_list["c_mode2"] - log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].") - message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].") - to_chat(world, "The mode is now: [GLOB.master_mode]") - Game() // updates the main game menu - if (askuser(usr, "Would you like to save this as the default mode for the server?", "Save mode", "Yes", "No", Timeout = null) == 1) - SSticker.save_mode(GLOB.master_mode) - HandleCMode() - - else if(href_list["f_secret2"]) - if(!check_rights(R_ADMIN|R_SERVER)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - GLOB.secret_force_mode = href_list["f_secret2"] - log_admin("[key_name(usr)] set the forced secret mode as [GLOB.secret_force_mode].") - message_admins("[key_name_admin(usr)] set the forced secret mode as [GLOB.secret_force_mode].") - Game() // updates the main game menu - HandleFSecret() - - else if(href_list["monkeyone"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["monkeyone"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - 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(TRUE, H.client) - - else if(href_list["makealien"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makealien"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - 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 - var/can_ghost = TRUE - if(!isobserver(usr)) - can_ghost = C.admin_ghost() - - if(!can_ghost) - return - var/mob/dead/observer/A = C.mob - var/mob/living/silicon/ai/I = AM //yogs start - adminfollow now follows AI eyes instead of the core - if(istype(I) && I.eyeobj) - A.ManualFollow(I.eyeobj) - else - A.ManualFollow(AM) //yogs stop - adminfollow now follows AI eyes instead of the core - - 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 - - //Yogs start - Cookies for all mobs! - var/mob/H = locate(href_list["adminspawncookie"]) - if(!H) - to_chat(usr, "The target of your cookie either doesn't exist or is not a /mob/.") - return - - var/obj/item/reagent_containers/food/snacks/cookie/cookie = new(H) - if(H.put_in_hands(cookie)) // They have hands and can use them to hold cookies - H.update_inv_hands() - log_admin("[key_name(H)] got their cookie in-hand, spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] got their cookie in-hand, spawned by [key_name(src.owner)].") - else // They do not have hands available, for some reason - cookie.loc = H.loc - log_admin("[key_name(H)] received their cookie at their feet, spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] received their cookie at their feet, spawned by [key_name(src.owner)].") - //Yogs end - Cookies for all! - 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/H = locate(href_list["adminsmite"]) in GLOB.mob_list // Yogs -- mob/living instead of mob/living/carbon/human - if(!H || !istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living") // Yogs -- mob/living instead of 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/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,lentext(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,lentext(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,lentext(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,lentext(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_finalize"]) - if(!check_rights(R_ADMIN)) - return - create_outfit_finalize(usr,href_list) - else if(href_list["load_outfit"]) - if(!check_rights(R_ADMIN)) - return - load_outfit(usr) - else if(href_list["create_outfit_menu"]) - if(!check_rights(R_ADMIN)) - return - create_outfit(usr) - else if(href_list["delete_outfit"]) - if(!check_rights(R_ADMIN)) - return - var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits - delete_outfit(usr,O) - else if(href_list["save_outfit"]) - if(!check_rights(R_ADMIN)) - return - var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits - save_outfit(usr,O) - else if(href_list["set_selfdestruct_code"]) - if(!check_rights(R_ADMIN)) - return - var/code = random_nukecode() - for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list) - SD.r_code = code - message_admins("[key_name_admin(usr)] has set the self-destruct \ - code to \"[code]\".") - - else if(href_list["add_station_goal"]) - if(!check_rights(R_ADMIN)) - return - var/list/type_choices = typesof(/datum/station_goal) - var/picked = input("Choose goal type") in type_choices|null - if(!picked) - return - var/datum/station_goal/G = new picked() - if(picked == /datum/station_goal) - var/newname = input("Enter goal name:") as text|null - if(!newname) - return - G.name = newname - var/description = input("Enter CentCom message contents:") as message|null - if(!description) - return - G.report_message = description - message_admins("[key_name(usr)] created \"[G.name]\" station goal.") - SSticker.mode.station_goals += G - modify_goals() - - else if(href_list["viewruntime"]) - var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) - if(!istype(error_viewer)) - to_chat(usr, "That runtime viewer no longer exists.") - return - - if(href_list["viewruntime_backto"]) - error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) - else - error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) - - else if(href_list["showrelatedacc"]) - if(!check_rights(R_ADMIN)) - return - var/client/C = locate(href_list["client"]) in GLOB.clients - var/thing_to_check - if(href_list["showrelatedacc"] == "cid") - thing_to_check = C.related_accounts_cid - else - thing_to_check = C.related_accounts_ip - thing_to_check = splittext(thing_to_check, ", ") - - - var/list/dat = list("Related accounts by [uppertext(href_list["showrelatedacc"])]:") - dat += thing_to_check - - usr << browse(dat.Join("
                "), "window=related_[C];size=420x300") - - else if(href_list["modantagrep"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["mob"]) in GLOB.mob_list - var/client/C = M.client - usr.client.cmd_admin_mod_antag_rep(C, href_list["modantagrep"]) - show_player_panel(M) - - else if(href_list["slowquery"]) - if(!check_rights(R_ADMIN)) - return - var/answer = href_list["slowquery"] - if(answer == "yes") - log_query_debug("[usr.key] | Reported a server hang") - if(alert(usr, "Had you just press any admin buttons?", "Query server hang report", "Yes", "No") == "Yes") - var/response = input(usr,"What were you just doing?","Query server hang report") as null|text - if(response) - log_query_debug("[usr.key] | [response]") - else if(answer == "no") - log_query_debug("[usr.key] | Reported no server hang") - - else if(href_list["ctf_toggle"]) - if(!check_rights(R_ADMIN)) - return - toggle_all_ctf(usr) - - else if(href_list["rebootworld"]) - if(!check_rights(R_ADMIN)) - return - var/confirm = alert("Are you sure you want to reboot the server?", "Confirm Reboot", "Yes", "No") - if(confirm == "No") - return - if(confirm == "Yes") - restart() - - else if(href_list["check_teams"]) - if(!check_rights(R_ADMIN)) - return - check_teams() - - else if(href_list["team_command"]) - if(!check_rights(R_ADMIN)) - return - switch(href_list["team_command"]) - if("create_team") - admin_create_team(usr) - if("rename_team") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_rename(usr) - if("communicate") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_communicate(usr) - if("delete_team") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_delete(usr) - if("add_objective") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_add_objective(usr) - if("remove_objective") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(!T) - return - var/datum/objective/O = locate(href_list["tobjective"]) in T.objectives - if(O) - T.admin_remove_objective(usr,O) - if("add_member") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_add_member(usr) - if("remove_member") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(!T) - return - var/datum/mind/M = locate(href_list["tmember"]) in T.members - if(M) - T.admin_remove_member(usr,M) - check_teams() - // yogs start - mentors - else if(href_list["makementor"]) - makeMentor(href_list["makementor"]) - - else if(href_list["removementor"]) - removeMentor(href_list["removementor"]) - // yogs end - - else if(href_list["newbankey"]) - var/player_key = href_list["newbankey"] - var/player_ip = href_list["newbanip"] - var/player_cid = href_list["newbancid"] - ban_panel(player_key, player_ip, player_cid) - - else if(href_list["intervaltype"]) //check for ban panel, intervaltype is used as it's the only value which will always be present - if(href_list["roleban_delimiter"]) - ban_parse_href(href_list) - else - ban_parse_href(href_list, TRUE) - - else if(href_list["searchunbankey"] || href_list["searchunbanadminkey"] || href_list["searchunbanip"] || href_list["searchunbancid"]) - var/player_key = href_list["searchunbankey"] - var/admin_key = href_list["searchunbanadminkey"] - var/player_ip = href_list["searchunbanip"] - var/player_cid = href_list["searchunbancid"] - unban_panel(player_key, admin_key, player_ip, player_cid) - - else if(href_list["unbanpagecount"]) - var/page = href_list["unbanpagecount"] - var/player_key = href_list["unbankey"] - var/admin_key = href_list["unbanadminkey"] - var/player_ip = href_list["unbanip"] - var/player_cid = href_list["unbancid"] - unban_panel(player_key, admin_key, player_ip, player_cid, page) - - else if(href_list["editbanid"]) - var/edit_id = href_list["editbanid"] - var/player_key = href_list["editbankey"] - var/player_ip = href_list["editbanip"] - var/player_cid = href_list["editbancid"] - var/role = href_list["editbanrole"] - var/duration = href_list["editbanduration"] - var/applies_to_admins = text2num(href_list["editbanadmins"]) - var/reason = url_decode(href_list["editbanreason"]) - var/page = href_list["editbanpage"] - var/admin_key = href_list["editbanadminkey"] - ban_panel(player_key, player_ip, player_cid, role, duration, applies_to_admins, reason, edit_id, page, admin_key) - - else if(href_list["unbanid"]) - var/ban_id = href_list["unbanid"] - var/player_key = href_list["unbankey"] - var/player_ip = href_list["unbanip"] - var/player_cid = href_list["unbancid"] - var/role = href_list["unbanrole"] - var/page = href_list["unbanpage"] - var/admin_key = href_list["unbanadminkey"] - unban(ban_id, player_key, player_ip, player_cid, role, page, admin_key) - - else if(href_list["unbanlog"]) - var/ban_id = href_list["unbanlog"] - ban_log(ban_id) - - else if(href_list["beakerpanel"]) - beaker_panel_act(href_list) - -/datum/admins/proc/HandleCMode() - if(!check_rights(R_ADMIN)) - return - - var/dat = {"What mode do you wish to play?
                "} - for(var/mode in config.modes) - dat += {"[config.mode_names[mode]]
                "} - dat += {"Secret
                "} - dat += {"Random
                "} - dat += {"Now: [GLOB.master_mode]"} - usr << browse(dat, "window=c_mode") - -/datum/admins/proc/HandleFSecret() - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
                "} - for(var/mode in config.modes) - dat += {"[config.mode_names[mode]]
                "} - dat += {"Random (default)
                "} - dat += {"Now: [GLOB.secret_force_mode]"} - usr << browse(dat, "window=f_secret") +/datum/admins/proc/CheckAdminHref(href, href_list) + var/auth = href_list["admin_token"] + . = auth && (auth == href_token || auth == GLOB.href_token) + if(.) + return + var/msg = !auth ? "no" : "a bad" + message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") + if(CONFIG_GET(flag/debug_admin_hrefs)) + message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") + log_world("UAH: [href]") + return TRUE + log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") + +/datum/admins/Topic(href, href_list) + ..() + + if(usr.client != src.owner || !check_rights(0)) + message_admins("[usr.key] has attempted to override the admin panel!") + log_admin("[key_name(usr)] tried to use the admin panel without authorization.") + return + + if(!CheckAdminHref(href, href_list)) + return + + if(href_list["afreeze"]) //yogs start - afreeze + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["afreeze"]) in GLOB.mob_list + if(!M || !M.client) + return + var/message + if(M.client.prefs.afreeze) + to_chat(M, "You are no longer frozen.") + M.client.prefs.afreeze = FALSE + M.client.show_popup_menus = TRUE + M.client.show_verb_panel = TRUE + M.notransform = FALSE + M.verbs += M.client.afreeze_stored_verbs + message = "[key_name(usr)] has unfrozen [key_name(M)]." + else + to_chat(M, "You have been frozen by an administrator.") + M.client.prefs.afreeze = TRUE + M.client.show_popup_menus = FALSE + M.client.show_verb_panel = FALSE + M.notransform = TRUE + M.client.afreeze_stored_verbs = M.verbs.Copy() + M.verbs.Cut() + message = "[key_name(usr)] has frozen [key_name(M)]." + log_admin(message) + message_admins(message) //yogs end + + 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.") + if("shadowling") + if(makeShadowling()) + message_admins("[key_name(usr)] created a shadowling.") + log_admin("[key_name(usr)] created a shadowling.") + else + message_admins("[key_name_admin(usr)] tried to create a shadowling. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a shadowling.") + 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["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["call_shuttle"]) + if(!check_rights(R_ADMIN)) + return + + + switch(href_list["call_shuttle"]) + if("1") + if(EMERGENCY_AT_LEAST_DOCKED) + return + SSshuttle.emergency.request() + log_admin("[key_name(usr)] called the Emergency Shuttle.") + message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") + + if("2") + if(EMERGENCY_AT_LEAST_DOCKED) + return + switch(SSshuttle.emergency.mode) + if(SHUTTLE_CALL) + SSshuttle.emergency.cancel() + log_admin("[key_name(usr)] sent the Emergency Shuttle back.") + message_admins("[key_name_admin(usr)] sent the Emergency Shuttle back.") + else + SSshuttle.emergency.cancel() + log_admin("[key_name(usr)] called the Emergency Shuttle.") + message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") + + + + else if(href_list["edit_shuttle_time"]) + if(!check_rights(R_SERVER)) + return + + var/timer = input("Enter new shuttle duration (seconds):","Edit Shuttle Timeleft", SSshuttle.emergency.timeLeft() ) as num|null + if(!timer) + return + SSshuttle.emergency.setTimer(timer*10) + log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") + minor_announce("The emergency shuttle will reach its destination in [round(SSshuttle.emergency.timeLeft(600))] minutes.") + message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") + else if(href_list["trigger_centcom_recall"]) + if(!check_rights(R_ADMIN)) + return + + usr.client.trigger_centcom_recall() + + else if(href_list["toggle_continuous"]) + if(!check_rights(R_ADMIN)) + return + var/list/continuous = CONFIG_GET(keyed_list/continuous) + if(!continuous[SSticker.mode.config_tag]) + continuous[SSticker.mode.config_tag] = TRUE + else + continuous[SSticker.mode.config_tag] = FALSE + + message_admins("[key_name_admin(usr)] toggled the round to [continuous[SSticker.mode.config_tag] ? "continue if all antagonists die" : "end with the antagonists"].") + check_antagonists() + + else if(href_list["toggle_midround_antag"]) + if(!check_rights(R_ADMIN)) + return + + var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) + if(!midround_antag[SSticker.mode.config_tag]) + midround_antag[SSticker.mode.config_tag] = TRUE + else + midround_antag[SSticker.mode.config_tag] = FALSE + + message_admins("[key_name_admin(usr)] toggled the round to [midround_antag[SSticker.mode.config_tag] ? "use" : "skip"] the midround antag system.") + check_antagonists() + + else if(href_list["alter_midround_time_limit"]) + if(!check_rights(R_ADMIN)) + return + + var/timer = input("Enter new maximum time",, CONFIG_GET(number/midround_antag_time_check)) as num|null + if(!timer) + return + CONFIG_SET(number/midround_antag_time_check, timer) + message_admins("[key_name_admin(usr)] edited the maximum midround antagonist time to [timer] minutes.") + check_antagonists() + + else if(href_list["alter_midround_life_limit"]) + if(!check_rights(R_ADMIN)) + return + + var/ratio = input("Enter new life ratio",, CONFIG_GET(number/midround_antag_life_check) * 100) as num + 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_ADMIN)) //YOGS - R_SERVER -> R_ADMIN + 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]") + 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 = FALSE + switch(alert("Delete old mob?","Message","Yes","No","Cancel")) + if("Cancel") + return + if("Yes") + delmob = TRUE + + log_admin("[key_name(usr)] has used rudimentary transformation on [key_name(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") + message_admins("[key_name_admin(usr)] has used rudimentary transformation on [key_name_admin(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") + switch(href_list["simplemake"]) + if("observer") + M.change_mob_type( /mob/dead/observer , null, null, delmob ) + if("drone") + M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob ) + if("hunter") + M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob ) + if("queen") + M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/queen , null, null, delmob ) + if("praetorian") + M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/praetorian , null, null, delmob ) + if("sentinel") + M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob ) + if("larva") + M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) + if("human") + var/posttransformoutfit = usr.client.robust_dress_shop() + if (!posttransformoutfit) + return + var/mob/living/carbon/human/newmob = M.change_mob_type( /mob/living/carbon/human , null, null, delmob ) + if(posttransformoutfit && istype(newmob)) + newmob.equipOutfit(posttransformoutfit) + if("slime") + M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) + if("monkey") + M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob ) + if("robot") + M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) + if("cat") + M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob ) + if("runtime") + M.change_mob_type( /mob/living/simple_animal/pet/cat/Runtime , null, null, delmob ) + if("corgi") + M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob ) + if("ian") + M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi/Ian , null, null, delmob ) + if("pug") + M.change_mob_type( /mob/living/simple_animal/pet/dog/pug , null, null, delmob ) + if("crab") + M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) + if("coffee") + M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob ) + if("parrot") + M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) + if("polyparrot") + M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob ) + if("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 ) + + 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 + /* yogs - admins don't need handholding if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") + return yogs - admins don't need handholding */ + 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)*/ //yogs - remove severity + + 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()*/ //yogs - simple fast interface thanks + usr << browse(edit_log,"window=noteedits") //yogs + qdel(query_get_message_edits) + + else if(href_list["mute"]) + if(!check_rights(R_ADMIN)) + return + cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) + + else if(href_list["c_mode"]) + return HandleCMode() + + else if(href_list["f_secret"]) + return HandleFSecret() + + else if(href_list["c_mode2"]) + if(!check_rights(R_ADMIN|R_SERVER)) + return + + if (SSticker.HasRoundStarted()) + if (askuser(usr, "The game has already started. Would you like to save this as the default mode effective next round?", "Save mode", "Yes", "Cancel", Timeout = null) == 1) + SSticker.save_mode(href_list["c_mode2"]) + HandleCMode() + return + GLOB.master_mode = href_list["c_mode2"] + log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].") + message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].") + to_chat(world, "The mode is now: [GLOB.master_mode]") + Game() // updates the main game menu + if (askuser(usr, "Would you like to save this as the default mode for the server?", "Save mode", "Yes", "No", Timeout = null) == 1) + SSticker.save_mode(GLOB.master_mode) + HandleCMode() + + else if(href_list["f_secret2"]) + if(!check_rights(R_ADMIN|R_SERVER)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + GLOB.secret_force_mode = href_list["f_secret2"] + log_admin("[key_name(usr)] set the forced secret mode as [GLOB.secret_force_mode].") + message_admins("[key_name_admin(usr)] set the forced secret mode as [GLOB.secret_force_mode].") + Game() // updates the main game menu + HandleFSecret() + + else if(href_list["monkeyone"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["monkeyone"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + 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(TRUE, H.client) + + else if(href_list["makealien"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makealien"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + 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 + var/can_ghost = TRUE + if(!isobserver(usr)) + can_ghost = C.admin_ghost() + + if(!can_ghost) + return + var/mob/dead/observer/A = C.mob + var/mob/living/silicon/ai/I = AM //yogs start - adminfollow now follows AI eyes instead of the core + if(istype(I) && I.eyeobj) + A.ManualFollow(I.eyeobj) + else + A.ManualFollow(AM) //yogs stop - adminfollow now follows AI eyes instead of the core + + 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 + + //Yogs start - Cookies for all mobs! + var/mob/H = locate(href_list["adminspawncookie"]) + if(!H) + to_chat(usr, "The target of your cookie either doesn't exist or is not a /mob/.") + return + + var/obj/item/reagent_containers/food/snacks/cookie/cookie = new(H) + if(H.put_in_hands(cookie)) // They have hands and can use them to hold cookies + H.update_inv_hands() + log_admin("[key_name(H)] got their cookie in-hand, spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] got their cookie in-hand, spawned by [key_name(src.owner)].") + else // They do not have hands available, for some reason + cookie.loc = H.loc + log_admin("[key_name(H)] received their cookie at their feet, spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] received their cookie at their feet, spawned by [key_name(src.owner)].") + //Yogs end - Cookies for all! + 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/H = locate(href_list["adminsmite"]) in GLOB.mob_list // Yogs -- mob/living instead of mob/living/carbon/human + if(!H || !istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living") // Yogs -- mob/living instead of 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/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,lentext(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,lentext(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,lentext(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,lentext(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_finalize"]) + if(!check_rights(R_ADMIN)) + return + create_outfit_finalize(usr,href_list) + else if(href_list["load_outfit"]) + if(!check_rights(R_ADMIN)) + return + load_outfit(usr) + else if(href_list["create_outfit_menu"]) + if(!check_rights(R_ADMIN)) + return + create_outfit(usr) + else if(href_list["delete_outfit"]) + if(!check_rights(R_ADMIN)) + return + var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits + delete_outfit(usr,O) + else if(href_list["save_outfit"]) + if(!check_rights(R_ADMIN)) + return + var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits + save_outfit(usr,O) + else if(href_list["set_selfdestruct_code"]) + if(!check_rights(R_ADMIN)) + return + var/code = random_nukecode() + for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list) + SD.r_code = code + message_admins("[key_name_admin(usr)] has set the self-destruct \ + code to \"[code]\".") + + else if(href_list["add_station_goal"]) + if(!check_rights(R_ADMIN)) + return + var/list/type_choices = typesof(/datum/station_goal) + var/picked = input("Choose goal type") in type_choices|null + if(!picked) + return + var/datum/station_goal/G = new picked() + if(picked == /datum/station_goal) + var/newname = input("Enter goal name:") as text|null + if(!newname) + return + G.name = newname + var/description = input("Enter CentCom message contents:") as message|null + if(!description) + return + G.report_message = description + message_admins("[key_name(usr)] created \"[G.name]\" station goal.") + SSticker.mode.station_goals += G + modify_goals() + + else if(href_list["viewruntime"]) + var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) + if(!istype(error_viewer)) + to_chat(usr, "That runtime viewer no longer exists.") + return + + if(href_list["viewruntime_backto"]) + error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) + else + error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) + + else if(href_list["showrelatedacc"]) + if(!check_rights(R_ADMIN)) + return + var/client/C = locate(href_list["client"]) in GLOB.clients + var/thing_to_check + if(href_list["showrelatedacc"] == "cid") + thing_to_check = C.related_accounts_cid + else + thing_to_check = C.related_accounts_ip + thing_to_check = splittext(thing_to_check, ", ") + + + var/list/dat = list("Related accounts by [uppertext(href_list["showrelatedacc"])]:") + dat += thing_to_check + + usr << browse(dat.Join("
                "), "window=related_[C];size=420x300") + + else if(href_list["modantagrep"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["mob"]) in GLOB.mob_list + var/client/C = M.client + usr.client.cmd_admin_mod_antag_rep(C, href_list["modantagrep"]) + show_player_panel(M) + + else if(href_list["slowquery"]) + if(!check_rights(R_ADMIN)) + return + var/answer = href_list["slowquery"] + if(answer == "yes") + log_query_debug("[usr.key] | Reported a server hang") + if(alert(usr, "Had you just press any admin buttons?", "Query server hang report", "Yes", "No") == "Yes") + var/response = input(usr,"What were you just doing?","Query server hang report") as null|text + if(response) + log_query_debug("[usr.key] | [response]") + else if(answer == "no") + log_query_debug("[usr.key] | Reported no server hang") + + else if(href_list["ctf_toggle"]) + if(!check_rights(R_ADMIN)) + return + toggle_all_ctf(usr) + + else if(href_list["rebootworld"]) + if(!check_rights(R_ADMIN)) + return + var/confirm = alert("Are you sure you want to reboot the server?", "Confirm Reboot", "Yes", "No") + if(confirm == "No") + return + if(confirm == "Yes") + restart() + + else if(href_list["check_teams"]) + if(!check_rights(R_ADMIN)) + return + check_teams() + + else if(href_list["team_command"]) + if(!check_rights(R_ADMIN)) + return + switch(href_list["team_command"]) + if("create_team") + admin_create_team(usr) + if("rename_team") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_rename(usr) + if("communicate") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_communicate(usr) + if("delete_team") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_delete(usr) + if("add_objective") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_add_objective(usr) + if("remove_objective") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(!T) + return + var/datum/objective/O = locate(href_list["tobjective"]) in T.objectives + if(O) + T.admin_remove_objective(usr,O) + if("add_member") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_add_member(usr) + if("remove_member") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(!T) + return + var/datum/mind/M = locate(href_list["tmember"]) in T.members + if(M) + T.admin_remove_member(usr,M) + check_teams() + // yogs start - mentors + else if(href_list["makementor"]) + makeMentor(href_list["makementor"]) + + else if(href_list["removementor"]) + removeMentor(href_list["removementor"]) + // yogs end + + else if(href_list["newbankey"]) + var/player_key = href_list["newbankey"] + var/player_ip = href_list["newbanip"] + var/player_cid = href_list["newbancid"] + ban_panel(player_key, player_ip, player_cid) + + else if(href_list["intervaltype"]) //check for ban panel, intervaltype is used as it's the only value which will always be present + if(href_list["roleban_delimiter"]) + ban_parse_href(href_list) + else + ban_parse_href(href_list, TRUE) + + else if(href_list["searchunbankey"] || href_list["searchunbanadminkey"] || href_list["searchunbanip"] || href_list["searchunbancid"]) + var/player_key = href_list["searchunbankey"] + var/admin_key = href_list["searchunbanadminkey"] + var/player_ip = href_list["searchunbanip"] + var/player_cid = href_list["searchunbancid"] + unban_panel(player_key, admin_key, player_ip, player_cid) + + else if(href_list["unbanpagecount"]) + var/page = href_list["unbanpagecount"] + var/player_key = href_list["unbankey"] + var/admin_key = href_list["unbanadminkey"] + var/player_ip = href_list["unbanip"] + var/player_cid = href_list["unbancid"] + unban_panel(player_key, admin_key, player_ip, player_cid, page) + + else if(href_list["editbanid"]) + var/edit_id = href_list["editbanid"] + var/player_key = href_list["editbankey"] + var/player_ip = href_list["editbanip"] + var/player_cid = href_list["editbancid"] + var/role = href_list["editbanrole"] + var/duration = href_list["editbanduration"] + var/applies_to_admins = text2num(href_list["editbanadmins"]) + var/reason = url_decode(href_list["editbanreason"]) + var/page = href_list["editbanpage"] + var/admin_key = href_list["editbanadminkey"] + ban_panel(player_key, player_ip, player_cid, role, duration, applies_to_admins, reason, edit_id, page, admin_key) + + else if(href_list["unbanid"]) + var/ban_id = href_list["unbanid"] + var/player_key = href_list["unbankey"] + var/player_ip = href_list["unbanip"] + var/player_cid = href_list["unbancid"] + var/role = href_list["unbanrole"] + var/page = href_list["unbanpage"] + var/admin_key = href_list["unbanadminkey"] + unban(ban_id, player_key, player_ip, player_cid, role, page, admin_key) + + else if(href_list["unbanlog"]) + var/ban_id = href_list["unbanlog"] + ban_log(ban_id) + + else if(href_list["beakerpanel"]) + beaker_panel_act(href_list) + +/datum/admins/proc/HandleCMode() + if(!check_rights(R_ADMIN)) + return + + var/dat = {"What mode do you wish to play?
                "} + for(var/mode in config.modes) + dat += {"[config.mode_names[mode]]
                "} + dat += {"Secret
                "} + dat += {"Random
                "} + dat += {"Now: [GLOB.master_mode]"} + usr << browse(dat, "window=c_mode") + +/datum/admins/proc/HandleFSecret() + if(!check_rights(R_ADMIN)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
                "} + for(var/mode in config.modes) + dat += {"[config.mode_names[mode]]
                "} + dat += {"Random (default)
                "} + dat += {"Now: [GLOB.secret_force_mode]"} + usr << browse(dat, "window=f_secret") diff --git a/code/modules/admin/verbs/BrokenInhands.dm b/code/modules/admin/verbs/BrokenInhands.dm index defb46446ec8..112dff352b4c 100644 --- a/code/modules/admin/verbs/BrokenInhands.dm +++ b/code/modules/admin/verbs/BrokenInhands.dm @@ -1,35 +1,35 @@ -/proc/getbrokeninhands() - var/text - for(var/A in typesof(/obj/item)) - var/obj/item/O = new A( locate(1,1,1) ) - if(!O) - continue - var/icon/IL = new(O.lefthand_file) - var/list/Lstates = IL.IconStates() - var/icon/IR = new(O.righthand_file) - var/list/Rstates = IR.IconStates() - var/icon/J = new(O.icon) - var/list/istates = J.IconStates() - if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.item_state)) - if(O.icon_state) - text += "[O.type] WANTS IN LEFT HAND CALLED\n\"[O.icon_state]\".\n" - if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.item_state)) - if(O.icon_state) - text += "[O.type] WANTS IN RIGHT HAND CALLED\n\"[O.icon_state]\".\n" - - - if(O.icon_state) - if(!istates.Find(O.icon_state)) - text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.icon_state]\" IN \"[O.icon]\"\n" - if(O.item_state) - if(!istates.Find(O.item_state)) - text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.item_state]\" IN \"[O.icon]\"\n" - text+="\n" - qdel(O) - if(text) - var/F = file("broken_icons.txt") - fdel(F) - WRITE_FILE(F, text) - to_chat(world, "Completely successfully and written to [F]") - - +/proc/getbrokeninhands() + var/text + for(var/A in typesof(/obj/item)) + var/obj/item/O = new A( locate(1,1,1) ) + if(!O) + continue + var/icon/IL = new(O.lefthand_file) + var/list/Lstates = IL.IconStates() + var/icon/IR = new(O.righthand_file) + var/list/Rstates = IR.IconStates() + var/icon/J = new(O.icon) + var/list/istates = J.IconStates() + if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.item_state)) + if(O.icon_state) + text += "[O.type] WANTS IN LEFT HAND CALLED\n\"[O.icon_state]\".\n" + if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.item_state)) + if(O.icon_state) + text += "[O.type] WANTS IN RIGHT HAND CALLED\n\"[O.icon_state]\".\n" + + + if(O.icon_state) + if(!istates.Find(O.icon_state)) + text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.icon_state]\" IN \"[O.icon]\"\n" + if(O.item_state) + if(!istates.Find(O.item_state)) + text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.item_state]\" IN \"[O.icon]\"\n" + text+="\n" + qdel(O) + if(text) + var/F = file("broken_icons.txt") + fdel(F) + WRITE_FILE(F, text) + to_chat(world, "Completely successfully and written to [F]") + + diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index a8b8af653534..4f4941037bd1 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -1,681 +1,681 @@ -// yogs - This file is mirrored to yogstation/modules/admin -/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("[usr.client.ckey] PM'd [initiator.ckey]") - 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()]: [formatted_message]" - -//Removes the ahelp verb and returns it after 2 minutes -/datum/admin_help/proc/TimeoutVerb() - initiator.verbs -= /client/verb/adminhelp - initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps - -//private -/datum/admin_help/proc/FullMonty(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - . = ADMIN_FULLMONTY_NONAME(initiator.mob) - if(state == AHELP_ACTIVE) - . += ClosureLinks(ref_src) - -//private -/datum/admin_help/proc/ClosureLinks(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - . = " (REJT)" - . += " (IC)" - . += " (CLOSE)" - . += " (RSLVE)" - -//private -/datum/admin_help/proc/LinkedReplyName(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - return "[initiator_key_name]" - -//private -/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket") - if(!ref_src) - ref_src = "[REF(src)]" - return "[msg]" - -//message from the initiator without a target, all admins will see this -//won't bug irc -/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]") - log_admin_private("Ticket #[id]: [key_name(initiator)]: [msg]") - - //send this msg to all admins - for(var/client/X in GLOB.admins) - if(X.prefs.toggles & SOUND_ADMINHELP) - SEND_SOUND(X, sound('sound/effects/adminhelp.ogg')) - window_flash(X, ignorepref = TRUE) - to_chat(X, admin_msg) - - //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) - 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 an admin. 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! -") - 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! -
                " - msg += "Your issue has been determined by an administrator to be an in character issue and does NOT require administrator intervention at this time. For further resolution you should pursue options that are in character." - - if(initiator) - to_chat(initiator, msg) - - 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) - -//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 += "[GLOB.TAB][TicketHref("Refresh", ref_src)][GLOB.TAB][TicketHref("Re-Title", ref_src, "retitle")]" - if(state != AHELP_ACTIVE) - dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]" - dat += "

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

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

                " - for(var/I in _interactions) - dat += "[I]
                " - - usr << browse(dat.Join(), "window=ahelp[id];size=620x480") - -/datum/admin_help/proc/Retitle() - var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null - if(new_title) - name = new_title - //not saying the original name cause it could be a long ass message - var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]" - message_admins(msg) - log_admin_private(msg) - TicketPanel() //we have to be here to do this - -//Forwarded action from admin/Topic -/datum/admin_help/proc/Action(action) - testing("Ahelp action: [action]") - switch(action) - if("ticket") - TicketPanel() - if("retitle") - Retitle() - if("reject") - Reject() - if("reply") - usr.client.cmd_ahelp_reply(initiator) - if("icissue") - ICIssue() - if("close") - Close() - if("resolve") - Resolve() - if("reopen") - Reopen() - -// -// TICKET STATCLICK -// - -/obj/effect/statclick/ahelp - var/datum/admin_help/ahelp_datum - -/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH) - ahelp_datum = AH - . = ..() - -/obj/effect/statclick/ahelp/update() - return ..(ahelp_datum.name) - -/obj/effect/statclick/ahelp/Click() - ahelp_datum.TicketPanel() - -/obj/effect/statclick/ahelp/Destroy() - ahelp_datum = null - return ..() - -// -// CLIENT PROCS -// - -/client/proc/giveadminhelpverb() - src.verbs |= /client/verb/adminhelp - deltimer(adminhelptimerid) - adminhelptimerid = 0 - -// Used for methods where input via arg doesn't work -/client/proc/get_adminhelp() - var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as text|null - 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 +// yogs - This file is mirrored to yogstation/modules/admin +/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("[usr.client.ckey] PM'd [initiator.ckey]") + 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()]: [formatted_message]" + +//Removes the ahelp verb and returns it after 2 minutes +/datum/admin_help/proc/TimeoutVerb() + initiator.verbs -= /client/verb/adminhelp + initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps + +//private +/datum/admin_help/proc/FullMonty(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + . = ADMIN_FULLMONTY_NONAME(initiator.mob) + if(state == AHELP_ACTIVE) + . += ClosureLinks(ref_src) + +//private +/datum/admin_help/proc/ClosureLinks(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + . = " (REJT)" + . += " (IC)" + . += " (CLOSE)" + . += " (RSLVE)" + +//private +/datum/admin_help/proc/LinkedReplyName(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + return "[initiator_key_name]" + +//private +/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket") + if(!ref_src) + ref_src = "[REF(src)]" + return "[msg]" + +//message from the initiator without a target, all admins will see this +//won't bug irc +/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]") + log_admin_private("Ticket #[id]: [key_name(initiator)]: [msg]") + + //send this msg to all admins + for(var/client/X in GLOB.admins) + if(X.prefs.toggles & SOUND_ADMINHELP) + SEND_SOUND(X, sound('sound/effects/adminhelp.ogg')) + window_flash(X, ignorepref = TRUE) + to_chat(X, admin_msg) + + //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) + 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 an admin. 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! -") + 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! -
                " + msg += "Your issue has been determined by an administrator to be an in character issue and does NOT require administrator intervention at this time. For further resolution you should pursue options that are in character." + + if(initiator) + to_chat(initiator, msg) + + 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) + +//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 += "[GLOB.TAB][TicketHref("Refresh", ref_src)][GLOB.TAB][TicketHref("Re-Title", ref_src, "retitle")]" + if(state != AHELP_ACTIVE) + dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]" + dat += "

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

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

                " + for(var/I in _interactions) + dat += "[I]
                " + + usr << browse(dat.Join(), "window=ahelp[id];size=620x480") + +/datum/admin_help/proc/Retitle() + var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null + if(new_title) + name = new_title + //not saying the original name cause it could be a long ass message + var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]" + message_admins(msg) + log_admin_private(msg) + TicketPanel() //we have to be here to do this + +//Forwarded action from admin/Topic +/datum/admin_help/proc/Action(action) + testing("Ahelp action: [action]") + switch(action) + if("ticket") + TicketPanel() + if("retitle") + Retitle() + if("reject") + Reject() + if("reply") + usr.client.cmd_ahelp_reply(initiator) + if("icissue") + ICIssue() + if("close") + Close() + if("resolve") + Resolve() + if("reopen") + Reopen() + +// +// TICKET STATCLICK +// + +/obj/effect/statclick/ahelp + var/datum/admin_help/ahelp_datum + +/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH) + ahelp_datum = AH + . = ..() + +/obj/effect/statclick/ahelp/update() + return ..(ahelp_datum.name) + +/obj/effect/statclick/ahelp/Click() + ahelp_datum.TicketPanel() + +/obj/effect/statclick/ahelp/Destroy() + ahelp_datum = null + return ..() + +// +// CLIENT PROCS +// + +/client/proc/giveadminhelpverb() + src.verbs |= /client/verb/adminhelp + deltimer(adminhelptimerid) + adminhelptimerid = 0 + +// Used for methods where input via arg doesn't work +/client/proc/get_adminhelp() + var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as text|null + 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 d3631f24d037..525e4e82c0d0 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/adminsay.dm b/code/modules/admin/verbs/adminsay.dm index 37b4b89be191..83b73a72c4ac 100644 --- a/code/modules/admin/verbs/adminsay.dm +++ b/code/modules/admin/verbs/adminsay.dm @@ -1,23 +1,23 @@ -//mirrored in the yogstation folder -/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 = emoji_parse(copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)) - if(!msg) - return - - mob.log_talk(msg, LOG_ASAY) - msg = keywords_lookup(msg) - var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "" : null // Yogs -- yogs asay - msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [custom_asay_color][msg][custom_asay_color ? "":null]" - 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|null - cmd_admin_say(msg) +//mirrored in the yogstation folder +/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 = emoji_parse(copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)) + if(!msg) + return + + mob.log_talk(msg, LOG_ASAY) + msg = keywords_lookup(msg) + var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "" : null // Yogs -- yogs asay + msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [custom_asay_color][msg][custom_asay_color ? "":null]" + 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|null + cmd_admin_say(msg) diff --git a/code/modules/admin/verbs/atmosdebug.dm b/code/modules/admin/verbs/atmosdebug.dm index 6075f1eace65..48434dba49ee 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/components/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") - - //Manifolds - for(var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") - - //Pipes - for(var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") - -/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/components/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") + + //Manifolds + for(var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") + + //Pipes + for(var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") + +/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 5aa3258f07ac..9b27a8d8e981 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 32692266b6ee..058d9d5d0268 100644 --- a/code/modules/admin/verbs/deadsay.dm +++ b/code/modules/admin/verbs/deadsay.dm @@ -1,42 +1,42 @@ -/client/proc/dsay(msg as text) - set category = "Special Verbs" - set name = "Dsay" - set hidden = 1 - if(!holder) - to_chat(src, "Only administrators may use this command.") - return - if(!mob) - return - if(prefs.muted & MUTE_DEADCHAT) - to_chat(src, "You cannot send DSAY messages (muted).") - return - - if (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/rank_name = holder.rank - var/admin_name = key - if(holder.fakekey) - rank_name = pick(strings("admin_nicknames.json", "ranks", "config")) - admin_name = pick(strings("admin_nicknames.json", "names", "config")) - var/rendered = "DEAD: [rank_name]([admin_name]) says, \"[emoji_parse(msg)]\"" - - for (var/mob/M in GLOB.player_list) - if(isnewplayer(M) && !(M.client && M.client.holder)) // Yogs -- Allows admins to hear admin deadsay while in the lobby - 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() - // yogs start - Cancel button - var/msg = input(src, null, "dsay \"text\"") as text|null - if(msg) - dsay(msg) - // yogs end +/client/proc/dsay(msg as text) + set category = "Special Verbs" + set name = "Dsay" + set hidden = 1 + if(!holder) + to_chat(src, "Only administrators may use this command.") + return + if(!mob) + return + if(prefs.muted & MUTE_DEADCHAT) + to_chat(src, "You cannot send DSAY messages (muted).") + return + + if (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/rank_name = holder.rank + var/admin_name = key + if(holder.fakekey) + rank_name = pick(strings("admin_nicknames.json", "ranks", "config")) + admin_name = pick(strings("admin_nicknames.json", "names", "config")) + var/rendered = "DEAD: [rank_name]([admin_name]) says, \"[emoji_parse(msg)]\"" + + for (var/mob/M in GLOB.player_list) + if(isnewplayer(M) && !(M.client && M.client.holder)) // Yogs -- Allows admins to hear admin deadsay while in the lobby + 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() + // yogs start - Cancel button + var/msg = input(src, null, "dsay \"text\"") as text|null + if(msg) + dsay(msg) + // yogs end diff --git a/code/modules/admin/verbs/fps.dm b/code/modules/admin/verbs/fps.dm index f120f540b9ff..a73895505261 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.change_fps(new_fps) +//replaces the old Ticklag verb, fps is easier to understand +/client/proc/set_server_fps() + set category = "Debug" + set name = "Set Server FPS" + set desc = "Sets game speed in frames-per-second. Can potentially break the game" + + if(!check_rights(R_DEBUG)) + return + + var/cfg_fps = CONFIG_GET(number/fps) + var/new_fps = round(input("Sets game frames-per-second. Can potentially break the game (default: [cfg_fps])","FPS", world.fps) as num|null) + + if(new_fps <= 0) + to_chat(src, "Error: set_server_fps(): Invalid world.fps value. No changes made.") + return + if(new_fps > cfg_fps * 1.5) + if(alert(src, "You are setting fps to a high value:\n\t[new_fps] frames-per-second\n\tconfig.fps = [cfg_fps]","Warning!","Confirm","ABORT-ABORT-ABORT") != "Confirm") + return + + var/msg = "[key_name(src)] has modified world.fps to [new_fps]" + log_admin(msg, 0) + message_admins(msg, 0) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Set Server FPS", "[new_fps]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + CONFIG_SET(number/fps, new_fps) + world.change_fps(new_fps) diff --git a/code/modules/admin/verbs/getlogs.dm b/code/modules/admin/verbs/getlogs.dm index 5f7b7becc6b4..21a722d32fd4 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 b59011827f84..6e6a76642514 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -1,376 +1,376 @@ -//- 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 75050b5e621a..ba199350e3c2 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 e942e771165e..bc3784e4e987 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, lentext(types[type])+1) - var/shorttype = copytext("[D.type]", lentext("[type]")+1) - if (lentext(shorttype) > lentext(fancytype)) - shorttype = fancytype - if (!lentext(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(src)] modified [original_name]'s [variable] from [var_value] to [var_new]" // yogs - Yog Tickets - message_admins(msg) - admin_ticket_log(O, msg) - return TRUE +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, lentext(types[type])+1) + var/shorttype = copytext("[D.type]", lentext("[type]")+1) + if (lentext(shorttype) > lentext(fancytype)) + shorttype = fancytype + if (!lentext(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(src)] modified [original_name]'s [variable] from [var_value] to [var_new]" // yogs - Yog Tickets + message_admins(msg) + admin_ticket_log(O, msg) + return TRUE diff --git a/code/modules/admin/verbs/onlyone.dm b/code/modules/admin/verbs/onlyone.dm index 05f56047cb82..f59d776a935f 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 5d1909de9aca..122c2ffc5946 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -1,180 +1,180 @@ -/client/proc/play_sound(S as sound) - set category = "Fun" - set name = "Play Global Sound" - if(!check_rights(R_SOUNDS)) - return - - var/freq = 1 - var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num - if(!vol) - return - vol = CLAMP(vol, 1, 100) - - var/sound/admin_sound = new() - admin_sound.file = S - admin_sound.priority = 250 - admin_sound.channel = CHANNEL_ADMIN - admin_sound.frequency = freq - admin_sound.wait = 1 - admin_sound.repeat = 0 - admin_sound.status = SOUND_STREAM - admin_sound.volume = vol - - var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel") - switch(res) - if("Yes") - to_chat(world, "An admin played: [S]") - if("Cancel") - return - - //log_admin("[key_name(src)] played sound [S]") // Yogs comment-out - //message_admins("[key_name_admin(src)] played sound [S]") // Yogs comment-out - var/count = 0 //yogs - - 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 - count++ //Yogs - //yogs start -- informs admins of how much of the server actually heard their sound - count = round(count / GLOB.player_list.len * 100,0.5) - log_admin("[key_name(src)] played sound [S] to [count]% of the server.") - message_admins("[key_name_admin(src)] played sound [S] to [count]% of the server.") - //yogs end - - 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/list/music_extra_data = list() - if(length(web_sound_input)) - - web_sound_input = trim(web_sound_input) - if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol)) - to_chat(src, "Non-http(s) URIs are not allowed.") - 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] --geo-bypass --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"") - var/errorlevel = output[SHELLEO_ERRORLEVEL] - var/stdout = output[SHELLEO_STDOUT] - var/stderr = output[SHELLEO_STDERR] - if(!errorlevel) - var/list/data - try - data = json_decode(stdout) - catch(var/exception/e) - to_chat(src, "Youtube-dl JSON parsing FAILED:") - 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]" - music_extra_data["start"] = data["start_time"] - music_extra_data["end"] = data["end_time"] - - var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel") - switch(res) - if("Yes") - to_chat(world, "An admin played: [webpage_url]") - 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, music_extra_data) - else - C.chatOutput.stopMusic() - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound") - -/client/proc/set_round_end_sound(S as sound) - set category = "Fun" - set name = "Set Round End Sound" - if(!check_rights(R_SOUNDS)) - return - - //Yogs start -- Adds confirm for whenever an admin has already set the roundend sound. - var/static/lastadmin - var/static/lastsound - - if(lastadmin && src.ckey != lastadmin) - if(alert("Warning: Another Admin, [lastadmin], already set the roundendsound to [lastsound]. Overwrite?",,"Yes","Cancel") != "Yes") - return - SSticker.SetRoundEndSound(S) - lastadmin = src.ckey - lastsound = "[S]" - //Yogs end - - log_admin("[key_name(src)] set the round end sound to [S]") - message_admins("[key_name_admin(src)] set the round end sound to [S]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Round End Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/stop_sounds() - set category = "Debug" - set name = "Stop All Playing Sounds" - if(!src.holder) - return - - log_admin("[key_name(src)] stopped all currently playing sounds.") - message_admins("[key_name_admin(src)] stopped all currently playing sounds.") - for(var/mob/M in GLOB.player_list) - 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/freq = 1 + var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num + if(!vol) + return + vol = CLAMP(vol, 1, 100) + + var/sound/admin_sound = new() + admin_sound.file = S + admin_sound.priority = 250 + admin_sound.channel = CHANNEL_ADMIN + admin_sound.frequency = freq + admin_sound.wait = 1 + admin_sound.repeat = 0 + admin_sound.status = SOUND_STREAM + admin_sound.volume = vol + + var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel") + switch(res) + if("Yes") + to_chat(world, "An admin played: [S]") + if("Cancel") + return + + //log_admin("[key_name(src)] played sound [S]") // Yogs comment-out + //message_admins("[key_name_admin(src)] played sound [S]") // Yogs comment-out + var/count = 0 //yogs + + 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 + count++ //Yogs + //yogs start -- informs admins of how much of the server actually heard their sound + count = round(count / GLOB.player_list.len * 100,0.5) + log_admin("[key_name(src)] played sound [S] to [count]% of the server.") + message_admins("[key_name_admin(src)] played sound [S] to [count]% of the server.") + //yogs end + + 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/list/music_extra_data = list() + if(length(web_sound_input)) + + web_sound_input = trim(web_sound_input) + if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol)) + to_chat(src, "Non-http(s) URIs are not allowed.") + 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] --geo-bypass --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"") + var/errorlevel = output[SHELLEO_ERRORLEVEL] + var/stdout = output[SHELLEO_STDOUT] + var/stderr = output[SHELLEO_STDERR] + if(!errorlevel) + var/list/data + try + data = json_decode(stdout) + catch(var/exception/e) + to_chat(src, "Youtube-dl JSON parsing FAILED:") + 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]" + music_extra_data["start"] = data["start_time"] + music_extra_data["end"] = data["end_time"] + + var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel") + switch(res) + if("Yes") + to_chat(world, "An admin played: [webpage_url]") + 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, music_extra_data) + else + C.chatOutput.stopMusic() + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound") + +/client/proc/set_round_end_sound(S as sound) + set category = "Fun" + set name = "Set Round End Sound" + if(!check_rights(R_SOUNDS)) + return + + //Yogs start -- Adds confirm for whenever an admin has already set the roundend sound. + var/static/lastadmin + var/static/lastsound + + if(lastadmin && src.ckey != lastadmin) + if(alert("Warning: Another Admin, [lastadmin], already set the roundendsound to [lastsound]. Overwrite?",,"Yes","Cancel") != "Yes") + return + SSticker.SetRoundEndSound(S) + lastadmin = src.ckey + lastsound = "[S]" + //Yogs end + + log_admin("[key_name(src)] set the round end sound to [S]") + message_admins("[key_name_admin(src)] set the round end sound to [S]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Round End Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/stop_sounds() + set category = "Debug" + set name = "Stop All Playing Sounds" + if(!src.holder) + return + + log_admin("[key_name(src)] stopped all currently playing sounds.") + message_admins("[key_name_admin(src)] stopped all currently playing sounds.") + for(var/mob/M in GLOB.player_list) + 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 cfa676cbeab9..49e2cf2da252 100644 --- a/code/modules/admin/verbs/possess.dm +++ b/code/modules/admin/verbs/possess.dm @@ -1,58 +1,58 @@ -/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(obj/O in world) //yogs - fixed release object - set name = "Release Obj" - set category = "Object" - //usr.loc = get_turf(usr) - -//Yogs start - fixed release object - if(!usr.control_object) - to_chat(usr, "You need to possess an object first!") - return -//Yogs end - - 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(obj/O in world) //yogs - fixed release object + set name = "Release Obj" + set category = "Object" + //usr.loc = get_turf(usr) + +//Yogs start - fixed release object + if(!usr.control_object) + to_chat(usr, "You need to possess an object first!") + return +//Yogs end + + 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 cc90c6b4078e..f85d4416511d 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 9fb22b2dc092..a308ca70daac 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1,1217 +1,1217 @@ -/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(usr)] made [key_name(M)] drop everything!" // yogs - Yog Tickets - 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(usr)] -> [key_name(M)] : [msg]" // yogs - Yog Tickets - 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(mob, H, input, LOG_ADMIN, "reply") - message_admins("[key_name_admin(src)] replied to [key_name_admin(H)]'s [sender] message with: \"[input]\"") - to_chat(H, "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from [sender == "Syndicate" ? "your benefactor" : "Central Command"]. Message as follows[sender == "Syndicate" ? ", agent." : ":"] [input]. Message ends.\"") - - 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]" // yogs - Yog Tickets - 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(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]" // yogs - Yog Tickets - 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(usr)] has spawned [ckey] as a filthy xeno [alien_caste]." // yogs - Yog Tickets - 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 - new_xeno.key = G_found.key - to_chat(new_xeno, "You have been fully respawned. Enjoy the game.") - var/msg = "[key_name(usr)] has respawned [new_xeno.key] as a filthy xeno." // yogs - Yog Tickets - message_admins(msg) - admin_ticket_log(new_xeno, msg) - return //all done. The ghost is auto-deleted - - //check if they were a monkey - else if(findtext(G_found.real_name,"monkey")) - if(alert("This character appears to have been a monkey. Would you like to respawn them as such?",,"Yes","No")=="Yes") - var/mob/living/carbon/monkey/new_monkey = new - SSjob.SendToLateJoin(new_monkey) - G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_monkey.key = G_found.key - to_chat(new_monkey, "You have been fully respawned. Enjoy the game.") - var/msg = "[key_name(usr)] has respawned [new_monkey.key] as a filthy xeno." // yogs - Yog Tickets - message_admins(msg) - admin_ticket_log(new_monkey, msg) - return //all done. The ghost is auto-deleted - - - //Ok, it's not a xeno or a monkey. So, spawn a human. - var/mob/living/carbon/human/new_character = new//The mob being spawned. - SSjob.SendToLateJoin(new_character) - - var/datum/data/record/record_found //Referenced to later to either randomize or not randomize the character. - if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something - /*Try and locate a record for the person being respawned through GLOB.data_core. - This isn't an exact science but it does the trick more often than not.*/ - var/id = md5("[G_found.real_name][G_found.mind.assigned_role]") - - record_found = find_record("id", id, GLOB.data_core.locked) - - if(record_found)//If they have a record we can determine a few things. - new_character.real_name = record_found.fields["name"] - new_character.gender = record_found.fields["gender"] - new_character.age = record_found.fields["age"] - new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"]) - else - var/datum/preferences/A = new() - A.copy_to(new_character) - A.real_name = G_found.real_name - new_character.dna.update_dna_identity() - - new_character.name = new_character.real_name - - if(G_found.mind && !G_found.mind.active) - G_found.mind.transfer_to(new_character) //be careful when doing stuff like this! I've already checked the mind isn't in use - else - new_character.mind_initialize() - if(!new_character.mind.assigned_role) - new_character.mind.assigned_role = "Assistant"//If they somehow got a null assigned role. - - new_character.key = G_found.key - - /* - The code below functions with the assumption that the mob is already a traitor if they have a special role. - So all it does is re-equip the mob with powers and/or items. Or not, if they have no special role. - If they don't have a mind, they obviously don't have a special role. - */ - - //Two variables to properly announce later on. - var/admin = key_name_admin(src) - var/player_key = G_found.key - - //Now for special roles and equipment. - var/datum/antagonist/traitor/traitordatum = new_character.mind.has_antag_datum(/datum/antagonist/traitor) - if(traitordatum) - SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1) - traitordatum.equip() - - - switch(new_character.mind.special_role) - if(ROLE_WIZARD) - new_character.forceMove(pick(GLOB.wizardstart)) - var/datum/antagonist/wizard/A = new_character.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) - A.equip_wizard() - if(ROLE_SYNDICATE) - new_character.forceMove(pick(GLOB.nukeop_start)) - var/datum/antagonist/nukeop/N = new_character.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) - N.equip_op() - if(ROLE_NINJA) - var/list/ninja_spawn = list() - for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) - ninja_spawn += L - var/datum/antagonist/ninja/ninjadatum = new_character.mind.has_antag_datum(/datum/antagonist/ninja) - ninjadatum.equip_space_ninja() - if(ninja_spawn.len) - new_character.forceMove(pick(ninja_spawn)) - - else//They may also be a cyborg or AI. - switch(new_character.mind.assigned_role) - if("Cyborg")//More rigging to make em' work and check if they're traitor. - new_character = new_character.Robotize(TRUE) - if("AI") - new_character = new_character.AIize() - else - SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1)//Or we simply equip them. - - //Announces the character on all the systems, based on the record. - if(!issilicon(new_character))//If they are not a cyborg/AI. - if(!record_found&&new_character.mind.assigned_role!=new_character.mind.special_role)//If there are no records for them. If they have a record, this info is already in there. MODE people are not announced anyway. - //Power to the user! - if(alert(new_character,"Warning: No data core entry detected. Would you like to announce the arrival of this character by adding them to various databases, such as medical records?",,"No","Yes")=="Yes") - GLOB.data_core.manifest_inject(new_character) - - if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") - AnnounceArrival(new_character, new_character.mind.assigned_role) - - var/msg = "[admin] has respawned [player_key] as [new_character.real_name]." // yogs - Yog Tickets - 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(usr)] healed / revived [key_name(M)]!" // yogs - Yog Tickets - 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, 'sound/ai/commandreport.ogg') - announce_command_report = FALSE - if("Cancel") - return - - print_command_report(input, "[announce_command_report ? "Classified " : ""][command_name()] Update", announce_command_report) - - log_admin("[key_name(src)] has created a command report: [input]") - message_admins("[key_name_admin(src)] has created a command report") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Create Command Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_change_command_name() - set category = "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_ADMIN)) //yogs - makes this +admin instead of +spawn/+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)) - //yogs start -- Adds customization and warnings - var/newview = input("Select view range:", "FUCK YE", 7) in list(7,10,12,14,32,64,128,"Custom...") - if(newview == "Custom...") - newview = input("Enter custom view range:","FUCK YEEEEE") as num - if(!newview) - return - if(newview > 64) - if(alert("Warning: Setting your view range to that large size may cause horrendous lag, visual bugs, and/or game crashes. Are you sure?",,"Yes","No") != "Yes") - return - change_view(newview) - //yogs end - 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","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! - -/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/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/turf/T = get_turf(mob) - var/z_level = input("Z-Level to target?", "Z-Level", T?.z) as num|null - if(!isnum(z_level)) - return - - SSweather.run_weather(weather_type, z_level) - - message_admins("[key_name_admin(usr)] started weather of type [weather_type] on the z-level [z_level].") - log_admin("[key_name(usr)] started weather of type [weather_type] on the z-level [z_level].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Run Weather") - -/client/proc/mass_zombie_infection() - set category = "Fun" - set name = "Mass Zombie Infection" - set desc = "Infects all humans with a latent organ that will zombify \ - them on death." - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Please confirm you want to add latent zombie organs in all humans?", "Confirm Zombies", "Yes", "No") - if(confirm != "Yes") - return - - for(var/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/target as mob) - set name = "Smite" - set category = "Fun" - if(!check_rights(R_ADMIN) || !check_rights(R_FUN)) - return - - var/list/punishment_list = list(ADMIN_PUNISHMENT_LIGHTNING, ADMIN_PUNISHMENT_BRAINDAMAGE, ADMIN_PUNISHMENT_GIB, ADMIN_PUNISHMENT_BSA, ADMIN_PUNISHMENT_FIREBALL, ADMIN_PUNISHMENT_ROD, ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, ADMIN_PUNISHMENT_SUPPLYPOD, ADMIN_PUNISHMENT_MAZING) - - 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) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - H.electrocution_animation(40) - to_chat(target, "The gods have punished you for your sins!") - if(ADMIN_PUNISHMENT_BRAINDAMAGE) - target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199) - if(ADMIN_PUNISHMENT_GIB) - target.gib(FALSE) - if(ADMIN_PUNISHMENT_BSA) - bluespace_artillery(target) - if(ADMIN_PUNISHMENT_FIREBALL) - new /obj/effect/temp_visual/target(get_turf(target)) - if(ADMIN_PUNISHMENT_ROD) - var/turf/T = get_turf(target) - var/startside = pick(GLOB.cardinals) - var/turf/startT = spaceDebrisStartLoc(startside, T.z) - var/turf/endT = spaceDebrisFinishLoc(startside, T.z) - new /obj/effect/immovablerod(startT, endT,target) - if(ADMIN_PUNISHMENT_SUPPLYPOD_QUICK) - var/target_path = input(usr,"Enter typepath of an atom you'd like to send with the pod (type \"empty\" to send an empty pod):" ,"Typepath","/obj/item/reagent_containers/food/snacks/grown/harebell") as null|text - var/obj/structure/closet/supplypod/centcompod/pod = new() - pod.damage = 40 - pod.explosionSize = list(0,0,0,2) - pod.effectStun = TRUE - if (isnull(target_path)) //The user pressed "Cancel" - return - if (target_path != "empty")//if you didn't type empty, we want to load the pod with a delivery - var/delivery = text2path(target_path) - if(!ispath(delivery)) - delivery = pick_closest_path(target_path) - if(!delivery) - alert("ERROR: Incorrect / improper path given.") - return - new delivery(pod) - new /obj/effect/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 - punish_log(target, punishment) - -/client/proc/punish_log(var/whom, var/punishment) - var/msg = "[key_name(usr)] punished [key_name_admin(whom)] with [punishment]." //yogs - Yog tickets - 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(usr)] made [key_name(M)] drop everything!" // yogs - Yog Tickets + 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(usr)] -> [key_name(M)] : [msg]" // yogs - Yog Tickets + 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(mob, H, input, LOG_ADMIN, "reply") + message_admins("[key_name_admin(src)] replied to [key_name_admin(H)]'s [sender] message with: \"[input]\"") + to_chat(H, "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from [sender == "Syndicate" ? "your benefactor" : "Central Command"]. Message as follows[sender == "Syndicate" ? ", agent." : ":"] [input]. Message ends.\"") + + 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]" // yogs - Yog Tickets + 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(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]" // yogs - Yog Tickets + 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(usr)] has spawned [ckey] as a filthy xeno [alien_caste]." // yogs - Yog Tickets + 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 + new_xeno.key = G_found.key + to_chat(new_xeno, "You have been fully respawned. Enjoy the game.") + var/msg = "[key_name(usr)] has respawned [new_xeno.key] as a filthy xeno." // yogs - Yog Tickets + message_admins(msg) + admin_ticket_log(new_xeno, msg) + return //all done. The ghost is auto-deleted + + //check if they were a monkey + else if(findtext(G_found.real_name,"monkey")) + if(alert("This character appears to have been a monkey. Would you like to respawn them as such?",,"Yes","No")=="Yes") + var/mob/living/carbon/monkey/new_monkey = new + SSjob.SendToLateJoin(new_monkey) + G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use + new_monkey.key = G_found.key + to_chat(new_monkey, "You have been fully respawned. Enjoy the game.") + var/msg = "[key_name(usr)] has respawned [new_monkey.key] as a filthy xeno." // yogs - Yog Tickets + message_admins(msg) + admin_ticket_log(new_monkey, msg) + return //all done. The ghost is auto-deleted + + + //Ok, it's not a xeno or a monkey. So, spawn a human. + var/mob/living/carbon/human/new_character = new//The mob being spawned. + SSjob.SendToLateJoin(new_character) + + var/datum/data/record/record_found //Referenced to later to either randomize or not randomize the character. + if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something + /*Try and locate a record for the person being respawned through GLOB.data_core. + This isn't an exact science but it does the trick more often than not.*/ + var/id = md5("[G_found.real_name][G_found.mind.assigned_role]") + + record_found = find_record("id", id, GLOB.data_core.locked) + + if(record_found)//If they have a record we can determine a few things. + new_character.real_name = record_found.fields["name"] + new_character.gender = record_found.fields["gender"] + new_character.age = record_found.fields["age"] + new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"]) + else + var/datum/preferences/A = new() + A.copy_to(new_character) + A.real_name = G_found.real_name + new_character.dna.update_dna_identity() + + new_character.name = new_character.real_name + + if(G_found.mind && !G_found.mind.active) + G_found.mind.transfer_to(new_character) //be careful when doing stuff like this! I've already checked the mind isn't in use + else + new_character.mind_initialize() + if(!new_character.mind.assigned_role) + new_character.mind.assigned_role = "Assistant"//If they somehow got a null assigned role. + + new_character.key = G_found.key + + /* + The code below functions with the assumption that the mob is already a traitor if they have a special role. + So all it does is re-equip the mob with powers and/or items. Or not, if they have no special role. + If they don't have a mind, they obviously don't have a special role. + */ + + //Two variables to properly announce later on. + var/admin = key_name_admin(src) + var/player_key = G_found.key + + //Now for special roles and equipment. + var/datum/antagonist/traitor/traitordatum = new_character.mind.has_antag_datum(/datum/antagonist/traitor) + if(traitordatum) + SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1) + traitordatum.equip() + + + switch(new_character.mind.special_role) + if(ROLE_WIZARD) + new_character.forceMove(pick(GLOB.wizardstart)) + var/datum/antagonist/wizard/A = new_character.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) + A.equip_wizard() + if(ROLE_SYNDICATE) + new_character.forceMove(pick(GLOB.nukeop_start)) + var/datum/antagonist/nukeop/N = new_character.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) + N.equip_op() + if(ROLE_NINJA) + var/list/ninja_spawn = list() + for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) + ninja_spawn += L + var/datum/antagonist/ninja/ninjadatum = new_character.mind.has_antag_datum(/datum/antagonist/ninja) + ninjadatum.equip_space_ninja() + if(ninja_spawn.len) + new_character.forceMove(pick(ninja_spawn)) + + else//They may also be a cyborg or AI. + switch(new_character.mind.assigned_role) + if("Cyborg")//More rigging to make em' work and check if they're traitor. + new_character = new_character.Robotize(TRUE) + if("AI") + new_character = new_character.AIize() + else + SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1)//Or we simply equip them. + + //Announces the character on all the systems, based on the record. + if(!issilicon(new_character))//If they are not a cyborg/AI. + if(!record_found&&new_character.mind.assigned_role!=new_character.mind.special_role)//If there are no records for them. If they have a record, this info is already in there. MODE people are not announced anyway. + //Power to the user! + if(alert(new_character,"Warning: No data core entry detected. Would you like to announce the arrival of this character by adding them to various databases, such as medical records?",,"No","Yes")=="Yes") + GLOB.data_core.manifest_inject(new_character) + + if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") + AnnounceArrival(new_character, new_character.mind.assigned_role) + + var/msg = "[admin] has respawned [player_key] as [new_character.real_name]." // yogs - Yog Tickets + 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(usr)] healed / revived [key_name(M)]!" // yogs - Yog Tickets + 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, 'sound/ai/commandreport.ogg') + announce_command_report = FALSE + if("Cancel") + return + + print_command_report(input, "[announce_command_report ? "Classified " : ""][command_name()] Update", announce_command_report) + + log_admin("[key_name(src)] has created a command report: [input]") + message_admins("[key_name_admin(src)] has created a command report") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Create Command Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_change_command_name() + set category = "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_ADMIN)) //yogs - makes this +admin instead of +spawn/+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)) + //yogs start -- Adds customization and warnings + var/newview = input("Select view range:", "FUCK YE", 7) in list(7,10,12,14,32,64,128,"Custom...") + if(newview == "Custom...") + newview = input("Enter custom view range:","FUCK YEEEEE") as num + if(!newview) + return + if(newview > 64) + if(alert("Warning: Setting your view range to that large size may cause horrendous lag, visual bugs, and/or game crashes. Are you sure?",,"Yes","No") != "Yes") + return + change_view(newview) + //yogs end + 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","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! + +/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/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/turf/T = get_turf(mob) + var/z_level = input("Z-Level to target?", "Z-Level", T?.z) as num|null + if(!isnum(z_level)) + return + + SSweather.run_weather(weather_type, z_level) + + message_admins("[key_name_admin(usr)] started weather of type [weather_type] on the z-level [z_level].") + log_admin("[key_name(usr)] started weather of type [weather_type] on the z-level [z_level].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Run Weather") + +/client/proc/mass_zombie_infection() + set category = "Fun" + set name = "Mass Zombie Infection" + set desc = "Infects all humans with a latent organ that will zombify \ + them on death." + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Please confirm you want to add latent zombie organs in all humans?", "Confirm Zombies", "Yes", "No") + if(confirm != "Yes") + return + + for(var/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/target as mob) + set name = "Smite" + set category = "Fun" + if(!check_rights(R_ADMIN) || !check_rights(R_FUN)) + return + + var/list/punishment_list = list(ADMIN_PUNISHMENT_LIGHTNING, ADMIN_PUNISHMENT_BRAINDAMAGE, ADMIN_PUNISHMENT_GIB, ADMIN_PUNISHMENT_BSA, ADMIN_PUNISHMENT_FIREBALL, ADMIN_PUNISHMENT_ROD, ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, ADMIN_PUNISHMENT_SUPPLYPOD, ADMIN_PUNISHMENT_MAZING) + + 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) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + H.electrocution_animation(40) + to_chat(target, "The gods have punished you for your sins!") + if(ADMIN_PUNISHMENT_BRAINDAMAGE) + target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199) + if(ADMIN_PUNISHMENT_GIB) + target.gib(FALSE) + if(ADMIN_PUNISHMENT_BSA) + bluespace_artillery(target) + if(ADMIN_PUNISHMENT_FIREBALL) + new /obj/effect/temp_visual/target(get_turf(target)) + if(ADMIN_PUNISHMENT_ROD) + var/turf/T = get_turf(target) + var/startside = pick(GLOB.cardinals) + var/turf/startT = spaceDebrisStartLoc(startside, T.z) + var/turf/endT = spaceDebrisFinishLoc(startside, T.z) + new /obj/effect/immovablerod(startT, endT,target) + if(ADMIN_PUNISHMENT_SUPPLYPOD_QUICK) + var/target_path = input(usr,"Enter typepath of an atom you'd like to send with the pod (type \"empty\" to send an empty pod):" ,"Typepath","/obj/item/reagent_containers/food/snacks/grown/harebell") as null|text + var/obj/structure/closet/supplypod/centcompod/pod = new() + pod.damage = 40 + pod.explosionSize = list(0,0,0,2) + pod.effectStun = TRUE + if (isnull(target_path)) //The user pressed "Cancel" + return + if (target_path != "empty")//if you didn't type empty, we want to load the pod with a delivery + var/delivery = text2path(target_path) + if(!ispath(delivery)) + delivery = pick_closest_path(target_path) + if(!delivery) + alert("ERROR: Incorrect / improper path given.") + return + new delivery(pod) + new /obj/effect/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 + punish_log(target, punishment) + +/client/proc/punish_log(var/whom, var/punishment) + var/msg = "[key_name(usr)] punished [key_name_admin(whom)] with [punishment]." //yogs - Yog tickets + 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/verbs/shuttlepanel.dm b/code/modules/admin/verbs/shuttlepanel.dm index cf11034f85d7..a80eafae027a 100644 --- a/code/modules/admin/verbs/shuttlepanel.dm +++ b/code/modules/admin/verbs/shuttlepanel.dm @@ -1,76 +1,76 @@ -/datum/admins/proc/open_shuttlepanel() - set category = "Admin" - set name = "Shuttle Manipulator" - set desc = "Opens the shuttle manipulator UI." - - if(!check_rights(R_ADMIN)) - return - - SSshuttle.ui_interact(usr) - - -/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user) - var/list/options = list() - - for(var/port in SSshuttle.stationary) - if (istype(port, /obj/docking_port/stationary/transit)) - continue // please don't do this - var/obj/docking_port/stationary/S = port - if (canDock(S) == SHUTTLE_CAN_DOCK) - options[S.name || S.id] = S - - options += "--------" - options += "Infinite Transit" - options += "Delete Shuttle" - options += "Into The Sunset (delete & greentext 'escape')" - - var/selection = input(user, "Select where to fly [name || id]:", "Fly Shuttle") as null|anything in options - if(!selection) - return - - switch(selection) - if("Infinite Transit") - destination = null - mode = SHUTTLE_IGNITING - setTimer(ignitionTime) - - if("Delete Shuttle") - if(alert(user, "Really delete [name || id]?", "Delete Shuttle", "Cancel", "Really!") != "Really!") - return - jumpToNullSpace() - - if("Into The Sunset (delete & greentext 'escape')") - if(alert(user, "Really delete [name || id] and greentext escape objectives?", "Delete Shuttle", "Cancel", "Really!") != "Really!") - return - intoTheSunset() - - else - if(options[selection]) - request(options[selection]) - -/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user) - return // use the existing verbs for this - -/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user) - switch(alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", "Fly", "Retarget", "Cancel")) - if("Cancel") - return - if("Fly") - return ..() - - var/list/options = list() - - for(var/port in SSshuttle.stationary) - if (istype(port, /obj/docking_port/stationary/transit)) - continue // please don't do this - var/obj/docking_port/stationary/S = port - if (canDock(S) == SHUTTLE_CAN_DOCK) - options[S.name || S.id] = S - - var/selection = input(user, "Select the new arrivals destination:", "Fly Shuttle") as null|anything in options - if(!selection) - return - target_dock = options[selection] - if(!QDELETED(target_dock)) - destination = target_dock - +/datum/admins/proc/open_shuttlepanel() + set category = "Admin" + set name = "Shuttle Manipulator" + set desc = "Opens the shuttle manipulator UI." + + if(!check_rights(R_ADMIN)) + return + + SSshuttle.ui_interact(usr) + + +/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user) + var/list/options = list() + + for(var/port in SSshuttle.stationary) + if (istype(port, /obj/docking_port/stationary/transit)) + continue // please don't do this + var/obj/docking_port/stationary/S = port + if (canDock(S) == SHUTTLE_CAN_DOCK) + options[S.name || S.id] = S + + options += "--------" + options += "Infinite Transit" + options += "Delete Shuttle" + options += "Into The Sunset (delete & greentext 'escape')" + + var/selection = input(user, "Select where to fly [name || id]:", "Fly Shuttle") as null|anything in options + if(!selection) + return + + switch(selection) + if("Infinite Transit") + destination = null + mode = SHUTTLE_IGNITING + setTimer(ignitionTime) + + if("Delete Shuttle") + if(alert(user, "Really delete [name || id]?", "Delete Shuttle", "Cancel", "Really!") != "Really!") + return + jumpToNullSpace() + + if("Into The Sunset (delete & greentext 'escape')") + if(alert(user, "Really delete [name || id] and greentext escape objectives?", "Delete Shuttle", "Cancel", "Really!") != "Really!") + return + intoTheSunset() + + else + if(options[selection]) + request(options[selection]) + +/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user) + return // use the existing verbs for this + +/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user) + switch(alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", "Fly", "Retarget", "Cancel")) + if("Cancel") + return + if("Fly") + return ..() + + var/list/options = list() + + for(var/port in SSshuttle.stationary) + if (istype(port, /obj/docking_port/stationary/transit)) + continue // please don't do this + var/obj/docking_port/stationary/S = port + if (canDock(S) == SHUTTLE_CAN_DOCK) + options[S.name || S.id] = S + + var/selection = input(user, "Select the new arrivals destination:", "Fly Shuttle") as null|anything in options + if(!selection) + return + target_dock = options[selection] + if(!QDELETED(target_dock)) + destination = target_dock + diff --git a/code/modules/admin/verbs/tripAI.dm b/code/modules/admin/verbs/tripAI.dm index d2cd1e7eafd0..7742d6ea6600 100644 --- a/code/modules/admin/verbs/tripAI.dm +++ b/code/modules/admin/verbs/tripAI.dm @@ -1,20 +1,20 @@ -/client/proc/triple_ai() - set category = "Fun" - set name = "Create AI Triumvirate" - - if(SSticker.current_state > GAME_STATE_PREGAME) - to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.") - return - - var/datum/job/job = SSjob.GetJob("AI") - if(!job) - to_chat(usr, "Unable to locate the AI job") - return - if(SSticker.triai) - SSticker.triai = 0 - to_chat(usr, "Only one AI will be spawned at round start.") - message_admins("[key_name_admin(usr)] has toggled off triple AIs at round start.") - else - SSticker.triai = 1 - to_chat(usr, "There will be an AI Triumvirate at round start.") - message_admins("[key_name_admin(usr)] has toggled on triple AIs at round start.") +/client/proc/triple_ai() + set category = "Fun" + set name = "Create AI Triumvirate" + + if(SSticker.current_state > GAME_STATE_PREGAME) + to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.") + return + + var/datum/job/job = SSjob.GetJob("AI") + if(!job) + to_chat(usr, "Unable to locate the AI job") + return + if(SSticker.triai) + SSticker.triai = 0 + to_chat(usr, "Only one AI will be spawned at round start.") + message_admins("[key_name_admin(usr)] has toggled off triple AIs at round start.") + else + SSticker.triai = 1 + to_chat(usr, "There will be an AI Triumvirate at round start.") + message_admins("[key_name_admin(usr)] has toggled on triple AIs at round start.") diff --git a/code/modules/admin/whitelist.dm b/code/modules/admin/whitelist.dm index 55206f6debbe..263268a5ca12 100644 --- a/code/modules/admin/whitelist.dm +++ b/code/modules/admin/whitelist.dm @@ -1,23 +1,23 @@ -#define WHITELISTFILE "[global.config.directory]/whitelist.txt" - -GLOBAL_LIST(whitelist) -GLOBAL_PROTECT(whitelist) - -/proc/load_whitelist() - GLOB.whitelist = list() - for(var/line in world.file2list(WHITELISTFILE)) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - GLOB.whitelist += ckey(line) - - if(!GLOB.whitelist.len) - GLOB.whitelist = null - -/proc/check_whitelist(var/ckey) - if(!GLOB.whitelist) - return FALSE - . = (ckey in GLOB.whitelist) - -#undef WHITELISTFILE +#define WHITELISTFILE "[global.config.directory]/whitelist.txt" + +GLOBAL_LIST(whitelist) +GLOBAL_PROTECT(whitelist) + +/proc/load_whitelist() + GLOB.whitelist = list() + for(var/line in world.file2list(WHITELISTFILE)) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + GLOB.whitelist += ckey(line) + + if(!GLOB.whitelist.len) + GLOB.whitelist = null + +/proc/check_whitelist(var/ckey) + if(!GLOB.whitelist) + return FALSE + . = (ckey in GLOB.whitelist) + +#undef WHITELISTFILE diff --git a/code/modules/antagonists/_common/antag_hud.dm b/code/modules/antagonists/_common/antag_hud.dm index 447a87ce2098..de6d0a4f819a 100644 --- a/code/modules/antagonists/_common/antag_hud.dm +++ b/code/modules/antagonists/_common/antag_hud.dm @@ -1,53 +1,53 @@ -/datum/atom_hud/antag - hud_icons = list(ANTAG_HUD) - var/self_visible = TRUE - -/datum/atom_hud/antag/hidden - self_visible = FALSE - -/datum/atom_hud/antag/proc/join_hud(mob/M) - //sees_hud should be set to 0 if the mob does not get to see it's own hud type. - if(!istype(M)) - CRASH("join_hud(): [M] ([M.type]) is not a mob!") - if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged - M.mind.antag_hud.leave_hud(M) - add_to_hud(M) - if(self_visible) - add_hud_to(M) - M.mind.antag_hud = src - -/datum/atom_hud/antag/proc/leave_hud(mob/M) - if(!M) - return - if(!istype(M)) - CRASH("leave_hud(): [M] ([M.type]) is not a mob!") - remove_from_hud(M) - remove_hud_from(M) - if(M.mind) - M.mind.antag_hud = null - - -//GAME_MODE PROCS -//called to set a mob's antag icon state -/proc/set_antag_hud(mob/M, new_icon_state) - if(!istype(M)) - CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!") - var/image/holder = M.hud_list[ANTAG_HUD] - if(holder) - holder.icon_state = new_icon_state - if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime - M.mind.antag_hud_icon_state = new_icon_state - - -//MIND PROCS -//these are called by mind.transfer_to() -/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud) - leave_all_antag_huds() - set_antag_hud(current, antag_hud_icon_state) - if(newhud) - newhud.join_hud(current) - -/datum/mind/proc/leave_all_antag_huds() - for(var/datum/atom_hud/antag/hud in GLOB.huds) - if(hud.hudusers[current]) +/datum/atom_hud/antag + hud_icons = list(ANTAG_HUD) + var/self_visible = TRUE + +/datum/atom_hud/antag/hidden + self_visible = FALSE + +/datum/atom_hud/antag/proc/join_hud(mob/M) + //sees_hud should be set to 0 if the mob does not get to see it's own hud type. + if(!istype(M)) + CRASH("join_hud(): [M] ([M.type]) is not a mob!") + if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged + M.mind.antag_hud.leave_hud(M) + add_to_hud(M) + if(self_visible) + add_hud_to(M) + M.mind.antag_hud = src + +/datum/atom_hud/antag/proc/leave_hud(mob/M) + if(!M) + return + if(!istype(M)) + CRASH("leave_hud(): [M] ([M.type]) is not a mob!") + remove_from_hud(M) + remove_hud_from(M) + if(M.mind) + M.mind.antag_hud = null + + +//GAME_MODE PROCS +//called to set a mob's antag icon state +/proc/set_antag_hud(mob/M, new_icon_state) + if(!istype(M)) + CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!") + var/image/holder = M.hud_list[ANTAG_HUD] + if(holder) + holder.icon_state = new_icon_state + if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime + M.mind.antag_hud_icon_state = new_icon_state + + +//MIND PROCS +//these are called by mind.transfer_to() +/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud) + leave_all_antag_huds() + set_antag_hud(current, antag_hud_icon_state) + if(newhud) + newhud.join_hud(current) + +/datum/mind/proc/leave_all_antag_huds() + for(var/datum/atom_hud/antag/hud in GLOB.huds) + if(hud.hudusers[current]) hud.leave_hud(current) \ No newline at end of file diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 7cbc05942c79..1483436c90c8 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -1,281 +1,281 @@ -/obj/item/antag_spawner - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/used = FALSE - -/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) - return - -/obj/item/antag_spawner/proc/equip_antag(mob/target) - return - - -///////////WIZARD - -/obj/item/antag_spawner/contract - name = "contract" - desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid." - icon = 'icons/obj/wizard.dmi' - icon_state ="scroll2" - -/obj/item/antag_spawner/contract/attack_self(mob/user) - user.set_machine(src) - var/dat - if(used) - dat = "You have already summoned your apprentice.
                " - else - dat = "Contract of Apprenticeship:
                " - dat += "Using this contract, you may summon an apprentice to aid you on your mission.
                " - dat += "If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.
                " - dat += "Which school of magic is your apprentice studying?:
                " - dat += "Destruction
                " - dat += "Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.
                " - dat += "Bluespace Manipulation
                " - dat += "Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.
                " - dat += "Healing
                " - dat += "Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.
                " - dat += "Robeless
                " - dat += "Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.
                " - user << browse(dat, "window=radio") - onclose(user, "radio") - return - -/obj/item/antag_spawner/contract/Topic(href, href_list) - ..() - var/mob/living/carbon/human/H = usr - - if(H.stat || H.restrained()) - return - if(!ishuman(H)) - return 1 - - if(loc == H || (in_range(src, H) && isturf(loc))) - H.set_machine(src) - if(href_list["school"]) - if(used) - to_chat(H, "You already used this contract!") - return - var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src) - if(LAZYLEN(candidates)) - if(QDELETED(src)) - return - if(used) - to_chat(H, "You already used this contract!") - return - used = TRUE - var/mob/dead/observer/C = pick(candidates) - spawn_antag(C.client, get_turf(src), href_list["school"],H.mind) - else - to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.") - -/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user) - new /obj/effect/particle_effect/smoke(T) - var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) - C.prefs.copy_to(M) - M.key = C.key - var/datum/mind/app_mind = M.mind - - var/datum/antagonist/wizard/apprentice/app = new() - app.master = user - app.school = kind - - var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard) - if(master_wizard) - if(!master_wizard.wiz_team) - master_wizard.create_wiz_team() - app.wiz_team = master_wizard.wiz_team - master_wizard.wiz_team.add_member(app_mind) - app_mind.add_antag_datum(app) - //TODO Kill these if possible - app_mind.assigned_role = "Apprentice" - app_mind.special_role = "apprentice" - // - SEND_SOUND(M, sound('sound/effects/magic.ogg')) - -///////////BORGS AND OPERATIVES - - -/obj/item/antag_spawner/nuke_ops - name = "syndicate operative teleporter" - desc = "A single-use teleporter designed to quickly reinforce operatives in the field." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - var/borg_to_spawn - -/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user) - if(used) - to_chat(user, "[src] is out of power!") - return FALSE - if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)) - to_chat(user, "AUTHENTICATION FAILURE. ACCESS DENIED.") - return FALSE - 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 = "[initial(R.mmi.name)]: [brainopsname]" - R.mmi.brain.name = "[brainopsname]'s brain" - R.mmi.brainmob.real_name = brainopsname - R.mmi.brainmob.name = brainopsname - R.real_name = R.name - - R.key = C.key - - var/datum/antagonist/nukeop/new_borg = new() - new_borg.send_to_spawnpoint = FALSE - R.mind.add_antag_datum(new_borg,creator_op.nuke_team) - R.mind.special_role = "Syndicate Cyborg" - -///////////SLAUGHTER DEMON - -/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game - name = "vial of blood" - desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures." - icon = 'icons/obj/wizard.dmi' - icon_state = "vial" - - var/shatter_msg = "You shatter the bottle, no turning back now!" - var/veil_msg = "You sense a dark presence lurking just beyond the veil..." - var/mob/living/demon_type = /mob/living/simple_animal/slaughter - var/antag_type = /datum/antagonist/slaughter - - -/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user) - if(!is_station_level(user.z)) - to_chat(user, "You should probably wait until you reach the station.") - return - if(used) - return - var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src) - if(LAZYLEN(candidates)) - if(used || QDELETED(src)) - return - used = TRUE - var/mob/dead/observer/C = pick(candidates) - spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind) - to_chat(user, shatter_msg) - to_chat(user, veil_msg) - playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 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/crawling/holder = new /obj/effect/dummy/crawling(T) //yogs start - //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 //yogs end - 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 in the same plane of existence as the station. \ - Alt+Click a blood pool to manifest.") //yogs - -/obj/item/antag_spawner/slaughter_demon/laughter - name = "vial of tickles" - desc = "A magically infused bottle of clown love, distilled from countless hugging attacks. Used in funny rituals to attract adorable creatures." - icon = 'icons/obj/wizard.dmi' - icon_state = "vial" - color = "#FF69B4" // HOT PINK - - veil_msg = "You sense an adorable presence lurking just beyond the veil..." - demon_type = /mob/living/simple_animal/slaughter/laughter - antag_type = /datum/antagonist/slaughter/laughter +/obj/item/antag_spawner + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/used = FALSE + +/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) + return + +/obj/item/antag_spawner/proc/equip_antag(mob/target) + return + + +///////////WIZARD + +/obj/item/antag_spawner/contract + name = "contract" + desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid." + icon = 'icons/obj/wizard.dmi' + icon_state ="scroll2" + +/obj/item/antag_spawner/contract/attack_self(mob/user) + user.set_machine(src) + var/dat + if(used) + dat = "You have already summoned your apprentice.
                " + else + dat = "Contract of Apprenticeship:
                " + dat += "Using this contract, you may summon an apprentice to aid you on your mission.
                " + dat += "If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.
                " + dat += "Which school of magic is your apprentice studying?:
                " + dat += "Destruction
                " + dat += "Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.
                " + dat += "Bluespace Manipulation
                " + dat += "Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.
                " + dat += "Healing
                " + dat += "Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.
                " + dat += "Robeless
                " + dat += "Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.
                " + user << browse(dat, "window=radio") + onclose(user, "radio") + return + +/obj/item/antag_spawner/contract/Topic(href, href_list) + ..() + var/mob/living/carbon/human/H = usr + + if(H.stat || H.restrained()) + return + if(!ishuman(H)) + return 1 + + if(loc == H || (in_range(src, H) && isturf(loc))) + H.set_machine(src) + if(href_list["school"]) + if(used) + to_chat(H, "You already used this contract!") + return + var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src) + if(LAZYLEN(candidates)) + if(QDELETED(src)) + return + if(used) + to_chat(H, "You already used this contract!") + return + used = TRUE + var/mob/dead/observer/C = pick(candidates) + spawn_antag(C.client, get_turf(src), href_list["school"],H.mind) + else + to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.") + +/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user) + new /obj/effect/particle_effect/smoke(T) + var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) + C.prefs.copy_to(M) + M.key = C.key + var/datum/mind/app_mind = M.mind + + var/datum/antagonist/wizard/apprentice/app = new() + app.master = user + app.school = kind + + var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard) + if(master_wizard) + if(!master_wizard.wiz_team) + master_wizard.create_wiz_team() + app.wiz_team = master_wizard.wiz_team + master_wizard.wiz_team.add_member(app_mind) + app_mind.add_antag_datum(app) + //TODO Kill these if possible + app_mind.assigned_role = "Apprentice" + app_mind.special_role = "apprentice" + // + SEND_SOUND(M, sound('sound/effects/magic.ogg')) + +///////////BORGS AND OPERATIVES + + +/obj/item/antag_spawner/nuke_ops + name = "syndicate operative teleporter" + desc = "A single-use teleporter designed to quickly reinforce operatives in the field." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + var/borg_to_spawn + +/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user) + if(used) + to_chat(user, "[src] is out of power!") + return FALSE + if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)) + to_chat(user, "AUTHENTICATION FAILURE. ACCESS DENIED.") + return FALSE + 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 = "[initial(R.mmi.name)]: [brainopsname]" + R.mmi.brain.name = "[brainopsname]'s brain" + R.mmi.brainmob.real_name = brainopsname + R.mmi.brainmob.name = brainopsname + R.real_name = R.name + + R.key = C.key + + var/datum/antagonist/nukeop/new_borg = new() + new_borg.send_to_spawnpoint = FALSE + R.mind.add_antag_datum(new_borg,creator_op.nuke_team) + R.mind.special_role = "Syndicate Cyborg" + +///////////SLAUGHTER DEMON + +/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game + name = "vial of blood" + desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures." + icon = 'icons/obj/wizard.dmi' + icon_state = "vial" + + var/shatter_msg = "You shatter the bottle, no turning back now!" + var/veil_msg = "You sense a dark presence lurking just beyond the veil..." + var/mob/living/demon_type = /mob/living/simple_animal/slaughter + var/antag_type = /datum/antagonist/slaughter + + +/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user) + if(!is_station_level(user.z)) + to_chat(user, "You should probably wait until you reach the station.") + return + if(used) + return + var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src) + if(LAZYLEN(candidates)) + if(used || QDELETED(src)) + return + used = TRUE + var/mob/dead/observer/C = pick(candidates) + spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind) + to_chat(user, shatter_msg) + to_chat(user, veil_msg) + playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 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/crawling/holder = new /obj/effect/dummy/crawling(T) //yogs start + //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 //yogs end + 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 in the same plane of existence as the station. \ + Alt+Click a blood pool to manifest.") //yogs + +/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/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index b8c8cc31101e..dc886cb531b0 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -1,85 +1,85 @@ -/* - * Don't use the apostrophe in name or desc. Causes script errors.//probably no longer true - */ - -/datum/action/changeling - name = "Prototype Sting - Debug button, ahelp this" - background_icon_state = "bg_changeling" - icon_icon = 'icons/mob/actions/actions_changeling.dmi' - var/needs_button = TRUE//for passive abilities like hivemind that dont need a button - var/helptext = "" // Details - var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands) - var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase (see changeling.dm), -1 = cannot be purchased - var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1 - var/req_human = 0 //if you need to be human to use this ability - var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting - var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD - var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag - var/active = FALSE//used by a few powers that toggle - -/* -changeling code now relies on on_purchase to grant powers. -if you override it, MAKE SURE you call parent or it will not be usable -the same goes for Remove(). if you override Remove(), call parent or else your power wont be removed on respec -*/ - -/datum/action/changeling/proc/on_purchase(mob/user, is_respec) - if(!is_respec) - SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) - if(needs_button) - Grant(user)//how powers are added rather than the checks in mob.dm - -/datum/action/changeling/Trigger() - var/mob/user = owner - if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) - return - try_to_sting(user) - -/datum/action/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)) - sting_feedback(user, target) - c.chem_charges -= chemical_cost - -/datum/action/changeling/proc/sting_action(mob/user, mob/target) - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) - return 0 - -/datum/action/changeling/proc/sting_feedback(mob/user, mob/target) - return 0 - -//Fairly important to remember to return 1 on success >.< - -/datum/action/changeling/proc/can_sting(mob/living/user, mob/target) - if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards - return 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 - -/datum/action/changeling/proc/can_be_used_by(mob/user) - if(!user || QDELETED(user)) - return 0 - if(!ishuman(user) && !ismonkey(user)) - return FALSE - if(req_human && !ishuman(user)) - return FALSE - return TRUE +/* + * Don't use the apostrophe in name or desc. Causes script errors.//probably no longer true + */ + +/datum/action/changeling + name = "Prototype Sting - Debug button, ahelp this" + background_icon_state = "bg_changeling" + icon_icon = 'icons/mob/actions/actions_changeling.dmi' + var/needs_button = TRUE//for passive abilities like hivemind that dont need a button + var/helptext = "" // Details + var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands) + var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase (see changeling.dm), -1 = cannot be purchased + var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1 + var/req_human = 0 //if you need to be human to use this ability + var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting + var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD + var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag + var/active = FALSE//used by a few powers that toggle + +/* +changeling code now relies on on_purchase to grant powers. +if you override it, MAKE SURE you call parent or it will not be usable +the same goes for Remove(). if you override Remove(), call parent or else your power wont be removed on respec +*/ + +/datum/action/changeling/proc/on_purchase(mob/user, is_respec) + if(!is_respec) + SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) + if(needs_button) + Grant(user)//how powers are added rather than the checks in mob.dm + +/datum/action/changeling/Trigger() + var/mob/user = owner + if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) + return + try_to_sting(user) + +/datum/action/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)) + sting_feedback(user, target) + c.chem_charges -= chemical_cost + +/datum/action/changeling/proc/sting_action(mob/user, mob/target) + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) + return 0 + +/datum/action/changeling/proc/sting_feedback(mob/user, mob/target) + return 0 + +//Fairly important to remember to return 1 on success >.< + +/datum/action/changeling/proc/can_sting(mob/living/user, mob/target) + if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards + return 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 + +/datum/action/changeling/proc/can_be_used_by(mob/user) + if(!user || QDELETED(user)) + return 0 + if(!ishuman(user) && !ismonkey(user)) + return FALSE + if(req_human && !ishuman(user)) + return FALSE + return TRUE diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index 4460ebb6c099..2d528367d106 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -1,145 +1,145 @@ -/datum/action/changeling/absorbDNA - name = "Absorb DNA" - desc = "Absorb the DNA of our victim. Requires us to strangle them." - button_icon_state = "absorb_dna" - chemical_cost = 0 - dna_cost = 0 - req_human = 1 - -/datum/action/changeling/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) - - - -/datum/action/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, absorbtimer)) - to_chat(user, "Our absorption of [target] has been interrupted!") - changeling.isabsorbing = 0 - return - - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4")) - user.visible_message("[user] sucks the fluids from [target]!", "We have absorbed [target].") - to_chat(target, "You are absorbed by the changeling!") - - if(!changeling.has_dna(target.dna)) - changeling.add_new_profile(target) - changeling.trueabsorbs++ - - if(user.nutrition < NUTRITION_LEVEL_WELL_FED) - user.set_nutrition(min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED)) - - if(target.mind && user.mind)//if the victim and user have minds - // Absorb a lizard, speak Draconic. - user.copy_known_languages_from(target) - - var/datum/mind/suckedbrain = target.mind - user.mind.memory += "
                We've absorbed [target]'s memories into our own...
                [suckedbrain.memory]
                " - for(var/A in suckedbrain.antag_datums) - var/datum/antagonist/antag_types = A - var/list/all_objectives = antag_types.objectives.Copy() - if(antag_types.antag_memory) - user.mind.memory += "[antag_types.antag_memory]
                " - if(LAZYLEN(all_objectives)) - user.mind.memory += "Objectives:" - var/obj_count = 1 - for(var/O in all_objectives) - var/datum/objective/objective = O - user.mind.memory += "
                Objective #[obj_count++]: [objective.explanation_text]" - var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain - if(other_owners.len) - user.mind.memory += "
                  " - for(var/mind in other_owners) - var/datum/mind/M = mind - user.mind.memory += "
                • Conspirator: [M.name]
                • " - user.mind.memory += "
                " - user.mind.memory += "That's all [target] had.
                " - user.memory() //I can read your mind, kekeke. Output all their notes. - - //Some of target's recent speech, so the changeling can attempt to imitate them better. - //Recent as opposed to all because rounds tend to have a LOT of text. - - var/list/recent_speech = list() - var/list/say_log = list() - var/log_source = target.logging - for(var/log_type in log_source) - var/nlog_type = text2num(log_type) - if(nlog_type & LOG_SAY) - var/list/reversed = log_source[log_type] - if(islist(reversed)) - say_log = reverseRange(reversed.Copy()) - break - - if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH) - recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list - else - for(var/spoken_memory in say_log) - if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH) - break - recent_speech[spoken_memory] = say_log[spoken_memory] - - if(recent_speech.len) - changeling.antag_memory += "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!
                " - to_chat(user, "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!") - for(var/spoken_memory in recent_speech) - changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"
                " - to_chat(user, "\"[recent_speech[spoken_memory]]\"") - changeling.antag_memory += "We have no more knowledge of [target]'s speech patterns.
                " - to_chat(user, "We have no more knowledge of [target]'s speech patterns.") - - - var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling) - if(target_ling)//If the target was a changeling, suck out their extra juice and objective points! - to_chat(user, "[target] was one of us. We have absorbed their power.") - target_ling.remove_changeling_powers() - changeling.geneticpoints += round(target_ling.geneticpoints/2) - 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 - target_ling.was_absorbed = TRUE - - - changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage) - - changeling.isabsorbing = 0 - changeling.canrespec = 1 - - target.death(0) - target.Drain() - return TRUE +/datum/action/changeling/absorbDNA + name = "Absorb DNA" + desc = "Absorb the DNA of our victim. Requires us to strangle them." + button_icon_state = "absorb_dna" + chemical_cost = 0 + dna_cost = 0 + req_human = 1 + +/datum/action/changeling/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) + + + +/datum/action/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, absorbtimer)) + to_chat(user, "Our absorption of [target] has been interrupted!") + changeling.isabsorbing = 0 + return + + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4")) + user.visible_message("[user] sucks the fluids from [target]!", "We have absorbed [target].") + to_chat(target, "You are absorbed by the changeling!") + + if(!changeling.has_dna(target.dna)) + changeling.add_new_profile(target) + changeling.trueabsorbs++ + + if(user.nutrition < NUTRITION_LEVEL_WELL_FED) + user.set_nutrition(min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED)) + + if(target.mind && user.mind)//if the victim and user have minds + // Absorb a lizard, speak Draconic. + user.copy_known_languages_from(target) + + var/datum/mind/suckedbrain = target.mind + user.mind.memory += "
                We've absorbed [target]'s memories into our own...
                [suckedbrain.memory]
                " + for(var/A in suckedbrain.antag_datums) + var/datum/antagonist/antag_types = A + var/list/all_objectives = antag_types.objectives.Copy() + if(antag_types.antag_memory) + user.mind.memory += "[antag_types.antag_memory]
                " + if(LAZYLEN(all_objectives)) + user.mind.memory += "Objectives:" + var/obj_count = 1 + for(var/O in all_objectives) + var/datum/objective/objective = O + user.mind.memory += "
                Objective #[obj_count++]: [objective.explanation_text]" + var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain + if(other_owners.len) + user.mind.memory += "
                  " + for(var/mind in other_owners) + var/datum/mind/M = mind + user.mind.memory += "
                • Conspirator: [M.name]
                • " + user.mind.memory += "
                " + user.mind.memory += "That's all [target] had.
                " + user.memory() //I can read your mind, kekeke. Output all their notes. + + //Some of target's recent speech, so the changeling can attempt to imitate them better. + //Recent as opposed to all because rounds tend to have a LOT of text. + + var/list/recent_speech = list() + var/list/say_log = list() + var/log_source = target.logging + for(var/log_type in log_source) + var/nlog_type = text2num(log_type) + if(nlog_type & LOG_SAY) + var/list/reversed = log_source[log_type] + if(islist(reversed)) + say_log = reverseRange(reversed.Copy()) + break + + if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH) + recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list + else + for(var/spoken_memory in say_log) + if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH) + break + recent_speech[spoken_memory] = say_log[spoken_memory] + + if(recent_speech.len) + changeling.antag_memory += "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!
                " + to_chat(user, "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!") + for(var/spoken_memory in recent_speech) + changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"
                " + to_chat(user, "\"[recent_speech[spoken_memory]]\"") + changeling.antag_memory += "We have no more knowledge of [target]'s speech patterns.
                " + to_chat(user, "We have no more knowledge of [target]'s speech patterns.") + + + var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling) + if(target_ling)//If the target was a changeling, suck out their extra juice and objective points! + to_chat(user, "[target] was one of us. We have absorbed their power.") + target_ling.remove_changeling_powers() + changeling.geneticpoints += round(target_ling.geneticpoints/2) + 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 + target_ling.was_absorbed = TRUE + + + changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage) + + changeling.isabsorbing = 0 + changeling.canrespec = 1 + + target.death(0) + target.Drain() + return TRUE diff --git a/code/modules/antagonists/changeling/powers/digitalcamo.dm b/code/modules/antagonists/changeling/powers/digitalcamo.dm index f193ceb105d8..c1d59608bece 100644 --- a/code/modules/antagonists/changeling/powers/digitalcamo.dm +++ b/code/modules/antagonists/changeling/powers/digitalcamo.dm @@ -1,24 +1,24 @@ -/datum/action/changeling/digitalcamo - name = "Digital Camouflage" - desc = "By evolving the ability to distort our form and proportions, we defeat common algorithms used to detect lifeforms on cameras." - helptext = "We cannot be tracked by camera or seen by AI units while using this skill. However, humans looking at us will find us... uncanny." - button_icon_state = "digital_camo" - dna_cost = 1 - -//Prevents AIs tracking you but makes you easily detectable to the human-eye. -/datum/action/changeling/digitalcamo/sting_action(mob/user) - ..() - if(user.digitalcamo) - to_chat(user, "We return to normal.") - user.digitalinvis = 0 - user.digitalcamo = 0 - else - to_chat(user, "We distort our form to hide from the AI.") - user.digitalcamo = 1 - user.digitalinvis = 1 - return TRUE - -/datum/action/changeling/digitalcamo/Remove(mob/user) - user.digitalcamo = FALSE - user.digitalinvis = FALSE - ..() +/datum/action/changeling/digitalcamo + name = "Digital Camouflage" + desc = "By evolving the ability to distort our form and proportions, we defeat common algorithms used to detect lifeforms on cameras." + helptext = "We cannot be tracked by camera or seen by AI units while using this skill. However, humans looking at us will find us... uncanny." + button_icon_state = "digital_camo" + dna_cost = 1 + +//Prevents AIs tracking you but makes you easily detectable to the human-eye. +/datum/action/changeling/digitalcamo/sting_action(mob/user) + ..() + if(user.digitalcamo) + to_chat(user, "We return to normal.") + user.digitalinvis = 0 + user.digitalcamo = 0 + else + to_chat(user, "We distort our form to hide from the AI.") + user.digitalcamo = 1 + user.digitalinvis = 1 + return TRUE + +/datum/action/changeling/digitalcamo/Remove(mob/user) + user.digitalcamo = FALSE + user.digitalinvis = FALSE + ..() diff --git a/code/modules/antagonists/changeling/powers/fakedeath.dm b/code/modules/antagonists/changeling/powers/fakedeath.dm index 3532a76936f4..2d617024b16c 100644 --- a/code/modules/antagonists/changeling/powers/fakedeath.dm +++ b/code/modules/antagonists/changeling/powers/fakedeath.dm @@ -1,74 +1,74 @@ -/datum/action/changeling/fakedeath - name = "Reviving Stasis" - desc = "We fall into a stasis, allowing us to regenerate and trick our enemies. Costs 15 chemicals." - button_icon_state = "fake_death" - chemical_cost = 15 - dna_cost = 0 - req_dna = 1 - req_stat = DEAD - ignores_fakedeath = TRUE - var/revive_ready = FALSE - -//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. -/datum/action/changeling/fakedeath/sting_action(mob/living/user) - ..() - if(revive_ready) - INVOKE_ASYNC(src, .proc/revive, user) - revive_ready = FALSE - name = "Reviving Stasis" - desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." - button_icon_state = "fake_death" - UpdateButtonIcon() - chemical_cost = 15 - to_chat(user, "We have revived ourselves.") - else - to_chat(user, "We begin our stasis, preparing energy to arise once more.") - if(user.stat != DEAD) - user.emote("deathgasp") - user.tod = station_time_timestamp() - user.fakedeath("changeling") //play dead - user.update_stat() - user.update_mobility() - addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE) - return TRUE - -/datum/action/changeling/fakedeath/proc/revive(mob/living/user) - if(!user || !istype(user)) - return - user.cure_fakedeath("changeling") - user.revive(full_heal = TRUE) - 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() - -/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user) - if(user && user.mind) - var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(C && C.purchasedpowers) - to_chat(user, "We are ready to revive.") - name = "Revive" - desc = "We arise once more." - button_icon_state = "revive" - UpdateButtonIcon() - chemical_cost = 0 - revive_ready = TRUE - -/datum/action/changeling/fakedeath/can_sting(mob/living/user) - if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling") && !revive_ready) - to_chat(user, "We are already reviving.") - return - if(!user.stat && !revive_ready) //Confirmation for living changelings if they want to fake their death - switch(alert("Are we sure we wish to fake our own death?",,"Yes", "No")) - if("No") - return - return ..() +/datum/action/changeling/fakedeath + name = "Reviving Stasis" + desc = "We fall into a stasis, allowing us to regenerate and trick our enemies. Costs 15 chemicals." + button_icon_state = "fake_death" + chemical_cost = 15 + dna_cost = 0 + req_dna = 1 + req_stat = DEAD + ignores_fakedeath = TRUE + var/revive_ready = FALSE + +//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. +/datum/action/changeling/fakedeath/sting_action(mob/living/user) + ..() + if(revive_ready) + INVOKE_ASYNC(src, .proc/revive, user) + revive_ready = FALSE + name = "Reviving Stasis" + desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." + button_icon_state = "fake_death" + UpdateButtonIcon() + chemical_cost = 15 + to_chat(user, "We have revived ourselves.") + else + to_chat(user, "We begin our stasis, preparing energy to arise once more.") + if(user.stat != DEAD) + user.emote("deathgasp") + user.tod = station_time_timestamp() + user.fakedeath("changeling") //play dead + user.update_stat() + user.update_mobility() + addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE) + return TRUE + +/datum/action/changeling/fakedeath/proc/revive(mob/living/user) + if(!user || !istype(user)) + return + user.cure_fakedeath("changeling") + user.revive(full_heal = TRUE) + 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() + +/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user) + if(user && user.mind) + var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(C && C.purchasedpowers) + to_chat(user, "We are ready to revive.") + name = "Revive" + desc = "We arise once more." + button_icon_state = "revive" + UpdateButtonIcon() + chemical_cost = 0 + revive_ready = TRUE + +/datum/action/changeling/fakedeath/can_sting(mob/living/user) + if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling") && !revive_ready) + to_chat(user, "We are already reviving.") + return + if(!user.stat && !revive_ready) //Confirmation for living changelings if they want to fake their death + switch(alert("Are we sure we wish to fake our own death?",,"Yes", "No")) + if("No") + return + return ..() diff --git a/code/modules/antagonists/changeling/powers/fleshmend.dm b/code/modules/antagonists/changeling/powers/fleshmend.dm index ff33afe856ce..83c4c933a455 100644 --- a/code/modules/antagonists/changeling/powers/fleshmend.dm +++ b/code/modules/antagonists/changeling/powers/fleshmend.dm @@ -1,21 +1,21 @@ -/datum/action/changeling/fleshmend - name = "Fleshmend" - desc = "Our flesh rapidly regenerates, healing our burns, bruises, and shortness of breath. Costs 20 chemicals." - helptext = "If we are on fire, the healing effect will not function. Does not regrow limbs or restore lost blood. Functions while unconscious." - button_icon_state = "fleshmend" - chemical_cost = 20 - dna_cost = 2 - req_stat = UNCONSCIOUS - -//Starts healing you every second for 10 seconds. -//Can be used whilst unconscious. -/datum/action/changeling/fleshmend/sting_action(mob/living/user) - if(user.has_status_effect(STATUS_EFFECT_FLESHMEND)) - to_chat(user, "We are already fleshmending!") - return - ..() - to_chat(user, "We begin to heal rapidly.") - user.apply_status_effect(STATUS_EFFECT_FLESHMEND) - return TRUE - -//Check buffs.dm for the fleshmend status effect code +/datum/action/changeling/fleshmend + name = "Fleshmend" + desc = "Our flesh rapidly regenerates, healing our burns, bruises, and shortness of breath. Costs 20 chemicals." + helptext = "If we are on fire, the healing effect will not function. Does not regrow limbs or restore lost blood. Functions while unconscious." + button_icon_state = "fleshmend" + chemical_cost = 20 + dna_cost = 2 + req_stat = UNCONSCIOUS + +//Starts healing you every second for 10 seconds. +//Can be used whilst unconscious. +/datum/action/changeling/fleshmend/sting_action(mob/living/user) + if(user.has_status_effect(STATUS_EFFECT_FLESHMEND)) + to_chat(user, "We are already fleshmending!") + return + ..() + to_chat(user, "We begin to heal rapidly.") + user.apply_status_effect(STATUS_EFFECT_FLESHMEND) + return TRUE + +//Check buffs.dm for the fleshmend status effect code diff --git a/code/modules/antagonists/changeling/powers/hivemind.dm b/code/modules/antagonists/changeling/powers/hivemind.dm index eed6869f1c71..ad0dde8c8fb5 100644 --- a/code/modules/antagonists/changeling/powers/hivemind.dm +++ b/code/modules/antagonists/changeling/powers/hivemind.dm @@ -1,117 +1,117 @@ -//HIVEMIND COMMUNICATION (:g) -/datum/action/changeling/hivemind_comms - name = "Hivemind Communication" - desc = "We tune our senses to the airwaves to allow us to discreetly communicate and exchange DNA with other changelings." - helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives." - needs_button = FALSE - dna_cost = 0 - chemical_cost = -1 - -/datum/action/changeling/hivemind_comms/on_purchase(mob/user, is_respec) - ..() - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.changeling_speak = 1 - to_chat(user, "Use say \"[MODE_TOKEN_CHANGELING] message\" to communicate with the other changelings.") - var/datum/action/changeling/hivemind_upload/S1 = new - if(!changeling.has_sting(S1)) - S1.Grant(user) - changeling.purchasedpowers+=S1 - var/datum/action/changeling/hivemind_download/S2 = new - if(!changeling.has_sting(S2)) - S2.Grant(user) - changeling.purchasedpowers+=S2 - -/datum/action/changeling/hivemind_comms/Remove(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.changeling_speak) - changeling.changeling_speak = FALSE - for(var/p in changeling.purchasedpowers) - var/datum/action/changeling/otherpower = p - if(istype(otherpower, /datum/action/changeling/hivemind_upload) || istype(otherpower, /datum/action/changeling/hivemind_download)) - changeling.purchasedpowers -= otherpower - otherpower.Remove(changeling.owner.current) - ..() - - -// HIVE MIND UPLOAD/DOWNLOAD DNA -GLOBAL_LIST_EMPTY(hivemind_bank) - -/datum/action/changeling/hivemind_upload - name = "Hive Channel DNA" - desc = "Allows us to channel DNA in the airwaves to allow other changelings to absorb it. Costs 10 chemicals." - button_icon_state = "hivemind_channel" - chemical_cost = 10 - dna_cost = -1 - -/datum/action/changeling/hivemind_upload/sting_action(var/mob/living/user) - if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) - to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") - return - ..() - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in changeling.stored_profiles) - if(!(prof in GLOB.hivemind_bank)) - names += prof.name - - if(names.len <= 0) - to_chat(user, "The airwaves already have all of our DNA.") - return - - var/chosen_name = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in names - if(!chosen_name) - return - - var/datum/changelingprofile/chosen_dna = changeling.get_dna(chosen_name) - if(!chosen_dna) - return - - var/datum/changelingprofile/uploaded_dna = new chosen_dna.type - chosen_dna.copy_profile(uploaded_dna) - GLOB.hivemind_bank += uploaded_dna - to_chat(user, "We channel the DNA of [chosen_name] to the air.") - return TRUE - -/datum/action/changeling/hivemind_download - name = "Hive Absorb DNA" - desc = "Allows us to absorb DNA that has been channeled to the airwaves. Does not count towards absorb objectives. Costs 10 chemicals." - button_icon_state = "hive_absorb" - chemical_cost = 10 - dna_cost = -1 - -/datum/action/changeling/hivemind_download/can_sting(mob/living/carbon/user) - if(!..()) - return - if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) - to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/datum/changelingprofile/first_prof = changeling.stored_profiles[1] - if(first_prof.name == user.real_name)//If our current DNA is the stalest, we gotta ditch it. - to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.") - return - return 1 - -/datum/action/changeling/hivemind_download/sting_action(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in GLOB.hivemind_bank) - if(!(prof in changeling.stored_profiles)) - names[prof.name] = prof - - if(names.len <= 0) - to_chat(user, "There's no new DNA to absorb from the air.") - return - - var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in names - if(!S) - return - var/datum/changelingprofile/chosen_prof = names[S] - if(!chosen_prof) - return - ..() - var/datum/changelingprofile/downloaded_prof = new chosen_prof.type - chosen_prof.copy_profile(downloaded_prof) - changeling.add_profile(downloaded_prof) - to_chat(user, "We absorb the DNA of [S] from the air.") - return TRUE +//HIVEMIND COMMUNICATION (:g) +/datum/action/changeling/hivemind_comms + name = "Hivemind Communication" + desc = "We tune our senses to the airwaves to allow us to discreetly communicate and exchange DNA with other changelings." + helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives." + needs_button = FALSE + dna_cost = 0 + chemical_cost = -1 + +/datum/action/changeling/hivemind_comms/on_purchase(mob/user, is_respec) + ..() + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.changeling_speak = 1 + to_chat(user, "Use say \"[MODE_TOKEN_CHANGELING] message\" to communicate with the other changelings.") + var/datum/action/changeling/hivemind_upload/S1 = new + if(!changeling.has_sting(S1)) + S1.Grant(user) + changeling.purchasedpowers+=S1 + var/datum/action/changeling/hivemind_download/S2 = new + if(!changeling.has_sting(S2)) + S2.Grant(user) + changeling.purchasedpowers+=S2 + +/datum/action/changeling/hivemind_comms/Remove(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.changeling_speak) + changeling.changeling_speak = FALSE + for(var/p in changeling.purchasedpowers) + var/datum/action/changeling/otherpower = p + if(istype(otherpower, /datum/action/changeling/hivemind_upload) || istype(otherpower, /datum/action/changeling/hivemind_download)) + changeling.purchasedpowers -= otherpower + otherpower.Remove(changeling.owner.current) + ..() + + +// HIVE MIND UPLOAD/DOWNLOAD DNA +GLOBAL_LIST_EMPTY(hivemind_bank) + +/datum/action/changeling/hivemind_upload + name = "Hive Channel DNA" + desc = "Allows us to channel DNA in the airwaves to allow other changelings to absorb it. Costs 10 chemicals." + button_icon_state = "hivemind_channel" + chemical_cost = 10 + dna_cost = -1 + +/datum/action/changeling/hivemind_upload/sting_action(var/mob/living/user) + if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) + to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") + return + ..() + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in changeling.stored_profiles) + if(!(prof in GLOB.hivemind_bank)) + names += prof.name + + if(names.len <= 0) + to_chat(user, "The airwaves already have all of our DNA.") + return + + var/chosen_name = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in names + if(!chosen_name) + return + + var/datum/changelingprofile/chosen_dna = changeling.get_dna(chosen_name) + if(!chosen_dna) + return + + var/datum/changelingprofile/uploaded_dna = new chosen_dna.type + chosen_dna.copy_profile(uploaded_dna) + GLOB.hivemind_bank += uploaded_dna + to_chat(user, "We channel the DNA of [chosen_name] to the air.") + return TRUE + +/datum/action/changeling/hivemind_download + name = "Hive Absorb DNA" + desc = "Allows us to absorb DNA that has been channeled to the airwaves. Does not count towards absorb objectives. Costs 10 chemicals." + button_icon_state = "hive_absorb" + chemical_cost = 10 + dna_cost = -1 + +/datum/action/changeling/hivemind_download/can_sting(mob/living/carbon/user) + if(!..()) + return + if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) + to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/datum/changelingprofile/first_prof = changeling.stored_profiles[1] + if(first_prof.name == user.real_name)//If our current DNA is the stalest, we gotta ditch it. + to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.") + return + return 1 + +/datum/action/changeling/hivemind_download/sting_action(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in GLOB.hivemind_bank) + if(!(prof in changeling.stored_profiles)) + names[prof.name] = prof + + if(names.len <= 0) + to_chat(user, "There's no new DNA to absorb from the air.") + return + + var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in names + if(!S) + return + var/datum/changelingprofile/chosen_prof = names[S] + if(!chosen_prof) + return + ..() + var/datum/changelingprofile/downloaded_prof = new chosen_prof.type + chosen_prof.copy_profile(downloaded_prof) + changeling.add_profile(downloaded_prof) + to_chat(user, "We absorb the DNA of [S] from the air.") + return TRUE diff --git a/code/modules/antagonists/changeling/powers/humanform.dm b/code/modules/antagonists/changeling/powers/humanform.dm index 588fdcccb22e..edac59960852 100644 --- a/code/modules/antagonists/changeling/powers/humanform.dm +++ b/code/modules/antagonists/changeling/powers/humanform.dm @@ -1,34 +1,34 @@ -/datum/action/changeling/humanform - name = "Human Form" - desc = "We change into a human. Costs 5 chemicals." - button_icon_state = "human_form" - chemical_cost = 5 - req_dna = 1 - -//Transform into a human. -/datum/action/changeling/humanform/sting_action(mob/living/carbon/user) - if(user.movement_type & VENTCRAWLING) - to_chat(user, "We must exit the pipes before we can transform back!") - return FALSE - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in changeling.stored_profiles) - names += "[prof.name]" - - var/chosen_name = input("Select the target DNA: ", "Target DNA", null) as null|anything in names - if(!chosen_name) - return - - var/datum/changelingprofile/chosen_prof = changeling.get_dna(chosen_name) - if(!chosen_prof) - return - if(!user || user.notransform) - return FALSE - to_chat(user, "We transform our appearance.") - ..() - changeling.purchasedpowers -= src - - var/newmob = user.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - - changeling_transform(newmob, chosen_prof) - return TRUE +/datum/action/changeling/humanform + name = "Human Form" + desc = "We change into a human. Costs 5 chemicals." + button_icon_state = "human_form" + chemical_cost = 5 + req_dna = 1 + +//Transform into a human. +/datum/action/changeling/humanform/sting_action(mob/living/carbon/user) + if(user.movement_type & VENTCRAWLING) + to_chat(user, "We must exit the pipes before we can transform back!") + return FALSE + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in changeling.stored_profiles) + names += "[prof.name]" + + var/chosen_name = input("Select the target DNA: ", "Target DNA", null) as null|anything in names + if(!chosen_name) + return + + var/datum/changelingprofile/chosen_prof = changeling.get_dna(chosen_name) + if(!chosen_prof) + return + if(!user || user.notransform) + return FALSE + to_chat(user, "We transform our appearance.") + ..() + changeling.purchasedpowers -= src + + var/newmob = user.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) + + changeling_transform(newmob, chosen_prof) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/lesserform.dm b/code/modules/antagonists/changeling/powers/lesserform.dm index 03ea226e9e52..7a1c83e243ea 100644 --- a/code/modules/antagonists/changeling/powers/lesserform.dm +++ b/code/modules/antagonists/changeling/powers/lesserform.dm @@ -1,17 +1,17 @@ -/datum/action/changeling/lesserform - name = "Lesser Form" - desc = "We debase ourselves and become lesser. We become a monkey. Costs 5 chemicals." - helptext = "The transformation greatly reduces our size, allowing us to slip out of cuffs and climb through vents." - button_icon_state = "lesser_form" - chemical_cost = 5 - dna_cost = 1 - req_human = 1 - -//Transform into a monkey. -/datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user) - if(!user || user.notransform) - return FALSE - to_chat(user, "Our genes cry out!") - ..() - user.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - return TRUE +/datum/action/changeling/lesserform + name = "Lesser Form" + desc = "We debase ourselves and become lesser. We become a monkey. Costs 5 chemicals." + helptext = "The transformation greatly reduces our size, allowing us to slip out of cuffs and climb through vents." + button_icon_state = "lesser_form" + chemical_cost = 5 + dna_cost = 1 + req_human = 1 + +//Transform into a monkey. +/datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user) + if(!user || user.notransform) + return FALSE + to_chat(user, "Our genes cry out!") + ..() + user.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/mimic_voice.dm b/code/modules/antagonists/changeling/powers/mimic_voice.dm index 8e2344343584..721adb262fba 100644 --- a/code/modules/antagonists/changeling/powers/mimic_voice.dm +++ b/code/modules/antagonists/changeling/powers/mimic_voice.dm @@ -1,27 +1,27 @@ -/datum/action/changeling/mimicvoice - name = "Mimic Voice" - desc = "We shape our vocal glands to sound like a desired voice. Maintaining this power slows chemical production." - button_icon_state = "mimic_voice" - helptext = "Will turn your voice into the name that you enter. We must constantly expend chemicals to maintain our form like this." - chemical_cost = 0//constant chemical drain hardcoded - dna_cost = 1 - req_human = 1 - -// Fake Voice -/datum/action/changeling/mimicvoice/sting_action(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.mimicing) - changeling.mimicing = "" - changeling.chem_recharge_slowdown -= 0.5 - to_chat(user, "We return our vocal glands to their original position.") - return - - var/mimic_voice = stripped_input(user, "Enter a name to mimic.", "Mimic Voice", null, MAX_NAME_LEN) - if(!mimic_voice) - return - ..() - changeling.mimicing = mimic_voice - changeling.chem_recharge_slowdown += 0.5 - to_chat(user, "We shape our glands to take the voice of [mimic_voice], this will slow down regenerating chemicals while active.") - to_chat(user, "Use this power again to return to our original voice and return chemical production to normal levels.") - return TRUE +/datum/action/changeling/mimicvoice + name = "Mimic Voice" + desc = "We shape our vocal glands to sound like a desired voice. Maintaining this power slows chemical production." + button_icon_state = "mimic_voice" + helptext = "Will turn your voice into the name that you enter. We must constantly expend chemicals to maintain our form like this." + chemical_cost = 0//constant chemical drain hardcoded + dna_cost = 1 + req_human = 1 + +// Fake Voice +/datum/action/changeling/mimicvoice/sting_action(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.mimicing) + changeling.mimicing = "" + changeling.chem_recharge_slowdown -= 0.5 + to_chat(user, "We return our vocal glands to their original position.") + return + + var/mimic_voice = stripped_input(user, "Enter a name to mimic.", "Mimic Voice", null, MAX_NAME_LEN) + if(!mimic_voice) + return + ..() + changeling.mimicing = mimic_voice + changeling.chem_recharge_slowdown += 0.5 + to_chat(user, "We shape our glands to take the voice of [mimic_voice], this will slow down regenerating chemicals while active.") + to_chat(user, "Use this power again to return to our original voice and return chemical production to normal levels.") + return TRUE diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm index 2da5a5d8eede..bf6547f83cc5 100644 --- a/code/modules/antagonists/changeling/powers/panacea.dm +++ b/code/modules/antagonists/changeling/powers/panacea.dm @@ -1,41 +1,41 @@ -/datum/action/changeling/panacea - name = "Anatomic Panacea" - desc = "Expels impurifications from our form; curing diseases, removing parasites, sobering us, purging toxins and radiation, and resetting our genetic code completely. Costs 20 chemicals." - helptext = "Can be used while unconscious." - button_icon_state = "panacea" - chemical_cost = 20 - dna_cost = 1 - req_stat = UNCONSCIOUS - -//Heals the things that the other regenerative abilities don't. -/datum/action/changeling/panacea/sting_action(mob/user) - to_chat(user, "We cleanse impurities from our form.") - ..() - var/list/bad_organs = list( - user.getorgan(/obj/item/organ/body_egg), - user.getorgan(/obj/item/organ/zombie_infection)) - - for(var/o in bad_organs) - var/obj/item/organ/O = o - if(!istype(O)) - continue - - O.Remove(user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.vomit(0, toxic = TRUE) - O.forceMove(get_turf(user)) - - user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10) - user.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 20) - user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10) - user.reagents.add_reagent(/datum/reagent/medicine/mannitol, 25) - - if(isliving(user)) - var/mob/living/L = user - for(var/thing in L.diseases) - var/datum/disease/D = thing - if(D.severity == DISEASE_SEVERITY_POSITIVE) - continue - D.cure() - return TRUE +/datum/action/changeling/panacea + name = "Anatomic Panacea" + desc = "Expels impurifications from our form; curing diseases, removing parasites, sobering us, purging toxins and radiation, and resetting our genetic code completely. Costs 20 chemicals." + helptext = "Can be used while unconscious." + button_icon_state = "panacea" + chemical_cost = 20 + dna_cost = 1 + req_stat = UNCONSCIOUS + +//Heals the things that the other regenerative abilities don't. +/datum/action/changeling/panacea/sting_action(mob/user) + to_chat(user, "We cleanse impurities from our form.") + ..() + var/list/bad_organs = list( + user.getorgan(/obj/item/organ/body_egg), + user.getorgan(/obj/item/organ/zombie_infection)) + + for(var/o in bad_organs) + var/obj/item/organ/O = o + if(!istype(O)) + continue + + O.Remove(user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.vomit(0, toxic = TRUE) + O.forceMove(get_turf(user)) + + user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10) + user.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 20) + user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10) + user.reagents.add_reagent(/datum/reagent/medicine/mannitol, 25) + + if(isliving(user)) + var/mob/living/L = user + for(var/thing in L.diseases) + var/datum/disease/D = thing + if(D.severity == DISEASE_SEVERITY_POSITIVE) + continue + D.cure() + return TRUE diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm index 6b5be5d23662..2a3b2e1fbe35 100644 --- a/code/modules/antagonists/changeling/powers/shriek.dm +++ b/code/modules/antagonists/changeling/powers/shriek.dm @@ -1,45 +1,45 @@ -/datum/action/changeling/resonant_shriek - name = "Resonant Shriek" - desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded. Costs 20 chemicals." - helptext = "Emits a high-frequency sound that confuses and deafens humans, blows out nearby lights and overloads cyborg sensors." - button_icon_state = "resonant_shriek" - chemical_cost = 20 - dna_cost = 1 - req_human = 1 - -//A flashy ability, good for crowd control and sowing chaos. -/datum/action/changeling/resonant_shriek/sting_action(mob/user) - ..() - for(var/mob/living/M in get_hearers_in_view(4, user)) - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling)) - C.adjustEarDamage(0, 30) - C.confused += 25 - C.Jitter(50) - else - SEND_SOUND(C, sound('sound/effects/screech.ogg')) - - if(issilicon(M)) - SEND_SOUND(M, sound('sound/weapons/flash.ogg')) - M.Paralyze(rand(100,200)) - - for(var/obj/machinery/light/L in range(4, user)) - L.on = 1 - L.break_light_tube() - return TRUE - -/datum/action/changeling/dissonant_shriek - name = "Dissonant Shriek" - desc = "We shift our vocal cords to release a high-frequency sound that overloads nearby electronics. Costs 20 chemicals." - button_icon_state = "dissonant_shriek" - chemical_cost = 20 - dna_cost = 1 - -/datum/action/changeling/dissonant_shriek/sting_action(mob/user) - ..() - for(var/obj/machinery/light/L in range(5, usr)) - L.on = 1 - L.break_light_tube() - empulse(get_turf(user), 2, 5, 1) - return TRUE +/datum/action/changeling/resonant_shriek + name = "Resonant Shriek" + desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded. Costs 20 chemicals." + helptext = "Emits a high-frequency sound that confuses and deafens humans, blows out nearby lights and overloads cyborg sensors." + button_icon_state = "resonant_shriek" + chemical_cost = 20 + dna_cost = 1 + req_human = 1 + +//A flashy ability, good for crowd control and sowing chaos. +/datum/action/changeling/resonant_shriek/sting_action(mob/user) + ..() + for(var/mob/living/M in get_hearers_in_view(4, user)) + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling)) + C.adjustEarDamage(0, 30) + C.confused += 25 + C.Jitter(50) + else + SEND_SOUND(C, sound('sound/effects/screech.ogg')) + + if(issilicon(M)) + SEND_SOUND(M, sound('sound/weapons/flash.ogg')) + M.Paralyze(rand(100,200)) + + for(var/obj/machinery/light/L in range(4, user)) + L.on = 1 + L.break_light_tube() + return TRUE + +/datum/action/changeling/dissonant_shriek + name = "Dissonant Shriek" + desc = "We shift our vocal cords to release a high-frequency sound that overloads nearby electronics. Costs 20 chemicals." + button_icon_state = "dissonant_shriek" + chemical_cost = 20 + dna_cost = 1 + +/datum/action/changeling/dissonant_shriek/sting_action(mob/user) + ..() + for(var/obj/machinery/light/L in range(5, usr)) + L.on = 1 + L.break_light_tube() + empulse(get_turf(user), 2, 5, 1) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/spiders.dm b/code/modules/antagonists/changeling/powers/spiders.dm index 85094228d895..71be5145e9de 100644 --- a/code/modules/antagonists/changeling/powers/spiders.dm +++ b/code/modules/antagonists/changeling/powers/spiders.dm @@ -1,14 +1,14 @@ -/datum/action/changeling/spiders - name = "Spread Infestation" - desc = "Our form divides, creating arachnids which will grow into deadly beasts." - helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA absorptions." - button_icon_state = "spread_infestation" - chemical_cost = 45 - dna_cost = 1 - req_absorbs = 3 - -//Makes some spiderlings. Good for setting traps and causing general trouble. -/datum/action/changeling/spiders/sting_action(mob/user) - ..() - spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE) - return TRUE +/datum/action/changeling/spiders + name = "Spread Infestation" + desc = "Our form divides, creating arachnids which will grow into deadly beasts." + helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA absorptions." + button_icon_state = "spread_infestation" + chemical_cost = 45 + dna_cost = 1 + req_absorbs = 3 + +//Makes some spiderlings. Good for setting traps and causing general trouble. +/datum/action/changeling/spiders/sting_action(mob/user) + ..() + spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm index 423aa6f3b268..70c8448c1600 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -1,250 +1,250 @@ -/datum/action/changeling/sting//parent path, not meant for users afaik - name = "Tiny Prick" - desc = "Stabby stabby" - var/sting_icon = null - -/datum/action/changeling/sting/Trigger() - var/mob/user = owner - if(!user || !user.mind) - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(!changeling) - return - if(!changeling.chosen_sting) - set_sting(user) - else - unset_sting(user) - return - -/datum/action/changeling/sting/proc/set_sting(mob/user) - to_chat(user, "We prepare our sting. Alt+click or click the middle mouse button on a target to sting them.") - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.chosen_sting = src - - user.hud_used.lingstingdisplay.icon_state = sting_icon - user.hud_used.lingstingdisplay.invisibility = 0 - -/datum/action/changeling/sting/proc/unset_sting(mob/user) - to_chat(user, "We retract our sting, we can't sting anyone for now.") - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.chosen_sting = null - - user.hud_used.lingstingdisplay.icon_state = null - user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT - -/mob/living/carbon/proc/unset_sting() - if(mind) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling && changeling.chosen_sting) - changeling.chosen_sting.unset_sting(src) - -/datum/action/changeling/sting/can_sting(mob/user, mob/target) - if(!..()) - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(!changeling.chosen_sting) - to_chat(user, "We haven't prepared our sting yet!") - if(!iscarbon(target)) - return - if(!isturf(user.loc)) - return - if(!AStar(user, target.loc, /turf/proc/Distance, changeling.sting_range, simulated_only = FALSE)) - return - if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) - sting_feedback(user, target) - changeling.chem_charges -= chemical_cost - return 1 - -/datum/action/changeling/sting/sting_feedback(mob/user, mob/target) - if(!target) - return - to_chat(user, "We stealthily sting [target.name].") - if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(target, "You feel a tiny prick.") - return 1 - - -/datum/action/changeling/sting/transformation - name = "Transformation Sting" - desc = "We silently sting a human, injecting a retrovirus that forces them to transform. Costs 50 chemicals." - helptext = "The victim will transform much like a changeling would. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human." - button_icon_state = "sting_transform" - sting_icon = "sting_transform" - chemical_cost = 50 - dna_cost = 3 - var/datum/changelingprofile/selected_dna = null - -/datum/action/changeling/sting/transformation/Trigger() - var/mob/user = usr - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.chosen_sting) - unset_sting(user) - return - selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA") - if(!selected_dna) - return - if(NOTRANSSTING in selected_dna.dna.species.species_traits) - to_chat(user, "That DNA is not compatible with changeling retrovirus!") - return - ..() - -/datum/action/changeling/sting/transformation/can_sting(mob/user, mob/living/carbon/target) - if(!..()) - return - if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits)) - to_chat(user, "Our sting appears ineffective against its DNA.") - return 0 - return 1 - -/datum/action/changeling/sting/transformation/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'") - var/datum/dna/NewDNA = selected_dna.dna - if(ismonkey(target)) - to_chat(user, "Our genes cry out as we sting [target.name]!") - - var/mob/living/carbon/C = target - . = TRUE - if(istype(C)) - C.real_name = NewDNA.real_name - NewDNA.transfer_identity(C) - if(ismonkey(C)) - C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG) - C.updateappearance(mutcolor_update=1) - - -/datum/action/changeling/sting/false_armblade - name = "False Armblade Sting" - desc = "We silently sting a human, injecting a retrovirus that mutates their arm to temporarily appear as an armblade. Costs 20 chemicals." - helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless." - button_icon_state = "sting_armblade" - sting_icon = "sting_armblade" - chemical_cost = 20 - dna_cost = 1 - -/obj/item/melee/arm_blade/false - desc = "A grotesque mass of flesh that used to be your arm. On the bright side, at least you can cut wood with this." - force = 30 //yogs -- Prevents dual-stinging people with this sting to render them defenseless. - //daily reminder that xantam is a closet furry - fake = TRUE - -/datum/action/changeling/sting/false_armblade/can_sting(mob/user, mob/target) - if(!..()) - return - if(isliving(target)) - var/mob/living/L = target - if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna()) - to_chat(user, "Our sting appears ineffective against its DNA.") - return 0 - return 1 - -/datum/action/changeling/sting/false_armblade/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", object="false armblade sting") - - var/obj/item/held = target.get_active_held_item() - if(held && !target.dropItemToGround(held)) - to_chat(user, "[held] is stuck to [target.p_their()] hand, you cannot grow a false armblade over it!") - return - ..() - if(ismonkey(target)) - to_chat(user, "Our genes cry out as we sting [target.name]!") - - var/obj/item/melee/arm_blade/false/blade = new(target,1) - target.put_in_hands(blade) - target.visible_message("A grotesque blade forms around [target.name]\'s arm!", "Your arm twists and mutates, transforming into a horrific monstrosity!", "You hear organic matter ripping and tearing!") - playsound(target, 'sound/effects/blobattack.ogg', 30, 1) - - addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600) - return TRUE - -/datum/action/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade) - playsound(target, 'sound/effects/blobattack.ogg', 30, 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 - -/datum/action/changeling/sting/LSD - name = "Hallucination Sting" - desc = "We cause mass terror to our victim." - helptext = "We evolve the ability to sting a target with a powerful hallucinogenic chemical. The target does not notice they have been stung, and the effect occurs after 30 to 60 seconds." - button_icon_state = "sting_lsd" - sting_icon = "sting_lsd" - chemical_cost = 10 - dna_cost = 1 - -/datum/action/changeling/sting/LSD/sting_action(mob/user, mob/living/carbon/target) - log_combat(user, target, "stung", "LSD sting") - if(target.reagents) - target.reagents.add_reagent(/datum/reagent/toxin/mindbreaker, 30) - return TRUE - -/datum/action/changeling/sting/cryo - name = "Cryogenic Sting" - desc = "We silently sting our victim with a cocktail of chemicals that freezes them from the inside. Costs 15 chemicals." - helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing." - button_icon_state = "sting_cryo" - sting_icon = "sting_cryo" - chemical_cost = 15 - dna_cost = 2 - -/datum/action/changeling/sting/cryo/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", "cryo sting") - if(target.reagents) - target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 30) - return TRUE +/datum/action/changeling/sting//parent path, not meant for users afaik + name = "Tiny Prick" + desc = "Stabby stabby" + var/sting_icon = null + +/datum/action/changeling/sting/Trigger() + var/mob/user = owner + if(!user || !user.mind) + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(!changeling) + return + if(!changeling.chosen_sting) + set_sting(user) + else + unset_sting(user) + return + +/datum/action/changeling/sting/proc/set_sting(mob/user) + to_chat(user, "We prepare our sting. Alt+click or click the middle mouse button on a target to sting them.") + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.chosen_sting = src + + user.hud_used.lingstingdisplay.icon_state = sting_icon + user.hud_used.lingstingdisplay.invisibility = 0 + +/datum/action/changeling/sting/proc/unset_sting(mob/user) + to_chat(user, "We retract our sting, we can't sting anyone for now.") + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.chosen_sting = null + + user.hud_used.lingstingdisplay.icon_state = null + user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT + +/mob/living/carbon/proc/unset_sting() + if(mind) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling && changeling.chosen_sting) + changeling.chosen_sting.unset_sting(src) + +/datum/action/changeling/sting/can_sting(mob/user, mob/target) + if(!..()) + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(!changeling.chosen_sting) + to_chat(user, "We haven't prepared our sting yet!") + if(!iscarbon(target)) + return + if(!isturf(user.loc)) + return + if(!AStar(user, target.loc, /turf/proc/Distance, changeling.sting_range, simulated_only = FALSE)) + return + if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) + sting_feedback(user, target) + changeling.chem_charges -= chemical_cost + return 1 + +/datum/action/changeling/sting/sting_feedback(mob/user, mob/target) + if(!target) + return + to_chat(user, "We stealthily sting [target.name].") + if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(target, "You feel a tiny prick.") + return 1 + + +/datum/action/changeling/sting/transformation + name = "Transformation Sting" + desc = "We silently sting a human, injecting a retrovirus that forces them to transform. Costs 50 chemicals." + helptext = "The victim will transform much like a changeling would. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human." + button_icon_state = "sting_transform" + sting_icon = "sting_transform" + chemical_cost = 50 + dna_cost = 3 + var/datum/changelingprofile/selected_dna = null + +/datum/action/changeling/sting/transformation/Trigger() + var/mob/user = usr + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.chosen_sting) + unset_sting(user) + return + selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA") + if(!selected_dna) + return + if(NOTRANSSTING in selected_dna.dna.species.species_traits) + to_chat(user, "That DNA is not compatible with changeling retrovirus!") + return + ..() + +/datum/action/changeling/sting/transformation/can_sting(mob/user, mob/living/carbon/target) + if(!..()) + return + if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits)) + to_chat(user, "Our sting appears ineffective against its DNA.") + return 0 + return 1 + +/datum/action/changeling/sting/transformation/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'") + var/datum/dna/NewDNA = selected_dna.dna + if(ismonkey(target)) + to_chat(user, "Our genes cry out as we sting [target.name]!") + + var/mob/living/carbon/C = target + . = TRUE + if(istype(C)) + C.real_name = NewDNA.real_name + NewDNA.transfer_identity(C) + if(ismonkey(C)) + C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG) + C.updateappearance(mutcolor_update=1) + + +/datum/action/changeling/sting/false_armblade + name = "False Armblade Sting" + desc = "We silently sting a human, injecting a retrovirus that mutates their arm to temporarily appear as an armblade. Costs 20 chemicals." + helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless." + button_icon_state = "sting_armblade" + sting_icon = "sting_armblade" + chemical_cost = 20 + dna_cost = 1 + +/obj/item/melee/arm_blade/false + desc = "A grotesque mass of flesh that used to be your arm. On the bright side, at least you can cut wood with this." + force = 30 //yogs -- Prevents dual-stinging people with this sting to render them defenseless. + //daily reminder that xantam is a closet furry + fake = TRUE + +/datum/action/changeling/sting/false_armblade/can_sting(mob/user, mob/target) + if(!..()) + return + if(isliving(target)) + var/mob/living/L = target + if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna()) + to_chat(user, "Our sting appears ineffective against its DNA.") + return 0 + return 1 + +/datum/action/changeling/sting/false_armblade/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", object="false armblade sting") + + var/obj/item/held = target.get_active_held_item() + if(held && !target.dropItemToGround(held)) + to_chat(user, "[held] is stuck to [target.p_their()] hand, you cannot grow a false armblade over it!") + return + ..() + if(ismonkey(target)) + to_chat(user, "Our genes cry out as we sting [target.name]!") + + var/obj/item/melee/arm_blade/false/blade = new(target,1) + target.put_in_hands(blade) + target.visible_message("A grotesque blade forms around [target.name]\'s arm!", "Your arm twists and mutates, transforming into a horrific monstrosity!", "You hear organic matter ripping and tearing!") + playsound(target, 'sound/effects/blobattack.ogg', 30, 1) + + addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600) + return TRUE + +/datum/action/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade) + playsound(target, 'sound/effects/blobattack.ogg', 30, 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 + +/datum/action/changeling/sting/LSD + name = "Hallucination Sting" + desc = "We cause mass terror to our victim." + helptext = "We evolve the ability to sting a target with a powerful hallucinogenic chemical. The target does not notice they have been stung, and the effect occurs after 30 to 60 seconds." + button_icon_state = "sting_lsd" + sting_icon = "sting_lsd" + chemical_cost = 10 + dna_cost = 1 + +/datum/action/changeling/sting/LSD/sting_action(mob/user, mob/living/carbon/target) + log_combat(user, target, "stung", "LSD sting") + if(target.reagents) + target.reagents.add_reagent(/datum/reagent/toxin/mindbreaker, 30) + return TRUE + +/datum/action/changeling/sting/cryo + name = "Cryogenic Sting" + desc = "We silently sting our victim with a cocktail of chemicals that freezes them from the inside. Costs 15 chemicals." + helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing." + button_icon_state = "sting_cryo" + sting_icon = "sting_cryo" + chemical_cost = 15 + dna_cost = 2 + +/datum/action/changeling/sting/cryo/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", "cryo sting") + if(target.reagents) + target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 30) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/transform.dm b/code/modules/antagonists/changeling/powers/transform.dm index 641b87b127a2..ec5ab29b34ba 100644 --- a/code/modules/antagonists/changeling/powers/transform.dm +++ b/code/modules/antagonists/changeling/powers/transform.dm @@ -1,162 +1,162 @@ -/datum/action/changeling/transform - name = "Transform" - desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals." - button_icon_state = "transform" - chemical_cost = 5 - dna_cost = 0 - req_dna = 1 - req_human = 1 - -/obj/item/clothing/glasses/changeling - name = "flesh" - -/obj/item/clothing/glasses/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/glasses/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/under/changeling - name = "flesh" - -/obj/item/clothing/under/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/under/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/suit/changeling - name = "flesh" - allowed = list(/obj/item/changeling) - -/obj/item/clothing/suit/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/suit/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/head/changeling - name = "flesh" - -/obj/item/clothing/head/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/head/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/shoes/changeling - name = "flesh" - -/obj/item/clothing/shoes/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/shoes/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/gloves/changeling - name = "flesh" - -/obj/item/clothing/gloves/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/gloves/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/mask/changeling - name = "flesh" - -/obj/item/clothing/mask/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/mask/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/changeling - name = "flesh" - slot_flags = ALL - allowed = list(/obj/item/changeling) - -/obj/item/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -//Change our DNA to that of somebody we've absorbed. -/datum/action/changeling/transform/sting_action(mob/living/carbon/human/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/datum/changelingprofile/chosen_prof = changeling.select_dna("Select the target DNA: ", "Target DNA") - - if(!chosen_prof) - return - ..() - changeling_transform(user, chosen_prof) - return TRUE - -/datum/antagonist/changeling/proc/select_dna(var/prompt, var/title) - var/mob/living/carbon/user = owner.current - if(!istype(user)) - return - var/list/names = list("Drop Flesh Disguise") - for(var/datum/changelingprofile/prof in stored_profiles) - names += "[prof.name]" - - var/chosen_name = input(prompt, title, null) as null|anything in names - if(!chosen_name) - return - - if(chosen_name == "Drop Flesh Disguise") - for(var/slot in GLOB.slots) - if(istype(user.vars[slot], GLOB.slot2type[slot])) - qdel(user.vars[slot]) - - var/datum/changelingprofile/prof = get_dna(chosen_name) - return prof +/datum/action/changeling/transform + name = "Transform" + desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals." + button_icon_state = "transform" + chemical_cost = 5 + dna_cost = 0 + req_dna = 1 + req_human = 1 + +/obj/item/clothing/glasses/changeling + name = "flesh" + +/obj/item/clothing/glasses/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/glasses/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/under/changeling + name = "flesh" + +/obj/item/clothing/under/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/under/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/suit/changeling + name = "flesh" + allowed = list(/obj/item/changeling) + +/obj/item/clothing/suit/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/suit/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/head/changeling + name = "flesh" + +/obj/item/clothing/head/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/head/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/shoes/changeling + name = "flesh" + +/obj/item/clothing/shoes/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/shoes/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/gloves/changeling + name = "flesh" + +/obj/item/clothing/gloves/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/gloves/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/mask/changeling + name = "flesh" + +/obj/item/clothing/mask/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/mask/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/changeling + name = "flesh" + slot_flags = ALL + allowed = list(/obj/item/changeling) + +/obj/item/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +//Change our DNA to that of somebody we've absorbed. +/datum/action/changeling/transform/sting_action(mob/living/carbon/human/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/datum/changelingprofile/chosen_prof = changeling.select_dna("Select the target DNA: ", "Target DNA") + + if(!chosen_prof) + return + ..() + changeling_transform(user, chosen_prof) + return TRUE + +/datum/antagonist/changeling/proc/select_dna(var/prompt, var/title) + var/mob/living/carbon/user = owner.current + if(!istype(user)) + return + var/list/names = list("Drop Flesh Disguise") + for(var/datum/changelingprofile/prof in stored_profiles) + names += "[prof.name]" + + var/chosen_name = input(prompt, title, null) as null|anything in names + if(!chosen_name) + return + + if(chosen_name == "Drop Flesh Disguise") + for(var/slot in GLOB.slots) + if(istype(user.vars[slot], GLOB.slot2type[slot])) + qdel(user.vars[slot]) + + var/datum/changelingprofile/prof = get_dna(chosen_name) + return prof diff --git a/code/modules/antagonists/nukeop/equipment/pinpointer.dm b/code/modules/antagonists/nukeop/equipment/pinpointer.dm index ffdb1ea6300d..ccf61b35dd52 100644 --- a/code/modules/antagonists/nukeop/equipment/pinpointer.dm +++ b/code/modules/antagonists/nukeop/equipment/pinpointer.dm @@ -1,90 +1,90 @@ -/obj/item/pinpointer/nuke - var/mode = TRACK_NUKE_DISK - -/obj/item/pinpointer/nuke/examine(mob/user) - . = ..() - var/msg = "Its tracking indicator reads " - switch(mode) - if(TRACK_NUKE_DISK) - msg += "\"nuclear_disk\"." - if(TRACK_MALF_AI) - msg += "\"01000001 01001001\"." - if(TRACK_INFILTRATOR) - msg += "\"vasvygengbefuvc\"." - else - msg = "Its tracking indicator is blank." - . += msg - for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines) - if(bomb.timing) - . += "Extreme danger. Arming signal detected. Time remaining: [bomb.get_time_left()]." - -/obj/item/pinpointer/nuke/process() - ..() - if(active) // If shit's going down - for(var/obj/machinery/nuclearbomb/bomb in GLOB.nuke_list) - if(bomb.timing) - if(!alert) - alert = TRUE - playsound(src, 'sound/items/nuke_toy_lowpower.ogg', 50, 0) - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "Your [name] vibrates and lets out a tinny alarm. Uh oh.") - -/obj/item/pinpointer/nuke/scan_for_target() - target = null - switch(mode) - if(TRACK_NUKE_DISK) - var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list - target = N - if(TRACK_MALF_AI) - for(var/V in GLOB.ai_list) - var/mob/living/silicon/ai/A = V - if(A.nuking) - target = A - for(var/V in GLOB.apcs_list) - var/obj/machinery/power/apc/A = V - if(A.malfhack && A.occupier) - target = A - if(TRACK_INFILTRATOR) - target = SSshuttle.getShuttle("syndicate") - ..() - -/obj/item/pinpointer/nuke/proc/switch_mode_to(new_mode) - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "Your [name] beeps as it reconfigures it's tracking algorithms.") - playsound(L, 'sound/machines/triple_beep.ogg', 50, 1) - mode = new_mode - scan_for_target() - -/obj/item/pinpointer/nuke/syndicate // Syndicate pinpointers automatically point towards the infiltrator once the nuke is active. - name = "syndicate pinpointer" - desc = "A handheld tracking device that locks onto certain signals. It's configured to switch tracking modes once it detects the activation signal of a nuclear device." - icon_state = "pinpointer_syndicate" - -/obj/item/pinpointer/syndicate_cyborg // Cyborg pinpointers just look for a random operative. - name = "cyborg syndicate pinpointer" - desc = "An integrated tracking device, jury-rigged to search for living Syndicate operatives." - flags_1 = NONE - -/obj/item/pinpointer/syndicate_cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/pinpointer/syndicate_cyborg/cyborg_unequip(mob/user) - if(!active) - return - toggle_on() - -/obj/item/pinpointer/syndicate_cyborg/scan_for_target() - target = null - var/list/possible_targets = list() - var/turf/here = get_turf(src) - for(var/V in get_antag_minds(/datum/antagonist/nukeop)) - var/datum/mind/M = V - if(ishuman(M.current) && M.current.stat != DEAD) - possible_targets |= M.current - var/mob/living/closest_operative = get_closest_atom(/mob/living/carbon/human, possible_targets, here) - if(closest_operative) - target = closest_operative - ..() +/obj/item/pinpointer/nuke + var/mode = TRACK_NUKE_DISK + +/obj/item/pinpointer/nuke/examine(mob/user) + . = ..() + var/msg = "Its tracking indicator reads " + switch(mode) + if(TRACK_NUKE_DISK) + msg += "\"nuclear_disk\"." + if(TRACK_MALF_AI) + msg += "\"01000001 01001001\"." + if(TRACK_INFILTRATOR) + msg += "\"vasvygengbefuvc\"." + else + msg = "Its tracking indicator is blank." + . += msg + for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines) + if(bomb.timing) + . += "Extreme danger. Arming signal detected. Time remaining: [bomb.get_time_left()]." + +/obj/item/pinpointer/nuke/process() + ..() + if(active) // If shit's going down + for(var/obj/machinery/nuclearbomb/bomb in GLOB.nuke_list) + if(bomb.timing) + if(!alert) + alert = TRUE + playsound(src, 'sound/items/nuke_toy_lowpower.ogg', 50, 0) + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "Your [name] vibrates and lets out a tinny alarm. Uh oh.") + +/obj/item/pinpointer/nuke/scan_for_target() + target = null + switch(mode) + if(TRACK_NUKE_DISK) + var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list + target = N + if(TRACK_MALF_AI) + for(var/V in GLOB.ai_list) + var/mob/living/silicon/ai/A = V + if(A.nuking) + target = A + for(var/V in GLOB.apcs_list) + var/obj/machinery/power/apc/A = V + if(A.malfhack && A.occupier) + target = A + if(TRACK_INFILTRATOR) + target = SSshuttle.getShuttle("syndicate") + ..() + +/obj/item/pinpointer/nuke/proc/switch_mode_to(new_mode) + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "Your [name] beeps as it reconfigures it's tracking algorithms.") + playsound(L, 'sound/machines/triple_beep.ogg', 50, 1) + mode = new_mode + scan_for_target() + +/obj/item/pinpointer/nuke/syndicate // Syndicate pinpointers automatically point towards the infiltrator once the nuke is active. + name = "syndicate pinpointer" + desc = "A handheld tracking device that locks onto certain signals. It's configured to switch tracking modes once it detects the activation signal of a nuclear device." + icon_state = "pinpointer_syndicate" + +/obj/item/pinpointer/syndicate_cyborg // Cyborg pinpointers just look for a random operative. + name = "cyborg syndicate pinpointer" + desc = "An integrated tracking device, jury-rigged to search for living Syndicate operatives." + flags_1 = NONE + +/obj/item/pinpointer/syndicate_cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/pinpointer/syndicate_cyborg/cyborg_unequip(mob/user) + if(!active) + return + toggle_on() + +/obj/item/pinpointer/syndicate_cyborg/scan_for_target() + target = null + var/list/possible_targets = list() + var/turf/here = get_turf(src) + for(var/V in get_antag_minds(/datum/antagonist/nukeop)) + var/datum/mind/M = V + if(ishuman(M.current) && M.current.stat != DEAD) + possible_targets |= M.current + var/mob/living/closest_operative = get_closest_atom(/mob/living/carbon/human, possible_targets, here) + if(closest_operative) + target = closest_operative + ..() diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index 0297e73f0220..3d1952b2d495 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -1,472 +1,472 @@ - -//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" - -/obj/item/veilrender/honkrender/honkhulkrender - name = "superior honk render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus. This one gleams with a special light." - spawn_type = /mob/living/simple_animal/hostile/retaliate/clown/clownhulk - spawn_amt = 5 - activate_descriptor = "depression" - rend_desc = "Gently wafting with the sounds of mirthful grunting." - icon_state = "clownrender" - -////TEAR IN REALITY - -/obj/singularity/wizard - name = "tear in the fabric of reality" - desc = "This isn't right." - icon = 'icons/effects/224x224.dmi' - icon_state = "reality" - pixel_x = -96 - pixel_y = -96 - dissipate = 0 - move_self = 0 - consume_range = 3 - grav_pull = 4 - current_size = STAGE_FOUR - allowed_size = STAGE_FOUR - -/obj/singularity/wizard/process() - move() - eat() - return - -/obj/singularity/wizard/attack_tk(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - var/datum/component/mood/insaneinthemembrane = C.GetComponent(/datum/component/mood) - if(insaneinthemembrane.sanity < 15) - return //they've already seen it and are about to die, or are just too insane to care - to_chat(C, "OH GOD! NONE OF IT IS REAL! NONE OF IT IS REEEEEEEEEEEEEEEEEEEEEEEEAL!") - insaneinthemembrane.sanity = 0 - for(var/lore in typesof(/datum/brain_trauma/severe)) - C.gain_trauma(lore) - addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, C), 100) - -/obj/singularity/wizard/proc/deranged(mob/living/carbon/C) - if(!C || C.stat == DEAD) - return - C.vomit(0, TRUE, TRUE, 3, TRUE) - C.spew_organ(3, 2) - C.death() - -/obj/singularity/wizard/mapped/admin_investigate_setup() - return - -/////////////////////////////////////////Scrying/////////////////// - -/obj/item/scrying - name = "scrying orb" - desc = "An incandescent orb of otherworldly energy, merely holding it gives you vision and hearing beyond mortal means, and staring into it lets you see the entire universe." - icon = 'icons/obj/projectiles.dmi' - icon_state ="bluespace" - throw_speed = 3 - throw_range = 7 - throwforce = 15 - damtype = BURN - force = 15 - hitsound = 'sound/items/welder2.ogg' - - var/mob/current_owner - -/obj/item/scrying/Initialize(mapload) - . = ..() - START_PROCESSING(SSobj, src) - -/obj/item/scrying/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/scrying/process() - var/mob/holder = get(loc, /mob) - if(current_owner && current_owner != holder) - - to_chat(current_owner, "Your otherworldly vision fades...") - - REMOVE_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) - REMOVE_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) - current_owner.update_sight() - - current_owner = null - - if(!current_owner) - current_owner = holder - - to_chat(current_owner, "You can see...everything!") - - ADD_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) - ADD_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) - current_owner.update_sight() - -/obj/item/scrying/attack_self(mob/user) - visible_message("[user] stares into [src], their eyes glazing over.") - user.ghostize(1) - -/////////////////////////////////////////Necromantic Stone/////////////////// - -/obj/item/necromantic_stone - name = "necromantic stone" - desc = "A shard capable of resurrecting humans as skeleton thralls." - icon = 'icons/obj/wizard.dmi' - icon_state = "necrostone" - 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 - - for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) //excludes new players - if(ghost.mind && ghost.mind.current == M && ghost.client) //the dead mobs list can contain clientless mobs - ghost.reenter_corpse() - break - - if(!M.mind || !M.client) - to_chat(user, "There is no soul connected to this body...") - return - - check_spooky()//clean out/refresh the list - if(spooky_scaries.len >= 3 && !unlimited) - to_chat(user, "This artifact can only affect three undead at a time!") - return - - M.set_species(/datum/species/skeleton, icon_update=0) - M.revive(full_heal = 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.is_hot()) - to_chat(target, "You suddenly feel very hot") - target.adjust_bodytemperature(50) - GiveHint(target) - else if(is_pointed(I)) - to_chat(target, "You feel a stabbing pain in [parse_zone(user.zone_selected)]!") - target.Paralyze(40) - GiveHint(target) - else if(istype(I, /obj/item/bikehorn)) - to_chat(target, "HONK") - SEND_SOUND(target, 'sound/items/airhorn.ogg') - 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() - possible = list() - if(!voodoo_link) - return - var/list/prints = voodoo_link.return_fingerprints() - if(!length(prints)) - return FALSE - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(prints[md5(H.dna.uni_identity)]) - possible |= H - -/obj/item/voodoo/proc/GiveHint(mob/victim,force=0) - if(prob(50) || force) - var/way = dir2text(get_dir(victim,get_turf(src))) - to_chat(victim, "You feel a dark presence from [way]") - if(prob(20) || force) - var/area/A = get_area(src) - to_chat(victim, "You feel a dark presence from [A.name]") - -/obj/item/voodoo/suicide_act(mob/living/carbon/user) - user.visible_message("[user] links the voodoo doll to [user.p_them()]self and sits on it, infinitely crushing [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!") - user.gib() - return(BRUTELOSS) - -/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume) - if(target) - target.adjust_fire_stacks(20) - target.IgniteMob() - GiveHint(target,1) - return ..() - -//Provides a decent heal, need to pump every 6 seconds -/obj/item/organ/heart/cursed/wizard - pump_delay = 60 - heal_brute = 25 - heal_burn = 25 - heal_oxy = 25 - -//Warp Whistle: Provides uncontrolled long distance teleportation. - -/obj/item/warpwhistle - name = "warp whistle" - desc = "One toot on this whistle will send you to a far away land!" - icon = 'icons/obj/wizard.dmi' - icon_state = "whistle" - var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown - var/mob/living/carbon/last_user - -/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user) - if(!user || QDELETED(src) || user.notransform) - on_cooldown = FALSE - return TRUE - return FALSE - -/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user) - user.invisibility = initial(user.invisibility) - user.status_flags &= ~GODMODE - user.update_mobility() - -/obj/item/warpwhistle/attack_self(mob/living/carbon/user) - if(!istype(user) || on_cooldown) - return - on_cooldown = TRUE - last_user = user - var/turf/T = get_turf(user) - playsound(T,'sound/magic/warpwhistle.ogg', 200, 1) - user.mobility_flags &= ~MOBILITY_MOVE - new /obj/effect/temp_visual/tornado(T) - sleep(20) - if(interrupted(user)) - return - user.invisibility = INVISIBILITY_MAXIMUM - user.status_flags |= GODMODE - sleep(20) - if(interrupted(user)) - end_effect(user) - return - var/breakout = 0 - while(breakout < 50) - var/turf/potential_T = find_safe_turf() - if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout) - do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC) - user.mobility_flags &= ~MOBILITY_MOVE - T = potential_T - break - breakout += 1 - new /obj/effect/temp_visual/tornado(T) - sleep(20) - end_effect(user) - if(interrupted(user)) - return - on_cooldown = 2 - 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" + +/obj/item/veilrender/honkrender/honkhulkrender + name = "superior honk render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus. This one gleams with a special light." + spawn_type = /mob/living/simple_animal/hostile/retaliate/clown/clownhulk + spawn_amt = 5 + activate_descriptor = "depression" + rend_desc = "Gently wafting with the sounds of mirthful grunting." + icon_state = "clownrender" + +////TEAR IN REALITY + +/obj/singularity/wizard + name = "tear in the fabric of reality" + desc = "This isn't right." + icon = 'icons/effects/224x224.dmi' + icon_state = "reality" + pixel_x = -96 + pixel_y = -96 + dissipate = 0 + move_self = 0 + consume_range = 3 + grav_pull = 4 + current_size = STAGE_FOUR + allowed_size = STAGE_FOUR + +/obj/singularity/wizard/process() + move() + eat() + return + +/obj/singularity/wizard/attack_tk(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/datum/component/mood/insaneinthemembrane = C.GetComponent(/datum/component/mood) + if(insaneinthemembrane.sanity < 15) + return //they've already seen it and are about to die, or are just too insane to care + to_chat(C, "OH GOD! NONE OF IT IS REAL! NONE OF IT IS REEEEEEEEEEEEEEEEEEEEEEEEAL!") + insaneinthemembrane.sanity = 0 + for(var/lore in typesof(/datum/brain_trauma/severe)) + C.gain_trauma(lore) + addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, C), 100) + +/obj/singularity/wizard/proc/deranged(mob/living/carbon/C) + if(!C || C.stat == DEAD) + return + C.vomit(0, TRUE, TRUE, 3, TRUE) + C.spew_organ(3, 2) + C.death() + +/obj/singularity/wizard/mapped/admin_investigate_setup() + return + +/////////////////////////////////////////Scrying/////////////////// + +/obj/item/scrying + name = "scrying orb" + desc = "An incandescent orb of otherworldly energy, merely holding it gives you vision and hearing beyond mortal means, and staring into it lets you see the entire universe." + icon = 'icons/obj/projectiles.dmi' + icon_state ="bluespace" + throw_speed = 3 + throw_range = 7 + throwforce = 15 + damtype = BURN + force = 15 + hitsound = 'sound/items/welder2.ogg' + + var/mob/current_owner + +/obj/item/scrying/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/scrying/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/scrying/process() + var/mob/holder = get(loc, /mob) + if(current_owner && current_owner != holder) + + to_chat(current_owner, "Your otherworldly vision fades...") + + REMOVE_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) + REMOVE_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) + current_owner.update_sight() + + current_owner = null + + if(!current_owner) + current_owner = holder + + to_chat(current_owner, "You can see...everything!") + + ADD_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) + ADD_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) + current_owner.update_sight() + +/obj/item/scrying/attack_self(mob/user) + visible_message("[user] stares into [src], their eyes glazing over.") + user.ghostize(1) + +/////////////////////////////////////////Necromantic Stone/////////////////// + +/obj/item/necromantic_stone + name = "necromantic stone" + desc = "A shard capable of resurrecting humans as skeleton thralls." + icon = 'icons/obj/wizard.dmi' + icon_state = "necrostone" + 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 + + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) //excludes new players + if(ghost.mind && ghost.mind.current == M && ghost.client) //the dead mobs list can contain clientless mobs + ghost.reenter_corpse() + break + + if(!M.mind || !M.client) + to_chat(user, "There is no soul connected to this body...") + return + + check_spooky()//clean out/refresh the list + if(spooky_scaries.len >= 3 && !unlimited) + to_chat(user, "This artifact can only affect three undead at a time!") + return + + M.set_species(/datum/species/skeleton, icon_update=0) + M.revive(full_heal = 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.is_hot()) + to_chat(target, "You suddenly feel very hot") + target.adjust_bodytemperature(50) + GiveHint(target) + else if(is_pointed(I)) + to_chat(target, "You feel a stabbing pain in [parse_zone(user.zone_selected)]!") + target.Paralyze(40) + GiveHint(target) + else if(istype(I, /obj/item/bikehorn)) + to_chat(target, "HONK") + SEND_SOUND(target, 'sound/items/airhorn.ogg') + 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() + possible = list() + if(!voodoo_link) + return + var/list/prints = voodoo_link.return_fingerprints() + if(!length(prints)) + return FALSE + for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) + if(prints[md5(H.dna.uni_identity)]) + possible |= H + +/obj/item/voodoo/proc/GiveHint(mob/victim,force=0) + if(prob(50) || force) + var/way = dir2text(get_dir(victim,get_turf(src))) + to_chat(victim, "You feel a dark presence from [way]") + if(prob(20) || force) + var/area/A = get_area(src) + to_chat(victim, "You feel a dark presence from [A.name]") + +/obj/item/voodoo/suicide_act(mob/living/carbon/user) + user.visible_message("[user] links the voodoo doll to [user.p_them()]self and sits on it, infinitely crushing [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!") + user.gib() + return(BRUTELOSS) + +/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume) + if(target) + target.adjust_fire_stacks(20) + target.IgniteMob() + GiveHint(target,1) + return ..() + +//Provides a decent heal, need to pump every 6 seconds +/obj/item/organ/heart/cursed/wizard + pump_delay = 60 + heal_brute = 25 + heal_burn = 25 + heal_oxy = 25 + +//Warp Whistle: Provides uncontrolled long distance teleportation. + +/obj/item/warpwhistle + name = "warp whistle" + desc = "One toot on this whistle will send you to a far away land!" + icon = 'icons/obj/wizard.dmi' + icon_state = "whistle" + var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown + var/mob/living/carbon/last_user + +/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user) + if(!user || QDELETED(src) || user.notransform) + on_cooldown = FALSE + return TRUE + return FALSE + +/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user) + user.invisibility = initial(user.invisibility) + user.status_flags &= ~GODMODE + user.update_mobility() + +/obj/item/warpwhistle/attack_self(mob/living/carbon/user) + if(!istype(user) || on_cooldown) + return + on_cooldown = TRUE + last_user = user + var/turf/T = get_turf(user) + playsound(T,'sound/magic/warpwhistle.ogg', 200, 1) + user.mobility_flags &= ~MOBILITY_MOVE + new /obj/effect/temp_visual/tornado(T) + sleep(20) + if(interrupted(user)) + return + user.invisibility = INVISIBILITY_MAXIMUM + user.status_flags |= GODMODE + sleep(20) + if(interrupted(user)) + end_effect(user) + return + var/breakout = 0 + while(breakout < 50) + var/turf/potential_T = find_safe_turf() + if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout) + do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC) + user.mobility_flags &= ~MOBILITY_MOVE + T = potential_T + break + breakout += 1 + new /obj/effect/temp_visual/tornado(T) + sleep(20) + end_effect(user) + if(interrupted(user)) + return + on_cooldown = 2 + 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 4d2574342fb2..4740af3b96de 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook.dm @@ -1,744 +1,744 @@ -/datum/spellbook_entry - var/name = "Entry Name" - - var/spell_type = null - var/desc = "" - var/category = "Offensive" - var/cost = 2 - var/refundable = TRUE - var/surplus = -1 // -1 for infinite, not used by anything atm - var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell - var/buy_word = "Learn" - var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook - var/list/no_coexistance_typecache //Used so you can't have specific spells together - -/datum/spellbook_entry/New() - ..() - no_coexistance_typecache = typecacheof(no_coexistance_typecache) - -/datum/spellbook_entry/proc/IsAvailible() // For config prefs / gamemode restrictions - these are round applied - return TRUE - -/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances - if(book.uses= aspell.level_max) - to_chat(user, "This spell cannot be improved further.") - return FALSE - else - aspell.name = initial(aspell.name) - aspell.spell_level++ - aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) - if(aspell.charge_max < aspell.charge_counter) - aspell.charge_counter = aspell.charge_max - switch(aspell.spell_level) - if(1) - to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") - aspell.name = "Efficient [aspell.name]" - if(2) - to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") - aspell.name = "Quickened [aspell.name]" - if(3) - to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") - aspell.name = "Free [aspell.name]" - if(4) - to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") - aspell.name = "Instant [aspell.name]" - if(aspell.spell_level >= aspell.level_max) - to_chat(user, "This spell cannot be strengthened any further.") - SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]")) - return TRUE - //No same spell found - just learn it - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - user.mind.AddSpell(S) - to_chat(user, "You have learned [S.name].") - return TRUE - -/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) - if(!refundable) - return FALSE - if(!S) - S = new spell_type() - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - return TRUE - return FALSE - -/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user,obj/item/spellbook/book) //return point value or -1 for failure - var/area/wizard_station/A = GLOB.areas_by_type[/area/wizard_station] - if(!(user in A.contents)) - to_chat(user, "You can only refund spells at the wizard lair") - return -1 - if(!S) - S = new spell_type() - var/spell_levels = 0 - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - spell_levels = aspell.spell_level - user.mind.spell_list.Remove(aspell) - qdel(S) - return cost * (spell_levels+1) - return -1 -/datum/spellbook_entry/proc/GetInfo() - if(!S) - S = new spell_type() - var/dat ="" - dat += "[initial(S.name)]" - if(S.charge_type == "recharge") - dat += " Cooldown:[S.charge_max/10]" - dat += " Cost:[cost]
                " - dat += "[S.desc][desc]
                " - dat += "[S.clothes_req?"Requires wizard garb.":"Can be cast without wizard garb."]
                " - return dat - -/datum/spellbook_entry/fireball - name = "Fireball" - spell_type = /obj/effect/proc_holder/spell/aimed/fireball - -/datum/spellbook_entry/spell_cards - name = "Spell Cards" - spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards - -/datum/spellbook_entry/rod_form - name = "Rod Form" - spell_type = /obj/effect/proc_holder/spell/targeted/rod_form - -/datum/spellbook_entry/magicm - name = "Magic Missile" - spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile - category = "Defensive" - -/datum/spellbook_entry/disintegrate - name = "Disintegrate" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate - -/datum/spellbook_entry/disabletech - name = "Disable Tech" - spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/repulse - name = "Repulse" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse - category = "Defensive" - -/datum/spellbook_entry/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 TRUE 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/tap - name = "Soul Tap" - spell_type = /obj/effect/proc_holder/spell/self/tap - category = "Assistance" - cost = 3 - -/datum/spellbook_entry/spacetime_dist - name = "Spacetime Distortion" - spell_type = /obj/effect/proc_holder/spell/spacetime_dist - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/the_traps - name = "The Traps!" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps - category = "Defensive" - cost = 1 - - -/datum/spellbook_entry/item - name = "Buy Item" - refundable = FALSE - buy_word = "Summon" - var/item_path= null - - -/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - new item_path(get_turf(user)) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - return TRUE - -/datum/spellbook_entry/item/GetInfo() - var/dat ="" - dat += "[name]" - dat += " Cost:[cost]
                " - dat += "[desc]
                " - if(surplus>=0) - dat += "[surplus] left.
                " - return dat - -/datum/spellbook_entry/item/staffchange - name = "Staff of Change" - desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." - item_path = /obj/item/gun/magic/staff/change - -/datum/spellbook_entry/item/staffanimation - name = "Staff of Animation" - desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." - item_path = /obj/item/gun/magic/staff/animate - category = "Assistance" - -/datum/spellbook_entry/item/staffchaos - name = "Staff of Chaos" - desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." - item_path = /obj/item/gun/magic/staff/chaos - -/datum/spellbook_entry/item/spellblade - name = "Spellblade" - desc = "A sword capable of firing blasts of energy which rip targets limb from limb." - item_path = /obj/item/gun/magic/staff/spellblade - -/datum/spellbook_entry/item/staffdoor - name = "Staff of Door Creation" - desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." - item_path = /obj/item/gun/magic/staff/door - cost = 1 - category = "Mobility" - -/datum/spellbook_entry/item/staffhealing - name = "Staff of Healing" - desc = "An altruistic staff that can heal the lame and raise the dead." - item_path = /obj/item/gun/magic/staff/healing - cost = 1 - category = "Defensive" - -/datum/spellbook_entry/item/lockerstaff - name = "Staff of the Locker" - desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." - item_path = /obj/item/gun/magic/staff/locker - category = "Defensive" - -/datum/spellbook_entry/item/scryingorb - name = "Scrying Orb" - desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." - item_path = /obj/item/scrying - category = "Defensive" - -/datum/spellbook_entry/item/soulstones - name = "Six Soul Stone Shards and the spell Artificer" - desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." - item_path = /obj/item/storage/belt/soulstone/full - category = "Assistance" - -/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . =..() - if(.) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) - return . - -/datum/spellbook_entry/item/necrostone - name = "A Necromantic Stone" - desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." - item_path = /obj/item/necromantic_stone - category = "Assistance" - -/datum/spellbook_entry/item/wands - name = "Wand Assortment" - desc = "A collection of wands that allow for a wide variety of utility. Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." - item_path = /obj/item/storage/belt/wands/full - category = "Defensive" - -/datum/spellbook_entry/item/armor - name = "Mastercrafted Armor Set" - desc = "An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space." - item_path = /obj/item/clothing/suit/space/hardsuit/wizard - category = "Defensive" - -/datum/spellbook_entry/item/armor/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/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" - -/datum/spellbook_entry/item/guardian - name = "Guardian Deck" - desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ - It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." - item_path = /obj/item/guardiancreator/choose/wizard - category = "Assistance" - -/datum/spellbook_entry/item/guardian/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/paper/guides/antag/guardian/wizard(get_turf(user)) - -/datum/spellbook_entry/item/bloodbottle - name = "Bottle of Blood" - desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim." - item_path = /obj/item/antag_spawner/slaughter_demon - limit = 3 - category = "Assistance" - -/datum/spellbook_entry/item/hugbottle - name = "Bottle of Tickles" - desc = "A bottle of magically infused fun, the smell of which will \ - attract adorable extradimensional beings when broken. These beings \ - are similar to slaughter demons, but they do not permamently kill \ - their victims, instead putting them in an extradimensional hugspace, \ - to be released on the demon's death. Chaotic, but not ultimately \ - damaging. The crew's reaction to the other hand could be very \ - destructive." - item_path = /obj/item/antag_spawner/slaughter_demon/laughter - cost = 1 //non-destructive; it's just a jape, sibling! - limit = 3 - category = "Assistance" - -/datum/spellbook_entry/item/mjolnir - name = "Mjolnir" - desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." - item_path = /obj/item/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 = FALSE - buy_word = "Cast" - var/active = FALSE - -/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) - return ..() && !active - -/datum/spellbook_entry/summon/GetInfo() - var/dat ="" - dat += "[name]" - if(cost>0) - dat += " Cost:[cost]
                " - else - dat += " No Cost
                " - dat += "[desc]
                " - if(active) - dat += "Already cast!
                " - return dat - -/datum/spellbook_entry/summon/ghosts - name = "Summon Ghosts" - desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." - cost = 0 - -/datum/spellbook_entry/summon/ghosts/IsAvailible() - if(!SSticker.mode) - return FALSE - else - return TRUE - -/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - new /datum/round_event/wizard/ghost() - active = TRUE - to_chat(user, "You have cast summon ghosts!") - playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 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. There is a good chance that they will shoot each other first." - -/datum/spellbook_entry/summon/guns/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - return !CONFIG_GET(flag/no_summon_guns) - -/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - rightandwrong(SUMMON_GUNS, user, 10) - active = TRUE - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon guns!") - return TRUE - -/datum/spellbook_entry/summon/magic - name = "Summon Magic" - desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." - -/datum/spellbook_entry/summon/magic/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - return !CONFIG_GET(flag/no_summon_magic) - -/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - rightandwrong(SUMMON_MAGIC, user, 10) - active = TRUE - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon magic!") - return TRUE - -/datum/spellbook_entry/summon/events - name = "Summon Events" - desc = "Give Murphy's law a little push and replace all events with special wizard ones that will confound and confuse everyone. Multiple castings increase the rate of these events." - var/times = 0 - -/datum/spellbook_entry/summon/events/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - return !CONFIG_GET(flag/no_summon_events) - -/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - summonevents() - times++ - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon events.") - return TRUE - -/datum/spellbook_entry/summon/events/GetInfo() - . = ..() - if(times>0) - . += "You cast it [times] times.
                " - return . - -/datum/spellbook_entry/summon/curse_of_madness - name = "Curse of Madness" - desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance." - cost = 4 - -/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - active = TRUE - var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness") - if(!message) - return FALSE - curse_of_madness(user, message) - to_chat(user, "You have cast the curse of insanity!") - playsound(user, 'sound/magic/mandswap.ogg', 50, 1) - return TRUE - -/obj/item/spellbook - name = "spell book" - desc = "An unearthly tome that glows with power." - icon = 'icons/obj/library.dmi' - icon_state ="book" - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/uses = 10 - var/temp = null - var/tab = null - var/mob/living/carbon/human/owner - var/list/datum/spellbook_entry/entries = list() - var/list/categories = list() - -/obj/item/spellbook/examine(mob/user) - . = ..() - if(owner) - . += {"There is a small signature on the front cover: "[owner]"."} - else - . += "It appears to have no author." - -/obj/item/spellbook/Initialize() - . = ..() - prepare_spells() - -/obj/item/spellbook/proc/prepare_spells() - var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - for(var/T in entry_types) - var/datum/spellbook_entry/E = new T - if(E.IsAvailible()) - entries |= E - categories |= E.category - else - qdel(E) - tab = categories[1] - -/obj/item/spellbook/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/antag_spawner/contract)) - var/obj/item/antag_spawner/contract/contract = O - if(contract.used) - to_chat(user, "The contract has been used, you can't get your points back now!") - else - to_chat(user, "You feed the contract back into the spellbook, refunding your points.") - uses++ - 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 TRUE - - if(H.mind.special_role == "apprentice") - temp = "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." - return - - var/datum/spellbook_entry/E = null - if(loc == H || (in_range(src, H) && isturf(loc))) - H.set_machine(src) - if(href_list["buy"]) - E = entries[text2num(href_list["buy"])] - if(E && E.CanBuy(H,src)) - if(E.Buy(H,src)) - if(E.limit) - E.limit-- - uses -= E.cost - else if(href_list["refund"]) - E = entries[text2num(href_list["refund"])] - if(E && E.refundable) - var/result = E.Refund(H,src) - if(result > 0) - if(!isnull(E.limit)) - E.limit += result - uses += result - else if(href_list["page"]) - tab = sanitize(href_list["page"]) - attack_self(H) - return +/datum/spellbook_entry + var/name = "Entry Name" + + var/spell_type = null + var/desc = "" + var/category = "Offensive" + var/cost = 2 + var/refundable = TRUE + var/surplus = -1 // -1 for infinite, not used by anything atm + var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell + var/buy_word = "Learn" + var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook + var/list/no_coexistance_typecache //Used so you can't have specific spells together + +/datum/spellbook_entry/New() + ..() + no_coexistance_typecache = typecacheof(no_coexistance_typecache) + +/datum/spellbook_entry/proc/IsAvailible() // For config prefs / gamemode restrictions - these are round applied + return TRUE + +/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances + if(book.uses= aspell.level_max) + to_chat(user, "This spell cannot be improved further.") + return FALSE + else + aspell.name = initial(aspell.name) + aspell.spell_level++ + aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) + if(aspell.charge_max < aspell.charge_counter) + aspell.charge_counter = aspell.charge_max + switch(aspell.spell_level) + if(1) + to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") + aspell.name = "Efficient [aspell.name]" + if(2) + to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") + aspell.name = "Quickened [aspell.name]" + if(3) + to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") + aspell.name = "Free [aspell.name]" + if(4) + to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") + aspell.name = "Instant [aspell.name]" + if(aspell.spell_level >= aspell.level_max) + to_chat(user, "This spell cannot be strengthened any further.") + SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]")) + return TRUE + //No same spell found - just learn it + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + user.mind.AddSpell(S) + to_chat(user, "You have learned [S.name].") + return TRUE + +/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) + if(!refundable) + return FALSE + if(!S) + S = new spell_type() + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) + return TRUE + return FALSE + +/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user,obj/item/spellbook/book) //return point value or -1 for failure + var/area/wizard_station/A = GLOB.areas_by_type[/area/wizard_station] + if(!(user in A.contents)) + to_chat(user, "You can only refund spells at the wizard lair") + return -1 + if(!S) + S = new spell_type() + var/spell_levels = 0 + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) + spell_levels = aspell.spell_level + user.mind.spell_list.Remove(aspell) + qdel(S) + return cost * (spell_levels+1) + return -1 +/datum/spellbook_entry/proc/GetInfo() + if(!S) + S = new spell_type() + var/dat ="" + dat += "[initial(S.name)]" + if(S.charge_type == "recharge") + dat += " Cooldown:[S.charge_max/10]" + dat += " Cost:[cost]
                " + dat += "[S.desc][desc]
                " + dat += "[S.clothes_req?"Requires wizard garb.":"Can be cast without wizard garb."]
                " + return dat + +/datum/spellbook_entry/fireball + name = "Fireball" + spell_type = /obj/effect/proc_holder/spell/aimed/fireball + +/datum/spellbook_entry/spell_cards + name = "Spell Cards" + spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards + +/datum/spellbook_entry/rod_form + name = "Rod Form" + spell_type = /obj/effect/proc_holder/spell/targeted/rod_form + +/datum/spellbook_entry/magicm + name = "Magic Missile" + spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile + category = "Defensive" + +/datum/spellbook_entry/disintegrate + name = "Disintegrate" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate + +/datum/spellbook_entry/disabletech + name = "Disable Tech" + spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/repulse + name = "Repulse" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse + category = "Defensive" + +/datum/spellbook_entry/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 TRUE 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/tap + name = "Soul Tap" + spell_type = /obj/effect/proc_holder/spell/self/tap + category = "Assistance" + cost = 3 + +/datum/spellbook_entry/spacetime_dist + name = "Spacetime Distortion" + spell_type = /obj/effect/proc_holder/spell/spacetime_dist + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/the_traps + name = "The Traps!" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps + category = "Defensive" + cost = 1 + + +/datum/spellbook_entry/item + name = "Buy Item" + refundable = FALSE + buy_word = "Summon" + var/item_path= null + + +/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + new item_path(get_turf(user)) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + return TRUE + +/datum/spellbook_entry/item/GetInfo() + var/dat ="" + dat += "[name]" + dat += " Cost:[cost]
                " + dat += "[desc]
                " + if(surplus>=0) + dat += "[surplus] left.
                " + return dat + +/datum/spellbook_entry/item/staffchange + name = "Staff of Change" + desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." + item_path = /obj/item/gun/magic/staff/change + +/datum/spellbook_entry/item/staffanimation + name = "Staff of Animation" + desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." + item_path = /obj/item/gun/magic/staff/animate + category = "Assistance" + +/datum/spellbook_entry/item/staffchaos + name = "Staff of Chaos" + desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." + item_path = /obj/item/gun/magic/staff/chaos + +/datum/spellbook_entry/item/spellblade + name = "Spellblade" + desc = "A sword capable of firing blasts of energy which rip targets limb from limb." + item_path = /obj/item/gun/magic/staff/spellblade + +/datum/spellbook_entry/item/staffdoor + name = "Staff of Door Creation" + desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." + item_path = /obj/item/gun/magic/staff/door + cost = 1 + category = "Mobility" + +/datum/spellbook_entry/item/staffhealing + name = "Staff of Healing" + desc = "An altruistic staff that can heal the lame and raise the dead." + item_path = /obj/item/gun/magic/staff/healing + cost = 1 + category = "Defensive" + +/datum/spellbook_entry/item/lockerstaff + name = "Staff of the Locker" + desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." + item_path = /obj/item/gun/magic/staff/locker + category = "Defensive" + +/datum/spellbook_entry/item/scryingorb + name = "Scrying Orb" + desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." + item_path = /obj/item/scrying + category = "Defensive" + +/datum/spellbook_entry/item/soulstones + name = "Six Soul Stone Shards and the spell Artificer" + desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." + item_path = /obj/item/storage/belt/soulstone/full + category = "Assistance" + +/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . =..() + if(.) + user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) + return . + +/datum/spellbook_entry/item/necrostone + name = "A Necromantic Stone" + desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." + item_path = /obj/item/necromantic_stone + category = "Assistance" + +/datum/spellbook_entry/item/wands + name = "Wand Assortment" + desc = "A collection of wands that allow for a wide variety of utility. Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." + item_path = /obj/item/storage/belt/wands/full + category = "Defensive" + +/datum/spellbook_entry/item/armor + name = "Mastercrafted Armor Set" + desc = "An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space." + item_path = /obj/item/clothing/suit/space/hardsuit/wizard + category = "Defensive" + +/datum/spellbook_entry/item/armor/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . = ..() + if(.) + new /obj/item/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" + +/datum/spellbook_entry/item/guardian + name = "Guardian Deck" + desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ + It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." + item_path = /obj/item/guardiancreator/choose/wizard + category = "Assistance" + +/datum/spellbook_entry/item/guardian/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . = ..() + if(.) + new /obj/item/paper/guides/antag/guardian/wizard(get_turf(user)) + +/datum/spellbook_entry/item/bloodbottle + name = "Bottle of Blood" + desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim." + item_path = /obj/item/antag_spawner/slaughter_demon + limit = 3 + category = "Assistance" + +/datum/spellbook_entry/item/hugbottle + name = "Bottle of Tickles" + desc = "A bottle of magically infused fun, the smell of which will \ + attract adorable extradimensional beings when broken. These beings \ + are similar to slaughter demons, but they do not permamently kill \ + their victims, instead putting them in an extradimensional hugspace, \ + to be released on the demon's death. Chaotic, but not ultimately \ + damaging. The crew's reaction to the other hand could be very \ + destructive." + item_path = /obj/item/antag_spawner/slaughter_demon/laughter + cost = 1 //non-destructive; it's just a jape, sibling! + limit = 3 + category = "Assistance" + +/datum/spellbook_entry/item/mjolnir + name = "Mjolnir" + desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." + item_path = /obj/item/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 = FALSE + buy_word = "Cast" + var/active = FALSE + +/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) + return ..() && !active + +/datum/spellbook_entry/summon/GetInfo() + var/dat ="" + dat += "[name]" + if(cost>0) + dat += " Cost:[cost]
                " + else + dat += " No Cost
                " + dat += "[desc]
                " + if(active) + dat += "Already cast!
                " + return dat + +/datum/spellbook_entry/summon/ghosts + name = "Summon Ghosts" + desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." + cost = 0 + +/datum/spellbook_entry/summon/ghosts/IsAvailible() + if(!SSticker.mode) + return FALSE + else + return TRUE + +/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + new /datum/round_event/wizard/ghost() + active = TRUE + to_chat(user, "You have cast summon ghosts!") + playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 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. There is a good chance that they will shoot each other first." + +/datum/spellbook_entry/summon/guns/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + return !CONFIG_GET(flag/no_summon_guns) + +/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + rightandwrong(SUMMON_GUNS, user, 10) + active = TRUE + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) + to_chat(user, "You have cast summon guns!") + return TRUE + +/datum/spellbook_entry/summon/magic + name = "Summon Magic" + desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." + +/datum/spellbook_entry/summon/magic/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + return !CONFIG_GET(flag/no_summon_magic) + +/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + rightandwrong(SUMMON_MAGIC, user, 10) + active = TRUE + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) + to_chat(user, "You have cast summon magic!") + return TRUE + +/datum/spellbook_entry/summon/events + name = "Summon Events" + desc = "Give Murphy's law a little push and replace all events with special wizard ones that will confound and confuse everyone. Multiple castings increase the rate of these events." + var/times = 0 + +/datum/spellbook_entry/summon/events/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + return !CONFIG_GET(flag/no_summon_events) + +/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + summonevents() + times++ + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) + to_chat(user, "You have cast summon events.") + return TRUE + +/datum/spellbook_entry/summon/events/GetInfo() + . = ..() + if(times>0) + . += "You cast it [times] times.
                " + return . + +/datum/spellbook_entry/summon/curse_of_madness + name = "Curse of Madness" + desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance." + cost = 4 + +/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + active = TRUE + var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness") + if(!message) + return FALSE + curse_of_madness(user, message) + to_chat(user, "You have cast the curse of insanity!") + playsound(user, 'sound/magic/mandswap.ogg', 50, 1) + return TRUE + +/obj/item/spellbook + name = "spell book" + desc = "An unearthly tome that glows with power." + icon = 'icons/obj/library.dmi' + icon_state ="book" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/uses = 10 + var/temp = null + var/tab = null + var/mob/living/carbon/human/owner + var/list/datum/spellbook_entry/entries = list() + var/list/categories = list() + +/obj/item/spellbook/examine(mob/user) + . = ..() + if(owner) + . += {"There is a small signature on the front cover: "[owner]"."} + else + . += "It appears to have no author." + +/obj/item/spellbook/Initialize() + . = ..() + prepare_spells() + +/obj/item/spellbook/proc/prepare_spells() + var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon + for(var/T in entry_types) + var/datum/spellbook_entry/E = new T + if(E.IsAvailible()) + entries |= E + categories |= E.category + else + qdel(E) + tab = categories[1] + +/obj/item/spellbook/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/antag_spawner/contract)) + var/obj/item/antag_spawner/contract/contract = O + if(contract.used) + to_chat(user, "The contract has been used, you can't get your points back now!") + else + to_chat(user, "You feed the contract back into the spellbook, refunding your points.") + uses++ + 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 TRUE + + if(H.mind.special_role == "apprentice") + temp = "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." + return + + var/datum/spellbook_entry/E = null + if(loc == H || (in_range(src, H) && isturf(loc))) + H.set_machine(src) + if(href_list["buy"]) + E = entries[text2num(href_list["buy"])] + if(E && E.CanBuy(H,src)) + if(E.Buy(H,src)) + if(E.limit) + E.limit-- + uses -= E.cost + else if(href_list["refund"]) + E = entries[text2num(href_list["refund"])] + if(E && E.refundable) + var/result = E.Refund(H,src) + if(result > 0) + if(!isnull(E.limit)) + E.limit += result + uses += result + else if(href_list["page"]) + tab = sanitize(href_list["page"]) + attack_self(H) + return diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index 847528840a0f..889ccd144142 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -1,126 +1,126 @@ -#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 - -/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 + 30 - return TRUE - - -/obj/item/assembly/proc/toggle_secure() - secured = !secured - update_icon() - return secured - - -/obj/item/assembly/attackby(obj/item/W, mob/user, params) - if(isassembly(W)) - var/obj/item/assembly/A = W - if((!A.secured) && (!secured)) - holder = new/obj/item/assembly_holder(get_turf(src)) - holder.assemble(src,A,user) - to_chat(user, "You attach and secure \the [A] to \the [src]!") - else - to_chat(user, "Both devices must be in attachable mode to be attached together.") - return - ..() - -/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I) - if(..()) - return TRUE - if(toggle_secure()) - to_chat(user, "\The [src] is ready!") - else - to_chat(user, "\The [src] can now be attached!") - add_fingerprint(user) - return TRUE - -/obj/item/assembly/examine(mob/user) - . = ..() - . += "\The [src] [secured? "is secured and ready to be used!" : "can be attached to other things."]" - - -/obj/item/assembly/attack_self(mob/user) - if(!user) - return FALSE - user.set_machine(src) - interact(user) - return TRUE - -/obj/item/assembly/interact(mob/user) - return ui_interact(user) +#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 + +/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 + 30 + return TRUE + + +/obj/item/assembly/proc/toggle_secure() + secured = !secured + update_icon() + return secured + + +/obj/item/assembly/attackby(obj/item/W, mob/user, params) + if(isassembly(W)) + var/obj/item/assembly/A = W + if((!A.secured) && (!secured)) + holder = new/obj/item/assembly_holder(get_turf(src)) + holder.assemble(src,A,user) + to_chat(user, "You attach and secure \the [A] to \the [src]!") + else + to_chat(user, "Both devices must be in attachable mode to be attached together.") + return + ..() + +/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + if(toggle_secure()) + to_chat(user, "\The [src] is ready!") + else + to_chat(user, "\The [src] can now be attached!") + add_fingerprint(user) + return TRUE + +/obj/item/assembly/examine(mob/user) + . = ..() + . += "\The [src] [secured? "is secured and ready to be used!" : "can be attached to other things."]" + + +/obj/item/assembly/attack_self(mob/user) + if(!user) + return FALSE + user.set_machine(src) + interact(user) + return TRUE + +/obj/item/assembly/interact(mob/user) + return ui_interact(user) diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index 7ebc80f64991..7a7bd2e98c3e 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -1,297 +1,297 @@ -#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) - light_color = LIGHT_COLOR_WHITE - light_power = FLASH_LIGHT_POWER - var/flashing_overlay = "flash-f" - var/times_used = 0 //Number of times it's been used. - var/burnt_out = FALSE //Is the flash burnt out? - var/burnout_resistance = 0 - var/last_used = 0 //last world.time it was used. - var/cooldown = 0 - var/last_trigger = 0 //Last time it was successfully triggered. - -/obj/item/assembly/flash/suicide_act(mob/living/user) - if(burnt_out) - user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but it's burnt out!") - return SHAME - else if(user.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(burnt_out) - add_overlay("flashburnt") - attached_overlays += "flashburnt" - if(flash) - add_overlay(flashing_overlay) - attached_overlays += flashing_overlay - addtimer(CALLBACK(src, .proc/update_icon), 5) - if(holder) - holder.update_icon() - -/obj/item/assembly/flash/proc/clown_check(mob/living/carbon/human/user) - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - flash_carbon(user, user, 15, 0) - return FALSE - return TRUE - -/obj/item/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something. - if(!burnt_out) - burnt_out = TRUE - update_icon() - if(ismob(loc)) - var/mob/M = loc - M.visible_message("[src] burns out!","[src] burns out!") - else - var/turf/T = get_turf(src) - T.visible_message("[src] burns out!") - -/obj/item/assembly/flash/proc/flash_recharge(interval = 10) - var/deciseconds_passed = world.time - last_used - for(var/seconds = deciseconds_passed / 10, seconds >= interval, seconds -= interval) //get 1 charge every interval - times_used-- - last_used = world.time - times_used = max(0, times_used) //sanity - if(max(0, prob(times_used * 3) - burnout_resistance)) //The more often it's used in a short span of time the more likely it will burn out - burn_out() - return FALSE - return TRUE - -//BYPASS CHECKS ALSO PREVENTS BURNOUT! -/obj/item/assembly/flash/proc/AOE_flash(bypass_checks = FALSE, range = 3, power = 5, targeted = FALSE, mob/user) - if(!bypass_checks && !try_use_flash()) - return FALSE - var/list/mob/targets = get_flash_targets(get_turf(src), range, FALSE) - if(user) - targets -= user - for(var/mob/living/carbon/C in targets) - flash_carbon(C, user, power, targeted, TRUE) - return TRUE - -/obj/item/assembly/flash/proc/get_flash_targets(atom/target_loc, range = 3, override_vision_checks = FALSE) - if(!target_loc) - target_loc = loc - if(override_vision_checks) - return get_hearers_in_view(range, get_turf(target_loc)) - if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc))) - return viewers(range, get_turf(target_loc)) - else - return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living) - -/obj/item/assembly/flash/proc/try_use_flash(mob/user = null) - if(burnt_out || (world.time < last_trigger + cooldown)) - return FALSE - last_trigger = world.time - playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) - flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) - times_used++ - flash_recharge() - update_icon(TRUE) - if(user && !clown_check(user)) - return FALSE - return TRUE - -/obj/item/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) - if(!istype(M)) - return - if(user) - log_combat(user, M, "[targeted? "flashed(targeted)" : "flashed(AOE)"]", src) - else //caused by emp/remote signal - M.log_message("was [targeted? "flashed(targeted)" : "flashed(AOE)"]",LOG_ATTACK) - if(generic_message && M != user) - to_chat(M, "[src] emits a blinding light!") - if(targeted) - if(M.flash_act(1, 1)) - if(M.confused < power) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) - if(user) - terrible_conversion_proc(M, user) - visible_message("[user] blinds [M] with the flash!") - to_chat(user, "You blind [M] with the flash!") - to_chat(M, "[user] blinds you with the flash!") - else - to_chat(M, "You are blinded by [src]!") - M.Paralyze(rand(80,120)) - else if(user) - visible_message("[user] fails to blind [M] with the flash!") - to_chat(user, "You fail to blind [M] with the flash!") - to_chat(M, "[user] fails to blind you with the flash!") - else - to_chat(M, "[src] fails to blind you!") - else - if(M.flash_act()) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) - -/obj/item/assembly/flash/attack(mob/living/M, mob/user) - if(!try_use_flash(user)) - return FALSE - if(iscarbon(M)) - flash_carbon(M, user, 5, 1) - return TRUE - else if(issilicon(M)) - var/mob/living/silicon/robot/R = M - log_combat(user, R, "flashed", src) - update_icon(1) - R.Paralyze(rand(80,120)) - var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - R.confused += min(5, diff) - R.flash_act(affect_silicon = 1) - user.visible_message("[user] overloads [R]'s sensors with the flash!", "You overload [R]'s sensors with the flash!") - return TRUE - - user.visible_message("[user] fails to blind [M] with the flash!", "You fail to blind [M] with the flash!") - -/obj/item/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0) - if(holder) - return FALSE - if(!AOE_flash(FALSE, 3, 5, FALSE, user)) - return FALSE - to_chat(user, "[src] emits a blinding light!") - -/obj/item/assembly/flash/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(!try_use_flash()) - return - AOE_flash() - burn_out() - -/obj/item/assembly/flash/activate()//AOE flash on signal received - if(!..()) - return - AOE_flash() - -/obj/item/assembly/flash/proc/terrible_conversion_proc(mob/living/carbon/H, mob/user) - if(istype(H) && H.stat != DEAD) - if(user.mind) - var/datum/antagonist/rev/head/converter = user.mind.has_antag_datum(/datum/antagonist/rev/head) - if(!converter) - return - if(!H.client) - to_chat(user, "This mind is so vacant that it is not susceptible to influence!") - return - if(H.stat != CONSCIOUS) - to_chat(user, "They must be conscious before you can convert [H.p_them()]!") - return - if(converter.add_revolutionary(H.mind)) - times_used -- //Flashes less likely to burn out for headrevs when used for conversion - else - to_chat(user, "This mind seems resistant to the flash!") - - -/obj/item/assembly/flash/cyborg - -/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user) - ..() - new /obj/effect/temp_visual/borgflash(get_turf(src)) - -/obj/item/assembly/flash/cyborg/attack_self(mob/user) - ..() - new /obj/effect/temp_visual/borgflash(get_turf(src)) - -/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, params) - return -/obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return - -/obj/item/assembly/flash/memorizer - name = "memorizer" - desc = "If you see this, you're not likely to remember it any time soon." - icon = 'icons/obj/device.dmi' - icon_state = "memorizer" - 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/hypnotic - desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance." - flashing_overlay = "flash-hypno" - light_color = LIGHT_COLOR_PINK - cooldown = 20 - -/obj/item/assembly/flash/hypnotic/burn_out() - return - -/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) - if(!istype(M)) - return - if(user) - log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src) - else //caused by emp/remote signal - M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK) - if(generic_message && M != user) - to_chat(M, "[src] emits a soothing light...") - if(targeted) - if(M.flash_act(1, 1)) - var/hypnosis = FALSE - if(M.hypnosis_vulnerable()) - hypnosis = TRUE - if(user) - user.visible_message("[user] blinds [M] with the flash!", "You hypno-flash [M]!") - - if(!hypnosis) - to_chat(M, "The light makes you feel oddly relaxed...") - M.confused += min(M.confused + 10, 20) - M.dizziness += min(M.dizziness + 10, 20) - M.drowsyness += min(M.drowsyness + 10, 20) - M.apply_status_effect(STATUS_EFFECT_PACIFY, 100) - else - M.apply_status_effect(/datum/status_effect/trance, 200, TRUE) - - else if(user) - user.visible_message("[user] fails to blind [M] with the flash!", "You fail to hypno-flash [M]!") - else - to_chat(M, "[src] fails to blind you!") - - else if(M.flash_act()) - to_chat(M, "Such a pretty light...") - M.confused += min(M.confused + 4, 20) - M.dizziness += min(M.dizziness + 4, 20) - M.drowsyness += min(M.drowsyness + 4, 20) - M.apply_status_effect(STATUS_EFFECT_PACIFY, 40) +#define CONFUSION_STACK_MAX_MULTIPLIER 2 +/obj/item/assembly/flash + name = "flash" + desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production." + icon_state = "flash" + 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) + light_color = LIGHT_COLOR_WHITE + light_power = FLASH_LIGHT_POWER + var/flashing_overlay = "flash-f" + var/times_used = 0 //Number of times it's been used. + var/burnt_out = FALSE //Is the flash burnt out? + var/burnout_resistance = 0 + var/last_used = 0 //last world.time it was used. + var/cooldown = 0 + var/last_trigger = 0 //Last time it was successfully triggered. + +/obj/item/assembly/flash/suicide_act(mob/living/user) + if(burnt_out) + user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but it's burnt out!") + return SHAME + else if(user.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(burnt_out) + add_overlay("flashburnt") + attached_overlays += "flashburnt" + if(flash) + add_overlay(flashing_overlay) + attached_overlays += flashing_overlay + addtimer(CALLBACK(src, .proc/update_icon), 5) + if(holder) + holder.update_icon() + +/obj/item/assembly/flash/proc/clown_check(mob/living/carbon/human/user) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + flash_carbon(user, user, 15, 0) + return FALSE + return TRUE + +/obj/item/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something. + if(!burnt_out) + burnt_out = TRUE + update_icon() + if(ismob(loc)) + var/mob/M = loc + M.visible_message("[src] burns out!","[src] burns out!") + else + var/turf/T = get_turf(src) + T.visible_message("[src] burns out!") + +/obj/item/assembly/flash/proc/flash_recharge(interval = 10) + var/deciseconds_passed = world.time - last_used + for(var/seconds = deciseconds_passed / 10, seconds >= interval, seconds -= interval) //get 1 charge every interval + times_used-- + last_used = world.time + times_used = max(0, times_used) //sanity + if(max(0, prob(times_used * 3) - burnout_resistance)) //The more often it's used in a short span of time the more likely it will burn out + burn_out() + return FALSE + return TRUE + +//BYPASS CHECKS ALSO PREVENTS BURNOUT! +/obj/item/assembly/flash/proc/AOE_flash(bypass_checks = FALSE, range = 3, power = 5, targeted = FALSE, mob/user) + if(!bypass_checks && !try_use_flash()) + return FALSE + var/list/mob/targets = get_flash_targets(get_turf(src), range, FALSE) + if(user) + targets -= user + for(var/mob/living/carbon/C in targets) + flash_carbon(C, user, power, targeted, TRUE) + return TRUE + +/obj/item/assembly/flash/proc/get_flash_targets(atom/target_loc, range = 3, override_vision_checks = FALSE) + if(!target_loc) + target_loc = loc + if(override_vision_checks) + return get_hearers_in_view(range, get_turf(target_loc)) + if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc))) + return viewers(range, get_turf(target_loc)) + else + return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living) + +/obj/item/assembly/flash/proc/try_use_flash(mob/user = null) + if(burnt_out || (world.time < last_trigger + cooldown)) + return FALSE + last_trigger = world.time + playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) + flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) + times_used++ + flash_recharge() + update_icon(TRUE) + if(user && !clown_check(user)) + return FALSE + return TRUE + +/obj/item/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) + if(!istype(M)) + return + if(user) + log_combat(user, M, "[targeted? "flashed(targeted)" : "flashed(AOE)"]", src) + else //caused by emp/remote signal + M.log_message("was [targeted? "flashed(targeted)" : "flashed(AOE)"]",LOG_ATTACK) + if(generic_message && M != user) + to_chat(M, "[src] emits a blinding light!") + if(targeted) + if(M.flash_act(1, 1)) + if(M.confused < power) + var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + M.confused += min(power, diff) + if(user) + terrible_conversion_proc(M, user) + visible_message("[user] blinds [M] with the flash!") + to_chat(user, "You blind [M] with the flash!") + to_chat(M, "[user] blinds you with the flash!") + else + to_chat(M, "You are blinded by [src]!") + M.Paralyze(rand(80,120)) + else if(user) + visible_message("[user] fails to blind [M] with the flash!") + to_chat(user, "You fail to blind [M] with the flash!") + to_chat(M, "[user] fails to blind you with the flash!") + else + to_chat(M, "[src] fails to blind you!") + else + if(M.flash_act()) + var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + M.confused += min(power, diff) + +/obj/item/assembly/flash/attack(mob/living/M, mob/user) + if(!try_use_flash(user)) + return FALSE + if(iscarbon(M)) + flash_carbon(M, user, 5, 1) + return TRUE + else if(issilicon(M)) + var/mob/living/silicon/robot/R = M + log_combat(user, R, "flashed", src) + update_icon(1) + R.Paralyze(rand(80,120)) + var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + R.confused += min(5, diff) + R.flash_act(affect_silicon = 1) + user.visible_message("[user] overloads [R]'s sensors with the flash!", "You overload [R]'s sensors with the flash!") + return TRUE + + user.visible_message("[user] fails to blind [M] with the flash!", "You fail to blind [M] with the flash!") + +/obj/item/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0) + if(holder) + return FALSE + if(!AOE_flash(FALSE, 3, 5, FALSE, user)) + return FALSE + to_chat(user, "[src] emits a blinding light!") + +/obj/item/assembly/flash/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(!try_use_flash()) + return + AOE_flash() + burn_out() + +/obj/item/assembly/flash/activate()//AOE flash on signal received + if(!..()) + return + AOE_flash() + +/obj/item/assembly/flash/proc/terrible_conversion_proc(mob/living/carbon/H, mob/user) + if(istype(H) && H.stat != DEAD) + if(user.mind) + var/datum/antagonist/rev/head/converter = user.mind.has_antag_datum(/datum/antagonist/rev/head) + if(!converter) + return + if(!H.client) + to_chat(user, "This mind is so vacant that it is not susceptible to influence!") + return + if(H.stat != CONSCIOUS) + to_chat(user, "They must be conscious before you can convert [H.p_them()]!") + return + if(converter.add_revolutionary(H.mind)) + times_used -- //Flashes less likely to burn out for headrevs when used for conversion + else + to_chat(user, "This mind seems resistant to the flash!") + + +/obj/item/assembly/flash/cyborg + +/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user) + ..() + new /obj/effect/temp_visual/borgflash(get_turf(src)) + +/obj/item/assembly/flash/cyborg/attack_self(mob/user) + ..() + new /obj/effect/temp_visual/borgflash(get_turf(src)) + +/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, params) + return +/obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I) + return + +/obj/item/assembly/flash/memorizer + name = "memorizer" + desc = "If you see this, you're not likely to remember it any time soon." + icon = 'icons/obj/device.dmi' + icon_state = "memorizer" + 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/hypnotic + desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance." + flashing_overlay = "flash-hypno" + light_color = LIGHT_COLOR_PINK + cooldown = 20 + +/obj/item/assembly/flash/hypnotic/burn_out() + return + +/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) + if(!istype(M)) + return + if(user) + log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src) + else //caused by emp/remote signal + M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK) + if(generic_message && M != user) + to_chat(M, "[src] emits a soothing light...") + if(targeted) + if(M.flash_act(1, 1)) + var/hypnosis = FALSE + if(M.hypnosis_vulnerable()) + hypnosis = TRUE + if(user) + user.visible_message("[user] blinds [M] with the flash!", "You hypno-flash [M]!") + + if(!hypnosis) + to_chat(M, "The light makes you feel oddly relaxed...") + M.confused += min(M.confused + 10, 20) + M.dizziness += min(M.dizziness + 10, 20) + M.drowsyness += min(M.drowsyness + 10, 20) + M.apply_status_effect(STATUS_EFFECT_PACIFY, 100) + else + M.apply_status_effect(/datum/status_effect/trance, 200, TRUE) + + else if(user) + user.visible_message("[user] fails to blind [M] with the flash!", "You fail to hypno-flash [M]!") + else + to_chat(M, "[src] fails to blind you!") + + else if(M.flash_act()) + to_chat(M, "Such a pretty light...") + M.confused += min(M.confused + 4, 20) + M.dizziness += min(M.dizziness + 4, 20) + M.drowsyness += min(M.drowsyness + 4, 20) + M.apply_status_effect(STATUS_EFFECT_PACIFY, 40) diff --git a/code/modules/assembly/helpers.dm b/code/modules/assembly/helpers.dm index 5ad5e129a052..3066b1485146 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 623906f70392..529e09cdcb1b 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -1,166 +1,166 @@ -/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/ComponentInitialize() - . = ..() - AddComponent( - /datum/component/simple_rotation, - ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS) - -/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) - // yogs start - signaller colors - if(istext(O)) - add_overlay("[O]_l") - else - var/mutable_appearance/A = new(O) - A.icon_state = "[A.icon_state]_l" - add_overlay(A) - // yogs end - - if(a_right) - if(a_right.is_position_sensitive) - add_overlay("[a_right.icon_state]_right") - for(var/O in a_right.attached_overlays) - // yogs start - signaller colors - if(istext(O)) - add_overlay("[O]_r") - else - var/mutable_appearance/A = new(O) - A.icon_state = "[A.icon_state]_r" - add_overlay(A) - // yogs end - 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) - // yogs start - signaller colors - if(istext(O)) - right.add_overlay("[O]_l") - else - var/mutable_appearance/A = new(O) - A.icon_state = "[A.icon_state]_l" - right.add_overlay(A) - // yogs end - add_overlay(right) - - if(master) - master.update_icon() - -/obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj) - if(a_left) - a_left.Crossed(AM) - if(a_right) - a_right.Crossed(AM) - -/obj/item/assembly_holder/on_found(mob/finder) - if(a_left) - a_left.on_found(finder) - if(a_right) - a_right.on_found(finder) - -/obj/item/assembly_holder/setDir() - . = ..() - if(a_left) - a_left.holder_movement() - if(a_right) - a_right.holder_movement() - -/obj/item/assembly_holder/dropped(mob/user) - . = ..() - if(a_left) - a_left.dropped() - if(a_right) - a_right.dropped() - -/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess - . = ..() - if(.) - return - if(a_left) - a_left.attack_hand() - if(a_right) - a_right.attack_hand() - -/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool) - if(..()) - return TRUE - to_chat(user, "You disassemble [src]!") - if(a_left) - a_left.on_detach() - a_left = null - if(a_right) - a_right.on_detach() - a_right = null - qdel(src) - return TRUE - -/obj/item/assembly_holder/attack_self(mob/user) - src.add_fingerprint(user) - if(!a_left || !a_right) - to_chat(user, "Assembly part missing!") - return - if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code - switch(alert("Which side would you like to use?",,"Left","Right")) - if("Left") - a_left.attack_self(user) - if("Right") - a_right.attack_self(user) - return - else - a_left.attack_self(user) - a_right.attack_self(user) - - -/obj/item/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1) - if(!D) - return FALSE - if((normal) && (a_right) && (a_left)) - if(a_right != D) - a_right.pulsed(FALSE) - if(a_left != D) - a_left.pulsed(FALSE) - if(master) - master.receive_signal() - return TRUE +/obj/item/assembly_holder + name = "Assembly" + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = "holder" + 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/ComponentInitialize() + . = ..() + AddComponent( + /datum/component/simple_rotation, + ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS) + +/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) + // yogs start - signaller colors + if(istext(O)) + add_overlay("[O]_l") + else + var/mutable_appearance/A = new(O) + A.icon_state = "[A.icon_state]_l" + add_overlay(A) + // yogs end + + if(a_right) + if(a_right.is_position_sensitive) + add_overlay("[a_right.icon_state]_right") + for(var/O in a_right.attached_overlays) + // yogs start - signaller colors + if(istext(O)) + add_overlay("[O]_r") + else + var/mutable_appearance/A = new(O) + A.icon_state = "[A.icon_state]_r" + add_overlay(A) + // yogs end + 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) + // yogs start - signaller colors + if(istext(O)) + right.add_overlay("[O]_l") + else + var/mutable_appearance/A = new(O) + A.icon_state = "[A.icon_state]_l" + right.add_overlay(A) + // yogs end + add_overlay(right) + + if(master) + master.update_icon() + +/obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj) + if(a_left) + a_left.Crossed(AM) + if(a_right) + a_right.Crossed(AM) + +/obj/item/assembly_holder/on_found(mob/finder) + if(a_left) + a_left.on_found(finder) + if(a_right) + a_right.on_found(finder) + +/obj/item/assembly_holder/setDir() + . = ..() + if(a_left) + a_left.holder_movement() + if(a_right) + a_right.holder_movement() + +/obj/item/assembly_holder/dropped(mob/user) + . = ..() + if(a_left) + a_left.dropped() + if(a_right) + a_right.dropped() + +/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess + . = ..() + if(.) + return + if(a_left) + a_left.attack_hand() + if(a_right) + a_right.attack_hand() + +/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool) + if(..()) + return TRUE + to_chat(user, "You disassemble [src]!") + if(a_left) + a_left.on_detach() + a_left = null + if(a_right) + a_right.on_detach() + a_right = null + qdel(src) + return TRUE + +/obj/item/assembly_holder/attack_self(mob/user) + src.add_fingerprint(user) + if(!a_left || !a_right) + to_chat(user, "Assembly part missing!") + return + if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code + switch(alert("Which side would you like to use?",,"Left","Right")) + if("Left") + a_left.attack_self(user) + if("Right") + a_right.attack_self(user) + return + else + a_left.attack_self(user) + a_right.attack_self(user) + + +/obj/item/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1) + if(!D) + return FALSE + if((normal) && (a_right) && (a_left)) + if(a_right != D) + a_right.pulsed(FALSE) + if(a_left != D) + a_left.pulsed(FALSE) + if(master) + master.receive_signal() + return TRUE diff --git a/code/modules/assembly/igniter.dm b/code/modules/assembly/igniter.dm index ec4848d47edb..e303e2341297 100644 --- a/code/modules/assembly/igniter.dm +++ b/code/modules/assembly/igniter.dm @@ -1,43 +1,43 @@ -/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 - 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/Initialize() - . = ..() - sparks = new - sparks.set_up(2, 0, src) - sparks.attach(src) - -/obj/item/assembly/igniter/Destroy() - if(sparks) - qdel(sparks) - sparks = null - . = ..() - -/obj/item/assembly/igniter/activate() - if(!..()) - return FALSE//Cooldown check - var/turf/location = get_turf(loc) - if(location) - location.hotspot_expose(1000,1000) - 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 + 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/Initialize() + . = ..() + sparks = new + sparks.set_up(2, 0, src) + sparks.attach(src) + +/obj/item/assembly/igniter/Destroy() + if(sparks) + qdel(sparks) + sparks = null + . = ..() + +/obj/item/assembly/igniter/activate() + if(!..()) + return FALSE//Cooldown check + var/turf/location = get_turf(loc) + if(location) + location.hotspot_expose(1000,1000) + 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 6314468139b1..e980068a6109 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -1,240 +1,240 @@ -/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, force) - . = ..() - olddir = dir - -/obj/item/assembly/infra/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(!olddir) - return - setDir(olddir) - olddir = null - -/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location) - refreshBeam() - switchListener(location) - if(!secured || !on || next_activate > world.time) - return FALSE - pulse(FALSE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - next_activate = world.time + 30 - -/obj/item/assembly/infra/proc/switchListener(turf/newloc) - if(listeningTo == newloc) - return - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED) - RegisterSignal(newloc, COMSIG_ATOM_EXITED, .proc/check_exit) - listeningTo = newloc - -/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender) - if(QDELETED(src)) - return - if(offender == src || istype(offender,/obj/effect/beam/i_beam)) - return - if (offender && isitem(offender)) - var/obj/item/I = offender - if (I.item_flags & ABSTRACT) - return - return refreshBeam() - -/obj/item/assembly/infra/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.canUseTopic(src, BE_CLOSE)) - 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, force) + . = ..() + olddir = dir + +/obj/item/assembly/infra/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(!olddir) + return + setDir(olddir) + olddir = null + +/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location) + refreshBeam() + switchListener(location) + if(!secured || !on || next_activate > world.time) + return FALSE + pulse(FALSE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + next_activate = world.time + 30 + +/obj/item/assembly/infra/proc/switchListener(turf/newloc) + if(listeningTo == newloc) + return + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED) + RegisterSignal(newloc, COMSIG_ATOM_EXITED, .proc/check_exit) + listeningTo = newloc + +/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender) + if(QDELETED(src)) + return + if(offender == src || istype(offender,/obj/effect/beam/i_beam)) + return + if (offender && isitem(offender)) + var/obj/item/I = offender + if (I.item_flags & ABSTRACT) + return + return refreshBeam() + +/obj/item/assembly/infra/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.canUseTopic(src, BE_CLOSE)) + 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 09ed3e94b8bb..5336692cba9c 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.Paralyze(60) - if(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) - if(!H.gloves) - affecting = H.get_bodypart(type) - H.Stun(60) - if(affecting) - if(affecting.receive_damage(1, 0)) - H.update_damage_overlays() - else if(ismouse(target)) - var/mob/living/simple_animal/mouse/M = target - visible_message("SPLAT!") - M.splat() - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - armed = FALSE - update_icon() - pulse(FALSE) - - -/obj/item/assembly/mousetrap/attack_self(mob/living/carbon/human/user) - if(!armed) - to_chat(user, "You arm [src].") - else - if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) - var/which_hand = BODY_ZONE_PRECISE_L_HAND - if(!(user.active_hand_index % 2)) - which_hand = BODY_ZONE_PRECISE_R_HAND - triggered(user, which_hand) - user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - return - to_chat(user, "You disarm [src].") - armed = !armed - update_icon() - playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) - - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/assembly/mousetrap/attack_hand(mob/living/carbon/human/user) - if(armed) - if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) - var/which_hand = BODY_ZONE_PRECISE_L_HAND - if(!(user.active_hand_index % 2)) - which_hand = BODY_ZONE_PRECISE_R_HAND - triggered(user, which_hand) - user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - return - return ..() - - -/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj) - if(armed) - if(ismob(AM)) - var/mob/MM = AM - if(!(MM.movement_type & FLYING)) - if(ishuman(AM)) - var/mob/living/carbon/H = AM - if(H.m_intent == MOVE_INTENT_RUN) - triggered(H) - H.visible_message("[H] accidentally steps on [src].", \ - "You accidentally step on [src]") - else if(ismouse(MM)) - triggered(MM) - else if(AM.density) // For mousetrap grenades, set off by anything heavy - triggered(AM) - ..() - - -/obj/item/assembly/mousetrap/on_found(mob/finder) - if(armed) - if(finder) - finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - triggered(finder, (finder.active_hand_index % 2 == 0) ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND) - return TRUE //end the search! - else - visible_message("[src] snaps shut!") - triggered(loc) - return FALSE - return FALSE - - -/obj/item/assembly/mousetrap/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(!armed) - return ..() - visible_message("[src] is triggered by [AM].") - triggered(null) - - -/obj/item/assembly/mousetrap/armed - icon_state = "mousetraparmed" - armed = TRUE +/obj/item/assembly/mousetrap + name = "mousetrap" + desc = "A handy little spring-loaded trap for catching pesty rodents." + icon_state = "mousetrap" + 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.Paralyze(60) + if(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) + if(!H.gloves) + affecting = H.get_bodypart(type) + H.Stun(60) + if(affecting) + if(affecting.receive_damage(1, 0)) + H.update_damage_overlays() + else if(ismouse(target)) + var/mob/living/simple_animal/mouse/M = target + visible_message("SPLAT!") + M.splat() + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + armed = FALSE + update_icon() + pulse(FALSE) + + +/obj/item/assembly/mousetrap/attack_self(mob/living/carbon/human/user) + if(!armed) + to_chat(user, "You arm [src].") + else + if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) + var/which_hand = BODY_ZONE_PRECISE_L_HAND + if(!(user.active_hand_index % 2)) + which_hand = BODY_ZONE_PRECISE_R_HAND + triggered(user, which_hand) + user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + return + to_chat(user, "You disarm [src].") + armed = !armed + update_icon() + playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) + + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/assembly/mousetrap/attack_hand(mob/living/carbon/human/user) + if(armed) + if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) + var/which_hand = BODY_ZONE_PRECISE_L_HAND + if(!(user.active_hand_index % 2)) + which_hand = BODY_ZONE_PRECISE_R_HAND + triggered(user, which_hand) + user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + return + return ..() + + +/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj) + if(armed) + if(ismob(AM)) + var/mob/MM = AM + if(!(MM.movement_type & FLYING)) + if(ishuman(AM)) + var/mob/living/carbon/H = AM + if(H.m_intent == MOVE_INTENT_RUN) + triggered(H) + H.visible_message("[H] accidentally steps on [src].", \ + "You accidentally step on [src]") + else if(ismouse(MM)) + triggered(MM) + else if(AM.density) // For mousetrap grenades, set off by anything heavy + triggered(AM) + ..() + + +/obj/item/assembly/mousetrap/on_found(mob/finder) + if(armed) + if(finder) + finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + triggered(finder, (finder.active_hand_index % 2 == 0) ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND) + return TRUE //end the search! + else + visible_message("[src] snaps shut!") + triggered(loc) + return FALSE + return FALSE + + +/obj/item/assembly/mousetrap/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(!armed) + return ..() + visible_message("[src] is triggered by [AM].") + triggered(null) + + +/obj/item/assembly/mousetrap/armed + icon_state = "mousetraparmed" + armed = TRUE diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm index 6b0017e9fc3f..7937bb21a9e6 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -1,159 +1,159 @@ -/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.canUseTopic(src, BE_CLOSE)) - 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.canUseTopic(src, BE_CLOSE)) + 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 8d9ba9880c10..e18909c9a244 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 0b1e03c744b3..f53c7ed487ba 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -1,240 +1,240 @@ -/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_NONLETHAL - -/obj/item/assembly/signaler/proc/manual_suicide(mob/living/carbon/user) - user.visible_message("[user]'s [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) - user.set_suicide(TRUE) - user.suicide_log() - -/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)] -+ -+
                - -Code: -- -- -[src.code] -+ -+
                -Color: [src.label_color]
                -[t1] -
                "} - user << browse(dat, "window=radio") // yogs - signaller colors - 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["freq"]) - var/new_frequency = (frequency + text2num(href_list["freq"])) - if(new_frequency < MIN_FREE_FREQ || new_frequency > MAX_FREE_FREQ) - new_frequency = sanitize_frequency(new_frequency) - set_frequency(new_frequency) - - if(href_list["code"]) - src.code += text2num(href_list["code"]) - src.code = round(src.code) - src.code = min(100, src.code) - src.code = max(1, src.code) - - if(href_list["send"]) - spawn( 0 ) - signal() - - // yogs start - signaller colors - if(href_list["color"]) - var/idx = label_colors.Find(label_color) - if(idx == label_colors.len || idx == 0) - idx = 1 - else - idx++ - label_color = label_colors[idx] - update_icon() - // yogs end - - 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) - // yogs start - signaller colors - label_color = signaler2.label_color - update_icon() - // yogs end - 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 - if(suicider) - manual_suicide(suicider) - for(var/obj/effect/anomaly/A in get_turf(src)) - A.anomalyNeutralize() - return TRUE - -/obj/item/assembly/signaler/anomaly/manual_suicide(mob/living/carbon/user) - user.visible_message("[user]'s [src] is reacting to the radio signal, warping [user.p_their()] body!") - user.set_suicide(TRUE) - user.suicide_log() - user.gib() - -/obj/item/assembly/signaler/anomaly/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_ANALYZER) - to_chat(user, "Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(frequency)], code [code].") - ..() - -/obj/item/assembly/signaler/anomaly/attack_self() - return - -/obj/item/assembly/signaler/cyborg - -/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params) - return -/obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return +/obj/item/assembly/signaler + name = "remote signaling device" + desc = "Used to remotely activate devices. Allows for syncing when using a secure signaler on another." + icon_state = "signaller" + 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_NONLETHAL + +/obj/item/assembly/signaler/proc/manual_suicide(mob/living/carbon/user) + user.visible_message("[user]'s [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) + user.set_suicide(TRUE) + user.suicide_log() + +/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)] ++ ++
                + +Code: +- +- +[src.code] ++ ++
                +Color: [src.label_color]
                +[t1] +
                "} + user << browse(dat, "window=radio") // yogs - signaller colors + 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["freq"]) + var/new_frequency = (frequency + text2num(href_list["freq"])) + if(new_frequency < MIN_FREE_FREQ || new_frequency > MAX_FREE_FREQ) + new_frequency = sanitize_frequency(new_frequency) + set_frequency(new_frequency) + + if(href_list["code"]) + src.code += text2num(href_list["code"]) + src.code = round(src.code) + src.code = min(100, src.code) + src.code = max(1, src.code) + + if(href_list["send"]) + spawn( 0 ) + signal() + + // yogs start - signaller colors + if(href_list["color"]) + var/idx = label_colors.Find(label_color) + if(idx == label_colors.len || idx == 0) + idx = 1 + else + idx++ + label_color = label_colors[idx] + update_icon() + // yogs end + + 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) + // yogs start - signaller colors + label_color = signaler2.label_color + update_icon() + // yogs end + 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 + if(suicider) + manual_suicide(suicider) + for(var/obj/effect/anomaly/A in get_turf(src)) + A.anomalyNeutralize() + return TRUE + +/obj/item/assembly/signaler/anomaly/manual_suicide(mob/living/carbon/user) + user.visible_message("[user]'s [src] is reacting to the radio signal, warping [user.p_their()] body!") + user.set_suicide(TRUE) + user.suicide_log() + user.gib() + +/obj/item/assembly/signaler/anomaly/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_ANALYZER) + to_chat(user, "Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(frequency)], code [code].") + ..() + +/obj/item/assembly/signaler/anomaly/attack_self() + return + +/obj/item/assembly/signaler/cyborg + +/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params) + return +/obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) + return diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm index 04a57764b391..275cb8a512f2 100644 --- a/code/modules/assembly/timer.dm +++ b/code/modules/assembly/timer.dm @@ -1,133 +1,133 @@ -/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)) - log_bomber(usr, "activated a", 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)) + log_bomber(usr, "activated a", 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 af09330e1376..da3943747af6 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -1,101 +1,101 @@ -#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/mode = 2 //yogs: starts on exclusive - 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) - . = ..() - if(speaker == src) - return - - if(listening && !radio_freq) - record_speech(speaker, raw_message, message_language) - else - if(check_activation(speaker, raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) - -/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) - switch(mode) - if(INCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", message_language) - if(EXCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", message_language) - if(RECOGNIZER_MODE) - recorded = speaker.GetVoice() - listening = FALSE - say("Your voice pattern is saved.", message_language) - if(VOICE_SENSOR_MODE) - if(length(raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) - -/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message) - . = FALSE - switch(mode) - if(INCLUSIVE_MODE) - if(findtext(raw_message, recorded)) - . = TRUE - if(EXCLUSIVE_MODE) - if(raw_message == recorded) - . = TRUE - if(RECOGNIZER_MODE) - if(speaker.GetVoice() == recorded) - . = TRUE - if(VOICE_SENSOR_MODE) - if(length(raw_message)) - . = TRUE - -/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I) - mode %= modes.len - mode++ - to_chat(user, "You set [src] into [modes[mode]] mode.") - listening = FALSE - recorded = "" - return TRUE - -/obj/item/assembly/voice/activate() - if(!secured || holder) - return FALSE - listening = !listening - say("[listening ? "Now" : "No longer"] recording input.") - return TRUE - -/obj/item/assembly/voice/attack_self(mob/user) - if(!user) - return FALSE - activate() - return TRUE - -/obj/item/assembly/voice/toggle_secure() - . = ..() - listening = FALSE - -#undef INCLUSIVE_MODE -#undef EXCLUSIVE_MODE -#undef RECOGNIZER_MODE -#undef VOICE_SENSOR_MODE +#define INCLUSIVE_MODE 1 +#define EXCLUSIVE_MODE 2 +#define RECOGNIZER_MODE 3 +#define VOICE_SENSOR_MODE 4 + +/obj/item/assembly/voice + name = "voice analyzer" + desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated." + icon_state = "voice" + 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/mode = 2 //yogs: starts on exclusive + 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) + . = ..() + if(speaker == src) + return + + if(listening && !radio_freq) + record_speech(speaker, raw_message, message_language) + else + if(check_activation(speaker, raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) + +/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) + switch(mode) + if(INCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", message_language) + if(EXCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", message_language) + if(RECOGNIZER_MODE) + recorded = speaker.GetVoice() + listening = FALSE + say("Your voice pattern is saved.", message_language) + if(VOICE_SENSOR_MODE) + if(length(raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) + +/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message) + . = FALSE + switch(mode) + if(INCLUSIVE_MODE) + if(findtext(raw_message, recorded)) + . = TRUE + if(EXCLUSIVE_MODE) + if(raw_message == recorded) + . = TRUE + if(RECOGNIZER_MODE) + if(speaker.GetVoice() == recorded) + . = TRUE + if(VOICE_SENSOR_MODE) + if(length(raw_message)) + . = TRUE + +/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I) + mode %= modes.len + mode++ + to_chat(user, "You set [src] into [modes[mode]] mode.") + listening = FALSE + recorded = "" + return TRUE + +/obj/item/assembly/voice/activate() + if(!secured || holder) + return FALSE + listening = !listening + say("[listening ? "Now" : "No longer"] recording input.") + return TRUE + +/obj/item/assembly/voice/attack_self(mob/user) + if(!user) + return FALSE + activate() + return TRUE + +/obj/item/assembly/voice/toggle_secure() + . = ..() + listening = FALSE + +#undef INCLUSIVE_MODE +#undef EXCLUSIVE_MODE +#undef RECOGNIZER_MODE +#undef VOICE_SENSOR_MODE diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 48e507d4599e..aae19bb7bb0d 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -1,459 +1,459 @@ - /* -What are the archived variables for? - Calculations are done using the archived variables with the results merged into the regular variables. - This prevents race conditions that arise based on the order of tile processing. -*/ -#define MINIMUM_HEAT_CAPACITY 0.0003 -#define MINIMUM_MOLE_COUNT 0.01 -#define 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 */ -GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm -GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) - -/proc/init_gaslist_cache() - . = list() - for(var/id in GLOB.meta_gas_info) - var/list/cached_gas = new(3) - - .[id] = cached_gas - - cached_gas[MOLES] = 0 - cached_gas[ARCHIVE] = 0 - cached_gas[GAS_META] = GLOB.meta_gas_info[id] - -/datum/gas_mixture - var/list/gases - var/temperature = 0 //kelvins - var/tmp/temperature_archived = 0 - var/volume = CELL_VOLUME //liters - var/last_share = 0 - var/list/reaction_results - var/list/analyzer_results //used for analyzer feedback - not initialized until its used - var/gc_share = FALSE // Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures - -/datum/gas_mixture/New(volume) - gases = new - if (!isnull(volume)) - src.volume = volume - reaction_results = new - -//listmos procs -//use the macros in performance intensive areas. for their definitions, refer to code/__DEFINES/atmospherics.dm - - //assert_gas(gas_id) - used to guarantee that the gas list for this id exists in gas_mixture.gases. - //Must be used before adding to a gas. May be used before reading from a gas. -/datum/gas_mixture/proc/assert_gas(gas_id) - ASSERT_GAS(gas_id, src) - - //assert_gases(args) - shorthand for calling ASSERT_GAS() once for each gas type. -/datum/gas_mixture/proc/assert_gases(...) - for(var/id in args) - ASSERT_GAS(id, src) - - //add_gas(gas_id) - similar to assert_gas(), but does not check for an existing - //gas list for this id. This can clobber existing gases. - //Used instead of assert_gas() when you know the gas does not exist. Faster than assert_gas(). -/datum/gas_mixture/proc/add_gas(gas_id) - ADD_GAS(gas_id, gases) - - //add_gases(args) - shorthand for calling add_gas() once for each gas_type. -/datum/gas_mixture/proc/add_gases(...) - var/cached_gases = gases - for(var/id in args) - ADD_GAS(id, cached_gases) - - //garbage_collect() - removes any gas list which is empty. - //If called with a list as an argument, only removes gas lists with IDs from that list. - //Must be used after subtracting from a gas. Must be used after assert_gas() - //if assert_gas() was called only to read from the gas. - //By removing empty gases, processing speed is increased. -/datum/gas_mixture/proc/garbage_collect(list/tocheck) - var/list/cached_gases = gases - for(var/id in (tocheck || cached_gases)) - if(QUANTIZE(cached_gases[id][MOLES]) <= 0 && QUANTIZE(cached_gases[id][ARCHIVE]) <= 0) - cached_gases -= id - - //PV = nRT - -/datum/gas_mixture/proc/heat_capacity(data = MOLES) //joules per kelvin - var/list/cached_gases = gases - . = 0 - for(var/id in cached_gases) - var/gas_data = cached_gases[id] - . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] - -/datum/gas_mixture/turf/heat_capacity(data = MOLES) // Same as above except vacuums return HEAT_CAPACITY_VACUUM - var/list/cached_gases = gases - . = 0 - for(var/id in cached_gases) - var/gas_data = cached_gases[id] - . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] - if(!.) - . += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space - -/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/archive() - //Update archived versions of variables - //Returns: 1 in all cases - -/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/archive() - var/list/cached_gases = gases - - temperature_archived = temperature - for(var/id in cached_gases) - cached_gases[id][ARCHIVE] = cached_gases[id][MOLES] - - return 1 - -/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) - ASSERT_GAS(giver_id, src) - cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] - - 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) - ADD_GAS(id, removed.gases) - removed_gases[id][MOLES] = QUANTIZE((cached_gases[id][MOLES] / sum) * amount) - cached_gases[id][MOLES] -= removed_gases[id][MOLES] - garbage_collect() - - return removed - -/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) - ADD_GAS(id, removed.gases) - removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio) - cached_gases[id][MOLES] -= removed_gases[id][MOLES] - - garbage_collect() - - return removed - -/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) - ADD_GAS(id, copy.gases) - copy_gases[id][MOLES] = cached_gases[id][MOLES] - - 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) - ASSERT_GAS(id,src) - cached_gases[id][MOLES] = sample_gases[id][MOLES] - - //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 - ADD_GAS(path, gases) - gases[path][MOLES] = 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 - - //GAS TRANSFER - for(var/id in sharer_gases - cached_gases) // create gases not in our cache - ADD_GAS(id, gases) - for(var/id in cached_gases) // transfer gases - ASSERT_GAS(id, sharer) - - var/gas = cached_gases[id] - var/sharergas = sharer_gases[id] - - var/delta = QUANTIZE(gas[ARCHIVE] - sharergas[ARCHIVE])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures - - if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - var/gas_heat_capacity = delta * gas[GAS_META][META_GAS_SPECIFIC_HEAT] - if(delta > 0) - heat_capacity_self_to_sharer += gas_heat_capacity - else - heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative. - - gas[MOLES] -= delta - sharergas[MOLES] += delta - moved_moles += delta - abs_moved_moles += abs(delta) - - last_share = abs_moved_moles - - //THERMAL ENERGY TRANSFER - if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer - var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self - - //transfer of thermal energy (via changed heat capacity) between self and sharer - if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY) - temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity - - if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) - sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity - //thermal energy of the system (self and sharer) is unchanged - - if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY) - if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.1) // <10% change in sharer heat capacity - temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT) - - if(length(cached_gases ^ sharer_gases)) //if all gases were present in both mixtures, we know that no gases are 0 - garbage_collect(cached_gases - sharer_gases) //any gases the sharer had, we are guaranteed to have. gases that it didn't have we are not. - sharer.garbage_collect(sharer_gases - cached_gases) //the reverse is equally true - if (initial(sharer.gc_share)) - sharer.garbage_collect() - if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE) - var/our_moles - TOTAL_MOLES(cached_gases,our_moles) - var/their_moles - TOTAL_MOLES(sharer_gases,their_moles) - return (temperature_archived*(our_moles + moved_moles) - sharer.temperature_archived*(their_moles - moved_moles)) * R_IDEAL_GAS_EQUATION / volume - -/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(ARCHIVE) - sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity(ARCHIVE) - - if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY)) - var/heat = conduction_coefficient*temperature_delta* \ - (self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity)) - - temperature = max(temperature - heat/self_heat_capacity, TCMB) - sharer_temperature = max(sharer_temperature + heat/sharer_heat_capacity, TCMB) - if(sharer) - sharer.temperature = sharer_temperature - 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] - gas_moles = gas_moles ? gas_moles[MOLES] : 0 - var/sample_moles = sample_gases[id] - sample_moles = sample_moles ? sample_moles[MOLES] : 0 - var/delta = abs(gas_moles - sample_moles) - if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \ - delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE) - return id - - var/our_moles - TOTAL_MOLES(cached_gases, our_moles) - if(our_moles > MINIMUM_MOLES_DELTA_TO_MOVE) - var/temp = temperature - var/sample_temp = sample.temperature - - var/temperature_delta = abs(temp - sample_temp) - if(temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) - return "temp" - - return "" - -/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] || cached_gases[id][MOLES] < min_reqs[id]) - continue reaction_loop - - //at this point, all requirements for the reaction are satisfied. we can now react() - - . |= reaction.react(src, holder) - if (. & STOP_REACTIONS) - break - if(.) - garbage_collect() - -//Takes the amount of the gas you want to PP as an argument -//So I don't have to do some hacky switches/defines/magic strings -//eg: -//Tox_PP = get_partial_pressure(gas_mixture.toxins) -//O2_PP = get_partial_pressure(gas_mixture.oxygen) - -/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure) - return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME -//inverse -/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure) - return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature) - -//Mathematical proofs: -/* -get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp -get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles() - -10/20*5 = 2.5 -10 = 2.5/5*20 -*/ + /* +What are the archived variables for? + Calculations are done using the archived variables with the results merged into the regular variables. + This prevents race conditions that arise based on the order of tile processing. +*/ +#define MINIMUM_HEAT_CAPACITY 0.0003 +#define MINIMUM_MOLE_COUNT 0.01 +#define 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 */ +GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm +GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) + +/proc/init_gaslist_cache() + . = list() + for(var/id in GLOB.meta_gas_info) + var/list/cached_gas = new(3) + + .[id] = cached_gas + + cached_gas[MOLES] = 0 + cached_gas[ARCHIVE] = 0 + cached_gas[GAS_META] = GLOB.meta_gas_info[id] + +/datum/gas_mixture + var/list/gases + var/temperature = 0 //kelvins + var/tmp/temperature_archived = 0 + var/volume = CELL_VOLUME //liters + var/last_share = 0 + var/list/reaction_results + var/list/analyzer_results //used for analyzer feedback - not initialized until its used + var/gc_share = FALSE // Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures + +/datum/gas_mixture/New(volume) + gases = new + if (!isnull(volume)) + src.volume = volume + reaction_results = new + +//listmos procs +//use the macros in performance intensive areas. for their definitions, refer to code/__DEFINES/atmospherics.dm + + //assert_gas(gas_id) - used to guarantee that the gas list for this id exists in gas_mixture.gases. + //Must be used before adding to a gas. May be used before reading from a gas. +/datum/gas_mixture/proc/assert_gas(gas_id) + ASSERT_GAS(gas_id, src) + + //assert_gases(args) - shorthand for calling ASSERT_GAS() once for each gas type. +/datum/gas_mixture/proc/assert_gases(...) + for(var/id in args) + ASSERT_GAS(id, src) + + //add_gas(gas_id) - similar to assert_gas(), but does not check for an existing + //gas list for this id. This can clobber existing gases. + //Used instead of assert_gas() when you know the gas does not exist. Faster than assert_gas(). +/datum/gas_mixture/proc/add_gas(gas_id) + ADD_GAS(gas_id, gases) + + //add_gases(args) - shorthand for calling add_gas() once for each gas_type. +/datum/gas_mixture/proc/add_gases(...) + var/cached_gases = gases + for(var/id in args) + ADD_GAS(id, cached_gases) + + //garbage_collect() - removes any gas list which is empty. + //If called with a list as an argument, only removes gas lists with IDs from that list. + //Must be used after subtracting from a gas. Must be used after assert_gas() + //if assert_gas() was called only to read from the gas. + //By removing empty gases, processing speed is increased. +/datum/gas_mixture/proc/garbage_collect(list/tocheck) + var/list/cached_gases = gases + for(var/id in (tocheck || cached_gases)) + if(QUANTIZE(cached_gases[id][MOLES]) <= 0 && QUANTIZE(cached_gases[id][ARCHIVE]) <= 0) + cached_gases -= id + + //PV = nRT + +/datum/gas_mixture/proc/heat_capacity(data = MOLES) //joules per kelvin + var/list/cached_gases = gases + . = 0 + for(var/id in cached_gases) + var/gas_data = cached_gases[id] + . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] + +/datum/gas_mixture/turf/heat_capacity(data = MOLES) // Same as above except vacuums return HEAT_CAPACITY_VACUUM + var/list/cached_gases = gases + . = 0 + for(var/id in cached_gases) + var/gas_data = cached_gases[id] + . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] + if(!.) + . += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space + +/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/archive() + //Update archived versions of variables + //Returns: 1 in all cases + +/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/archive() + var/list/cached_gases = gases + + temperature_archived = temperature + for(var/id in cached_gases) + cached_gases[id][ARCHIVE] = cached_gases[id][MOLES] + + return 1 + +/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) + ASSERT_GAS(giver_id, src) + cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] + + 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) + ADD_GAS(id, removed.gases) + removed_gases[id][MOLES] = QUANTIZE((cached_gases[id][MOLES] / sum) * amount) + cached_gases[id][MOLES] -= removed_gases[id][MOLES] + garbage_collect() + + return removed + +/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) + ADD_GAS(id, removed.gases) + removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio) + cached_gases[id][MOLES] -= removed_gases[id][MOLES] + + garbage_collect() + + return removed + +/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) + ADD_GAS(id, copy.gases) + copy_gases[id][MOLES] = cached_gases[id][MOLES] + + 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) + ASSERT_GAS(id,src) + cached_gases[id][MOLES] = sample_gases[id][MOLES] + + //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 + ADD_GAS(path, gases) + gases[path][MOLES] = 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 + + //GAS TRANSFER + for(var/id in sharer_gases - cached_gases) // create gases not in our cache + ADD_GAS(id, gases) + for(var/id in cached_gases) // transfer gases + ASSERT_GAS(id, sharer) + + var/gas = cached_gases[id] + var/sharergas = sharer_gases[id] + + var/delta = QUANTIZE(gas[ARCHIVE] - sharergas[ARCHIVE])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures + + if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + var/gas_heat_capacity = delta * gas[GAS_META][META_GAS_SPECIFIC_HEAT] + if(delta > 0) + heat_capacity_self_to_sharer += gas_heat_capacity + else + heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative. + + gas[MOLES] -= delta + sharergas[MOLES] += delta + moved_moles += delta + abs_moved_moles += abs(delta) + + last_share = abs_moved_moles + + //THERMAL ENERGY TRANSFER + if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer + var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self + + //transfer of thermal energy (via changed heat capacity) between self and sharer + if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY) + temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity + + if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) + sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity + //thermal energy of the system (self and sharer) is unchanged + + if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY) + if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.1) // <10% change in sharer heat capacity + temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT) + + if(length(cached_gases ^ sharer_gases)) //if all gases were present in both mixtures, we know that no gases are 0 + garbage_collect(cached_gases - sharer_gases) //any gases the sharer had, we are guaranteed to have. gases that it didn't have we are not. + sharer.garbage_collect(sharer_gases - cached_gases) //the reverse is equally true + if (initial(sharer.gc_share)) + sharer.garbage_collect() + if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE) + var/our_moles + TOTAL_MOLES(cached_gases,our_moles) + var/their_moles + TOTAL_MOLES(sharer_gases,their_moles) + return (temperature_archived*(our_moles + moved_moles) - sharer.temperature_archived*(their_moles - moved_moles)) * R_IDEAL_GAS_EQUATION / volume + +/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(ARCHIVE) + sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity(ARCHIVE) + + if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY)) + var/heat = conduction_coefficient*temperature_delta* \ + (self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity)) + + temperature = max(temperature - heat/self_heat_capacity, TCMB) + sharer_temperature = max(sharer_temperature + heat/sharer_heat_capacity, TCMB) + if(sharer) + sharer.temperature = sharer_temperature + 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] + gas_moles = gas_moles ? gas_moles[MOLES] : 0 + var/sample_moles = sample_gases[id] + sample_moles = sample_moles ? sample_moles[MOLES] : 0 + var/delta = abs(gas_moles - sample_moles) + if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \ + delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE) + return id + + var/our_moles + TOTAL_MOLES(cached_gases, our_moles) + if(our_moles > MINIMUM_MOLES_DELTA_TO_MOVE) + var/temp = temperature + var/sample_temp = sample.temperature + + var/temperature_delta = abs(temp - sample_temp) + if(temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) + return "temp" + + return "" + +/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] || cached_gases[id][MOLES] < min_reqs[id]) + continue reaction_loop + + //at this point, all requirements for the reaction are satisfied. we can now react() + + . |= reaction.react(src, holder) + if (. & STOP_REACTIONS) + break + if(.) + garbage_collect() + +//Takes the amount of the gas you want to PP as an argument +//So I don't have to do some hacky switches/defines/magic strings +//eg: +//Tox_PP = get_partial_pressure(gas_mixture.toxins) +//O2_PP = get_partial_pressure(gas_mixture.oxygen) + +/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure) + return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME +//inverse +/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure) + return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature) + +//Mathematical proofs: +/* +get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp +get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles() + +10/20*5 = 2.5 +10 = 2.5/5*20 +*/ diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 674fbb648d92..6ce7abcaccdf 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -1,339 +1,339 @@ -// Quick overview: -// -// Pipes combine to form pipelines -// Pipelines and other atmospheric objects combine to form pipe_networks -// Note: A single pipe_network represents a completely open space -// -// Pipes -> Pipelines -// Pipelines + Other Objects -> Pipe network - -#define PIPE_VISIBLE_LEVEL 2 -#define PIPE_HIDDEN_LEVEL 1 - -/obj/machinery/atmospherics - anchored = TRUE - move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues. - idle_power_usage = 0 - active_power_usage = 0 - power_channel = ENVIRON - layer = GAS_PIPE_HIDDEN_LAYER //under wires - resistance_flags = FIRE_PROOF - max_integrity = 200 - obj_flags = CAN_BE_HIT | ON_BLUEPRINTS - var/can_unwrench = 0 - var/initialize_directions = 0 - var/pipe_color - var/piping_layer = PIPING_LAYER_DEFAULT - var/pipe_flags = NONE - - var/global/list/iconsetids = list() - var/global/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() - switch(dir) - if(SOUTH) - setDir(NORTH) - if(WEST) - setDir(EAST) - -//this is called just after the air controller sets up turfs -/obj/machinery/atmospherics/proc/atmosinit(list/node_connects) - if(!node_connects) //for pipes where order of nodes doesn't matter - node_connects = getNodeConnects() - - for(var/i in 1 to device_type) - for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) - if(can_be_node(target, i)) - nodes[i] = target - break - update_icon() - -/obj/machinery/atmospherics/proc/setPipingLayer(new_layer) - piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer - update_icon() - -/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration) - return connection_check(target, piping_layer) - -//Find a connecting /obj/machinery/atmospherics in specified direction -/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) - for(var/obj/machinery/atmospherics/target in get_step(src, direction)) - if(target.initialize_directions & get_dir(target,src)) - if(connection_check(target, prompted_layer)) - return target - -/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) - if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) - return TRUE - return FALSE - -/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) - if(isnull(given_layer)) - given_layer = piping_layer - if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) - return TRUE - return FALSE - -/obj/machinery/atmospherics/proc/pipeline_expansion() - return nodes - -/obj/machinery/atmospherics/proc/SetInitDirections() - return - -/obj/machinery/atmospherics/proc/GetInitDirections() - return initialize_directions - -/obj/machinery/atmospherics/proc/returnPipenet() - return - -/obj/machinery/atmospherics/proc/returnPipenetAir() - return - -/obj/machinery/atmospherics/proc/setPipenet() - return - -/obj/machinery/atmospherics/proc/replacePipenet() - return - -/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) - if(istype(reference, /obj/machinery/atmospherics/pipe)) - var/obj/machinery/atmospherics/pipe/P = reference - P.destroy_network() - nodes[nodes.Find(reference)] = null - update_icon() - -/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pipe)) //lets you autodrop - var/obj/item/pipe/pipe = W - if(user.dropItemToGround(pipe)) - pipe.setPipingLayer(piping_layer) //align it with us - return TRUE - else - return ..() - -/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) - if(!can_unwrench(user)) - return ..() - - var/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() - - user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") - - // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir - // otherwise, edge_target_turf uses a random cardinal direction - // range is pressures / 250 - // speed is pressures / 1250 - user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250) - -/obj/machinery/atmospherics/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(can_unwrench) - var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) - stored.setPipingLayer(piping_layer) - if(!disassembled) - stored.obj_integrity = stored.max_integrity * 0.5 - transfer_fingerprints_to(stored) - ..() - -/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=2) - - //Add identifiers for the iconset - if(iconsetids[iconset] == null) - iconsetids[iconset] = num2text(iconsetids.len + 1) - - //Generate a unique identifier for this image combination - var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]" - - if((!(. = pipeimages[identifier]))) - var/image/pipe_overlay - pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) - pipe_overlay.color = col - PIPING_LAYER_SHIFT(pipe_overlay, piping_layer) - -/obj/machinery/atmospherics/on_construction(obj_color, set_layer) - if(can_unwrench) - add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) - pipe_color = obj_color - setPipingLayer(set_layer) - 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.") - - //PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER? - -/obj/machinery/atmospherics/AltClick(mob/living/L) - if(istype(L) && is_type_in_list(src, GLOB.ventcrawl_machinery)) - L.handle_ventcrawl(src) - return - ..() - - -/obj/machinery/atmospherics/proc/can_crawl_through() - return TRUE - -/obj/machinery/atmospherics/proc/returnPipenets() - return list() - -/obj/machinery/atmospherics/update_remote_sight(mob/user) - user.sight |= (SEE_TURFS|BLIND) - -//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. -/obj/machinery/atmospherics/proc/can_see_pipes() - return TRUE - -/obj/machinery/atmospherics/proc/update_layer() - layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE +// Quick overview: +// +// Pipes combine to form pipelines +// Pipelines and other atmospheric objects combine to form pipe_networks +// Note: A single pipe_network represents a completely open space +// +// Pipes -> Pipelines +// Pipelines + Other Objects -> Pipe network + +#define PIPE_VISIBLE_LEVEL 2 +#define PIPE_HIDDEN_LEVEL 1 + +/obj/machinery/atmospherics + anchored = TRUE + move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues. + idle_power_usage = 0 + active_power_usage = 0 + power_channel = ENVIRON + layer = GAS_PIPE_HIDDEN_LAYER //under wires + resistance_flags = FIRE_PROOF + max_integrity = 200 + obj_flags = CAN_BE_HIT | ON_BLUEPRINTS + var/can_unwrench = 0 + var/initialize_directions = 0 + var/pipe_color + var/piping_layer = PIPING_LAYER_DEFAULT + var/pipe_flags = NONE + + var/global/list/iconsetids = list() + var/global/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() + switch(dir) + if(SOUTH) + setDir(NORTH) + if(WEST) + setDir(EAST) + +//this is called just after the air controller sets up turfs +/obj/machinery/atmospherics/proc/atmosinit(list/node_connects) + if(!node_connects) //for pipes where order of nodes doesn't matter + node_connects = getNodeConnects() + + for(var/i in 1 to device_type) + for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) + if(can_be_node(target, i)) + nodes[i] = target + break + update_icon() + +/obj/machinery/atmospherics/proc/setPipingLayer(new_layer) + piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer + update_icon() + +/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration) + return connection_check(target, piping_layer) + +//Find a connecting /obj/machinery/atmospherics in specified direction +/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) + for(var/obj/machinery/atmospherics/target in get_step(src, direction)) + if(target.initialize_directions & get_dir(target,src)) + if(connection_check(target, prompted_layer)) + return target + +/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) + if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) + return TRUE + return FALSE + +/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) + if(isnull(given_layer)) + given_layer = piping_layer + if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) + return TRUE + return FALSE + +/obj/machinery/atmospherics/proc/pipeline_expansion() + return nodes + +/obj/machinery/atmospherics/proc/SetInitDirections() + return + +/obj/machinery/atmospherics/proc/GetInitDirections() + return initialize_directions + +/obj/machinery/atmospherics/proc/returnPipenet() + return + +/obj/machinery/atmospherics/proc/returnPipenetAir() + return + +/obj/machinery/atmospherics/proc/setPipenet() + return + +/obj/machinery/atmospherics/proc/replacePipenet() + return + +/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) + if(istype(reference, /obj/machinery/atmospherics/pipe)) + var/obj/machinery/atmospherics/pipe/P = reference + P.destroy_network() + nodes[nodes.Find(reference)] = null + update_icon() + +/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pipe)) //lets you autodrop + var/obj/item/pipe/pipe = W + if(user.dropItemToGround(pipe)) + pipe.setPipingLayer(piping_layer) //align it with us + return TRUE + else + return ..() + +/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) + if(!can_unwrench(user)) + return ..() + + var/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() + + user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") + + // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir + // otherwise, edge_target_turf uses a random cardinal direction + // range is pressures / 250 + // speed is pressures / 1250 + user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250) + +/obj/machinery/atmospherics/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(can_unwrench) + var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) + stored.setPipingLayer(piping_layer) + if(!disassembled) + stored.obj_integrity = stored.max_integrity * 0.5 + transfer_fingerprints_to(stored) + ..() + +/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=2) + + //Add identifiers for the iconset + if(iconsetids[iconset] == null) + iconsetids[iconset] = num2text(iconsetids.len + 1) + + //Generate a unique identifier for this image combination + var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]" + + if((!(. = pipeimages[identifier]))) + var/image/pipe_overlay + pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) + pipe_overlay.color = col + PIPING_LAYER_SHIFT(pipe_overlay, piping_layer) + +/obj/machinery/atmospherics/on_construction(obj_color, set_layer) + if(can_unwrench) + add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) + pipe_color = obj_color + setPipingLayer(set_layer) + 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.") + + //PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER? + +/obj/machinery/atmospherics/AltClick(mob/living/L) + if(istype(L) && is_type_in_list(src, GLOB.ventcrawl_machinery)) + L.handle_ventcrawl(src) + return + ..() + + +/obj/machinery/atmospherics/proc/can_crawl_through() + return TRUE + +/obj/machinery/atmospherics/proc/returnPipenets() + return list() + +/obj/machinery/atmospherics/update_remote_sight(mob/user) + user.sight |= (SEE_TURFS|BLIND) + +//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. +/obj/machinery/atmospherics/proc/can_see_pipes() + return TRUE + +/obj/machinery/atmospherics/proc/update_layer() + layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE diff --git a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm index 6167d7ea015a..45ccaf1e867a 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm @@ -1,21 +1,21 @@ -/obj/machinery/atmospherics/components/binary - icon = 'icons/obj/atmospherics/components/binary_devices.dmi' - dir = SOUTH - initialize_directions = SOUTH|NORTH - use_power = IDLE_POWER_USE - device_type = BINARY - layer = GAS_PUMP_LAYER - -/obj/machinery/atmospherics/components/binary/SetInitDirections() - switch(dir) - if(NORTH, SOUTH) - initialize_directions = NORTH|SOUTH - if(EAST, WEST) - initialize_directions = EAST|WEST - -/obj/machinery/atmospherics/components/binary/hide(intact) - update_icon() - ..() - -/obj/machinery/atmospherics/components/binary/getNodeConnects() - return list(turn(dir, 180), dir) +/obj/machinery/atmospherics/components/binary + icon = 'icons/obj/atmospherics/components/binary_devices.dmi' + dir = SOUTH + initialize_directions = SOUTH|NORTH + use_power = IDLE_POWER_USE + device_type = BINARY + layer = GAS_PUMP_LAYER + +/obj/machinery/atmospherics/components/binary/SetInitDirections() + switch(dir) + if(NORTH, SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST, WEST) + initialize_directions = EAST|WEST + +/obj/machinery/atmospherics/components/binary/hide(intact) + update_icon() + ..() + +/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 6bafba9abc39..73cb8eeecb11 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 8ad990b60953..9238b8602bda 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,244 +1,244 @@ -//Acts like a normal vent, but has an input AND output. - -#define EXT_BOUND 1 -#define INPUT_MIN 2 -#define OUTPUT_MAX 4 - -/obj/machinery/atmospherics/components/binary/dp_vent_pump - icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons! - icon_state = "dpvent_map-2" - - //node2 is output port - //node1 is input port - - name = "dual-port air vent" - desc = "Has a valve and pump attached to it. There are two ports." - - 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/Destroy() - SSradio.remove_object(src, frequency) - return ..() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() - cut_overlays() - if(showpipe) - var/image/cap = getpipeimage(icon, "dpvent_cap", dir, piping_layer = piping_layer) - add_overlay(cap) - - if(!on || !is_operational()) - icon_state = "vent_off" - else - icon_state = pump_direction ? "vent_out" : "vent_in" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos() - ..() - - if(!on) - return - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - - var/datum/gas_mixture/environment = loc.return_air() - var/environment_pressure = environment.return_pressure() - - if(pump_direction) //input -> external - var/pressure_delta = 10000 - - if(pressure_checks&EXT_BOUND) - pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure)) - if(pressure_checks&INPUT_MIN) - pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min)) - - if(pressure_delta > 0) - if(air1.temperature > 0) - var/transfer_moles = pressure_delta*environment.volume/(air1.temperature * R_IDEAL_GAS_EQUATION) - - var/datum/gas_mixture/removed = air1.remove(transfer_moles) - //Removed can be null if there is no atmosphere in air1 - if(!removed) - return - - loc.assume_air(removed) - air_update_turf() - - var/datum/pipeline/parent1 = parents[1] - parent1.update = 1 - - else //external -> output - var/pressure_delta = 10000 - - if(pressure_checks&EXT_BOUND) - pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound)) - if(pressure_checks&INPUT_MIN) - pressure_delta = min(pressure_delta, (output_pressure_max - air2.return_pressure())) - - if(pressure_delta > 0) - if(environment.temperature > 0) - var/transfer_moles = pressure_delta*air2.volume/(environment.temperature * R_IDEAL_GAS_EQUATION) - - var/datum/gas_mixture/removed = loc.remove_air(transfer_moles) - //removed can be null if there is no air in the location - if(!removed) - return - - air2.merge(removed) - air_update_turf() - - var/datum/pipeline/parent2 = parents[2] - parent2.update = 1 - - //Radio remote control - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - if(frequency) - radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA) - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status() - if(!radio_connection) - return - - var/datum/signal/signal = new(list( - "tag" = id, - "device" = "ADVP", - "power" = on, - "direction" = pump_direction?("release"):("siphon"), - "checks" = pressure_checks, - "input" = input_pressure_min, - "output" = output_pressure_max, - "external" = external_pressure_bound, - "sigtype" = "status" - )) - radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit() - ..() - if(frequency) - set_frequency(frequency) - broadcast_status() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal) - if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command")) - return - - if("power" in signal.data) - on = text2num(signal.data["power"]) - - if("power_toggle" in signal.data) - on = !on - - if("set_direction" in signal.data) - pump_direction = text2num(signal.data["set_direction"]) - - if("checks" in signal.data) - pressure_checks = text2num(signal.data["checks"]) - - if("purge" in signal.data) - pressure_checks &= ~1 - pump_direction = 0 - - if("stabilize" in signal.data) - pressure_checks |= 1 - pump_direction = 1 - - if("set_input_pressure" in signal.data) - input_pressure_min = CLAMP(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50) - - if("set_output_pressure" in signal.data) - output_pressure_max = CLAMP(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50) - - if("set_external_pressure" in signal.data) - external_pressure_bound = CLAMP(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50) - - if("status" in signal.data) - spawn(2) - broadcast_status() - return //do not update_icon - spawn(2) - broadcast_status() - update_icon() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume - name = "large dual-port air vent" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New() - ..() - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - air1.volume = 1000 - air2.volume = 1000 - -// Mapping - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer1 - piping_layer = 1 - icon_state = "dpvent_map-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3 - piping_layer = 3 - icon_state = "dpvent_map-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on - on = TRUE - icon_state = "dpvent_map_on-2" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1 - piping_layer = 1 - icon_state = "dpvent_map_on-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3 - piping_layer = 3 - icon_state = "dpvent_map_on-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_toxmix - id = INCINERATOR_TOXMIX_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_atmos - id = INCINERATOR_ATMOS_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_syndicatelava - id = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer1 - piping_layer = 1 - icon_state = "dpvent_map-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3 - piping_layer = 3 - icon_state = "dpvent_map-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on - on = TRUE - icon_state = "dpvent_map_on-2" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1 - piping_layer = 1 - icon_state = "dpvent_map_on-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3 - piping_layer = 3 - icon_state = "dpvent_map_on-3" - -#undef EXT_BOUND -#undef INPUT_MIN -#undef OUTPUT_MAX +//Acts like a normal vent, but has an input AND output. + +#define EXT_BOUND 1 +#define INPUT_MIN 2 +#define OUTPUT_MAX 4 + +/obj/machinery/atmospherics/components/binary/dp_vent_pump + icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons! + icon_state = "dpvent_map-2" + + //node2 is output port + //node1 is input port + + name = "dual-port air vent" + desc = "Has a valve and pump attached to it. There are two ports." + + 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/Destroy() + SSradio.remove_object(src, frequency) + return ..() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() + cut_overlays() + if(showpipe) + var/image/cap = getpipeimage(icon, "dpvent_cap", dir, piping_layer = piping_layer) + add_overlay(cap) + + if(!on || !is_operational()) + icon_state = "vent_off" + else + icon_state = pump_direction ? "vent_out" : "vent_in" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos() + ..() + + if(!on) + return + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + + var/datum/gas_mixture/environment = loc.return_air() + var/environment_pressure = environment.return_pressure() + + if(pump_direction) //input -> external + var/pressure_delta = 10000 + + if(pressure_checks&EXT_BOUND) + pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure)) + if(pressure_checks&INPUT_MIN) + pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min)) + + if(pressure_delta > 0) + if(air1.temperature > 0) + var/transfer_moles = pressure_delta*environment.volume/(air1.temperature * R_IDEAL_GAS_EQUATION) + + var/datum/gas_mixture/removed = air1.remove(transfer_moles) + //Removed can be null if there is no atmosphere in air1 + if(!removed) + return + + loc.assume_air(removed) + air_update_turf() + + var/datum/pipeline/parent1 = parents[1] + parent1.update = 1 + + else //external -> output + var/pressure_delta = 10000 + + if(pressure_checks&EXT_BOUND) + pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound)) + if(pressure_checks&INPUT_MIN) + pressure_delta = min(pressure_delta, (output_pressure_max - air2.return_pressure())) + + if(pressure_delta > 0) + if(environment.temperature > 0) + var/transfer_moles = pressure_delta*air2.volume/(environment.temperature * R_IDEAL_GAS_EQUATION) + + var/datum/gas_mixture/removed = loc.remove_air(transfer_moles) + //removed can be null if there is no air in the location + if(!removed) + return + + air2.merge(removed) + air_update_turf() + + var/datum/pipeline/parent2 = parents[2] + parent2.update = 1 + + //Radio remote control + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA) + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status() + if(!radio_connection) + return + + var/datum/signal/signal = new(list( + "tag" = id, + "device" = "ADVP", + "power" = on, + "direction" = pump_direction?("release"):("siphon"), + "checks" = pressure_checks, + "input" = input_pressure_min, + "output" = output_pressure_max, + "external" = external_pressure_bound, + "sigtype" = "status" + )) + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit() + ..() + if(frequency) + set_frequency(frequency) + broadcast_status() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal) + if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command")) + return + + if("power" in signal.data) + on = text2num(signal.data["power"]) + + if("power_toggle" in signal.data) + on = !on + + if("set_direction" in signal.data) + pump_direction = text2num(signal.data["set_direction"]) + + if("checks" in signal.data) + pressure_checks = text2num(signal.data["checks"]) + + if("purge" in signal.data) + pressure_checks &= ~1 + pump_direction = 0 + + if("stabilize" in signal.data) + pressure_checks |= 1 + pump_direction = 1 + + if("set_input_pressure" in signal.data) + input_pressure_min = CLAMP(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50) + + if("set_output_pressure" in signal.data) + output_pressure_max = CLAMP(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50) + + if("set_external_pressure" in signal.data) + external_pressure_bound = CLAMP(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50) + + if("status" in signal.data) + spawn(2) + broadcast_status() + return //do not update_icon + spawn(2) + broadcast_status() + update_icon() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume + name = "large dual-port air vent" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New() + ..() + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + air1.volume = 1000 + air2.volume = 1000 + +// Mapping + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer1 + piping_layer = 1 + icon_state = "dpvent_map-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3 + piping_layer = 3 + icon_state = "dpvent_map-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on + on = TRUE + icon_state = "dpvent_map_on-2" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1 + piping_layer = 1 + icon_state = "dpvent_map_on-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3 + piping_layer = 3 + icon_state = "dpvent_map_on-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_toxmix + id = INCINERATOR_TOXMIX_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_atmos + id = INCINERATOR_ATMOS_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_syndicatelava + id = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer1 + piping_layer = 1 + icon_state = "dpvent_map-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3 + piping_layer = 3 + icon_state = "dpvent_map-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on + on = TRUE + icon_state = "dpvent_map_on-2" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1 + piping_layer = 1 + icon_state = "dpvent_map_on-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3 + piping_layer = 3 + icon_state = "dpvent_map_on-3" + +#undef EXT_BOUND +#undef INPUT_MIN +#undef OUTPUT_MAX diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm b/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm index 8ba105fdb122..b448c2125679 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 f5fb17c16f8a..9a5aefbccab5 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -1,453 +1,453 @@ -#define CRYOMOBS 'icons/obj/cryo_mobs.dmi' - -/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 = TRUE - var/volume = 100 - - var/efficiency = 1 - var/sleep_factor = 0.00125 - var/unconscious_factor = 0.001 - var/heat_capacity = 20000 - var/conduction_coefficient = 0.3 - - var/obj/item/reagent_containers/glass/beaker = null - var/reagent_transfer = 0 - - var/obj/item/radio/radio - var/radio_key = /obj/item/encryptionkey/headset_med - var/radio_channel = RADIO_CHANNEL_MEDICAL - - var/running_anim = FALSE - - var/escape_in_progress = FALSE - var/message_cooldown - var/breakout_time = 300 - fair_market_price = 10 - payment_department = ACCOUNT_MED - - -/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize() - . = ..() - initialize_directions = dir - - radio = new(src) - radio.keyslot = new radio_key - radio.subspace_transmission = TRUE - radio.canhear_range = 0 - radio.recalculateChannels() - -/obj/machinery/atmospherics/components/unary/cryo_cell/Exited(atom/movable/AM, atom/newloc) - var/oldoccupant = occupant - . = ..() // Parent proc takes care of removing occupant if necessary - if (AM == oldoccupant) - update_icon() - -/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction() - ..(dir, dir) - -/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts() - var/C - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - C += M.rating - - efficiency = initial(efficiency) * C - sleep_factor = initial(sleep_factor) * C - unconscious_factor = initial(unconscious_factor) * C - heat_capacity = initial(heat_capacity) / C - conduction_coefficient = initial(conduction_coefficient) * C - -/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results" - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Efficiency at [efficiency*100]%." - -/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy() - QDEL_NULL(radio) - QDEL_NULL(beaker) - 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/nap_violation(mob/violator) - open_machine() - -/obj/machinery/atmospherics/components/unary/cryo_cell/process() - ..() - - if(!on) - return - if(!is_operational()) - on = FALSE - update_icon() - return - if(!occupant) - return - - var/mob/living/mob_occupant = occupant - if(!check_nap_violations()) - return - 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][MOLES] -= max(0,air1.gases[/datum/gas/oxygen][MOLES] - 2 / efficiency) //Let's use gas for this - air1.garbage_collect() - if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). - reagent_transfer = 0 - - return 1 - -/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos() - ..() - - if(!on) - return - - var/datum/gas_mixture/air1 = airs[1] - - if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen][MOLES] < 5) // Turn off if the machine won't work. - on = FALSE - update_icon() - return - - if(occupant) - var/mob/living/mob_occupant = occupant - var/cold_protection = 0 - var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant. - - if(ishuman(occupant)) - var/mob/living/carbon/human/H = occupant - cold_protection = H.get_cold_protection(air1.temperature) - - if(abs(temperature_delta) > 1) - var/air_heat_capacity = air1.heat_capacity() - - var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity)) - - air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB) - mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) - - air1.gases[/datum/gas/oxygen][MOLES] = max(0,air1.gases[/datum/gas/oxygen][MOLES] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. - air1.garbage_collect() - -/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 = FALSE) - if(!state_open && !panel_open) - on = FALSE - ..() - for(var/mob/M in contents) //only drop mobs - M.forceMove(get_turf(src)) - if(isliving(M)) - var/mob/living/L = M - L.update_mobility() - occupant = null - 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.incapacitated() || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) - return - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - close_machine(target) - else - user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].") - if (do_after(user, 25, target=target)) - close_machine(target) - -/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/glass)) - . = 1 //no afterattack - if(beaker) - to_chat(user, "A beaker is already loaded into [src]!") - return - if(!user.transferItemToLoc(I, src)) - return - beaker = I - user.visible_message("[user] places [I] in [src].", \ - "You place [I] in [src].") - var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list) - log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]") - return - if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \ - || default_change_direction_wrench(user, I) \ - || default_pry_open(I) \ - || default_deconstruction_crowbar(I)) - update_icon() - return - else if(I.tool_behaviour == TOOL_SCREWDRIVER) - to_chat(user, "You can't access the maintenance panel while the pod is " \ - + (on ? "active" : (occupant ? "full" : "open")) + ".") - return - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "cryo", name, 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) - log_game("[key_name(usr)] removed an [beaker] to cryo containing [beaker.reagents.reagent_list]") // yogs -- Adds logging for when the beaker's removed from cryo - beaker = null - . = TRUE - update_icon() - -/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 +#define CRYOMOBS 'icons/obj/cryo_mobs.dmi' + +/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 = TRUE + var/volume = 100 + + var/efficiency = 1 + var/sleep_factor = 0.00125 + var/unconscious_factor = 0.001 + var/heat_capacity = 20000 + var/conduction_coefficient = 0.3 + + var/obj/item/reagent_containers/glass/beaker = null + var/reagent_transfer = 0 + + var/obj/item/radio/radio + var/radio_key = /obj/item/encryptionkey/headset_med + var/radio_channel = RADIO_CHANNEL_MEDICAL + + var/running_anim = FALSE + + var/escape_in_progress = FALSE + var/message_cooldown + var/breakout_time = 300 + fair_market_price = 10 + payment_department = ACCOUNT_MED + + +/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize() + . = ..() + initialize_directions = dir + + radio = new(src) + radio.keyslot = new radio_key + radio.subspace_transmission = TRUE + radio.canhear_range = 0 + radio.recalculateChannels() + +/obj/machinery/atmospherics/components/unary/cryo_cell/Exited(atom/movable/AM, atom/newloc) + var/oldoccupant = occupant + . = ..() // Parent proc takes care of removing occupant if necessary + if (AM == oldoccupant) + update_icon() + +/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction() + ..(dir, dir) + +/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts() + var/C + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + C += M.rating + + efficiency = initial(efficiency) * C + sleep_factor = initial(sleep_factor) * C + unconscious_factor = initial(unconscious_factor) * C + heat_capacity = initial(heat_capacity) / C + conduction_coefficient = initial(conduction_coefficient) * C + +/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results" + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Efficiency at [efficiency*100]%." + +/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy() + QDEL_NULL(radio) + QDEL_NULL(beaker) + 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/nap_violation(mob/violator) + open_machine() + +/obj/machinery/atmospherics/components/unary/cryo_cell/process() + ..() + + if(!on) + return + if(!is_operational()) + on = FALSE + update_icon() + return + if(!occupant) + return + + var/mob/living/mob_occupant = occupant + if(!check_nap_violations()) + return + 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][MOLES] -= max(0,air1.gases[/datum/gas/oxygen][MOLES] - 2 / efficiency) //Let's use gas for this + air1.garbage_collect() + if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). + reagent_transfer = 0 + + return 1 + +/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos() + ..() + + if(!on) + return + + var/datum/gas_mixture/air1 = airs[1] + + if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen][MOLES] < 5) // Turn off if the machine won't work. + on = FALSE + update_icon() + return + + if(occupant) + var/mob/living/mob_occupant = occupant + var/cold_protection = 0 + var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant. + + if(ishuman(occupant)) + var/mob/living/carbon/human/H = occupant + cold_protection = H.get_cold_protection(air1.temperature) + + if(abs(temperature_delta) > 1) + var/air_heat_capacity = air1.heat_capacity() + + var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity)) + + air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB) + mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) + + air1.gases[/datum/gas/oxygen][MOLES] = max(0,air1.gases[/datum/gas/oxygen][MOLES] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. + air1.garbage_collect() + +/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 = FALSE) + if(!state_open && !panel_open) + on = FALSE + ..() + for(var/mob/M in contents) //only drop mobs + M.forceMove(get_turf(src)) + if(isliving(M)) + var/mob/living/L = M + L.update_mobility() + occupant = null + 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.incapacitated() || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) + return + if(isliving(target)) + var/mob/living/L = target + if(L.incapacitated()) + close_machine(target) + else + user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].") + if (do_after(user, 25, target=target)) + close_machine(target) + +/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/glass)) + . = 1 //no afterattack + if(beaker) + to_chat(user, "A beaker is already loaded into [src]!") + return + if(!user.transferItemToLoc(I, src)) + return + beaker = I + user.visible_message("[user] places [I] in [src].", \ + "You place [I] in [src].") + var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list) + log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]") + return + if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \ + || default_change_direction_wrench(user, I) \ + || default_pry_open(I) \ + || default_deconstruction_crowbar(I)) + update_icon() + return + else if(I.tool_behaviour == TOOL_SCREWDRIVER) + to_chat(user, "You can't access the maintenance panel while the pod is " \ + + (on ? "active" : (occupant ? "full" : "open")) + ".") + return + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "cryo", name, 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) + log_game("[key_name(usr)] removed an [beaker] to cryo containing [beaker.reagents.reagent_list]") // yogs -- Adds logging for when the beaker's removed from cryo + beaker = null + . = TRUE + update_icon() + +/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/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm index 9467287fb4b1..5853bc726503 100644 --- a/code/modules/atmospherics/machinery/other/meter.dm +++ b/code/modules/atmospherics/machinery/other/meter.dm @@ -1,147 +1,147 @@ -/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(new_layer) - target_layer = new_layer - PIPING_LAYER_DOUBLE_SHIFT(src, target_layer) - -/obj/machinery/meter/process_atmos() - if(!target) - 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(new_layer) + target_layer = new_layer + PIPING_LAYER_DOUBLE_SHIFT(src, target_layer) + +/obj/machinery/meter/process_atmos() + if(!target) + 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 97f5b7c8ce03..234a76107ded 100644 --- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm +++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm @@ -1,139 +1,139 @@ -/obj/machinery/atmospherics/pipe/layer_manifold - name = "layer adaptor" - icon = 'icons/obj/atmospherics/pipes/manifold.dmi' - icon_state = "manifoldlayer" - desc = "A special pipe to bridge pipe layers with." - dir = SOUTH - initialize_directions = NORTH|SOUTH - pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE - piping_layer = PIPING_LAYER_DEFAULT - device_type = 0 - volume = 260 - construction_type = /obj/item/pipe/binary - pipe_state = "manifoldlayer" - - var/list/front_nodes - var/list/back_nodes - -/obj/machinery/atmospherics/pipe/layer_manifold/Initialize() - front_nodes = list() - back_nodes = list() - icon_state = "manifoldlayer_center" - return ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/Destroy() - nullifyAllNodes() - return ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/nullifyAllNodes() - var/list/obj/machinery/atmospherics/needs_nullifying = get_all_connected_nodes() - front_nodes = null - back_nodes = null - nodes = list() - for(var/obj/machinery/atmospherics/A in needs_nullifying) - A.disconnect(src) - 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!! - cut_overlays() - layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else. - - for(var/node in front_nodes) - add_attached_images(node) - for(var/node in back_nodes) - add_attached_images(node) - - update_alpha() - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_images(obj/machinery/atmospherics/A) - if(!A) - return - if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) - for(var/i in PIPING_LAYER_MIN to PIPING_LAYER_MAX) - add_attached_image(get_dir(src, A), i) - return - add_attached_image(get_dir(src, A), A.piping_layer, A.pipe_color) - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(p_dir, p_layer, p_color = null) - var/image/I - - if(p_color) - I = getpipeimage(icon, "pipe", p_dir, p_color, piping_layer = p_layer) - else - I = getpipeimage(icon, "pipe", p_dir, piping_layer = p_layer) - - I.layer = layer - 0.01 - add_overlay(I) - -/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections() - switch(dir) - if(NORTH || SOUTH) - initialize_directions = NORTH|SOUTH - if(EAST || WEST) - initialize_directions = EAST|WEST - -/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer) - if(!given_layer) - return TRUE - . = ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/findAllConnections() - front_nodes = list() - back_nodes = list() - var/list/new_nodes = list() - for(var/iter in PIPING_LAYER_MIN to PIPING_LAYER_MAX) - var/obj/machinery/atmospherics/foundfront = findConnecting(dir, iter) - var/obj/machinery/atmospherics/foundback = findConnecting(turn(dir, 180), iter) - front_nodes += foundfront - back_nodes += foundback - if(foundfront && !QDELETED(foundfront)) - new_nodes += foundfront - if(foundback && !QDELETED(foundback)) - new_nodes += foundback - update_icon() - return new_nodes - -/obj/machinery/atmospherics/pipe/layer_manifold/atmosinit() - normalize_cardinal_directions() - findAllConnections() - 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/visible - level = PIPE_VISIBLE_LEVEL - layer = GAS_PIPE_VISIBLE_LAYER +/obj/machinery/atmospherics/pipe/layer_manifold + name = "layer adaptor" + icon = 'icons/obj/atmospherics/pipes/manifold.dmi' + icon_state = "manifoldlayer" + desc = "A special pipe to bridge pipe layers with." + dir = SOUTH + initialize_directions = NORTH|SOUTH + pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE + piping_layer = PIPING_LAYER_DEFAULT + device_type = 0 + volume = 260 + construction_type = /obj/item/pipe/binary + pipe_state = "manifoldlayer" + + var/list/front_nodes + var/list/back_nodes + +/obj/machinery/atmospherics/pipe/layer_manifold/Initialize() + front_nodes = list() + back_nodes = list() + icon_state = "manifoldlayer_center" + return ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/Destroy() + nullifyAllNodes() + return ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/nullifyAllNodes() + var/list/obj/machinery/atmospherics/needs_nullifying = get_all_connected_nodes() + front_nodes = null + back_nodes = null + nodes = list() + for(var/obj/machinery/atmospherics/A in needs_nullifying) + A.disconnect(src) + 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!! + cut_overlays() + layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else. + + for(var/node in front_nodes) + add_attached_images(node) + for(var/node in back_nodes) + add_attached_images(node) + + update_alpha() + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_images(obj/machinery/atmospherics/A) + if(!A) + return + if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) + for(var/i in PIPING_LAYER_MIN to PIPING_LAYER_MAX) + add_attached_image(get_dir(src, A), i) + return + add_attached_image(get_dir(src, A), A.piping_layer, A.pipe_color) + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(p_dir, p_layer, p_color = null) + var/image/I + + if(p_color) + I = getpipeimage(icon, "pipe", p_dir, p_color, piping_layer = p_layer) + else + I = getpipeimage(icon, "pipe", p_dir, piping_layer = p_layer) + + I.layer = layer - 0.01 + add_overlay(I) + +/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections() + switch(dir) + if(NORTH || SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST || WEST) + initialize_directions = EAST|WEST + +/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer) + if(!given_layer) + return TRUE + . = ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/findAllConnections() + front_nodes = list() + back_nodes = list() + var/list/new_nodes = list() + for(var/iter in PIPING_LAYER_MIN to PIPING_LAYER_MAX) + var/obj/machinery/atmospherics/foundfront = findConnecting(dir, iter) + var/obj/machinery/atmospherics/foundback = findConnecting(turn(dir, 180), iter) + front_nodes += foundfront + back_nodes += foundback + if(foundfront && !QDELETED(foundfront)) + new_nodes += foundfront + if(foundback && !QDELETED(foundback)) + new_nodes += foundback + update_icon() + return new_nodes + +/obj/machinery/atmospherics/pipe/layer_manifold/atmosinit() + normalize_cardinal_directions() + findAllConnections() + 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/visible + level = PIPE_VISIBLE_LEVEL + layer = GAS_PIPE_VISIBLE_LAYER \ No newline at end of file diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm index 933b3960d108..76192325dfd7 100644 --- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm +++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm @@ -1,163 +1,163 @@ -/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/ex_act(severity, target) - if(severity == 1 || target == src) - if(resistance_flags & INDESTRUCTIBLE) - return //Indestructable cans shouldn't release air - - //This explosion will destroy the can, release its air. - var/turf/T = get_turf(src) - T.assume_air(air_contents) - T.air_update_turf() - - return ..() - -/obj/machinery/portable_atmospherics/process_atmos() - if(!connected_port) // Pipe network handles reactions if connected. - air_contents.react(src) - -/obj/machinery/portable_atmospherics/return_air() - return air_contents - -/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port) - //Make sure not already connected to something else - if(connected_port || !new_port || new_port.connected_device) - return FALSE - - //Make sure are close enough for a valid connection - if(new_port.loc != get_turf(src)) - return FALSE - - //Perform the connection - connected_port = new_port - connected_port.connected_device = src - var/datum/pipeline/connected_port_parent = connected_port.parents[1] - connected_port_parent.reconcile_air() - - anchored = TRUE //Prevent movement - pixel_x = new_port.pixel_x - pixel_y = new_port.pixel_y - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/Move() - . = ..() - if(.) - disconnect() - -/obj/machinery/portable_atmospherics/proc/disconnect() - if(!connected_port) - return FALSE - anchored = FALSE - connected_port.connected_device = null - connected_port = null - pixel_x = 0 - pixel_y = 0 - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/AltClick(mob/living/user) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user))) - return - if(holding) - to_chat(user, "You remove [holding] from [src].") - replace_tank(user, TRUE) - -/obj/machinery/portable_atmospherics/examine(mob/user) - . = ..() - if(holding) - . += "\The [src] contains [holding]. Alt-click [src] to remove it."+\ - "Click [src] with another gas tank to hot swap [holding]." - -/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) - if(holding) - holding.forceMove(drop_location()) - if(Adjacent(user) && !issiliconoradminghost(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(W.tool_behaviour == TOOL_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/ex_act(severity, target) + if(severity == 1 || target == src) + if(resistance_flags & INDESTRUCTIBLE) + return //Indestructable cans shouldn't release air + + //This explosion will destroy the can, release its air. + var/turf/T = get_turf(src) + T.assume_air(air_contents) + T.air_update_turf() + + return ..() + +/obj/machinery/portable_atmospherics/process_atmos() + if(!connected_port) // Pipe network handles reactions if connected. + air_contents.react(src) + +/obj/machinery/portable_atmospherics/return_air() + return air_contents + +/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port) + //Make sure not already connected to something else + if(connected_port || !new_port || new_port.connected_device) + return FALSE + + //Make sure are close enough for a valid connection + if(new_port.loc != get_turf(src)) + return FALSE + + //Perform the connection + connected_port = new_port + connected_port.connected_device = src + var/datum/pipeline/connected_port_parent = connected_port.parents[1] + connected_port_parent.reconcile_air() + + anchored = TRUE //Prevent movement + pixel_x = new_port.pixel_x + pixel_y = new_port.pixel_y + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/Move() + . = ..() + if(.) + disconnect() + +/obj/machinery/portable_atmospherics/proc/disconnect() + if(!connected_port) + return FALSE + anchored = FALSE + connected_port.connected_device = null + connected_port = null + pixel_x = 0 + pixel_y = 0 + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/AltClick(mob/living/user) + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user))) + return + if(holding) + to_chat(user, "You remove [holding] from [src].") + replace_tank(user, TRUE) + +/obj/machinery/portable_atmospherics/examine(mob/user) + . = ..() + if(holding) + . += "\The [src] contains [holding]. Alt-click [src] to remove it."+\ + "Click [src] with another gas tank to hot swap [holding]." + +/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) + if(holding) + holding.forceMove(drop_location()) + if(Adjacent(user) && !issiliconoradminghost(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(W.tool_behaviour == TOOL_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 c87f796479a5..b9017a78492e 100644 --- a/code/modules/atmospherics/machinery/portable/pump.dm +++ b/code/modules/atmospherics/machinery/portable/pump.dm @@ -1,155 +1,155 @@ -#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) - replace_tank(usr, FALSE) - . = TRUE - update_icon() +#define PUMP_OUT "out" +#define PUMP_IN "in" +#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 25) +#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10) +#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE) + +/obj/machinery/portable_atmospherics/pump + name = "portable air pump" + icon_state = "psiphon:0" + density = TRUE + + 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) + replace_tank(usr, FALSE) + . = TRUE + update_icon() diff --git a/code/modules/awaymissions/bluespaceartillery.dm b/code/modules/awaymissions/bluespaceartillery.dm index 9bebcfe54621..deb05920e229 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 361f8169b9e1..976ab1844337 100644 --- a/code/modules/awaymissions/exile.dm +++ b/code/modules/awaymissions/exile.dm @@ -1,9 +1,9 @@ - -/obj/structure/closet/secure_closet/exile - name = "exile implants" - req_access = list(ACCESS_HOS) - -/obj/structure/closet/secure_closet/exile/PopulateContents() - new /obj/item/implanter/exile(src) - for(var/i in 1 to 5) - new /obj/item/implantcase/exile(src) + +/obj/structure/closet/secure_closet/exile + name = "exile implants" + req_access = list(ACCESS_HOS) + +/obj/structure/closet/secure_closet/exile/PopulateContents() + new /obj/item/implanter/exile(src) + for(var/i in 1 to 5) + new /obj/item/implantcase/exile(src) diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm index 0b2a64eb7289..9f97280137e4 100644 --- a/code/modules/awaymissions/gateway.dm +++ b/code/modules/awaymissions/gateway.dm @@ -1,253 +1,253 @@ -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(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) - 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/multitool_act(mob/living/user, obj/item/I) - if(calibrated) - to_chat(user, "\black The gate is already calibrated, there is no work for you to do here.") - else - to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.") - calibrated = TRUE - return TRUE - -/////////////////////////////////////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 Bluespace 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(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) + 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/multitool_act(mob/living/user, obj/item/I) + if(calibrated) + to_chat(user, "\black The gate is already calibrated, there is no work for you to do here.") + else + to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.") + calibrated = TRUE + return TRUE + +/////////////////////////////////////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 Bluespace Research" + name = "Confidential Correspondence, Pg 1" diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm index 48d7a6d2f6d3..1e48e6e38f24 100644 --- a/code/modules/awaymissions/mission_code/Academy.dm +++ b/code/modules/awaymissions/mission_code/Academy.dm @@ -1,390 +1,390 @@ - -//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 - current_wizard.key = C.key - -/obj/structure/academy_wizard_spawner/proc/summon_wizard() - var/turf/T = src.loc - var/mob/living/carbon/human/wizbody = new(T) - wizbody.fully_replace_character_name(wizbody.real_name, "Academy Teacher") - wizbody.mind_initialize() - var/datum/mind/wizmind = wizbody.mind - wizmind.special_role = "Academy Defender" - wizmind.add_antag_datum(/datum/antagonist/wizard/academy) - current_wizard = wizbody - - give_control() - -/obj/structure/academy_wizard_spawner/deconstruct(disassembled = TRUE) - if(!broken) - broken = 1 - visible_message("[src] breaks down!") - icon_state = "forge_off" - STOP_PROCESSING(SSobj, src) - -/datum/outfit/wizard/academy - name = "Academy Wizard" - r_pocket = null - r_hand = null - suit = /obj/item/clothing/suit/wizrobe/red - head = /obj/item/clothing/head/wizard/red - backpack_contents = list(/obj/item/storage/box/survival = 1) - -/obj/item/dice/d20/fate - name = "\improper Die of Fate" - desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky." - icon_state = "d20" - sides = 20 - microwave_riggable = FALSE - var/reusable = TRUE - var/used = FALSE - -/obj/item/dice/d20/fate/stealth - name = "d20" - desc = "A die with twenty sides. The preferred die to throw at the GM." - -/obj/item/dice/d20/fate/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/one_use/stealth - name = "d20" - desc = "A die with twenty sides. The preferred die to throw at the GM." - -/obj/item/dice/d20/fate/cursed - name = "cursed Die of Fate" - desc = "A die with twenty sides. You feel that rolling this is a REALLY bad idea." - color = "#00BB00" - - rigged = DICE_TOTALLY_RIGGED - rigged_value = 1 - -/obj/item/dice/d20/fate/diceroll(mob/user) - . = ..() - if(!used) - if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) - to_chat(user, "You feel the magic of the dice is restricted to ordinary humans!") - return - - if(!reusable) - used = TRUE - - var/turf/T = get_turf(src) - T.visible_message("[src] flares briefly.") - - addtimer(CALLBACK(src, .proc/effect, user, .), 1 SECONDS) - -/obj/item/dice/d20/fate/equipped(mob/user, slot) - if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) - to_chat(user, "You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.") - user.dropItemToGround(src) - - -/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll) - var/turf/T = get_turf(src) - switch(roll) - if(1) - //Dust - T.visible_message("[user] turns to dust!") - user.hellbound = TRUE - user.dust() - if(2) - //Death - T.visible_message("[user] suddenly dies!") - user.death() - if(3) - //Swarm of creatures - T.visible_message("A swarm of creatures surround [user]!") - for(var/direction in GLOB.alldirs) - new /mob/living/simple_animal/hostile/netherworld(get_step(get_turf(user),direction)) - if(4) - //Destroy Equipment - T.visible_message("Everything [user] is holding and wearing disappears!") - for(var/obj/item/I in user) - if(istype(I, /obj/item/implant)) - continue - qdel(I) - if(5) - //Monkeying - T.visible_message("[user] transforms into a monkey!") - user.monkeyize() - if(6) - //Cut speed - T.visible_message("[user] starts moving slower!") - user.add_movespeed_modifier(MOVESPEED_ID_DIE_OF_FATE, update=TRUE, priority=100, multiplicative_slowdown=1) - if(7) - //Throw - T.visible_message("Unseen forces throw [user]!") - user.Stun(60) - user.adjustBruteLoss(50) - var/throw_dir = pick(GLOB.cardinals) - var/atom/throw_target = get_edge_target_turf(user, throw_dir) - user.throw_at(throw_target, 200, 4) - if(8) - //Fueltank Explosion - T.visible_message("An explosion bursts into existence around [user]!") - explosion(get_turf(user),-1,0,2, flame_range = 2) - if(9) - //Cold - var/datum/disease/D = new /datum/disease/cold() - T.visible_message("[user] looks a little under the weather!") - user.ForceContractDisease(D, FALSE, TRUE) - if(10) - //Nothing - T.visible_message("Nothing seems to happen.") - if(11) - //Cookie - T.visible_message("A cookie appears out of thin air!") - var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location()) - do_smoke(0, drop_location()) - C.name = "Cookie of Fate" - if(12) - //Healing - T.visible_message("[user] looks very healthy!") - user.revive(full_heal = 1, admin_revive = 1) - if(13) - //Mad Dosh - T.visible_message("Mad dosh shoots out of [src]!") - var/turf/Start = get_turf(src) - for(var/direction in GLOB.alldirs) - var/turf/dirturf = get_step(Start,direction) - if(rand(0,1)) - new /obj/item/stack/spacecash/c1000(dirturf) - else - var/obj/item/storage/bag/money/M = new(dirturf) - for(var/i in 1 to rand(5,50)) - new /obj/item/coin/gold(M) - if(14) - //Free Gun - T.visible_message("An impressive gun appears!") - do_smoke(0, drop_location()) - new /obj/item/gun/ballistic/revolver/mateba(drop_location()) - if(15) - //Random One-use spellbook - T.visible_message("A magical looking book drops to the floor!") - do_smoke(0, drop_location()) - new /obj/item/book/granter/spell/random(drop_location()) - if(16) - //Servant & Servant Summon - T.visible_message("A Dice Servant appears in a cloud of smoke!") - var/mob/living/carbon/human/H = new(drop_location()) - do_smoke(0, drop_location()) - - H.equipOutfit(/datum/outfit/butler) - var/datum/mind/servant_mind = new /datum/mind() - var/datum/antagonist/magic_servant/A = new - servant_mind.add_antag_datum(A) - A.setup_master(user) - servant_mind.transfer_to(H) - - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [user.real_name] Servant?", ROLE_WIZARD, null, ROLE_WIZARD, 50, H) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") - H.key = C.key - - var/obj/effect/proc_holder/spell/targeted/summonmob/S = new - S.target_mob = H - user.mind.AddSpell(S) - - if(17) - //Tator Kit - T.visible_message("A suspicious box appears!") - new /obj/item/storage/box/syndicate/bundle_A(drop_location()) - do_smoke(0, drop_location()) - if(18) - //Captain ID - T.visible_message("A golden identification card appears!") - new /obj/item/card/id/captains_spare(drop_location()) - do_smoke(0, drop_location()) - if(19) - //Instrinct Resistance - T.visible_message("[user] looks very robust!") - user.physiology.brute_mod *= 0.5 - user.physiology.burn_mod *= 0.5 - - if(20) - //Free wizard! - T.visible_message("Magic flows out of [src] and into [user]!") - user.mind.make_Wizard() - -/datum/outfit/butler - name = "Butler" - uniform = /obj/item/clothing/under/suit_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 + current_wizard.key = C.key + +/obj/structure/academy_wizard_spawner/proc/summon_wizard() + var/turf/T = src.loc + var/mob/living/carbon/human/wizbody = new(T) + wizbody.fully_replace_character_name(wizbody.real_name, "Academy Teacher") + wizbody.mind_initialize() + var/datum/mind/wizmind = wizbody.mind + wizmind.special_role = "Academy Defender" + wizmind.add_antag_datum(/datum/antagonist/wizard/academy) + current_wizard = wizbody + + give_control() + +/obj/structure/academy_wizard_spawner/deconstruct(disassembled = TRUE) + if(!broken) + broken = 1 + visible_message("[src] breaks down!") + icon_state = "forge_off" + STOP_PROCESSING(SSobj, src) + +/datum/outfit/wizard/academy + name = "Academy Wizard" + r_pocket = null + r_hand = null + suit = /obj/item/clothing/suit/wizrobe/red + head = /obj/item/clothing/head/wizard/red + backpack_contents = list(/obj/item/storage/box/survival = 1) + +/obj/item/dice/d20/fate + name = "\improper Die of Fate" + desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky." + icon_state = "d20" + sides = 20 + microwave_riggable = FALSE + var/reusable = TRUE + var/used = FALSE + +/obj/item/dice/d20/fate/stealth + name = "d20" + desc = "A die with twenty sides. The preferred die to throw at the GM." + +/obj/item/dice/d20/fate/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/one_use/stealth + name = "d20" + desc = "A die with twenty sides. The preferred die to throw at the GM." + +/obj/item/dice/d20/fate/cursed + name = "cursed Die of Fate" + desc = "A die with twenty sides. You feel that rolling this is a REALLY bad idea." + color = "#00BB00" + + rigged = DICE_TOTALLY_RIGGED + rigged_value = 1 + +/obj/item/dice/d20/fate/diceroll(mob/user) + . = ..() + if(!used) + if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) + to_chat(user, "You feel the magic of the dice is restricted to ordinary humans!") + return + + if(!reusable) + used = TRUE + + var/turf/T = get_turf(src) + T.visible_message("[src] flares briefly.") + + addtimer(CALLBACK(src, .proc/effect, user, .), 1 SECONDS) + +/obj/item/dice/d20/fate/equipped(mob/user, slot) + if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) + to_chat(user, "You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.") + user.dropItemToGround(src) + + +/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll) + var/turf/T = get_turf(src) + switch(roll) + if(1) + //Dust + T.visible_message("[user] turns to dust!") + user.hellbound = TRUE + user.dust() + if(2) + //Death + T.visible_message("[user] suddenly dies!") + user.death() + if(3) + //Swarm of creatures + T.visible_message("A swarm of creatures surround [user]!") + for(var/direction in GLOB.alldirs) + new /mob/living/simple_animal/hostile/netherworld(get_step(get_turf(user),direction)) + if(4) + //Destroy Equipment + T.visible_message("Everything [user] is holding and wearing disappears!") + for(var/obj/item/I in user) + if(istype(I, /obj/item/implant)) + continue + qdel(I) + if(5) + //Monkeying + T.visible_message("[user] transforms into a monkey!") + user.monkeyize() + if(6) + //Cut speed + T.visible_message("[user] starts moving slower!") + user.add_movespeed_modifier(MOVESPEED_ID_DIE_OF_FATE, update=TRUE, priority=100, multiplicative_slowdown=1) + if(7) + //Throw + T.visible_message("Unseen forces throw [user]!") + user.Stun(60) + user.adjustBruteLoss(50) + var/throw_dir = pick(GLOB.cardinals) + var/atom/throw_target = get_edge_target_turf(user, throw_dir) + user.throw_at(throw_target, 200, 4) + if(8) + //Fueltank Explosion + T.visible_message("An explosion bursts into existence around [user]!") + explosion(get_turf(user),-1,0,2, flame_range = 2) + if(9) + //Cold + var/datum/disease/D = new /datum/disease/cold() + T.visible_message("[user] looks a little under the weather!") + user.ForceContractDisease(D, FALSE, TRUE) + if(10) + //Nothing + T.visible_message("Nothing seems to happen.") + if(11) + //Cookie + T.visible_message("A cookie appears out of thin air!") + var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location()) + do_smoke(0, drop_location()) + C.name = "Cookie of Fate" + if(12) + //Healing + T.visible_message("[user] looks very healthy!") + user.revive(full_heal = 1, admin_revive = 1) + if(13) + //Mad Dosh + T.visible_message("Mad dosh shoots out of [src]!") + var/turf/Start = get_turf(src) + for(var/direction in GLOB.alldirs) + var/turf/dirturf = get_step(Start,direction) + if(rand(0,1)) + new /obj/item/stack/spacecash/c1000(dirturf) + else + var/obj/item/storage/bag/money/M = new(dirturf) + for(var/i in 1 to rand(5,50)) + new /obj/item/coin/gold(M) + if(14) + //Free Gun + T.visible_message("An impressive gun appears!") + do_smoke(0, drop_location()) + new /obj/item/gun/ballistic/revolver/mateba(drop_location()) + if(15) + //Random One-use spellbook + T.visible_message("A magical looking book drops to the floor!") + do_smoke(0, drop_location()) + new /obj/item/book/granter/spell/random(drop_location()) + if(16) + //Servant & Servant Summon + T.visible_message("A Dice Servant appears in a cloud of smoke!") + var/mob/living/carbon/human/H = new(drop_location()) + do_smoke(0, drop_location()) + + H.equipOutfit(/datum/outfit/butler) + var/datum/mind/servant_mind = new /datum/mind() + var/datum/antagonist/magic_servant/A = new + servant_mind.add_antag_datum(A) + A.setup_master(user) + servant_mind.transfer_to(H) + + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [user.real_name] Servant?", ROLE_WIZARD, null, ROLE_WIZARD, 50, H) + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") + H.key = C.key + + var/obj/effect/proc_holder/spell/targeted/summonmob/S = new + S.target_mob = H + user.mind.AddSpell(S) + + if(17) + //Tator Kit + T.visible_message("A suspicious box appears!") + new /obj/item/storage/box/syndicate/bundle_A(drop_location()) + do_smoke(0, drop_location()) + if(18) + //Captain ID + T.visible_message("A golden identification card appears!") + new /obj/item/card/id/captains_spare(drop_location()) + do_smoke(0, drop_location()) + if(19) + //Instrinct Resistance + T.visible_message("[user] looks very robust!") + user.physiology.brute_mod *= 0.5 + user.physiology.burn_mod *= 0.5 + + if(20) + //Free wizard! + T.visible_message("Magic flows out of [src] and into [user]!") + user.mind.make_Wizard() + +/datum/outfit/butler + name = "Butler" + uniform = /obj/item/clothing/under/suit_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 8a9b5f3a1133..b785431bad1a 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" - ambientsounds = list('sound/ambience/ambigen3.ogg') - -/area/awaymission/centcomAway/maint - name = "XCC-P5831 Maintenance" - icon_state = "away1" - ambientsounds = list('sound/ambience/ambisin1.ogg') - -/area/awaymission/centcomAway/thunderdome - name = "XCC-P5831 Thunderdome" - icon_state = "away2" - ambientsounds = list('sound/ambience/ambisin2.ogg') - -/area/awaymission/centcomAway/cafe - name = "XCC-P5831 Kitchen Arena" - icon_state = "away3" - ambientsounds = list('sound/ambience/ambisin3.ogg') - -/area/awaymission/centcomAway/courtroom - name = "XCC-P5831 Courtroom" - icon_state = "away4" - ambientsounds = list('sound/ambience/ambisin4.ogg') - -/area/awaymission/centcomAway/hangar - name = "XCC-P5831 Hangars" - icon_state = "away4" - ambientsounds = list('sound/ambience/ambigen5.ogg') - -//centcomAway items - -/obj/item/paper/pamphlet/centcom/visitor_info - name = "Visitor Info Pamphlet" - info = " XCC-P5831 Visitor Information
                \ - Greetings, visitor, to XCC-P5831! As you may know, this outpost was once \ - used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \ - projects across the vastness of space.
                \ - Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \ - acts as NT's base of operations but still plays a very important role its corporate affairs; \ - serving as a supply and repair depot, as well as being host to its most important legal proceedings\ - and the thrilling pay-per-view broadcasts of PLASTEEL CHEF and THUNDERDOME LIVE.
                \ - We hope you enjoy your stay!" - -/obj/item/paper/fluff/awaymissions/centcom/gateway_memo - name = "Memo to XCC-P5831 QM" - info = "From: XCC-P5831 Management Office
                \ - To: Rolf Ingram, XCC-P5831 Quartermaster
                \ - Hey, Rolf, once you pack that gateway into the ferry hangar, make absolutely sure \ - to deactivate it! As you may know, SS13 has recently got its network up and running, \ - which means that until we get this gate shipped off to the next colonization staging \ - area, they'll be able to hop straight in here if its hooked up on our end.
                \ - Obviously, that's something I'd very much rather avoid. Our forensics and medical \ - teams never did figure out what happened that last time... and I can't wrap my head \ - around it myself. Why would a shuttle full of evacuees all snap and beat each other \ - to death the moment they reached safety?
                \ +//centcomAway areas + +/area/awaymission/centcomAway + name = "XCC-P5831" + icon_state = "away" + requires_power = FALSE + +/area/awaymission/centcomAway/general + name = "XCC-P5831" + ambientsounds = list('sound/ambience/ambigen3.ogg') + +/area/awaymission/centcomAway/maint + name = "XCC-P5831 Maintenance" + icon_state = "away1" + ambientsounds = list('sound/ambience/ambisin1.ogg') + +/area/awaymission/centcomAway/thunderdome + name = "XCC-P5831 Thunderdome" + icon_state = "away2" + ambientsounds = list('sound/ambience/ambisin2.ogg') + +/area/awaymission/centcomAway/cafe + name = "XCC-P5831 Kitchen Arena" + icon_state = "away3" + ambientsounds = list('sound/ambience/ambisin3.ogg') + +/area/awaymission/centcomAway/courtroom + name = "XCC-P5831 Courtroom" + icon_state = "away4" + ambientsounds = list('sound/ambience/ambisin4.ogg') + +/area/awaymission/centcomAway/hangar + name = "XCC-P5831 Hangars" + icon_state = "away4" + ambientsounds = list('sound/ambience/ambigen5.ogg') + +//centcomAway items + +/obj/item/paper/pamphlet/centcom/visitor_info + name = "Visitor Info Pamphlet" + info = " XCC-P5831 Visitor Information
                \ + Greetings, visitor, to XCC-P5831! As you may know, this outpost was once \ + used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \ + projects across the vastness of space.
                \ + Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \ + acts as NT's base of operations but still plays a very important role its corporate affairs; \ + serving as a supply and repair depot, as well as being host to its most important legal proceedings\ + and the thrilling pay-per-view broadcasts of PLASTEEL CHEF and THUNDERDOME LIVE.
                \ + We hope you enjoy your stay!" + +/obj/item/paper/fluff/awaymissions/centcom/gateway_memo + name = "Memo to XCC-P5831 QM" + info = "From: XCC-P5831 Management Office
                \ + To: Rolf Ingram, XCC-P5831 Quartermaster
                \ + Hey, Rolf, once you pack that gateway into the ferry hangar, make absolutely sure \ + to deactivate it! As you may know, SS13 has recently got its network up and running, \ + which means that until we get this gate shipped off to the next colonization staging \ + area, they'll be able to hop straight in here if its hooked up on our end.
                \ + Obviously, that's something I'd very much rather avoid. Our forensics and medical \ + teams never did figure out what happened that last time... and I can't wrap my head \ + around it myself. Why would a shuttle full of evacuees all snap and beat each other \ + to death the moment they reached safety?
                \ - D. Cereza" \ 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 964d546be67c..ef7bf0745edc 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 it's just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...it's calling to me." - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page8 - name = "Planer Saul's Journal: Page 8" - info = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside." - - -/* - * Wish Granter - */ -/obj/machinery/wish_granter_dark - name = "Wish Granter" - desc = "You're not so sure about this, anymore..." - icon = 'icons/obj/device.dmi' - icon_state = "syndbeacon" - - density = TRUE - use_power = NO_POWER_USE - - var/chargesa = 1 - var/insistinga = 0 - -/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user) - if(chargesa <= 0) - to_chat(user, "The Wish Granter lies silent.") - return - - else if(!ishuman(user)) - to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") - return - - else if(is_special_character(user)) - to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") - - else if (!insistinga) - to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") - insistinga++ - - else - chargesa-- - insistinga = 0 - var/wish = input("You want...","Wish") as null|anything in 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(SPACEMUT) - user.dna.add_mutation(XRAY) - user.set_species(/datum/species/shadow) - if("Wealth") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - new /obj/structure/closet/syndicate/resources/everything(loc) - user.set_species(/datum/species/shadow) - if("Immortality") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - user.verbs += /mob/living/carbon/proc/immortality - user.set_species(/datum/species/shadow) - if("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 it's just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...it's calling to me." + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page8 + name = "Planer Saul's Journal: Page 8" + info = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside." + + +/* + * Wish Granter + */ +/obj/machinery/wish_granter_dark + name = "Wish Granter" + desc = "You're not so sure about this, anymore..." + icon = 'icons/obj/device.dmi' + icon_state = "syndbeacon" + + density = TRUE + use_power = NO_POWER_USE + + var/chargesa = 1 + var/insistinga = 0 + +/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user) + if(chargesa <= 0) + to_chat(user, "The Wish Granter lies silent.") + return + + else if(!ishuman(user)) + to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") + return + + else if(is_special_character(user)) + to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") + + else if (!insistinga) + to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") + insistinga++ + + else + chargesa-- + insistinga = 0 + var/wish = input("You want...","Wish") as null|anything in 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(SPACEMUT) + user.dna.add_mutation(XRAY) + user.set_species(/datum/species/shadow) + if("Wealth") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") + new /obj/structure/closet/syndicate/resources/everything(loc) + user.set_species(/datum/species/shadow) + if("Immortality") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") + user.verbs += /mob/living/carbon/proc/immortality + user.set_species(/datum/species/shadow) + if("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 94ebb919726e..7cbbde096d6e 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 a96acb027bb4..744fb78ba525 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/bsql/core/connection.dm b/code/modules/bsql/core/connection.dm index 692d1333f005..0e0b891f650e 100644 --- a/code/modules/bsql/core/connection.dm +++ b/code/modules/bsql/core/connection.dm @@ -1,68 +1,68 @@ -/datum/BSQL_Connection - var/id - var/connection_type - -BSQL_PROTECT_DATUM(/datum/BSQL_Connection) - -/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit) - if(asyncTimeout == null) - asyncTimeout = BSQL_DEFAULT_TIMEOUT - if(blockingTimeout == null) - blockingTimeout = asyncTimeout - if(threadLimit == null) - threadLimit = BSQL_DEFAULT_THREAD_LIMIT - - src.connection_type = connection_type - - world._BSQL_InitCheck(src) - - var/error = world._BSQL_Internal_Call("CreateConnection", connection_type, "[asyncTimeout]", "[blockingTimeout]", "[threadLimit]") - if(error) - BSQL_ERROR(error) - return - - id = world._BSQL_Internal_Call("GetConnection") - if(!id) - BSQL_ERROR("BSQL library failed to provide connect operation for connection id [id]([connection_type])!") - -BSQL_DEL_PROC(/datum/BSQL_Connection) - var/error - if(id) - error = world._BSQL_Internal_Call("ReleaseConnection", id) - . = ..() - if(error) - BSQL_ERROR(error) - -/datum/BSQL_Connection/BeginConnect(ipaddress, port, username, password, database) - var/error = world._BSQL_Internal_Call("OpenConnection", id, ipaddress, "[port]", username, password, database) - if(error) - BSQL_ERROR(error) - return - - var/op_id = world._BSQL_Internal_Call("GetOperation") - if(!op_id) - BSQL_ERROR("Library failed to provide connect operation for connection id [id]([connection_type])!") - return - - return new /datum/BSQL_Operation(src, op_id) - - -/datum/BSQL_Connection/BeginQuery(query) - var/error = world._BSQL_Internal_Call("NewQuery", id, query) - if(error) - BSQL_ERROR(error) - return - - var/op_id = world._BSQL_Internal_Call("GetOperation") - if(!op_id) - BSQL_ERROR("Library failed to provide query operation for connection id [id]([connection_type])!") - return - - return new /datum/BSQL_Operation/Query(src, op_id) - -/datum/BSQL_Connection/Quote(str) - if(!str) - return null; - . = world._BSQL_Internal_Call("QuoteString", id, "[str]") - if(!.) +/datum/BSQL_Connection + var/id + var/connection_type + +BSQL_PROTECT_DATUM(/datum/BSQL_Connection) + +/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit) + if(asyncTimeout == null) + asyncTimeout = BSQL_DEFAULT_TIMEOUT + if(blockingTimeout == null) + blockingTimeout = asyncTimeout + if(threadLimit == null) + threadLimit = BSQL_DEFAULT_THREAD_LIMIT + + src.connection_type = connection_type + + world._BSQL_InitCheck(src) + + var/error = world._BSQL_Internal_Call("CreateConnection", connection_type, "[asyncTimeout]", "[blockingTimeout]", "[threadLimit]") + if(error) + BSQL_ERROR(error) + return + + id = world._BSQL_Internal_Call("GetConnection") + if(!id) + BSQL_ERROR("BSQL library failed to provide connect operation for connection id [id]([connection_type])!") + +BSQL_DEL_PROC(/datum/BSQL_Connection) + var/error + if(id) + error = world._BSQL_Internal_Call("ReleaseConnection", id) + . = ..() + if(error) + BSQL_ERROR(error) + +/datum/BSQL_Connection/BeginConnect(ipaddress, port, username, password, database) + var/error = world._BSQL_Internal_Call("OpenConnection", id, ipaddress, "[port]", username, password, database) + if(error) + BSQL_ERROR(error) + return + + var/op_id = world._BSQL_Internal_Call("GetOperation") + if(!op_id) + BSQL_ERROR("Library failed to provide connect operation for connection id [id]([connection_type])!") + return + + return new /datum/BSQL_Operation(src, op_id) + + +/datum/BSQL_Connection/BeginQuery(query) + var/error = world._BSQL_Internal_Call("NewQuery", id, query) + if(error) + BSQL_ERROR(error) + return + + var/op_id = world._BSQL_Internal_Call("GetOperation") + if(!op_id) + BSQL_ERROR("Library failed to provide query operation for connection id [id]([connection_type])!") + return + + return new /datum/BSQL_Operation/Query(src, op_id) + +/datum/BSQL_Connection/Quote(str) + if(!str) + return null; + . = world._BSQL_Internal_Call("QuoteString", id, "[str]") + if(!.) BSQL_ERROR("Library failed to provide quote for [str]!") \ No newline at end of file diff --git a/code/modules/bsql/core/library.dm b/code/modules/bsql/core/library.dm index 2651d01c8268..79285be3d374 100644 --- a/code/modules/bsql/core/library.dm +++ b/code/modules/bsql/core/library.dm @@ -1,43 +1,43 @@ -/world/proc/_BSQL_Internal_Call(func, ...) - var/list/call_args = args.Copy(2) - BSQL_Debug("_BSQL_Internal_Call: [args[1]]([call_args.Join(", ")])") - . = call(_BSQL_Library_Path(), func)(arglist(call_args)) - BSQL_Debug("Result: [. == null ? "NULL" : "\"[.]\""]") - -/world/proc/_BSQL_Library_Path() - return system_type == MS_WINDOWS ? "BSQL.dll" : "libBSQL.so" - -/world/proc/_BSQL_InitCheck(datum/BSQL_Connection/caller) - var/static/library_initialized = FALSE - if(_BSQL_Initialized()) - return - var/libPath = _BSQL_Library_Path() - if(!fexists(libPath)) - BSQL_DEL_CALL(caller) - BSQL_ERROR("Could not find [libPath]!") - return - - var/version = _BSQL_Internal_Call("Version") - if(version != BSQL_VERSION) - BSQL_DEL_CALL(caller) - BSQL_ERROR("BSQL DMAPI version mismatch! Expected [BSQL_VERSION], got [version == null ? "NULL" : version]!") - return - - var/result = _BSQL_Internal_Call("Initialize") - if(result) - BSQL_DEL_CALL(caller) - BSQL_ERROR(result) - return - _BSQL_Initialized(TRUE) - -/world/proc/_BSQL_Initialized(new_val) - var/static/bsql_library_initialized = FALSE - if(new_val != null) - bsql_library_initialized = new_val - return bsql_library_initialized - -/world/BSQL_Shutdown() - if(!_BSQL_Initialized()) - return - _BSQL_Internal_Call("Shutdown") - _BSQL_Initialized(FALSE) +/world/proc/_BSQL_Internal_Call(func, ...) + var/list/call_args = args.Copy(2) + BSQL_Debug("_BSQL_Internal_Call: [args[1]]([call_args.Join(", ")])") + . = call(_BSQL_Library_Path(), func)(arglist(call_args)) + BSQL_Debug("Result: [. == null ? "NULL" : "\"[.]\""]") + +/world/proc/_BSQL_Library_Path() + return system_type == MS_WINDOWS ? "BSQL.dll" : "libBSQL.so" + +/world/proc/_BSQL_InitCheck(datum/BSQL_Connection/caller) + var/static/library_initialized = FALSE + if(_BSQL_Initialized()) + return + var/libPath = _BSQL_Library_Path() + if(!fexists(libPath)) + BSQL_DEL_CALL(caller) + BSQL_ERROR("Could not find [libPath]!") + return + + var/version = _BSQL_Internal_Call("Version") + if(version != BSQL_VERSION) + BSQL_DEL_CALL(caller) + BSQL_ERROR("BSQL DMAPI version mismatch! Expected [BSQL_VERSION], got [version == null ? "NULL" : version]!") + return + + var/result = _BSQL_Internal_Call("Initialize") + if(result) + BSQL_DEL_CALL(caller) + BSQL_ERROR(result) + return + _BSQL_Initialized(TRUE) + +/world/proc/_BSQL_Initialized(new_val) + var/static/bsql_library_initialized = FALSE + if(new_val != null) + bsql_library_initialized = new_val + return bsql_library_initialized + +/world/BSQL_Shutdown() + if(!_BSQL_Initialized()) + return + _BSQL_Internal_Call("Shutdown") + _BSQL_Initialized(FALSE) diff --git a/code/modules/bsql/core/operation.dm b/code/modules/bsql/core/operation.dm index 50dce6ae5f64..a2cdbbe1eec0 100644 --- a/code/modules/bsql/core/operation.dm +++ b/code/modules/bsql/core/operation.dm @@ -1,47 +1,47 @@ -/datum/BSQL_Operation - var/datum/BSQL_Connection/connection - var/id - -BSQL_PROTECT_DATUM(/datum/BSQL_Operation) - -/datum/BSQL_Operation/New(datum/BSQL_Connection/connection, id) - src.connection = connection - src.id = id - -BSQL_DEL_PROC(/datum/BSQL_Operation) - var/error - if(!BSQL_IS_DELETED(connection)) - error = world._BSQL_Internal_Call("ReleaseOperation", connection.id, id) - . = ..() - if(error) - BSQL_ERROR(error) - -/datum/BSQL_Operation/IsComplete() - if(BSQL_IS_DELETED(connection)) - return TRUE - var/result = world._BSQL_Internal_Call("OpComplete", connection.id, id) - if(!result) - BSQL_ERROR("Error fetching operation [id] for connection [connection.id]!") - return - return result == "DONE" - -/datum/BSQL_Operation/GetError() - if(BSQL_IS_DELETED(connection)) - return "Connection deleted!" - return world._BSQL_Internal_Call("GetError", connection.id, id) - -/datum/BSQL_Operation/GetErrorCode() - if(BSQL_IS_DELETED(connection)) - return -2 - return text2num(world._BSQL_Internal_Call("GetErrorCode", connection.id, id)) - -/datum/BSQL_Operation/WaitForCompletion() - if(BSQL_IS_DELETED(connection)) - return - var/error = world._BSQL_Internal_Call("BlockOnOperation", connection.id, id) - if(error) - if(error == "Operation timed out!") //match this with the implementation - return FALSE - BSQL_ERROR("Error waiting for operation [id] for connection [connection.id]! [error]") - return - return TRUE +/datum/BSQL_Operation + var/datum/BSQL_Connection/connection + var/id + +BSQL_PROTECT_DATUM(/datum/BSQL_Operation) + +/datum/BSQL_Operation/New(datum/BSQL_Connection/connection, id) + src.connection = connection + src.id = id + +BSQL_DEL_PROC(/datum/BSQL_Operation) + var/error + if(!BSQL_IS_DELETED(connection)) + error = world._BSQL_Internal_Call("ReleaseOperation", connection.id, id) + . = ..() + if(error) + BSQL_ERROR(error) + +/datum/BSQL_Operation/IsComplete() + if(BSQL_IS_DELETED(connection)) + return TRUE + var/result = world._BSQL_Internal_Call("OpComplete", connection.id, id) + if(!result) + BSQL_ERROR("Error fetching operation [id] for connection [connection.id]!") + return + return result == "DONE" + +/datum/BSQL_Operation/GetError() + if(BSQL_IS_DELETED(connection)) + return "Connection deleted!" + return world._BSQL_Internal_Call("GetError", connection.id, id) + +/datum/BSQL_Operation/GetErrorCode() + if(BSQL_IS_DELETED(connection)) + return -2 + return text2num(world._BSQL_Internal_Call("GetErrorCode", connection.id, id)) + +/datum/BSQL_Operation/WaitForCompletion() + if(BSQL_IS_DELETED(connection)) + return + var/error = world._BSQL_Internal_Call("BlockOnOperation", connection.id, id) + if(error) + if(error == "Operation timed out!") //match this with the implementation + return FALSE + BSQL_ERROR("Error waiting for operation [id] for connection [connection.id]! [error]") + return + return TRUE diff --git a/code/modules/bsql/core/query.dm b/code/modules/bsql/core/query.dm index 96c3714c7156..fc09fb06b0be 100644 --- a/code/modules/bsql/core/query.dm +++ b/code/modules/bsql/core/query.dm @@ -1,35 +1,35 @@ -/datum/BSQL_Operation/Query - var/last_result_json - var/list/last_result - -BSQL_PROTECT_DATUM(/datum/BSQL_Operation/Query) - -/datum/BSQL_Operation/Query/CurrentRow() - return last_result - -/datum/BSQL_Operation/Query/IsComplete() - //whole different ballgame here - if(BSQL_IS_DELETED(connection)) - return TRUE - var/result = world._BSQL_Internal_Call("ReadyRow", connection.id, id) - switch(result) - if("DONE") - //load the data - LoadQueryResult() - return TRUE - if("NOTDONE") - return FALSE - else - BSQL_ERROR(result) - -/datum/BSQL_Operation/Query/WaitForCompletion() - . = ..() - if(.) - LoadQueryResult() - -/datum/BSQL_Operation/Query/proc/LoadQueryResult() - last_result_json = world._BSQL_Internal_Call("GetRow", connection.id, id) - if(last_result_json) - last_result = json_decode(last_result_json) - else - last_result = null +/datum/BSQL_Operation/Query + var/last_result_json + var/list/last_result + +BSQL_PROTECT_DATUM(/datum/BSQL_Operation/Query) + +/datum/BSQL_Operation/Query/CurrentRow() + return last_result + +/datum/BSQL_Operation/Query/IsComplete() + //whole different ballgame here + if(BSQL_IS_DELETED(connection)) + return TRUE + var/result = world._BSQL_Internal_Call("ReadyRow", connection.id, id) + switch(result) + if("DONE") + //load the data + LoadQueryResult() + return TRUE + if("NOTDONE") + return FALSE + else + BSQL_ERROR(result) + +/datum/BSQL_Operation/Query/WaitForCompletion() + . = ..() + if(.) + LoadQueryResult() + +/datum/BSQL_Operation/Query/proc/LoadQueryResult() + last_result_json = world._BSQL_Internal_Call("GetRow", connection.id, id) + if(last_result_json) + last_result = json_decode(last_result_json) + else + last_result = null diff --git a/code/modules/bsql/includes.dm b/code/modules/bsql/includes.dm index ec199a5513a6..d05dcb64519b 100644 --- a/code/modules/bsql/includes.dm +++ b/code/modules/bsql/includes.dm @@ -1,4 +1,4 @@ -#include "core\connection.dm" -#include "core\library.dm" -#include "core\operation.dm" -#include "core\query.dm" +#include "core\connection.dm" +#include "core\library.dm" +#include "core\operation.dm" +#include "core\query.dm" diff --git a/code/modules/cargo/bounty_console.dm b/code/modules/cargo/bounty_console.dm index ea98605506df..3e01faf874f6 100644 --- a/code/modules/cargo/bounty_console.dm +++ b/code/modules/cargo/bounty_console.dm @@ -1,96 +1,96 @@ -#define PRINTER_TIMEOUT 10 - - - -/obj/machinery/computer/bounty - name = "Nanotrasen bounty console" - desc = "Used to check and claim bounties offered by Nanotrasen" - icon_screen = "bounty" - circuit = /obj/item/circuitboard/computer/bounty - light_color = "#E2853D"//orange - var/printer_ready = 0 //cooldown var - -/obj/machinery/computer/bounty/Initialize() - . = ..() - printer_ready = world.time + PRINTER_TIMEOUT - -/obj/machinery/computer/bounty/proc/print_paper() - new /obj/item/paper/bounty_printout(loc) - -/obj/item/paper/bounty_printout - name = "paper - Bounties" - -/obj/item/paper/bounty_printout/Initialize() - . = ..() - info = "

                Nanotrasen Cargo Bounties


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

                [B.name]

                -
                • Reward: [B.reward_string()]
                • -
                • Completed: [B.completion_string()]
                "} - -/obj/machinery/computer/bounty/ui_interact(mob/user) - . = ..() - - if(!GLOB.bounties_list.len) - setup_bounties() - - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - var/list/dat = list({"Refresh - Print Paper -

                Credits: [D.account_balance]

                - - "}) - for(var/datum/bounty/B in GLOB.bounties_list) - if(B.claimed) - dat += "" - else if(B.can_claim()) - dat += "" - else - dat += "" - - if(B.high_priority) - dat += {" - - "} - else - dat += {" - - "} - dat += "" - if(B.claimed) - dat += "" - else if(B.can_claim()) - dat += "" - else - dat += "" - dat += "" - dat += "
                NameDescriptionRewardCompletionStatus
                [B.name]High Priority: [B.description][B.reward_string()][B.name][B.description][B.reward_string()][B.completion_string()]ClaimedClaimUnclaimed
                " - dat = dat.Join() - var/datum/browser/popup = new(user, "bounties", "Nanotrasen Bounties", 700, 600) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/bounty/Topic(href, href_list) - if(..()) - return - - switch(href_list["choice"]) - if("Print") - if(printer_ready < world.time) - printer_ready = world.time + PRINTER_TIMEOUT - print_paper() - - if("Claim") - var/datum/bounty/B = locate(href_list["d_rec"]) in GLOB.bounties_list - if(B) - B.claim() - - if(href_list["refresh"]) - playsound(src, "terminal_type", 25, 0) - - updateUsrDialog() +#define PRINTER_TIMEOUT 10 + + + +/obj/machinery/computer/bounty + name = "Nanotrasen bounty console" + desc = "Used to check and claim bounties offered by Nanotrasen" + icon_screen = "bounty" + circuit = /obj/item/circuitboard/computer/bounty + light_color = "#E2853D"//orange + var/printer_ready = 0 //cooldown var + +/obj/machinery/computer/bounty/Initialize() + . = ..() + printer_ready = world.time + PRINTER_TIMEOUT + +/obj/machinery/computer/bounty/proc/print_paper() + new /obj/item/paper/bounty_printout(loc) + +/obj/item/paper/bounty_printout + name = "paper - Bounties" + +/obj/item/paper/bounty_printout/Initialize() + . = ..() + info = "

                Nanotrasen Cargo Bounties


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

                [B.name]

                +
                • Reward: [B.reward_string()]
                • +
                • Completed: [B.completion_string()]
                "} + +/obj/machinery/computer/bounty/ui_interact(mob/user) + . = ..() + + if(!GLOB.bounties_list.len) + setup_bounties() + + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + var/list/dat = list({"Refresh + Print Paper +

                Credits: [D.account_balance]

                + + "}) + for(var/datum/bounty/B in GLOB.bounties_list) + if(B.claimed) + dat += "" + else if(B.can_claim()) + dat += "" + else + dat += "" + + if(B.high_priority) + dat += {" + + "} + else + dat += {" + + "} + dat += "" + if(B.claimed) + dat += "" + else if(B.can_claim()) + dat += "" + else + dat += "" + dat += "" + dat += "
                NameDescriptionRewardCompletionStatus
                [B.name]High Priority: [B.description][B.reward_string()][B.name][B.description][B.reward_string()][B.completion_string()]ClaimedClaimUnclaimed
                " + dat = dat.Join() + var/datum/browser/popup = new(user, "bounties", "Nanotrasen Bounties", 700, 600) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/bounty/Topic(href, href_list) + if(..()) + return + + switch(href_list["choice"]) + if("Print") + if(printer_ready < world.time) + printer_ready = world.time + PRINTER_TIMEOUT + print_paper() + + if("Claim") + var/datum/bounty/B = locate(href_list["d_rec"]) in GLOB.bounties_list + if(B) + B.claim() + + if(href_list["refresh"]) + playsound(src, "terminal_type", 25, 0) + + updateUsrDialog() diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm index 219f5b283909..c7296595418c 100644 --- a/code/modules/cargo/console.dm +++ b/code/modules/cargo/console.dm @@ -1,241 +1,241 @@ -/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 - 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 - 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() - var/cat = EXPORT_CARGO - if(contraband) - cat |= EXPORT_CONTRABAND - if(obj_flags & EMAGGED) - cat |= 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 - -/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() - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - data["points"] = D.account_balance - data["away"] = SSshuttle.supply.getDockedId() == "supply_away" - data["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. - "small_item" = P.small_item, - "access" = P.access - )) - - data["cart"] = list() - for(var/datum/supply_order/SO in SSshuttle.shoppinglist) - data["cart"] += list(list( - "object" = SO.pack.name, - "cost" = SO.pack.cost, - "id" = SO.id, - "orderer" = SO.orderer, - "paid" = !isnull(SO.paying_account) //paid by requester - )) - - data["requests"] = list() - for(var/datum/supply_order/SO in SSshuttle.requestlist) - data["requests"] += list(list( - "object" = SO.pack.name, - "cost" = SO.pack.cost, - "orderer" = SO.orderer, - "reason" = SO.reason, - "id" = SO.id - )) - - return data - -/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui) - if(..()) - 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/self_paid = text2num(params["self_paid"]) - var/datum/supply_pack/pack = SSshuttle.supply_packs[id] - if(!istype(pack)) - return - if((pack.hidden && !(obj_flags & EMAGGED)) || (pack.contraband && !contraband) || pack.DropPodOnly) - return - - var/name = "*None Provided*" - var/rank = "*None Provided*" - var/ckey = usr.ckey - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - name = H.get_authentification_name() - rank = H.get_assignment(hand_first = TRUE) - else if(issilicon(usr)) - name = usr.real_name - rank = "Silicon" - - var/datum/bank_account/account - if(self_paid && ishuman(usr)) - var/mob/living/carbon/human/H = usr - var/obj/item/card/id/id_card = H.get_idcard(TRUE) - if(!istype(id_card)) - say("No ID card detected.") - return - account = id_card.registered_account - if(!istype(account)) - say("Invalid bank account.") - return - - var/reason = "" - if(requestonly && !self_paid) - reason = stripped_input("Reason:", name, "") - if(isnull(reason) || ..()) - return - - var/turf/T = get_turf(src) - var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account) - SO.generateRequisition(T) - if(requestonly && !self_paid) - SSshuttle.requestlist += SO - else - SSshuttle.shoppinglist += SO - if(self_paid) - say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") - . = 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 + 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 + 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() + var/cat = EXPORT_CARGO + if(contraband) + cat |= EXPORT_CONTRABAND + if(obj_flags & EMAGGED) + cat |= 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 + +/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() + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + data["points"] = D.account_balance + data["away"] = SSshuttle.supply.getDockedId() == "supply_away" + data["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. + "small_item" = P.small_item, + "access" = P.access + )) + + data["cart"] = list() + for(var/datum/supply_order/SO in SSshuttle.shoppinglist) + data["cart"] += list(list( + "object" = SO.pack.name, + "cost" = SO.pack.cost, + "id" = SO.id, + "orderer" = SO.orderer, + "paid" = !isnull(SO.paying_account) //paid by requester + )) + + data["requests"] = list() + for(var/datum/supply_order/SO in SSshuttle.requestlist) + data["requests"] += list(list( + "object" = SO.pack.name, + "cost" = SO.pack.cost, + "orderer" = SO.orderer, + "reason" = SO.reason, + "id" = SO.id + )) + + return data + +/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui) + if(..()) + 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/self_paid = text2num(params["self_paid"]) + var/datum/supply_pack/pack = SSshuttle.supply_packs[id] + if(!istype(pack)) + return + if((pack.hidden && !(obj_flags & EMAGGED)) || (pack.contraband && !contraband) || pack.DropPodOnly) + return + + var/name = "*None Provided*" + var/rank = "*None Provided*" + var/ckey = usr.ckey + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + name = H.get_authentification_name() + rank = H.get_assignment(hand_first = TRUE) + else if(issilicon(usr)) + name = usr.real_name + rank = "Silicon" + + var/datum/bank_account/account + if(self_paid && ishuman(usr)) + var/mob/living/carbon/human/H = usr + var/obj/item/card/id/id_card = H.get_idcard(TRUE) + if(!istype(id_card)) + say("No ID card detected.") + return + account = id_card.registered_account + if(!istype(account)) + say("Invalid bank account.") + return + + var/reason = "" + if(requestonly && !self_paid) + reason = stripped_input("Reason:", name, "") + if(isnull(reason) || ..()) + return + + var/turf/T = get_turf(src) + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account) + SO.generateRequisition(T) + if(requestonly && !self_paid) + SSshuttle.requestlist += SO + else + SSshuttle.shoppinglist += SO + if(self_paid) + say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") + . = 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 d4a970b5614e..37c66f7656cc 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -1,212 +1,212 @@ -#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//doesnt effect circuit board, making reversal possible - to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.") - qdel(W) - return TRUE - else if(istype(W, /obj/item/supplypod_beacon)) - var/obj/item/supplypod_beacon/sb = W - if (sb.express_console != src) - sb.link_console(src, user) - return TRUE - else - to_chat(user, "[src] is already linked to [sb].") - ..() - -/obj/machinery/computer/cargo/express/emag_act(mob/living/user) - if(obj_flags & EMAGGED) - return - user.visible_message("[user] swipes a suspicious card through [src]!", - "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.") - obj_flags |= EMAGGED - // This also sets this on the circuit board - var/obj/item/circuitboard/computer/cargo/board = circuit - board.obj_flags |= EMAGGED - packin_up() - -/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry - meme_pack_data = list() // sorry for what? - for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all - if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice - meme_pack_data[P.group] = list( // it gets cheaper when I return it - "name" = P.group, // mmhm - "packs" = list() // sometimes, I return it so much, I rip the manifest - ) // see, my quartermaster taught me a few things too - if((P.hidden) || (P.special)) // like, how not to rip the manifest - continue// by using someone else's crate - if(!(obj_flags & EMAGGED) && P.contraband) // will you show me? - continue // i'd be right happy to - meme_pack_data[P.group]["packs"] += list(list( - "name" = P.name, - "cost" = P.cost, - "id" = pack, - "desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name. - )) - -/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "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() - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - data["points"] = D.account_balance - data["locked"] = locked//swipe an ID to unlock - data["siliconUser"] = user.has_unlimited_silicon_privilege - data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui - data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay? - data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location? - data["canBuyBeacon"] = cooldown <= 0 && D.account_balance >= BEACON_COST - data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary - data["hasBeacon"] = beacon != null//is there a linked beacon? - data["beaconName"] = beacon ? beacon.name : "No Beacon Found" - data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons - data["supplies"] = list() - message = "Sales are near-instantaneous - please choose carefully." - if(SSshuttle.supplyBlocked) - message = blockade_warning - if(usingBeacon && !beacon) - message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed - else if (usingBeacon && !canBeacon) - message = "BEACON ERROR: MUST BE EXPOSED"//beacon's loc/user's loc must be a turf - if(obj_flags & EMAGGED) - message = "(&!#@ERROR: ROUTING_#PROTOCOL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD." - data["message"] = message - if(!meme_pack_data) - packin_up() - stack_trace("You didn't give the cargo tech good advice, and he ripped the manifest. As a result, there was no pack data for [src]") - data["supplies"] = meme_pack_data - if (cooldown > 0)//cooldown used for printing beacons - cooldown-- - return data - -/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) - switch(action) - if("LZCargo") - usingBeacon = FALSE - if (beacon) - beacon.update_status(SP_UNREADY) //ready light on beacon will turn off - if("LZBeacon") - usingBeacon = TRUE - if (beacon) - beacon.update_status(SP_READY) //turns on the beacon's ready light - if("printBeacon") - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - if(D.adjust_money(-BEACON_COST)) - cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam - var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location()) - C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc) - printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1 - beacon.name = "Supply Pod Beacon #[printed_beacons]" - - - if("add")//Generate Supply Order first - var/id = text2path(params["id"]) - var/datum/supply_pack/pack = SSshuttle.supply_packs[id] - if(!istype(pack)) - return - var/name = "*None Provided*" - var/rank = "*None Provided*" - var/ckey = usr.ckey - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - name = H.get_authentification_name() - rank = H.get_assignment(hand_first = TRUE) - else if(issilicon(usr)) - name = usr.real_name - rank = "Silicon" - var/reason = "" - var/list/empty_turfs - var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) - var/points_to_check - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - points_to_check = D.account_balance - if(!(obj_flags & EMAGGED)) - if(SO.pack.cost <= points_to_check) - var/LZ - if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay - LZ = get_turf(beacon) - beacon.update_status(SP_LAUNCH) - else if (!usingBeacon)//find a suitable supplypod landing zone in cargobay - landingzone = GLOB.areas_by_type[/area/quartermaster/storage] - if (!landingzone) - WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.") - landingzone = get_area(src) - for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone - if(is_blocked_turf(T)) - continue - LAZYADD(empty_turfs, T) - CHECK_TICK - if(empty_turfs && empty_turfs.len) - LZ = pick(empty_turfs) - if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call - D.adjust_money(-SO.pack.cost) - new /obj/effect/DPtarget(LZ, podType, SO) - . = TRUE - update_icon() - else - if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^) - landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone - for(var/turf/open/floor/T in landingzone.contents) - if(is_blocked_turf(T)) - continue - LAZYADD(empty_turfs, T) - CHECK_TICK - if(empty_turfs && empty_turfs.len) - D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS))) - - SO.generateRequisition(get_turf(src)) - for(var/i in 1 to MAX_EMAG_ROCKETS) - var/LZ = pick(empty_turfs) - LAZYREMOVE(empty_turfs, LZ) - new /obj/effect/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//doesnt effect circuit board, making reversal possible + to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.") + qdel(W) + return TRUE + else if(istype(W, /obj/item/supplypod_beacon)) + var/obj/item/supplypod_beacon/sb = W + if (sb.express_console != src) + sb.link_console(src, user) + return TRUE + else + to_chat(user, "[src] is already linked to [sb].") + ..() + +/obj/machinery/computer/cargo/express/emag_act(mob/living/user) + if(obj_flags & EMAGGED) + return + user.visible_message("[user] swipes a suspicious card through [src]!", + "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.") + obj_flags |= EMAGGED + // This also sets this on the circuit board + var/obj/item/circuitboard/computer/cargo/board = circuit + board.obj_flags |= EMAGGED + packin_up() + +/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry + meme_pack_data = list() // sorry for what? + for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs + var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all + if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice + meme_pack_data[P.group] = list( // it gets cheaper when I return it + "name" = P.group, // mmhm + "packs" = list() // sometimes, I return it so much, I rip the manifest + ) // see, my quartermaster taught me a few things too + if((P.hidden) || (P.special)) // like, how not to rip the manifest + continue// by using someone else's crate + if(!(obj_flags & EMAGGED) && P.contraband) // will you show me? + continue // i'd be right happy to + meme_pack_data[P.group]["packs"] += list(list( + "name" = P.name, + "cost" = P.cost, + "id" = pack, + "desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name. + )) + +/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "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() + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + data["points"] = D.account_balance + data["locked"] = locked//swipe an ID to unlock + data["siliconUser"] = user.has_unlimited_silicon_privilege + data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui + data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay? + data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location? + data["canBuyBeacon"] = cooldown <= 0 && D.account_balance >= BEACON_COST + data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary + data["hasBeacon"] = beacon != null//is there a linked beacon? + data["beaconName"] = beacon ? beacon.name : "No Beacon Found" + data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons + data["supplies"] = list() + message = "Sales are near-instantaneous - please choose carefully." + if(SSshuttle.supplyBlocked) + message = blockade_warning + if(usingBeacon && !beacon) + message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed + else if (usingBeacon && !canBeacon) + message = "BEACON ERROR: MUST BE EXPOSED"//beacon's loc/user's loc must be a turf + if(obj_flags & EMAGGED) + message = "(&!#@ERROR: ROUTING_#PROTOCOL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD." + data["message"] = message + if(!meme_pack_data) + packin_up() + stack_trace("You didn't give the cargo tech good advice, and he ripped the manifest. As a result, there was no pack data for [src]") + data["supplies"] = meme_pack_data + if (cooldown > 0)//cooldown used for printing beacons + cooldown-- + return data + +/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) + switch(action) + if("LZCargo") + usingBeacon = FALSE + if (beacon) + beacon.update_status(SP_UNREADY) //ready light on beacon will turn off + if("LZBeacon") + usingBeacon = TRUE + if (beacon) + beacon.update_status(SP_READY) //turns on the beacon's ready light + if("printBeacon") + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + if(D.adjust_money(-BEACON_COST)) + cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam + var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location()) + C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc) + printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1 + beacon.name = "Supply Pod Beacon #[printed_beacons]" + + + if("add")//Generate Supply Order first + var/id = text2path(params["id"]) + var/datum/supply_pack/pack = SSshuttle.supply_packs[id] + if(!istype(pack)) + return + var/name = "*None Provided*" + var/rank = "*None Provided*" + var/ckey = usr.ckey + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + name = H.get_authentification_name() + rank = H.get_assignment(hand_first = TRUE) + else if(issilicon(usr)) + name = usr.real_name + rank = "Silicon" + var/reason = "" + var/list/empty_turfs + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) + var/points_to_check + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + points_to_check = D.account_balance + if(!(obj_flags & EMAGGED)) + if(SO.pack.cost <= points_to_check) + var/LZ + if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay + LZ = get_turf(beacon) + beacon.update_status(SP_LAUNCH) + else if (!usingBeacon)//find a suitable supplypod landing zone in cargobay + landingzone = GLOB.areas_by_type[/area/quartermaster/storage] + if (!landingzone) + WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.") + landingzone = get_area(src) + for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone + if(is_blocked_turf(T)) + continue + LAZYADD(empty_turfs, T) + CHECK_TICK + if(empty_turfs && empty_turfs.len) + LZ = pick(empty_turfs) + if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call + D.adjust_money(-SO.pack.cost) + new /obj/effect/DPtarget(LZ, podType, SO) + . = TRUE + update_icon() + else + if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^) + landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone + for(var/turf/open/floor/T in landingzone.contents) + if(is_blocked_turf(T)) + continue + LAZYADD(empty_turfs, T) + CHECK_TICK + if(empty_turfs && empty_turfs.len) + D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS))) + + SO.generateRequisition(get_turf(src)) + for(var/i in 1 to MAX_EMAG_ROCKETS) + var/LZ = pick(empty_turfs) + LAZYREMOVE(empty_turfs, LZ) + new /obj/effect/DPtarget(LZ, podType, SO) + . = TRUE + update_icon() + CHECK_TICK diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm index 80301c036d64..1ddcf4ac1f8e 100644 --- a/code/modules/cargo/gondolapod.dm +++ b/code/modules/cargo/gondolapod.dm @@ -1,73 +1,73 @@ -/mob/living/simple_animal/pet/gondola/gondolapod - name = "gondola" - real_name = "gondola" - desc = "The silent walker. This one seems to be part of a delivery agency." - response_help = "pets" - response_disarm = "bops" - response_harm = "kicks" - faction = list("gondola") - turns_per_move = 10 - icon = 'icons/mob/gondolapod.dmi' - icon_state = "gondolapod" - icon_living = "gondolapod" - pixel_x = -16//2x2 sprite - pixel_y = -5 - layer = TABLE_LAYER//so that deliveries dont appear underneath it - loot = list(/obj/effect/decal/cleanable/blood/gibs, /obj/item/stack/sheet/animalhide/gondola = 2, /obj/item/reagent_containers/food/snacks/meat/slab/gondola = 2) - //Gondolas aren't affected by cold. - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = 1500 - maxHealth = 200 - health = 200 - del_on_death = TRUE - var/opened = FALSE - var/obj/structure/closet/supplypod/centcompod/linked_pod - -/mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod) - linked_pod = pod - name = linked_pod.name - . = ..() - -/mob/living/simple_animal/pet/gondola/gondolapod/proc/update_icon() - if(opened) - icon_state = "gondolapod_open" - else - icon_state = "gondolapod" - -/mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver() - set name = "Release Contents" - set category = "Gondola" - set desc = "Release any contents stored within your vast belly." - linked_pod.open(src, forced = TRUE) - -/mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user) - . = ..() - if (contents.len) - . += "It looks like it hasn't made its delivery yet.
                " - else - . += "It looks like it has already made its delivery.
                " - -/mob/living/simple_animal/pet/gondola/gondolapod/verb/check() - set name = "Count Contents" - set category = "Gondola" - set desc = "Take a deep look inside youself, and count up what's inside" - var/total = contents.len - if (total) - to_chat(src, "You detect [total] object\s within your incredibly vast belly.") - else - to_chat(src, "A closer look inside yourself reveals... nothing.") - -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setOpened() - opened = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/setClosed), 50) - -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setClosed() - opened = FALSE - update_icon() - -/mob/living/simple_animal/pet/gondola/gondolapod/death() - qdel(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death - qdel(src) - ..() +/mob/living/simple_animal/pet/gondola/gondolapod + name = "gondola" + real_name = "gondola" + desc = "The silent walker. This one seems to be part of a delivery agency." + response_help = "pets" + response_disarm = "bops" + response_harm = "kicks" + faction = list("gondola") + turns_per_move = 10 + icon = 'icons/mob/gondolapod.dmi' + icon_state = "gondolapod" + icon_living = "gondolapod" + pixel_x = -16//2x2 sprite + pixel_y = -5 + layer = TABLE_LAYER//so that deliveries dont appear underneath it + loot = list(/obj/effect/decal/cleanable/blood/gibs, /obj/item/stack/sheet/animalhide/gondola = 2, /obj/item/reagent_containers/food/snacks/meat/slab/gondola = 2) + //Gondolas aren't affected by cold. + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + maxbodytemp = 1500 + maxHealth = 200 + health = 200 + del_on_death = TRUE + var/opened = FALSE + var/obj/structure/closet/supplypod/centcompod/linked_pod + +/mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod) + linked_pod = pod + name = linked_pod.name + . = ..() + +/mob/living/simple_animal/pet/gondola/gondolapod/proc/update_icon() + if(opened) + icon_state = "gondolapod_open" + else + icon_state = "gondolapod" + +/mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver() + set name = "Release Contents" + set category = "Gondola" + set desc = "Release any contents stored within your vast belly." + linked_pod.open(src, forced = TRUE) + +/mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user) + . = ..() + if (contents.len) + . += "It looks like it hasn't made its delivery yet." + else + . += "It looks like it has already made its delivery." + +/mob/living/simple_animal/pet/gondola/gondolapod/verb/check() + set name = "Count Contents" + set category = "Gondola" + set desc = "Take a deep look inside youself, and count up what's inside" + var/total = contents.len + if (total) + to_chat(src, "You detect [total] object\s within your incredibly vast belly.") + else + to_chat(src, "A closer look inside yourself reveals... nothing.") + +/mob/living/simple_animal/pet/gondola/gondolapod/proc/setOpened() + opened = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/setClosed), 50) + +/mob/living/simple_animal/pet/gondola/gondolapod/proc/setClosed() + opened = FALSE + update_icon() + +/mob/living/simple_animal/pet/gondola/gondolapod/death() + qdel(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death + qdel(src) + ..() diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 2473650f85c0..7185ae5ffbda 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/ban_cache = null //Used to cache this client's bans 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 - - var/total_message_count = 0 //How many messages sent in the last 10 seconds - var/total_count_reset = 0 //Next tick to reset the total message counter - - ///////// - //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/encoding = "1252" // yogs - LibVG + +/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/ban_cache = null //Used to cache this client's bans 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 + + var/total_message_count = 0 //How many messages sent in the last 10 seconds + var/total_count_reset = 0 //Next tick to reset the total message counter + + ///////// + //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/encoding = "1252" // yogs - LibVG diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index aec552d9e52e..7ed61345ed7b 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -1,1741 +1,1741 @@ -GLOBAL_LIST_EMPTY(preferences_datums) - -/datum/preferences - var/client/parent - //doohickeys for savefiles - var/path - var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used - var/max_save_slots = 3 - - //non-preference stuff - var/muted = 0 - var/last_ip - var/last_id - - //game-preferences - var/lastchangelog = "" //Saved changlog filesize to detect if there was a change - var/ooccolor = "#c43b23" - var/asaycolor = null - var/enable_tips = TRUE - var/tip_delay = 500 //tip delay in milliseconds - - //Antag preferences - var/list/be_special = list() //Special role selection - var/tmp/old_be_special = 0 //Bitflag version of be_special, used to update old savefiles and nothing more - //If it's 0, that's good, if it's anything but 0, the owner of this prefs file's antag choices were, - //autocorrected this round, not that you'd need to check that. - - var/UI_style = null - var/buttons_locked = FALSE - var/hotkeys = TRUE // yogs - Rebindable Keybindings - var/tgui_fancy = TRUE - var/tgui_lock = TRUE - var/windowflashing = TRUE - var/toggles = TOGGLES_DEFAULT - var/db_flags - var/chat_toggles = TOGGLES_DEFAULT_CHAT - var/ghost_form = "ghost" - var/ghost_orbit = GHOST_ORBIT_CIRCLE - var/ghost_accs = GHOST_ACCS_DEFAULT_OPTION - var/ghost_others = GHOST_OTHERS_DEFAULT_OPTION - var/ghost_hud = 1 - var/inquisitive_ghost = 1 - var/allow_midround_antag = 1 - var/preferred_map = null - var/pda_style = MONO - var/pda_color = "#808000" - - var/uses_glasses_colour = 0 - - //character preferences - var/real_name //our character's name - var/be_random_name = 0 //whether we'll have a random name every round - var/be_random_body = 0 //whether we'll have a random body every round - var/gender = MALE //gender of character (well duh) - var/age = 30 //age of character - var/underwear = "Nude" //underwear type - var/undershirt = "Nude" //undershirt type - var/socks = "Nude" //socks type - var/backbag = DBACKPACK //backpack type - var/hair_style = "Bald" //Hair type - var/hair_color = "000" //Hair color - var/facial_hair_style = "Shaved" //Face hair type - var/facial_hair_color = "000" //Facial hair color - var/skin_tone = "caucasian1" //Skin color - var/eye_color = "000" //Eye color - var/datum/species/pref_species = new /datum/species/human() //Mutant race - var/list/features = list("mcolor" = "FFF", "ethcolor" = "9c3030", "tail_lizard" = "Smooth", "tail_human" = "None", "snout" = "Round", "horns" = "None", "ears" = "None", "wings" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs", "moth_wings" = "Plain") - var/list/genders = list(MALE, FEMALE, PLURAL) - var/list/friendlyGenders = list("Male" = "male", "Female" = "female", "Other" = "plural") - - var/list/custom_names = list() - var/preferred_ai_core_display = "Blue" - var/prefered_security_department = SEC_DEPT_RANDOM - - //Quirk list - var/list/all_quirks = list() - - //Job preferences 2.0 - indexed by job title , no key or value implies never - var/list/job_preferences = list() - - // Want randomjob if preferences already filled - Donkie - var/joblessrole = BERANDOMJOB //defaults to 1 for fewer assistants - - // 0 = character settings, 1 = game preferences - var/current_tab = 0 - - var/unlock_content = 0 - - var/list/ignoring = list() - - var/clientfps = 0 - - var/parallax - - var/ambientocclusion = TRUE - var/auto_fit_viewport = FALSE - var/widescreenpref = TRUE - - var/uplink_spawn_loc = UPLINK_PDA - - var/list/exp = list() - var/list/menuoptions - - var/action_buttons_screen_locs = list() - -/datum/preferences/New(client/C) - parent = C - - for(var/custom_name_id in GLOB.preferences_custom_names) - custom_names[custom_name_id] = get_default_name(custom_name_id) - - UI_style = GLOB.available_ui_styles[1] - if(istype(C)) - if(!IsGuestKey(C.key)) - load_path(C.ckey) - unlock_content |= C.IsByondMember() // yogs - Donor features - if(unlock_content) - max_save_slots = 8 - // yogs start - Donor features - else if(is_donator(C) || (C.ckey in get_donators())) // the Latter handles race cases where the prefs are not fully loaded in, or GLOB.donators hasn't loaded in yet - max_save_slots = DONOR_CHARACTER_SLOTS - // yogs end - var/loaded_preferences_successfully = load_preferences() - if(loaded_preferences_successfully) - if(load_character()) - return - //we couldn't load character data so just randomize the character appearance + name - random_character() //let's create a random character then - rather than a fat, bald and naked man. - real_name = pref_species.random_name(gender,1) - if(!loaded_preferences_successfully) - save_preferences() - save_character() //let's save this new random character so it doesn't keep generating new ones. - menuoptions = list() - return - -#define APPEARANCE_CATEGORY_COLUMN "" -#define MAX_MUTANT_ROWS 4 - -/datum/preferences/proc/ShowChoices(mob/user) - if(!user || !user.client) - return - - if(!SSjob || (SSjob.occupations.len <= 0)) - to_chat(user, "The job SSticker is not yet finished creating jobs, please try again later") - return - - update_preview_icon() - var/list/dat = list("
                ") - - dat += "Character Settings" - dat += "Game Preferences" - dat += "OOC Preferences" - dat += "Donator Preferences" // yogs - Donor features - dat += "Keybindings" // yogs - Custom keybindings - - if(!path) - dat += "
                Please create an account to save your preferences
                " - - dat += "
                " - - dat += "
                " - - switch(current_tab) - if (0) // Character Settings# - if(path) - var/savefile/S = new /savefile(path) - if(S) - dat += "
                " - var/name - var/unspaced_slots = 0 - for(var/i=1, i<=max_save_slots, i++) - unspaced_slots++ - if(unspaced_slots > 4) - dat += "
                " - unspaced_slots = 0 - S.cd = "/character[i]" - S["real_name"] >> name - if(!name) - name = "Character[i]" - dat += "[name] " - dat += "
                " - - dat += "

                Occupation Choices

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

                Quirk Setup

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

                Identity

                " - dat += "" - - dat += "
                " - if(is_banned_from(user.ckey, "Appearance")) - dat += "You are banned from using custom names and appearances. You can continue to adjust your characters, but you will be randomised once you join the game.
                " - dat += "Random Name " - dat += "Always Random Name: [be_random_name ? "Yes" : "No"]
                " - - dat += "Name: " - dat += "[real_name]
                " - - if(!(AGENDER in pref_species.species_traits)) - var/dispGender - if(gender == MALE) - dispGender = "Male" - else if(gender == FEMALE) - dispGender = "Female" - else - dispGender = "Other" - dat += "Gender: [dispGender]
                " - dat += "Age: [age]
                " - - dat += "Special Names:
                " - var/old_group - for(var/custom_name_id in GLOB.preferences_custom_names) - var/namedata = GLOB.preferences_custom_names[custom_name_id] - if(!old_group) - old_group = namedata["group"] - else if(old_group != namedata["group"]) - old_group = namedata["group"] - dat += "
                " - dat += "[namedata["pref_name"]]: [custom_names[custom_name_id]] " - dat += "

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

                Body

                " - dat += "Random Body " - dat += "Always Random Body: [be_random_body ? "Yes" : "No"]
                " - - dat += "" - - var/use_skintones = pref_species.use_skintones - if(use_skintones) - - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Skin Tone

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

                Mutant Color

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

                Ethereal Color

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

                Eye Color

                " - - dat += "   Change
                " - - dat += "" - else if(use_skintones || mutant_colors) - dat += "" - - if(HAIR in pref_species.species_traits) - - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Hair Style

                " - - dat += "[hair_style]
                " - dat += "<>
                " - dat += "   Change
                " - - dat += "

                Facial Hair Style

                " - - dat += "[facial_hair_style]
                " - dat += "<>
                " - dat += "   Change
                " - - dat += "" - - //Mutant stuff - var/mutant_category = 0 - - if("tail_lizard" in pref_species.default_features) - if(!mutant_category) - dat += APPEARANCE_CATEGORY_COLUMN - - dat += "

                Tail

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

                Snout

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

                Horns

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

                Frills

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

                Spines

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

                Body Markings

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

                Legs

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

                Moth wings

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

                Tail

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

                Ears

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

                Wings

                " - - dat += "[features["wings"]]
                " - - mutant_category++ - if(mutant_category >= MAX_MUTANT_ROWS) - dat += "" - mutant_category = 0 - - if(mutant_category) - dat += "" - mutant_category = 0 - dat += "
                " - - dat += "Species:
                [pref_species.name]
                " - - dat += "Underwear:
                [underwear]
                " - dat += "Undershirt:
                [undershirt]
                " - dat += "Socks:
                [socks]
                " - dat += "Backpack:
                [backbag]
                " - dat += "Uplink Spawn Location:
                [uplink_spawn_loc]
                " - - - if (1) // Game Preferences - dat += "
                " - dat += "

                General Settings

                " - dat += "UI Style: [UI_style]
                " - dat += "tgui Monitors: [(tgui_lock) ? "Primary" : "All"]
                " - dat += "tgui Style: [(tgui_fancy) ? "Fancy" : "No Frills"]
                " - dat += "
                " - dat += "Action Buttons: [(buttons_locked) ? "Locked In Place" : "Unlocked"]
                " - //dat += "Keybindings: [(hotkeys) ? "Hotkeys" : "Default"]
                " // yogs - Custom keybindings - dat += "
                " - dat += "PDA Color:     Change
                " - dat += "PDA Style: [pda_style]
                " - dat += "
                " - dat += "Ghost Ears: [(chat_toggles & CHAT_GHOSTEARS) ? "All Speech" : "Nearest Creatures"]
                " - dat += "Ghost Radio: [(chat_toggles & CHAT_GHOSTRADIO) ? "All Messages":"No Messages"]
                " - dat += "Ghost Sight: [(chat_toggles & CHAT_GHOSTSIGHT) ? "All Emotes" : "Nearest Creatures"]
                " - dat += "Ghost Whispers: [(chat_toggles & CHAT_GHOSTWHISPER) ? "All Speech" : "Nearest Creatures"]
                " - dat += "Ghost PDA: [(chat_toggles & CHAT_GHOSTPDA) ? "All Messages" : "Nearest Creatures"]
                " - - if(unlock_content) - dat += "Ghost Form: [ghost_form]
                " - dat += "Ghost Orbit: [ghost_orbit]
                " - - var/button_name = "If you see this something went wrong." - switch(ghost_accs) - if(GHOST_ACCS_FULL) - button_name = GHOST_ACCS_FULL_NAME - if(GHOST_ACCS_DIR) - button_name = GHOST_ACCS_DIR_NAME - if(GHOST_ACCS_NONE) - button_name = GHOST_ACCS_NONE_NAME - - dat += "Ghost Accessories: [button_name]
                " - - switch(ghost_others) - if(GHOST_OTHERS_THEIR_SETTING) - button_name = GHOST_OTHERS_THEIR_SETTING_NAME - if(GHOST_OTHERS_DEFAULT_SPRITE) - button_name = GHOST_OTHERS_DEFAULT_SPRITE_NAME - if(GHOST_OTHERS_SIMPLE) - button_name = GHOST_OTHERS_SIMPLE_NAME - - dat += "Ghosts of Others: [button_name]
                " - dat += "
                " - - dat += "Income Updates: [(chat_toggles & CHAT_BANKCARD) ? "Allowed" : "Muted"]
                " - dat += "
                " - - dat += "FPS: [clientfps]
                " - - dat += "Parallax (Fancy Space): " - switch (parallax) - if (PARALLAX_LOW) - dat += "Low" - if (PARALLAX_MED) - dat += "Medium" - if (PARALLAX_INSANE) - dat += "Insane" - if (PARALLAX_DISABLE) - dat += "Disabled" - else - dat += "High" - dat += "
                " - - dat += "Ambient Occlusion: [ambientocclusion ? "Enabled" : "Disabled"]
                " - dat += "Fit Viewport: [auto_fit_viewport ? "Auto" : "Manual"]
                " - if (CONFIG_GET(string/default_view) != CONFIG_GET(string/default_view_square)) - dat += "Widescreen: [widescreenpref ? "Enabled ([CONFIG_GET(string/default_view)])" : "Disabled ([CONFIG_GET(string/default_view_square)])"]
                " - - if (CONFIG_GET(flag/maprotation)) - var/p_map = preferred_map - if (!p_map) - p_map = "Default" - if (config.defaultmap) - p_map += " ([config.defaultmap.map_name])" - else - if (p_map in config.maplist) - var/datum/map_config/VM = config.maplist[p_map] - if (!VM) - p_map += " (No longer exists)" - else - p_map = VM.map_name - else - p_map += " (No longer exists)" - if(CONFIG_GET(flag/preference_map_voting)) - dat += "Preferred Map: [p_map]
                " - //yogs start -- Mood preference toggling - if(CONFIG_GET(flag/disable_human_mood)) - dat += "Mood: [yogtoggles & PREF_MOOD ? "Enabled" : "Disabled"]
                " - //yogs end - - dat += "
                " - - dat += "

                Special Role Settings

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

                OOC Settings

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

                Admin Settings

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

                Deadmin While Playing

                " - if(CONFIG_GET(flag/auto_deadmin_players)) - dat += "Always Deadmin: FORCED
                " - else - dat += "Always Deadmin: [(toggles & DEADMIN_ALWAYS)?"Enabled":"Disabled"]
                " - if(!(toggles & DEADMIN_ALWAYS)) - dat += "
                " - if(!CONFIG_GET(flag/auto_deadmin_antagonists)) - dat += "As Antag: [(toggles & DEADMIN_ANTAGONIST)?"Deadmin":"Keep Admin"]
                " - else - dat += "As Antag: FORCED
                " - - if(!CONFIG_GET(flag/auto_deadmin_heads)) - dat += "As Command: [(toggles & DEADMIN_POSITION_HEAD)?"Deadmin":"Keep Admin"]
                " - else - dat += "As Command: FORCED
                " - - if(!CONFIG_GET(flag/auto_deadmin_security)) - dat += "As Security: [(toggles & DEADMIN_POSITION_SECURITY)?"Deadmin":"Keep Admin"]
                " - else - dat += "As Security: FORCED
                " - - if(!CONFIG_GET(flag/auto_deadmin_silicons)) - dat += "As Silicon: [(toggles & DEADMIN_POSITION_SILICON)?"Deadmin":"Keep Admin"]
                " - else - dat += "As Silicon: FORCED
                " - - dat += "
                " - // yogs start - Donor features - if (3) //Donator preferences - dat += "
                " - dat += "

                Donator Preferences

                " - if(is_donator(user.client)) - dat += "Quiet round: [(src.yogtoggles & QUIET_ROUND) ? "Yes" : "No"]
                " - dat += "Fancy Hat: " - var/typehat = donor_hat ? donor_start_items[donor_hat] : null - var/temp_hat = donor_hat ? (new typehat()) : "None selected" - dat += "Pick [temp_hat]
                " - if(donor_hat) - qdel(temp_hat) - dat += "Fancy Item: " - var/typeitem = donor_item ? donor_start_tools[donor_item] : null - var/temp_item = donor_item ? (new typeitem()) : "None selected" - dat += "Pick [temp_item]
                " - if(donor_item) - qdel(temp_item) - dat += "Fancy PDA: " - dat += "[donor_pdas[donor_pda]]
                " - dat += "Purrbation (Humans only) " - dat += "[purrbation ? "Yes" : "No"]
                " - else - dat += "Donate here" - dat += "
                " - // yogs end - - // yogs start - Custom keybindings - if (4) // Keybindings - dat += "
                [(hotkeys) ? "Hotkeys" : "Default"]" - dat += "Reset to default
                " - if(hotkeys) - var/button - var/button_bound - - dat += "
                " - dat += "

                Client

                " - BUTTON_KEY_MOVEMENT("Move North (up)", ACTION_MOVENORTH, NORTH) - BUTTON_KEY_MOVEMENT("Move West (left)", ACTION_MOVEWEST, WEST) - BUTTON_KEY_MOVEMENT("Move South (down)", ACTION_MOVESOUTH, SOUTH) - BUTTON_KEY_MOVEMENT("Move East (right)", ACTION_MOVEEAST, EAST) - - BUTTON_KEY("OOC", ACTION_OOC) - BUTTON_KEY("LOOC", ACTION_LOOC) - BUTTON_KEY("Adminhelp", ACTION_AHELP) - BUTTON_KEY("Screenshot", ACTION_SCREENSHOT) - BUTTON_KEY("Minimal HUD", ACTION_MINHUD) - - - dat += "

                Mob

                " - BUTTON_KEY("Say", ACTION_SAY) - BUTTON_KEY("Emote", ACTION_ME) - BUTTON_KEY("Stop pulling", ACTION_STOPPULLING) - BUTTON_KEY("Cycle intent clockwise", ACTION_INTENTRIGHT) - BUTTON_KEY("Cycle intent counter-clockwise", ACTION_INTENTLEFT) - BUTTON_KEY("Swap hands", ACTION_SWAPHAND) - BUTTON_KEY("Use item on self", ACTION_USESELF) - BUTTON_KEY("Drop", ACTION_DROP) - BUTTON_KEY("Equip", ACTION_EQUIP) - - dat += "
                " - - dat += "

                Mob

                " - BUTTON_KEY("Target head", ACTION_TARGETHEAD) - BUTTON_KEY("Target right arm", ACTION_TARGETRARM) - BUTTON_KEY("Target chest", ACTION_TARGETCHEST) - BUTTON_KEY("Target left arm", ACTION_TARGETLARM) - BUTTON_KEY("Target right leg", ACTION_TARGETRLEG) - BUTTON_KEY("Target groin", ACTION_TARGETGROIN) - BUTTON_KEY("Target left leg", ACTION_TARGETLLEG) - - BUTTON_KEY("Resist", ACTION_RESIST) - BUTTON_KEY("Toggle throw", ACTION_TOGGLETHROW) - BUTTON_KEY("Help intent", ACTION_INTENTHELP) - BUTTON_KEY("Disarm intent", ACTION_INTENTDISARM) - BUTTON_KEY("Grab intent", ACTION_INTENTGRAB) - BUTTON_KEY("Harm intent", ACTION_INTENTHARM) - - if(parent) - if(parent.mentor_datum) - dat += "

                Mentor

                " - BUTTON_KEY("Mentorsay", ACTION_MENTORCHAT) - - if(parent.holder) - dat += "

                Admin

                " - BUTTON_KEY("Adminchat", ACTION_ASAY) - BUTTON_KEY("Admin ghost", ACTION_AGHOST) - BUTTON_KEY("Player panel", ACTION_PLAYERPANEL) - BUTTON_KEY("Toggle build mode", ACTION_BUILDMODE) - BUTTON_KEY("Stealth mode", ACTION_STEALTHMIN) - BUTTON_KEY("Deadchat", ACTION_DSAY) - - dat += "
                " - else - dat += "Default keybindings selected" - // yogs end - dat += "
                " - - if(!IsGuestKey(user.key)) - dat += "Undo " - dat += "Save Setup " - - dat += "Reset Setup" - dat += "
                " - - winshow(user, "preferences_window", TRUE) - var/datum/browser/popup = new(user, "preferences_browser", "
                Character Setup
                ", 640, 770) - popup.set_content(dat.Join()) - popup.open(FALSE) - onclose(user, "preferences_window", src) - -#undef APPEARANCE_CATEGORY_COLUMN -#undef MAX_MUTANT_ROWS - -/datum/preferences/proc/SetChoices(mob/user, limit = 17, list/splitJobs = list("Chief Engineer"), widthPerColumn = 295, height = 620) - if(!SSjob) - return - - //limit - The amount of jobs allowed per column. Defaults to 17 to make it look nice. - //splitJobs - Allows you split the table by job. You can make different tables for each department by including their heads. Defaults to CE to make it look nice. - //widthPerColumn - Screen's width for every column. - //height - Screen's height. - - var/width = widthPerColumn - - var/HTML = "
                " - if(SSjob.occupations.len <= 0) - HTML += "The job SSticker is not yet finished creating jobs, please try again later" - HTML += "
                Done

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

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

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

                " - else - dat += "
                Choose quirk setup

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

                " - for(var/V in SSquirks.quirks) - var/datum/quirk/T = SSquirks.quirks[V] - var/quirk_name = initial(T.name) - var/has_quirk - var/quirk_cost = initial(T.value) * -1 - var/lock_reason = FALSE // Also marks whether this quirk ought to be locked at all; FALSE implies it's OK for this person to have this quirk - for(var/_V in all_quirks) - if(_V == quirk_name) - has_quirk = TRUE - if(initial(T.mood_quirk) && (CONFIG_GET(flag/disable_human_mood) && !(yogtoggles & PREF_MOOD)))//Yogs -- Adds mood to preferences - lock_reason = "Mood is disabled." - else - var/datum/quirk/t = new T(no_init = TRUE) - lock_reason = t.check_quirk(src) // Yogs -- allows for specific denial of quirks based on current preferences - qdel(t) - if(has_quirk) - if(lock_reason) - all_quirks -= quirk_name - has_quirk = FALSE - else - quirk_cost *= -1 //invert it back, since we'd be regaining this amount - if(quirk_cost > 0) - quirk_cost = "+[quirk_cost]" - var/font_color = "#AAAAFF" - if(initial(T.value) != 0) - font_color = initial(T.value) > 0 ? "#AAFFAA" : "#FFAAAA" - if(lock_reason) - dat += "[quirk_name] - [initial(T.desc)] \ - LOCKED: [lock_reason]
                " - else - if(has_quirk) - dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ - [quirk_name] - [initial(T.desc)]
                " - else - dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ - [quirk_name] - [initial(T.desc)]
                " - dat += "
                Reset Quirks
                " - - var/datum/browser/popup = new(user, "mob_occupation", "
                Quirk Preferences
                ", 900, 600) //no reason not to reuse the occupation window, as it's cleaner that way - popup.set_window_options("can_close=0") - popup.set_content(dat.Join()) - popup.open(FALSE) - -/datum/preferences/proc/GetQuirkBalance() - var/bal = 0 - for(var/V in all_quirks) - var/datum/quirk/T = SSquirks.quirks[V] - bal -= initial(T.value) - return bal - -/datum/preferences/proc/GetPositiveQuirkCount() - . = 0 - for(var/q in all_quirks) - if(SSquirks.quirk_points[q] > 0) - .++ - -/datum/preferences/Topic(href, href_list, hsrc) //yeah, gotta do this I guess.. - . = ..() - if(href_list["close"]) - var/client/C = usr.client - if(C) - C.clear_character_previews() - -/datum/preferences/proc/process_link(mob/user, list/href_list) - // yogs start - Donor features - if(href_list["preference"] == "donor") - if(is_donator(user)) - switch(href_list["task"]) - if("hat") - var/item = input(usr, "What would you like to start with?","Donator fun","Nothing") as null|anything in donor_start_items - if(item) - donor_hat = donor_start_items.Find(item) - else - donor_hat = 0 - if("item") - var/item = input(usr, "What would you like to start with?","Donator fun","Nothing") as null|anything in donor_start_tools - if(item) - donor_item = donor_start_tools.Find(item) - else - donor_item = 0 - if("quiet_round") - yogtoggles ^= QUIET_ROUND - if("pda") - donor_pda = donor_pda % donor_pdas.len + 1 - if("purrbation") - purrbation = !purrbation - else - message_admins("EXPLOIT \[donor\]: [user] tried to access donor only functions (as a non-donor). Attempt made on \"[href_list["preference"]]\" -> \"[href_list["task"]]\".") - // yogs end - if(href_list["bancheck"]) - var/list/ban_details = is_banned_from_with_details(user.ckey, user.client.address, user.client.computer_id, href_list["bancheck"]) - var/admin = FALSE - if(GLOB.admin_datums[user.ckey] || GLOB.deadmins[user.ckey]) - admin = TRUE - for(var/i in ban_details) - if(admin && !text2num(i["applies_to_admins"])) - continue - ban_details = i - break //we only want to get the most recent ban's details - if(ban_details && ban_details.len) - var/expires = "This is a permanent ban." - if(ban_details["expiration_time"]) - expires = " The ban is for [DisplayTimeText(text2num(ban_details["duration"]) MINUTES)] and expires on [ban_details["expiration_time"]] (server time)." - to_chat(user, "You, or another user of this computer or connection ([ban_details["key"]]) is banned from playing [href_list["bancheck"]].
                The ban reason is: [ban_details["reason"]]
                This ban (BanID #[ban_details["id"]]) was applied by [ban_details["admin_key"]] on [ban_details["bantime"]] during round ID [ban_details["round_id"]].
                [expires]
                ") - return - if(href_list["preference"] == "job") - switch(href_list["task"]) - if("close") - user << browse(null, "window=mob_occupation") - ShowChoices(user) - if("reset") - ResetJobs() - SetChoices(user) - if("random") - switch(joblessrole) - if(RETURNTOLOBBY) - if(is_banned_from(user.ckey, SSjob.overflow_role)) - joblessrole = BERANDOMJOB - else - joblessrole = BEOVERFLOW - if(BEOVERFLOW) - joblessrole = BERANDOMJOB - if(BERANDOMJOB) - joblessrole = RETURNTOLOBBY - SetChoices(user) - if("setJobLevel") - UpdateJobPreference(user, href_list["text"], text2num(href_list["level"])) - else - SetChoices(user) - return 1 - - else if(href_list["preference"] == "trait") - switch(href_list["task"]) - if("close") - user << browse(null, "window=mob_occupation") - ShowChoices(user) - if("update") - var/quirk = href_list["trait"] - if(!SSquirks.quirks[quirk]) - return - for(var/V in SSquirks.quirk_blacklist) //V is a list - var/list/L = V - for(var/Q in all_quirks) - if((quirk in L) && (Q in L) && !(Q == quirk)) //two quirks have lined up in the list of the list of quirks that conflict with each other, so return (see quirks.dm for more details) - to_chat(user, "[quirk] is incompatible with [Q].") - return - var/value = SSquirks.quirk_points[quirk] - var/balance = GetQuirkBalance() - if(quirk in all_quirks) - if(balance + value < 0) - to_chat(user, "Refunding this would cause you to go below your balance!") - return - all_quirks -= quirk - else - if(GetPositiveQuirkCount() >= MAX_QUIRKS) - to_chat(user, "You can't have more than [MAX_QUIRKS] positive quirks!") - return - if(balance - value < 0) - to_chat(user, "You don't have enough balance to gain this quirk!") - return - all_quirks += quirk - SetQuirks(user) - if("reset") - all_quirks = list() - SetQuirks(user) - else - SetQuirks(user) - return TRUE - - switch(href_list["task"]) - if("random") - switch(href_list["preference"]) - if("name") - real_name = pref_species.random_name(gender,1) - if("age") - age = rand(AGE_MIN, AGE_MAX) - if("hair") - hair_color = random_short_color() - if("hair_style") - hair_style = random_hair_style(gender) - if("facial") - facial_hair_color = random_short_color() - if("facial_hair_style") - facial_hair_style = random_facial_hair_style(gender) - if("underwear") - underwear = random_underwear(gender) - if("undershirt") - undershirt = random_undershirt(gender) - if("socks") - socks = random_socks() - if(BODY_ZONE_PRECISE_EYES) - eye_color = random_eye_color() - if("s_tone") - skin_tone = random_skin_tone() - if("bag") - backbag = pick(GLOB.backbaglist) - if("all") - random_character() - - if("input") - - if(href_list["preference"] in GLOB.preferences_custom_names) - ask_for_custom_name(user,href_list["preference"]) - - - switch(href_list["preference"]) - if("ghostform") - if(unlock_content) - var/new_form = input(user, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms - if(new_form) - ghost_form = new_form - if("ghostorbit") - if(unlock_content) - var/new_orbit = input(user, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND", null) as null|anything in GLOB.ghost_orbits - if(new_orbit) - ghost_orbit = new_orbit - - if("ghostaccs") - var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,GHOST_ACCS_FULL_NAME, GHOST_ACCS_DIR_NAME, GHOST_ACCS_NONE_NAME) - switch(new_ghost_accs) - if(GHOST_ACCS_FULL_NAME) - ghost_accs = GHOST_ACCS_FULL - if(GHOST_ACCS_DIR_NAME) - ghost_accs = GHOST_ACCS_DIR - if(GHOST_ACCS_NONE_NAME) - ghost_accs = GHOST_ACCS_NONE - - if("ghostothers") - var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,GHOST_OTHERS_THEIR_SETTING_NAME, GHOST_OTHERS_DEFAULT_SPRITE_NAME, GHOST_OTHERS_SIMPLE_NAME) - switch(new_ghost_others) - if(GHOST_OTHERS_THEIR_SETTING_NAME) - ghost_others = GHOST_OTHERS_THEIR_SETTING - if(GHOST_OTHERS_DEFAULT_SPRITE_NAME) - ghost_others = GHOST_OTHERS_DEFAULT_SPRITE - if(GHOST_OTHERS_SIMPLE_NAME) - ghost_others = GHOST_OTHERS_SIMPLE - - if("name") - var/new_name = input(user, "Choose your character's name:", "Character Preference") as text|null - if(new_name) - new_name = reject_bad_name(new_name) - if(new_name) - real_name = new_name - else - to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and .") - - if("age") - var/new_age = input(user, "Choose your character's age:\n([AGE_MIN]-[AGE_MAX])", "Character Preference") as num|null - if(new_age) - age = max(min( round(text2num(new_age)), AGE_MAX),AGE_MIN) - - if("hair") - var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference","#"+hair_color) as color|null - if(new_hair) - hair_color = sanitize_hexcolor(new_hair) - - if("hair_style") - var/new_hair_style - if(gender == MALE) - new_hair_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in GLOB.hair_styles_male_list - else if(gender == FEMALE) - new_hair_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in GLOB.hair_styles_female_list - else - new_hair_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in GLOB.hair_styles_list - if(new_hair_style) - hair_style = new_hair_style - - if("next_hair_style") - if (gender == MALE) - hair_style = next_list_item(hair_style, GLOB.hair_styles_male_list) - else if(gender == FEMALE) - hair_style = next_list_item(hair_style, GLOB.hair_styles_female_list) - else - hair_style = next_list_item(hair_style, GLOB.hair_styles_list) - - if("previous_hair_style") - if (gender == MALE) - hair_style = previous_list_item(hair_style, GLOB.hair_styles_male_list) - else if(gender == FEMALE) - hair_style = previous_list_item(hair_style, GLOB.hair_styles_female_list) - else - hair_style = previous_list_item(hair_style, GLOB.hair_styles_list) - - if("facial") - var/new_facial = input(user, "Choose your character's facial-hair colour:", "Character Preference","#"+facial_hair_color) as color|null - if(new_facial) - facial_hair_color = sanitize_hexcolor(new_facial) - - if("facial_hair_style") - var/new_facial_hair_style - if(gender == MALE) - new_facial_hair_style = input(user, "Choose your character's facial-hair style:", "Character Preference") as null|anything in GLOB.facial_hair_styles_male_list - else if(gender == FEMALE) - new_facial_hair_style = input(user, "Choose your character's facial-hair style:", "Character Preference") as null|anything in GLOB.facial_hair_styles_female_list - else - new_facial_hair_style = input(user, "Choose your character's facial-hair style:", "Character Preference") as null|anything in GLOB.facial_hair_styles_list - if(new_facial_hair_style) - facial_hair_style = new_facial_hair_style - - if("next_facehair_style") - if (gender == MALE) - facial_hair_style = next_list_item(facial_hair_style, GLOB.facial_hair_styles_male_list) - else if(gender == FEMALE) - facial_hair_style = next_list_item(facial_hair_style, GLOB.facial_hair_styles_female_list) - else - facial_hair_style = next_list_item(facial_hair_style, GLOB.facial_hair_styles_list) - - if("previous_facehair_style") - if (gender == MALE) - facial_hair_style = previous_list_item(facial_hair_style, GLOB.facial_hair_styles_male_list) - else if (gender == FEMALE) - facial_hair_style = previous_list_item(facial_hair_style, GLOB.facial_hair_styles_female_list) - else - facial_hair_style = previous_list_item(facial_hair_style, GLOB.facial_hair_styles_list) - - if("underwear") - var/new_underwear - if(gender == MALE) - new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_m - else if(gender == FEMALE) - new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_f - else - new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_list - if(new_underwear) - underwear = new_underwear - - if("undershirt") - var/new_undershirt - if(gender == MALE) - new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_m - else if(gender == FEMALE) - new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_f - else - new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_list - if(new_undershirt) - undershirt = new_undershirt - - if("socks") - var/new_socks - new_socks = input(user, "Choose your character's socks:", "Character Preference") as null|anything in GLOB.socks_list - if(new_socks) - socks = new_socks - - if(BODY_ZONE_PRECISE_EYES) - var/new_eyes = input(user, "Choose your character's eye colour:", "Character Preference","#"+eye_color) as color|null - if(new_eyes) - eye_color = sanitize_hexcolor(new_eyes) - - if("species") - - var/result = input(user, "Select a species", "Species Selection") as null|anything in GLOB.roundstart_races - - if(result) - var/newtype = GLOB.species_list[result] - pref_species = new newtype() - //Now that we changed our species, we must verify that the mutant colour is still allowed. - var/temp_hsv = RGBtoHSV(features["mcolor"]) - if(features["mcolor"] == "#000" || (!(MUTCOLORS_PARTSONLY in pref_species.species_traits) && ReadHSV(temp_hsv)[3] < ReadHSV("#7F7F7F")[3])) - features["mcolor"] = pref_species.default_color - - if("mutant_color") - var/new_mutantcolor = input(user, "Choose your character's alien/mutant color:", "Character Preference","#"+features["mcolor"]) as color|null - if(new_mutantcolor) - var/temp_hsv = RGBtoHSV(new_mutantcolor) - if(new_mutantcolor == "#000000") - features["mcolor"] = pref_species.default_color - else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright, but only if they affect the skin - features["mcolor"] = sanitize_hexcolor(new_mutantcolor) - else - to_chat(user, "Invalid color. Your color is not bright enough.") - - if("color_ethereal") - var/new_etherealcolor = input(user, "Choose your ethereal color", "Character Preference") as null|anything in GLOB.color_list_ethereal - if(new_etherealcolor) - features["ethcolor"] = GLOB.color_list_ethereal[new_etherealcolor] - - - if("tail_lizard") - var/new_tail - new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in GLOB.tails_list_lizard - if(new_tail) - features["tail_lizard"] = new_tail - - if("tail_human") - var/new_tail - new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in GLOB.tails_list_human - if(new_tail) - features["tail_human"] = new_tail - - if("snout") - var/new_snout - new_snout = input(user, "Choose your character's snout:", "Character Preference") as null|anything in GLOB.snouts_list - if(new_snout) - features["snout"] = new_snout - - if("horns") - var/new_horns - new_horns = input(user, "Choose your character's horns:", "Character Preference") as null|anything in GLOB.horns_list - if(new_horns) - features["horns"] = new_horns - - if("ears") - var/new_ears - new_ears = input(user, "Choose your character's ears:", "Character Preference") as null|anything in GLOB.ears_list - if(new_ears) - features["ears"] = new_ears - - if("wings") - var/new_wings - new_wings = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.r_wings_list - if(new_wings) - features["wings"] = new_wings - - if("frills") - var/new_frills - new_frills = input(user, "Choose your character's frills:", "Character Preference") as null|anything in GLOB.frills_list - if(new_frills) - features["frills"] = new_frills - - if("spines") - var/new_spines - new_spines = input(user, "Choose your character's spines:", "Character Preference") as null|anything in GLOB.spines_list - if(new_spines) - features["spines"] = new_spines - - if("body_markings") - var/new_body_markings - new_body_markings = input(user, "Choose your character's body markings:", "Character Preference") as null|anything in GLOB.body_markings_list - if(new_body_markings) - features["body_markings"] = new_body_markings - - if("legs") - var/new_legs - new_legs = input(user, "Choose your character's legs:", "Character Preference") as null|anything in GLOB.legs_list - if(new_legs) - features["legs"] = new_legs - - if("moth_wings") - var/new_moth_wings - new_moth_wings = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.moth_wings_list - if(new_moth_wings) - features["moth_wings"] = new_moth_wings - - if("s_tone") - var/new_s_tone = input(user, "Choose your character's skin-tone:", "Character Preference") as null|anything in GLOB.skin_tones - if(new_s_tone) - skin_tone = new_s_tone - - if("ooccolor") - var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference",ooccolor) as color|null - if(new_ooccolor) - ooccolor = new_ooccolor - - if("asaycolor") - var/new_asaycolor = input(user, "Choose your ASAY color:", "Game Preference",asaycolor) as color|null - if(new_asaycolor) - asaycolor = new_asaycolor - - if("bag") - var/new_backbag = input(user, "Choose your character's style of bag:", "Character Preference") as null|anything in GLOB.backbaglist - if(new_backbag) - backbag = new_backbag - - if("uplink_loc") - var/new_loc = input(user, "Choose your character's traitor uplink spawn location:", "Character Preference") as null|anything in GLOB.uplink_spawn_loc_list - if(new_loc) - uplink_spawn_loc = new_loc - - if("ai_core_icon") - var/ai_core_icon = input(user, "Choose your preferred AI core display screen:", "AI Core Display Screen Selection") as null|anything in GLOB.ai_core_display_screens - if(ai_core_icon) - preferred_ai_core_display = ai_core_icon - - if("sec_dept") - var/department = input(user, "Choose your preferred security department:", "Security Departments") as null|anything in GLOB.security_depts_prefs - if(department) - prefered_security_department = department - - if ("preferred_map") - var/maplist = list() - var/default = "Default" - if (config.defaultmap) - default += " ([config.defaultmap.map_name])" - for (var/M in config.maplist) - var/datum/map_config/VM = config.maplist[M] - if(!VM.votable) - continue - var/friendlyname = "[VM.map_name] " - if (VM.voteweight <= 0) - friendlyname += " (disabled)" - maplist[friendlyname] = VM.map_name - maplist[default] = null - var/pickedmap = input(user, "Choose your preferred map. This will be used to help weight random map selection.", "Character Preference") as null|anything in maplist - if (pickedmap) - preferred_map = maplist[pickedmap] - - if ("clientfps") - var/desiredfps = input(user, "Choose your desired fps. (0 = synced with server tick rate (currently:[world.fps]))", "Character Preference", clientfps) as null|num - if (!isnull(desiredfps)) - clientfps = desiredfps - parent.fps = desiredfps - if("ui") - var/pickedui = input(user, "Choose your UI style.", "Character Preference", UI_style) as null|anything in GLOB.available_ui_styles - if(pickedui) - UI_style = pickedui - if (parent && parent.mob && parent.mob.hud_used) - parent.mob.hud_used.update_ui_style(ui_style2icon(UI_style)) - if("pda_style") - var/pickedPDAStyle = input(user, "Choose your PDA style.", "Character Preference", pda_style) as null|anything in GLOB.pda_styles - if(pickedPDAStyle) - pda_style = pickedPDAStyle - if("pda_color") - var/pickedPDAColor = input(user, "Choose your PDA Interface color.", "Character Preference",pda_color) as color|null - if(pickedPDAColor) - pda_color = pickedPDAColor - // yogs start - Custom keybindings - if(href_list["keybinding"]) - update_keybindings(user, href_list["keybinding"], href_list["dir"]) - // yogs end - else - switch(href_list["preference"]) - if("publicity") - if(unlock_content) - toggles ^= MEMBER_PUBLIC - if("gender") - var/pickedGender = input(user, "Choose your gender.", "Character Preference", gender) as null|anything in friendlyGenders - if(pickedGender && friendlyGenders[pickedGender] != gender) - gender = friendlyGenders[pickedGender] - underwear = random_underwear(gender) - undershirt = random_undershirt(gender) - socks = random_socks() - facial_hair_style = random_facial_hair_style(gender) - hair_style = random_hair_style(gender) - - if("hotkeys") - hotkeys = !hotkeys - if(hotkeys) - bindings.bind_movement() // yogs - Rebindable keys - winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_DISABLED] mainwindow.macro=default") // yogs - Rebindable keys - else - bindings.unbind_movement() // yogs - Rebindable keys - winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=old_default") - if("action_buttons") - buttons_locked = !buttons_locked - if("tgui_fancy") - tgui_fancy = !tgui_fancy - if("tgui_lock") - tgui_lock = !tgui_lock - if("winflash") - windowflashing = !windowflashing - - //here lies the badmins - if("hear_adminhelps") - user.client.toggleadminhelpsound() - if("hear_prayers") - user.client.toggle_prayer_sound() - if("announce_login") - user.client.toggleannouncelogin() - if("combohud_lighting") - toggles ^= COMBOHUD_LIGHTING - if("toggle_dead_chat") - user.client.deadchat() - if("toggle_radio_chatter") - user.client.toggle_hear_radio() - if("toggle_prayers") - user.client.toggleprayers() - if("toggle_deadmin_always") - toggles ^= DEADMIN_ALWAYS - if("toggle_deadmin_antag") - toggles ^= DEADMIN_ANTAGONIST - if("toggle_deadmin_head") - toggles ^= DEADMIN_POSITION_HEAD - if("toggle_deadmin_security") - toggles ^= DEADMIN_POSITION_SECURITY - if("toggle_deadmin_silicon") - toggles ^= DEADMIN_POSITION_SILICON - - - if("be_special") - var/be_special_type = href_list["be_special_type"] - if(be_special_type in be_special) - be_special -= be_special_type - else - be_special += be_special_type - - if("name") - be_random_name = !be_random_name - - if("all") - be_random_body = !be_random_body - - if("hear_midis") - toggles ^= SOUND_MIDI - - if("lobby_music") - toggles ^= SOUND_LOBBY - if((toggles & SOUND_LOBBY) && user.client && isnewplayer(user)) - user.client.playtitlemusic() - else - user.stop_sound_channel(CHANNEL_LOBBYMUSIC) - - if("ghost_ears") - chat_toggles ^= CHAT_GHOSTEARS - - if("ghost_sight") - chat_toggles ^= CHAT_GHOSTSIGHT - - if("ghost_whispers") - chat_toggles ^= CHAT_GHOSTWHISPER - - if("ghost_radio") - chat_toggles ^= CHAT_GHOSTRADIO - - if("ghost_pda") - chat_toggles ^= CHAT_GHOSTPDA - - if("income_pings") - chat_toggles ^= CHAT_BANKCARD - - if("pull_requests") - chat_toggles ^= CHAT_PULLR - - if("allow_midround_antag") - toggles ^= MIDROUND_ANTAG - - if("parallaxup") - parallax = WRAP(parallax + 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1) - if (parent && parent.mob && parent.mob.hud_used) - parent.mob.hud_used.update_parallax_pref(parent.mob) - - if("parallaxdown") - parallax = WRAP(parallax - 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1) - if (parent && parent.mob && parent.mob.hud_used) - parent.mob.hud_used.update_parallax_pref(parent.mob) - - if("ambientocclusion") - ambientocclusion = !ambientocclusion - if(parent && parent.screen && parent.screen.len) - var/obj/screen/plane_master/game_world/PM = locate(/obj/screen/plane_master/game_world) in parent.screen - PM.backdrop(parent.mob) - - if("auto_fit_viewport") - auto_fit_viewport = !auto_fit_viewport - if(auto_fit_viewport && parent) - parent.fit_viewport() - - if("widescreenpref") - widescreenpref = !widescreenpref - user.client.change_view(CONFIG_GET(string/default_view)) - - if("save") - save_preferences() - save_character() - - if("load") - load_preferences() - load_character() - - if("changeslot") - if(!load_character(text2num(href_list["num"]))) - random_character() - real_name = random_unique_name(gender) - save_character() - - if("tab") - if (href_list["tab"]) - current_tab = text2num(href_list["tab"]) - - // yogs start - Custom keybindings - if("reset_bindings") - reset_keybindings() - - if("mood") - yogtoggles ^= PREF_MOOD - // yogs end - - ShowChoices(user) - return 1 - -/datum/preferences/proc/copy_to(mob/living/carbon/human/character, icon_updates = 1, roundstart_checks = TRUE) - if(be_random_name) - real_name = pref_species.random_name(gender) - - if(be_random_body) - random_character(gender) - - if(roundstart_checks) - if(CONFIG_GET(flag/humans_need_surnames) && (pref_species.id == "human")) - var/firstspace = findtext(real_name, " ") - var/name_length = length(real_name) - if(!firstspace) //we need a surname - real_name += " [pick(GLOB.last_names)]" - else if(firstspace == name_length) - real_name += "[pick(GLOB.last_names)]" - - character.real_name = real_name - character.name = character.real_name - - character.gender = gender - character.age = age - - character.eye_color = eye_color - var/obj/item/organ/eyes/organ_eyes = character.getorgan(/obj/item/organ/eyes) - if(organ_eyes) - if(!initial(organ_eyes.eye_color)) - organ_eyes.eye_color = eye_color - organ_eyes.old_eye_color = eye_color - character.hair_color = hair_color - character.facial_hair_color = facial_hair_color - - character.skin_tone = skin_tone - character.hair_style = hair_style - character.facial_hair_style = facial_hair_style - character.underwear = underwear - character.undershirt = undershirt - character.socks = socks - - character.backbag = backbag - - var/datum/species/chosen_species - chosen_species = pref_species.type - if(roundstart_checks && !(pref_species.id in GLOB.roundstart_races) && !(pref_species.id in (CONFIG_GET(keyed_list/roundstart_no_hard_check)))) - chosen_species = /datum/species/human - pref_species = new /datum/species/human - save_character() - - character.dna.features = features.Copy() - character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE) - character.dna.real_name = character.real_name - - if("tail_lizard" in pref_species.default_features) - character.dna.species.mutant_bodyparts |= "tail_lizard" - - if(icon_updates) - character.update_body() - character.update_hair() - character.update_body_parts() - -/datum/preferences/proc/get_default_name(name_id) - switch(name_id) - if("human") - return random_unique_name() - if("ai") - return pick(GLOB.ai_names) - if("cyborg") - return DEFAULT_CYBORG_NAME - if("clown") - return pick(GLOB.clown_names) - if("mime") - return pick(GLOB.mime_names) - if("religion") - return DEFAULT_RELIGION - if("deity") - return DEFAULT_DEITY - return random_unique_name() - -/datum/preferences/proc/ask_for_custom_name(mob/user,name_id) - var/namedata = GLOB.preferences_custom_names[name_id] - if(!namedata) - return - - var/raw_name = input(user, "Choose your character's [namedata["qdesc"]]:","Character Preference") as text|null - if(!raw_name) - if(namedata["allow_null"]) - custom_names[name_id] = get_default_name(name_id) - else - return - else - var/sanitized_name = reject_bad_name(raw_name,namedata["allow_numbers"]) - if(!sanitized_name) - to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z,[namedata["allow_numbers"] ? ",0-9," : ""] -, ' and .") - return - else - custom_names[name_id] = sanitized_name +GLOBAL_LIST_EMPTY(preferences_datums) + +/datum/preferences + var/client/parent + //doohickeys for savefiles + var/path + var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used + var/max_save_slots = 3 + + //non-preference stuff + var/muted = 0 + var/last_ip + var/last_id + + //game-preferences + var/lastchangelog = "" //Saved changlog filesize to detect if there was a change + var/ooccolor = "#c43b23" + var/asaycolor = null + var/enable_tips = TRUE + var/tip_delay = 500 //tip delay in milliseconds + + //Antag preferences + var/list/be_special = list() //Special role selection + var/tmp/old_be_special = 0 //Bitflag version of be_special, used to update old savefiles and nothing more + //If it's 0, that's good, if it's anything but 0, the owner of this prefs file's antag choices were, + //autocorrected this round, not that you'd need to check that. + + var/UI_style = null + var/buttons_locked = FALSE + var/hotkeys = TRUE // yogs - Rebindable Keybindings + var/tgui_fancy = TRUE + var/tgui_lock = TRUE + var/windowflashing = TRUE + var/toggles = TOGGLES_DEFAULT + var/db_flags + var/chat_toggles = TOGGLES_DEFAULT_CHAT + var/ghost_form = "ghost" + var/ghost_orbit = GHOST_ORBIT_CIRCLE + var/ghost_accs = GHOST_ACCS_DEFAULT_OPTION + var/ghost_others = GHOST_OTHERS_DEFAULT_OPTION + var/ghost_hud = 1 + var/inquisitive_ghost = 1 + var/allow_midround_antag = 1 + var/preferred_map = null + var/pda_style = MONO + var/pda_color = "#808000" + + var/uses_glasses_colour = 0 + + //character preferences + var/real_name //our character's name + var/be_random_name = 0 //whether we'll have a random name every round + var/be_random_body = 0 //whether we'll have a random body every round + var/gender = MALE //gender of character (well duh) + var/age = 30 //age of character + var/underwear = "Nude" //underwear type + var/undershirt = "Nude" //undershirt type + var/socks = "Nude" //socks type + var/backbag = DBACKPACK //backpack type + var/hair_style = "Bald" //Hair type + var/hair_color = "000" //Hair color + var/facial_hair_style = "Shaved" //Face hair type + var/facial_hair_color = "000" //Facial hair color + var/skin_tone = "caucasian1" //Skin color + var/eye_color = "000" //Eye color + var/datum/species/pref_species = new /datum/species/human() //Mutant race + var/list/features = list("mcolor" = "FFF", "ethcolor" = "9c3030", "tail_lizard" = "Smooth", "tail_human" = "None", "snout" = "Round", "horns" = "None", "ears" = "None", "wings" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs", "moth_wings" = "Plain") + var/list/genders = list(MALE, FEMALE, PLURAL) + var/list/friendlyGenders = list("Male" = "male", "Female" = "female", "Other" = "plural") + + var/list/custom_names = list() + var/preferred_ai_core_display = "Blue" + var/prefered_security_department = SEC_DEPT_RANDOM + + //Quirk list + var/list/all_quirks = list() + + //Job preferences 2.0 - indexed by job title , no key or value implies never + var/list/job_preferences = list() + + // Want randomjob if preferences already filled - Donkie + var/joblessrole = BERANDOMJOB //defaults to 1 for fewer assistants + + // 0 = character settings, 1 = game preferences + var/current_tab = 0 + + var/unlock_content = 0 + + var/list/ignoring = list() + + var/clientfps = 0 + + var/parallax + + var/ambientocclusion = TRUE + var/auto_fit_viewport = FALSE + var/widescreenpref = TRUE + + var/uplink_spawn_loc = UPLINK_PDA + + var/list/exp = list() + var/list/menuoptions + + var/action_buttons_screen_locs = list() + +/datum/preferences/New(client/C) + parent = C + + for(var/custom_name_id in GLOB.preferences_custom_names) + custom_names[custom_name_id] = get_default_name(custom_name_id) + + UI_style = GLOB.available_ui_styles[1] + if(istype(C)) + if(!IsGuestKey(C.key)) + load_path(C.ckey) + unlock_content |= C.IsByondMember() // yogs - Donor features + if(unlock_content) + max_save_slots = 8 + // yogs start - Donor features + else if(is_donator(C) || (C.ckey in get_donators())) // the Latter handles race cases where the prefs are not fully loaded in, or GLOB.donators hasn't loaded in yet + max_save_slots = DONOR_CHARACTER_SLOTS + // yogs end + var/loaded_preferences_successfully = load_preferences() + if(loaded_preferences_successfully) + if(load_character()) + return + //we couldn't load character data so just randomize the character appearance + name + random_character() //let's create a random character then - rather than a fat, bald and naked man. + real_name = pref_species.random_name(gender,1) + if(!loaded_preferences_successfully) + save_preferences() + save_character() //let's save this new random character so it doesn't keep generating new ones. + menuoptions = list() + return + +#define APPEARANCE_CATEGORY_COLUMN "" +#define MAX_MUTANT_ROWS 4 + +/datum/preferences/proc/ShowChoices(mob/user) + if(!user || !user.client) + return + + if(!SSjob || (SSjob.occupations.len <= 0)) + to_chat(user, "The job SSticker is not yet finished creating jobs, please try again later") + return + + update_preview_icon() + var/list/dat = list("
                ") + + dat += "Character Settings" + dat += "Game Preferences" + dat += "OOC Preferences" + dat += "Donator Preferences" // yogs - Donor features + dat += "Keybindings" // yogs - Custom keybindings + + if(!path) + dat += "
                Please create an account to save your preferences
                " + + dat += "
                " + + dat += "
                " + + switch(current_tab) + if (0) // Character Settings# + if(path) + var/savefile/S = new /savefile(path) + if(S) + dat += "
                " + var/name + var/unspaced_slots = 0 + for(var/i=1, i<=max_save_slots, i++) + unspaced_slots++ + if(unspaced_slots > 4) + dat += "
                " + unspaced_slots = 0 + S.cd = "/character[i]" + S["real_name"] >> name + if(!name) + name = "Character[i]" + dat += "[name] " + dat += "
                " + + dat += "

                Occupation Choices

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

                Quirk Setup

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

                Identity

                " + dat += "" + + dat += "
                " + if(is_banned_from(user.ckey, "Appearance")) + dat += "You are banned from using custom names and appearances. You can continue to adjust your characters, but you will be randomised once you join the game.
                " + dat += "Random Name " + dat += "Always Random Name: [be_random_name ? "Yes" : "No"]
                " + + dat += "Name: " + dat += "[real_name]
                " + + if(!(AGENDER in pref_species.species_traits)) + var/dispGender + if(gender == MALE) + dispGender = "Male" + else if(gender == FEMALE) + dispGender = "Female" + else + dispGender = "Other" + dat += "Gender: [dispGender]
                " + dat += "Age: [age]
                " + + dat += "Special Names:
                " + var/old_group + for(var/custom_name_id in GLOB.preferences_custom_names) + var/namedata = GLOB.preferences_custom_names[custom_name_id] + if(!old_group) + old_group = namedata["group"] + else if(old_group != namedata["group"]) + old_group = namedata["group"] + dat += "
                " + dat += "[namedata["pref_name"]]: [custom_names[custom_name_id]] " + dat += "

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

                Body

                " + dat += "Random Body " + dat += "Always Random Body: [be_random_body ? "Yes" : "No"]
                " + + dat += "" + + var/use_skintones = pref_species.use_skintones + if(use_skintones) + + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Skin Tone

                " + + dat += "[skin_tone]
                " + + var/mutant_colors + if((MUTCOLORS in pref_species.species_traits) || (MUTCOLORS_PARTSONLY in pref_species.species_traits)) + + if(!use_skintones) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Mutant Color

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

                Ethereal Color

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

                Eye Color

                " + + dat += "   Change
                " + + dat += "" + else if(use_skintones || mutant_colors) + dat += "" + + if(HAIR in pref_species.species_traits) + + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Hair Style

                " + + dat += "[hair_style]
                " + dat += "<>
                " + dat += "   Change
                " + + dat += "

                Facial Hair Style

                " + + dat += "[facial_hair_style]
                " + dat += "<>
                " + dat += "   Change
                " + + dat += "" + + //Mutant stuff + var/mutant_category = 0 + + if("tail_lizard" in pref_species.default_features) + if(!mutant_category) + dat += APPEARANCE_CATEGORY_COLUMN + + dat += "

                Tail

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

                Snout

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

                Horns

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

                Frills

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

                Spines

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

                Body Markings

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

                Legs

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

                Moth wings

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

                Tail

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

                Ears

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

                Wings

                " + + dat += "[features["wings"]]
                " + + mutant_category++ + if(mutant_category >= MAX_MUTANT_ROWS) + dat += "" + mutant_category = 0 + + if(mutant_category) + dat += "" + mutant_category = 0 + dat += "
                " + + dat += "Species:
                [pref_species.name]
                " + + dat += "Underwear:
                [underwear]
                " + dat += "Undershirt:
                [undershirt]
                " + dat += "Socks:
                [socks]
                " + dat += "Backpack:
                [backbag]
                " + dat += "Uplink Spawn Location:
                [uplink_spawn_loc]
                " + + + if (1) // Game Preferences + dat += "
                " + dat += "

                General Settings

                " + dat += "UI Style: [UI_style]
                " + dat += "tgui Monitors: [(tgui_lock) ? "Primary" : "All"]
                " + dat += "tgui Style: [(tgui_fancy) ? "Fancy" : "No Frills"]
                " + dat += "
                " + dat += "Action Buttons: [(buttons_locked) ? "Locked In Place" : "Unlocked"]
                " + //dat += "Keybindings: [(hotkeys) ? "Hotkeys" : "Default"]
                " // yogs - Custom keybindings + dat += "
                " + dat += "PDA Color:     Change
                " + dat += "PDA Style: [pda_style]
                " + dat += "
                " + dat += "Ghost Ears: [(chat_toggles & CHAT_GHOSTEARS) ? "All Speech" : "Nearest Creatures"]
                " + dat += "Ghost Radio: [(chat_toggles & CHAT_GHOSTRADIO) ? "All Messages":"No Messages"]
                " + dat += "Ghost Sight: [(chat_toggles & CHAT_GHOSTSIGHT) ? "All Emotes" : "Nearest Creatures"]
                " + dat += "Ghost Whispers: [(chat_toggles & CHAT_GHOSTWHISPER) ? "All Speech" : "Nearest Creatures"]
                " + dat += "Ghost PDA: [(chat_toggles & CHAT_GHOSTPDA) ? "All Messages" : "Nearest Creatures"]
                " + + if(unlock_content) + dat += "Ghost Form: [ghost_form]
                " + dat += "Ghost Orbit: [ghost_orbit]
                " + + var/button_name = "If you see this something went wrong." + switch(ghost_accs) + if(GHOST_ACCS_FULL) + button_name = GHOST_ACCS_FULL_NAME + if(GHOST_ACCS_DIR) + button_name = GHOST_ACCS_DIR_NAME + if(GHOST_ACCS_NONE) + button_name = GHOST_ACCS_NONE_NAME + + dat += "Ghost Accessories: [button_name]
                " + + switch(ghost_others) + if(GHOST_OTHERS_THEIR_SETTING) + button_name = GHOST_OTHERS_THEIR_SETTING_NAME + if(GHOST_OTHERS_DEFAULT_SPRITE) + button_name = GHOST_OTHERS_DEFAULT_SPRITE_NAME + if(GHOST_OTHERS_SIMPLE) + button_name = GHOST_OTHERS_SIMPLE_NAME + + dat += "Ghosts of Others: [button_name]
                " + dat += "
                " + + dat += "Income Updates: [(chat_toggles & CHAT_BANKCARD) ? "Allowed" : "Muted"]
                " + dat += "
                " + + dat += "FPS: [clientfps]
                " + + dat += "Parallax (Fancy Space): " + switch (parallax) + if (PARALLAX_LOW) + dat += "Low" + if (PARALLAX_MED) + dat += "Medium" + if (PARALLAX_INSANE) + dat += "Insane" + if (PARALLAX_DISABLE) + dat += "Disabled" + else + dat += "High" + dat += "
                " + + dat += "Ambient Occlusion: [ambientocclusion ? "Enabled" : "Disabled"]
                " + dat += "Fit Viewport: [auto_fit_viewport ? "Auto" : "Manual"]
                " + if (CONFIG_GET(string/default_view) != CONFIG_GET(string/default_view_square)) + dat += "Widescreen: [widescreenpref ? "Enabled ([CONFIG_GET(string/default_view)])" : "Disabled ([CONFIG_GET(string/default_view_square)])"]
                " + + if (CONFIG_GET(flag/maprotation)) + var/p_map = preferred_map + if (!p_map) + p_map = "Default" + if (config.defaultmap) + p_map += " ([config.defaultmap.map_name])" + else + if (p_map in config.maplist) + var/datum/map_config/VM = config.maplist[p_map] + if (!VM) + p_map += " (No longer exists)" + else + p_map = VM.map_name + else + p_map += " (No longer exists)" + if(CONFIG_GET(flag/preference_map_voting)) + dat += "Preferred Map: [p_map]
                " + //yogs start -- Mood preference toggling + if(CONFIG_GET(flag/disable_human_mood)) + dat += "Mood: [yogtoggles & PREF_MOOD ? "Enabled" : "Disabled"]
                " + //yogs end + + dat += "
                " + + dat += "

                Special Role Settings

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

                OOC Settings

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

                Admin Settings

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

                Deadmin While Playing

                " + if(CONFIG_GET(flag/auto_deadmin_players)) + dat += "Always Deadmin: FORCED
                " + else + dat += "Always Deadmin: [(toggles & DEADMIN_ALWAYS)?"Enabled":"Disabled"]
                " + if(!(toggles & DEADMIN_ALWAYS)) + dat += "
                " + if(!CONFIG_GET(flag/auto_deadmin_antagonists)) + dat += "As Antag: [(toggles & DEADMIN_ANTAGONIST)?"Deadmin":"Keep Admin"]
                " + else + dat += "As Antag: FORCED
                " + + if(!CONFIG_GET(flag/auto_deadmin_heads)) + dat += "As Command: [(toggles & DEADMIN_POSITION_HEAD)?"Deadmin":"Keep Admin"]
                " + else + dat += "As Command: FORCED
                " + + if(!CONFIG_GET(flag/auto_deadmin_security)) + dat += "As Security: [(toggles & DEADMIN_POSITION_SECURITY)?"Deadmin":"Keep Admin"]
                " + else + dat += "As Security: FORCED
                " + + if(!CONFIG_GET(flag/auto_deadmin_silicons)) + dat += "As Silicon: [(toggles & DEADMIN_POSITION_SILICON)?"Deadmin":"Keep Admin"]
                " + else + dat += "As Silicon: FORCED
                " + + dat += "
                " + // yogs start - Donor features + if (3) //Donator preferences + dat += "
                " + dat += "

                Donator Preferences

                " + if(is_donator(user.client)) + dat += "Quiet round: [(src.yogtoggles & QUIET_ROUND) ? "Yes" : "No"]
                " + dat += "Fancy Hat: " + var/typehat = donor_hat ? donor_start_items[donor_hat] : null + var/temp_hat = donor_hat ? (new typehat()) : "None selected" + dat += "Pick [temp_hat]
                " + if(donor_hat) + qdel(temp_hat) + dat += "Fancy Item: " + var/typeitem = donor_item ? donor_start_tools[donor_item] : null + var/temp_item = donor_item ? (new typeitem()) : "None selected" + dat += "Pick [temp_item]
                " + if(donor_item) + qdel(temp_item) + dat += "Fancy PDA: " + dat += "[donor_pdas[donor_pda]]
                " + dat += "Purrbation (Humans only) " + dat += "[purrbation ? "Yes" : "No"]
                " + else + dat += "Donate here" + dat += "
                " + // yogs end + + // yogs start - Custom keybindings + if (4) // Keybindings + dat += "
                [(hotkeys) ? "Hotkeys" : "Default"]" + dat += "Reset to default
                " + if(hotkeys) + var/button + var/button_bound + + dat += "
                " + dat += "

                Client

                " + BUTTON_KEY_MOVEMENT("Move North (up)", ACTION_MOVENORTH, NORTH) + BUTTON_KEY_MOVEMENT("Move West (left)", ACTION_MOVEWEST, WEST) + BUTTON_KEY_MOVEMENT("Move South (down)", ACTION_MOVESOUTH, SOUTH) + BUTTON_KEY_MOVEMENT("Move East (right)", ACTION_MOVEEAST, EAST) + + BUTTON_KEY("OOC", ACTION_OOC) + BUTTON_KEY("LOOC", ACTION_LOOC) + BUTTON_KEY("Adminhelp", ACTION_AHELP) + BUTTON_KEY("Screenshot", ACTION_SCREENSHOT) + BUTTON_KEY("Minimal HUD", ACTION_MINHUD) + + + dat += "

                Mob

                " + BUTTON_KEY("Say", ACTION_SAY) + BUTTON_KEY("Emote", ACTION_ME) + BUTTON_KEY("Stop pulling", ACTION_STOPPULLING) + BUTTON_KEY("Cycle intent clockwise", ACTION_INTENTRIGHT) + BUTTON_KEY("Cycle intent counter-clockwise", ACTION_INTENTLEFT) + BUTTON_KEY("Swap hands", ACTION_SWAPHAND) + BUTTON_KEY("Use item on self", ACTION_USESELF) + BUTTON_KEY("Drop", ACTION_DROP) + BUTTON_KEY("Equip", ACTION_EQUIP) + + dat += "
                " + + dat += "

                Mob

                " + BUTTON_KEY("Target head", ACTION_TARGETHEAD) + BUTTON_KEY("Target right arm", ACTION_TARGETRARM) + BUTTON_KEY("Target chest", ACTION_TARGETCHEST) + BUTTON_KEY("Target left arm", ACTION_TARGETLARM) + BUTTON_KEY("Target right leg", ACTION_TARGETRLEG) + BUTTON_KEY("Target groin", ACTION_TARGETGROIN) + BUTTON_KEY("Target left leg", ACTION_TARGETLLEG) + + BUTTON_KEY("Resist", ACTION_RESIST) + BUTTON_KEY("Toggle throw", ACTION_TOGGLETHROW) + BUTTON_KEY("Help intent", ACTION_INTENTHELP) + BUTTON_KEY("Disarm intent", ACTION_INTENTDISARM) + BUTTON_KEY("Grab intent", ACTION_INTENTGRAB) + BUTTON_KEY("Harm intent", ACTION_INTENTHARM) + + if(parent) + if(parent.mentor_datum) + dat += "

                Mentor

                " + BUTTON_KEY("Mentorsay", ACTION_MENTORCHAT) + + if(parent.holder) + dat += "

                Admin

                " + BUTTON_KEY("Adminchat", ACTION_ASAY) + BUTTON_KEY("Admin ghost", ACTION_AGHOST) + BUTTON_KEY("Player panel", ACTION_PLAYERPANEL) + BUTTON_KEY("Toggle build mode", ACTION_BUILDMODE) + BUTTON_KEY("Stealth mode", ACTION_STEALTHMIN) + BUTTON_KEY("Deadchat", ACTION_DSAY) + + dat += "
                " + else + dat += "Default keybindings selected" + // yogs end + dat += "
                " + + if(!IsGuestKey(user.key)) + dat += "Undo " + dat += "Save Setup " + + dat += "Reset Setup" + dat += "
                " + + winshow(user, "preferences_window", TRUE) + var/datum/browser/popup = new(user, "preferences_browser", "
                Character Setup
                ", 640, 770) + popup.set_content(dat.Join()) + popup.open(FALSE) + onclose(user, "preferences_window", src) + +#undef APPEARANCE_CATEGORY_COLUMN +#undef MAX_MUTANT_ROWS + +/datum/preferences/proc/SetChoices(mob/user, limit = 17, list/splitJobs = list("Chief Engineer"), widthPerColumn = 295, height = 620) + if(!SSjob) + return + + //limit - The amount of jobs allowed per column. Defaults to 17 to make it look nice. + //splitJobs - Allows you split the table by job. You can make different tables for each department by including their heads. Defaults to CE to make it look nice. + //widthPerColumn - Screen's width for every column. + //height - Screen's height. + + var/width = widthPerColumn + + var/HTML = "
                " + if(SSjob.occupations.len <= 0) + HTML += "The job SSticker is not yet finished creating jobs, please try again later" + HTML += "
                Done

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

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

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

                " + else + dat += "
                Choose quirk setup

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

                " + for(var/V in SSquirks.quirks) + var/datum/quirk/T = SSquirks.quirks[V] + var/quirk_name = initial(T.name) + var/has_quirk + var/quirk_cost = initial(T.value) * -1 + var/lock_reason = FALSE // Also marks whether this quirk ought to be locked at all; FALSE implies it's OK for this person to have this quirk + for(var/_V in all_quirks) + if(_V == quirk_name) + has_quirk = TRUE + if(initial(T.mood_quirk) && (CONFIG_GET(flag/disable_human_mood) && !(yogtoggles & PREF_MOOD)))//Yogs -- Adds mood to preferences + lock_reason = "Mood is disabled." + else + var/datum/quirk/t = new T(no_init = TRUE) + lock_reason = t.check_quirk(src) // Yogs -- allows for specific denial of quirks based on current preferences + qdel(t) + if(has_quirk) + if(lock_reason) + all_quirks -= quirk_name + has_quirk = FALSE + else + quirk_cost *= -1 //invert it back, since we'd be regaining this amount + if(quirk_cost > 0) + quirk_cost = "+[quirk_cost]" + var/font_color = "#AAAAFF" + if(initial(T.value) != 0) + font_color = initial(T.value) > 0 ? "#AAFFAA" : "#FFAAAA" + if(lock_reason) + dat += "[quirk_name] - [initial(T.desc)] \ + LOCKED: [lock_reason]
                " + else + if(has_quirk) + dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ + [quirk_name] - [initial(T.desc)]
                " + else + dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ + [quirk_name] - [initial(T.desc)]
                " + dat += "
                Reset Quirks
                " + + var/datum/browser/popup = new(user, "mob_occupation", "
                Quirk Preferences
                ", 900, 600) //no reason not to reuse the occupation window, as it's cleaner that way + popup.set_window_options("can_close=0") + popup.set_content(dat.Join()) + popup.open(FALSE) + +/datum/preferences/proc/GetQuirkBalance() + var/bal = 0 + for(var/V in all_quirks) + var/datum/quirk/T = SSquirks.quirks[V] + bal -= initial(T.value) + return bal + +/datum/preferences/proc/GetPositiveQuirkCount() + . = 0 + for(var/q in all_quirks) + if(SSquirks.quirk_points[q] > 0) + .++ + +/datum/preferences/Topic(href, href_list, hsrc) //yeah, gotta do this I guess.. + . = ..() + if(href_list["close"]) + var/client/C = usr.client + if(C) + C.clear_character_previews() + +/datum/preferences/proc/process_link(mob/user, list/href_list) + // yogs start - Donor features + if(href_list["preference"] == "donor") + if(is_donator(user)) + switch(href_list["task"]) + if("hat") + var/item = input(usr, "What would you like to start with?","Donator fun","Nothing") as null|anything in donor_start_items + if(item) + donor_hat = donor_start_items.Find(item) + else + donor_hat = 0 + if("item") + var/item = input(usr, "What would you like to start with?","Donator fun","Nothing") as null|anything in donor_start_tools + if(item) + donor_item = donor_start_tools.Find(item) + else + donor_item = 0 + if("quiet_round") + yogtoggles ^= QUIET_ROUND + if("pda") + donor_pda = donor_pda % donor_pdas.len + 1 + if("purrbation") + purrbation = !purrbation + else + message_admins("EXPLOIT \[donor\]: [user] tried to access donor only functions (as a non-donor). Attempt made on \"[href_list["preference"]]\" -> \"[href_list["task"]]\".") + // yogs end + if(href_list["bancheck"]) + var/list/ban_details = is_banned_from_with_details(user.ckey, user.client.address, user.client.computer_id, href_list["bancheck"]) + var/admin = FALSE + if(GLOB.admin_datums[user.ckey] || GLOB.deadmins[user.ckey]) + admin = TRUE + for(var/i in ban_details) + if(admin && !text2num(i["applies_to_admins"])) + continue + ban_details = i + break //we only want to get the most recent ban's details + if(ban_details && ban_details.len) + var/expires = "This is a permanent ban." + if(ban_details["expiration_time"]) + expires = " The ban is for [DisplayTimeText(text2num(ban_details["duration"]) MINUTES)] and expires on [ban_details["expiration_time"]] (server time)." + to_chat(user, "You, or another user of this computer or connection ([ban_details["key"]]) is banned from playing [href_list["bancheck"]].
                The ban reason is: [ban_details["reason"]]
                This ban (BanID #[ban_details["id"]]) was applied by [ban_details["admin_key"]] on [ban_details["bantime"]] during round ID [ban_details["round_id"]].
                [expires]
                ") + return + if(href_list["preference"] == "job") + switch(href_list["task"]) + if("close") + user << browse(null, "window=mob_occupation") + ShowChoices(user) + if("reset") + ResetJobs() + SetChoices(user) + if("random") + switch(joblessrole) + if(RETURNTOLOBBY) + if(is_banned_from(user.ckey, SSjob.overflow_role)) + joblessrole = BERANDOMJOB + else + joblessrole = BEOVERFLOW + if(BEOVERFLOW) + joblessrole = BERANDOMJOB + if(BERANDOMJOB) + joblessrole = RETURNTOLOBBY + SetChoices(user) + if("setJobLevel") + UpdateJobPreference(user, href_list["text"], text2num(href_list["level"])) + else + SetChoices(user) + return 1 + + else if(href_list["preference"] == "trait") + switch(href_list["task"]) + if("close") + user << browse(null, "window=mob_occupation") + ShowChoices(user) + if("update") + var/quirk = href_list["trait"] + if(!SSquirks.quirks[quirk]) + return + for(var/V in SSquirks.quirk_blacklist) //V is a list + var/list/L = V + for(var/Q in all_quirks) + if((quirk in L) && (Q in L) && !(Q == quirk)) //two quirks have lined up in the list of the list of quirks that conflict with each other, so return (see quirks.dm for more details) + to_chat(user, "[quirk] is incompatible with [Q].") + return + var/value = SSquirks.quirk_points[quirk] + var/balance = GetQuirkBalance() + if(quirk in all_quirks) + if(balance + value < 0) + to_chat(user, "Refunding this would cause you to go below your balance!") + return + all_quirks -= quirk + else + if(GetPositiveQuirkCount() >= MAX_QUIRKS) + to_chat(user, "You can't have more than [MAX_QUIRKS] positive quirks!") + return + if(balance - value < 0) + to_chat(user, "You don't have enough balance to gain this quirk!") + return + all_quirks += quirk + SetQuirks(user) + if("reset") + all_quirks = list() + SetQuirks(user) + else + SetQuirks(user) + return TRUE + + switch(href_list["task"]) + if("random") + switch(href_list["preference"]) + if("name") + real_name = pref_species.random_name(gender,1) + if("age") + age = rand(AGE_MIN, AGE_MAX) + if("hair") + hair_color = random_short_color() + if("hair_style") + hair_style = random_hair_style(gender) + if("facial") + facial_hair_color = random_short_color() + if("facial_hair_style") + facial_hair_style = random_facial_hair_style(gender) + if("underwear") + underwear = random_underwear(gender) + if("undershirt") + undershirt = random_undershirt(gender) + if("socks") + socks = random_socks() + if(BODY_ZONE_PRECISE_EYES) + eye_color = random_eye_color() + if("s_tone") + skin_tone = random_skin_tone() + if("bag") + backbag = pick(GLOB.backbaglist) + if("all") + random_character() + + if("input") + + if(href_list["preference"] in GLOB.preferences_custom_names) + ask_for_custom_name(user,href_list["preference"]) + + + switch(href_list["preference"]) + if("ghostform") + if(unlock_content) + var/new_form = input(user, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms + if(new_form) + ghost_form = new_form + if("ghostorbit") + if(unlock_content) + var/new_orbit = input(user, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND", null) as null|anything in GLOB.ghost_orbits + if(new_orbit) + ghost_orbit = new_orbit + + if("ghostaccs") + var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,GHOST_ACCS_FULL_NAME, GHOST_ACCS_DIR_NAME, GHOST_ACCS_NONE_NAME) + switch(new_ghost_accs) + if(GHOST_ACCS_FULL_NAME) + ghost_accs = GHOST_ACCS_FULL + if(GHOST_ACCS_DIR_NAME) + ghost_accs = GHOST_ACCS_DIR + if(GHOST_ACCS_NONE_NAME) + ghost_accs = GHOST_ACCS_NONE + + if("ghostothers") + var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,GHOST_OTHERS_THEIR_SETTING_NAME, GHOST_OTHERS_DEFAULT_SPRITE_NAME, GHOST_OTHERS_SIMPLE_NAME) + switch(new_ghost_others) + if(GHOST_OTHERS_THEIR_SETTING_NAME) + ghost_others = GHOST_OTHERS_THEIR_SETTING + if(GHOST_OTHERS_DEFAULT_SPRITE_NAME) + ghost_others = GHOST_OTHERS_DEFAULT_SPRITE + if(GHOST_OTHERS_SIMPLE_NAME) + ghost_others = GHOST_OTHERS_SIMPLE + + if("name") + var/new_name = input(user, "Choose your character's name:", "Character Preference") as text|null + if(new_name) + new_name = reject_bad_name(new_name) + if(new_name) + real_name = new_name + else + to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and .") + + if("age") + var/new_age = input(user, "Choose your character's age:\n([AGE_MIN]-[AGE_MAX])", "Character Preference") as num|null + if(new_age) + age = max(min( round(text2num(new_age)), AGE_MAX),AGE_MIN) + + if("hair") + var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference","#"+hair_color) as color|null + if(new_hair) + hair_color = sanitize_hexcolor(new_hair) + + if("hair_style") + var/new_hair_style + if(gender == MALE) + new_hair_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in GLOB.hair_styles_male_list + else if(gender == FEMALE) + new_hair_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in GLOB.hair_styles_female_list + else + new_hair_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in GLOB.hair_styles_list + if(new_hair_style) + hair_style = new_hair_style + + if("next_hair_style") + if (gender == MALE) + hair_style = next_list_item(hair_style, GLOB.hair_styles_male_list) + else if(gender == FEMALE) + hair_style = next_list_item(hair_style, GLOB.hair_styles_female_list) + else + hair_style = next_list_item(hair_style, GLOB.hair_styles_list) + + if("previous_hair_style") + if (gender == MALE) + hair_style = previous_list_item(hair_style, GLOB.hair_styles_male_list) + else if(gender == FEMALE) + hair_style = previous_list_item(hair_style, GLOB.hair_styles_female_list) + else + hair_style = previous_list_item(hair_style, GLOB.hair_styles_list) + + if("facial") + var/new_facial = input(user, "Choose your character's facial-hair colour:", "Character Preference","#"+facial_hair_color) as color|null + if(new_facial) + facial_hair_color = sanitize_hexcolor(new_facial) + + if("facial_hair_style") + var/new_facial_hair_style + if(gender == MALE) + new_facial_hair_style = input(user, "Choose your character's facial-hair style:", "Character Preference") as null|anything in GLOB.facial_hair_styles_male_list + else if(gender == FEMALE) + new_facial_hair_style = input(user, "Choose your character's facial-hair style:", "Character Preference") as null|anything in GLOB.facial_hair_styles_female_list + else + new_facial_hair_style = input(user, "Choose your character's facial-hair style:", "Character Preference") as null|anything in GLOB.facial_hair_styles_list + if(new_facial_hair_style) + facial_hair_style = new_facial_hair_style + + if("next_facehair_style") + if (gender == MALE) + facial_hair_style = next_list_item(facial_hair_style, GLOB.facial_hair_styles_male_list) + else if(gender == FEMALE) + facial_hair_style = next_list_item(facial_hair_style, GLOB.facial_hair_styles_female_list) + else + facial_hair_style = next_list_item(facial_hair_style, GLOB.facial_hair_styles_list) + + if("previous_facehair_style") + if (gender == MALE) + facial_hair_style = previous_list_item(facial_hair_style, GLOB.facial_hair_styles_male_list) + else if (gender == FEMALE) + facial_hair_style = previous_list_item(facial_hair_style, GLOB.facial_hair_styles_female_list) + else + facial_hair_style = previous_list_item(facial_hair_style, GLOB.facial_hair_styles_list) + + if("underwear") + var/new_underwear + if(gender == MALE) + new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_m + else if(gender == FEMALE) + new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_f + else + new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_list + if(new_underwear) + underwear = new_underwear + + if("undershirt") + var/new_undershirt + if(gender == MALE) + new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_m + else if(gender == FEMALE) + new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_f + else + new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_list + if(new_undershirt) + undershirt = new_undershirt + + if("socks") + var/new_socks + new_socks = input(user, "Choose your character's socks:", "Character Preference") as null|anything in GLOB.socks_list + if(new_socks) + socks = new_socks + + if(BODY_ZONE_PRECISE_EYES) + var/new_eyes = input(user, "Choose your character's eye colour:", "Character Preference","#"+eye_color) as color|null + if(new_eyes) + eye_color = sanitize_hexcolor(new_eyes) + + if("species") + + var/result = input(user, "Select a species", "Species Selection") as null|anything in GLOB.roundstart_races + + if(result) + var/newtype = GLOB.species_list[result] + pref_species = new newtype() + //Now that we changed our species, we must verify that the mutant colour is still allowed. + var/temp_hsv = RGBtoHSV(features["mcolor"]) + if(features["mcolor"] == "#000" || (!(MUTCOLORS_PARTSONLY in pref_species.species_traits) && ReadHSV(temp_hsv)[3] < ReadHSV("#7F7F7F")[3])) + features["mcolor"] = pref_species.default_color + + if("mutant_color") + var/new_mutantcolor = input(user, "Choose your character's alien/mutant color:", "Character Preference","#"+features["mcolor"]) as color|null + if(new_mutantcolor) + var/temp_hsv = RGBtoHSV(new_mutantcolor) + if(new_mutantcolor == "#000000") + features["mcolor"] = pref_species.default_color + else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright, but only if they affect the skin + features["mcolor"] = sanitize_hexcolor(new_mutantcolor) + else + to_chat(user, "Invalid color. Your color is not bright enough.") + + if("color_ethereal") + var/new_etherealcolor = input(user, "Choose your ethereal color", "Character Preference") as null|anything in GLOB.color_list_ethereal + if(new_etherealcolor) + features["ethcolor"] = GLOB.color_list_ethereal[new_etherealcolor] + + + if("tail_lizard") + var/new_tail + new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in GLOB.tails_list_lizard + if(new_tail) + features["tail_lizard"] = new_tail + + if("tail_human") + var/new_tail + new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in GLOB.tails_list_human + if(new_tail) + features["tail_human"] = new_tail + + if("snout") + var/new_snout + new_snout = input(user, "Choose your character's snout:", "Character Preference") as null|anything in GLOB.snouts_list + if(new_snout) + features["snout"] = new_snout + + if("horns") + var/new_horns + new_horns = input(user, "Choose your character's horns:", "Character Preference") as null|anything in GLOB.horns_list + if(new_horns) + features["horns"] = new_horns + + if("ears") + var/new_ears + new_ears = input(user, "Choose your character's ears:", "Character Preference") as null|anything in GLOB.ears_list + if(new_ears) + features["ears"] = new_ears + + if("wings") + var/new_wings + new_wings = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.r_wings_list + if(new_wings) + features["wings"] = new_wings + + if("frills") + var/new_frills + new_frills = input(user, "Choose your character's frills:", "Character Preference") as null|anything in GLOB.frills_list + if(new_frills) + features["frills"] = new_frills + + if("spines") + var/new_spines + new_spines = input(user, "Choose your character's spines:", "Character Preference") as null|anything in GLOB.spines_list + if(new_spines) + features["spines"] = new_spines + + if("body_markings") + var/new_body_markings + new_body_markings = input(user, "Choose your character's body markings:", "Character Preference") as null|anything in GLOB.body_markings_list + if(new_body_markings) + features["body_markings"] = new_body_markings + + if("legs") + var/new_legs + new_legs = input(user, "Choose your character's legs:", "Character Preference") as null|anything in GLOB.legs_list + if(new_legs) + features["legs"] = new_legs + + if("moth_wings") + var/new_moth_wings + new_moth_wings = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.moth_wings_list + if(new_moth_wings) + features["moth_wings"] = new_moth_wings + + if("s_tone") + var/new_s_tone = input(user, "Choose your character's skin-tone:", "Character Preference") as null|anything in GLOB.skin_tones + if(new_s_tone) + skin_tone = new_s_tone + + if("ooccolor") + var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference",ooccolor) as color|null + if(new_ooccolor) + ooccolor = new_ooccolor + + if("asaycolor") + var/new_asaycolor = input(user, "Choose your ASAY color:", "Game Preference",asaycolor) as color|null + if(new_asaycolor) + asaycolor = new_asaycolor + + if("bag") + var/new_backbag = input(user, "Choose your character's style of bag:", "Character Preference") as null|anything in GLOB.backbaglist + if(new_backbag) + backbag = new_backbag + + if("uplink_loc") + var/new_loc = input(user, "Choose your character's traitor uplink spawn location:", "Character Preference") as null|anything in GLOB.uplink_spawn_loc_list + if(new_loc) + uplink_spawn_loc = new_loc + + if("ai_core_icon") + var/ai_core_icon = input(user, "Choose your preferred AI core display screen:", "AI Core Display Screen Selection") as null|anything in GLOB.ai_core_display_screens + if(ai_core_icon) + preferred_ai_core_display = ai_core_icon + + if("sec_dept") + var/department = input(user, "Choose your preferred security department:", "Security Departments") as null|anything in GLOB.security_depts_prefs + if(department) + prefered_security_department = department + + if ("preferred_map") + var/maplist = list() + var/default = "Default" + if (config.defaultmap) + default += " ([config.defaultmap.map_name])" + for (var/M in config.maplist) + var/datum/map_config/VM = config.maplist[M] + if(!VM.votable) + continue + var/friendlyname = "[VM.map_name] " + if (VM.voteweight <= 0) + friendlyname += " (disabled)" + maplist[friendlyname] = VM.map_name + maplist[default] = null + var/pickedmap = input(user, "Choose your preferred map. This will be used to help weight random map selection.", "Character Preference") as null|anything in maplist + if (pickedmap) + preferred_map = maplist[pickedmap] + + if ("clientfps") + var/desiredfps = input(user, "Choose your desired fps. (0 = synced with server tick rate (currently:[world.fps]))", "Character Preference", clientfps) as null|num + if (!isnull(desiredfps)) + clientfps = desiredfps + parent.fps = desiredfps + if("ui") + var/pickedui = input(user, "Choose your UI style.", "Character Preference", UI_style) as null|anything in GLOB.available_ui_styles + if(pickedui) + UI_style = pickedui + if (parent && parent.mob && parent.mob.hud_used) + parent.mob.hud_used.update_ui_style(ui_style2icon(UI_style)) + if("pda_style") + var/pickedPDAStyle = input(user, "Choose your PDA style.", "Character Preference", pda_style) as null|anything in GLOB.pda_styles + if(pickedPDAStyle) + pda_style = pickedPDAStyle + if("pda_color") + var/pickedPDAColor = input(user, "Choose your PDA Interface color.", "Character Preference",pda_color) as color|null + if(pickedPDAColor) + pda_color = pickedPDAColor + // yogs start - Custom keybindings + if(href_list["keybinding"]) + update_keybindings(user, href_list["keybinding"], href_list["dir"]) + // yogs end + else + switch(href_list["preference"]) + if("publicity") + if(unlock_content) + toggles ^= MEMBER_PUBLIC + if("gender") + var/pickedGender = input(user, "Choose your gender.", "Character Preference", gender) as null|anything in friendlyGenders + if(pickedGender && friendlyGenders[pickedGender] != gender) + gender = friendlyGenders[pickedGender] + underwear = random_underwear(gender) + undershirt = random_undershirt(gender) + socks = random_socks() + facial_hair_style = random_facial_hair_style(gender) + hair_style = random_hair_style(gender) + + if("hotkeys") + hotkeys = !hotkeys + if(hotkeys) + bindings.bind_movement() // yogs - Rebindable keys + winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_DISABLED] mainwindow.macro=default") // yogs - Rebindable keys + else + bindings.unbind_movement() // yogs - Rebindable keys + winset(user, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=old_default") + if("action_buttons") + buttons_locked = !buttons_locked + if("tgui_fancy") + tgui_fancy = !tgui_fancy + if("tgui_lock") + tgui_lock = !tgui_lock + if("winflash") + windowflashing = !windowflashing + + //here lies the badmins + if("hear_adminhelps") + user.client.toggleadminhelpsound() + if("hear_prayers") + user.client.toggle_prayer_sound() + if("announce_login") + user.client.toggleannouncelogin() + if("combohud_lighting") + toggles ^= COMBOHUD_LIGHTING + if("toggle_dead_chat") + user.client.deadchat() + if("toggle_radio_chatter") + user.client.toggle_hear_radio() + if("toggle_prayers") + user.client.toggleprayers() + if("toggle_deadmin_always") + toggles ^= DEADMIN_ALWAYS + if("toggle_deadmin_antag") + toggles ^= DEADMIN_ANTAGONIST + if("toggle_deadmin_head") + toggles ^= DEADMIN_POSITION_HEAD + if("toggle_deadmin_security") + toggles ^= DEADMIN_POSITION_SECURITY + if("toggle_deadmin_silicon") + toggles ^= DEADMIN_POSITION_SILICON + + + if("be_special") + var/be_special_type = href_list["be_special_type"] + if(be_special_type in be_special) + be_special -= be_special_type + else + be_special += be_special_type + + if("name") + be_random_name = !be_random_name + + if("all") + be_random_body = !be_random_body + + if("hear_midis") + toggles ^= SOUND_MIDI + + if("lobby_music") + toggles ^= SOUND_LOBBY + if((toggles & SOUND_LOBBY) && user.client && isnewplayer(user)) + user.client.playtitlemusic() + else + user.stop_sound_channel(CHANNEL_LOBBYMUSIC) + + if("ghost_ears") + chat_toggles ^= CHAT_GHOSTEARS + + if("ghost_sight") + chat_toggles ^= CHAT_GHOSTSIGHT + + if("ghost_whispers") + chat_toggles ^= CHAT_GHOSTWHISPER + + if("ghost_radio") + chat_toggles ^= CHAT_GHOSTRADIO + + if("ghost_pda") + chat_toggles ^= CHAT_GHOSTPDA + + if("income_pings") + chat_toggles ^= CHAT_BANKCARD + + if("pull_requests") + chat_toggles ^= CHAT_PULLR + + if("allow_midround_antag") + toggles ^= MIDROUND_ANTAG + + if("parallaxup") + parallax = WRAP(parallax + 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1) + if (parent && parent.mob && parent.mob.hud_used) + parent.mob.hud_used.update_parallax_pref(parent.mob) + + if("parallaxdown") + parallax = WRAP(parallax - 1, PARALLAX_INSANE, PARALLAX_DISABLE + 1) + if (parent && parent.mob && parent.mob.hud_used) + parent.mob.hud_used.update_parallax_pref(parent.mob) + + if("ambientocclusion") + ambientocclusion = !ambientocclusion + if(parent && parent.screen && parent.screen.len) + var/obj/screen/plane_master/game_world/PM = locate(/obj/screen/plane_master/game_world) in parent.screen + PM.backdrop(parent.mob) + + if("auto_fit_viewport") + auto_fit_viewport = !auto_fit_viewport + if(auto_fit_viewport && parent) + parent.fit_viewport() + + if("widescreenpref") + widescreenpref = !widescreenpref + user.client.change_view(CONFIG_GET(string/default_view)) + + if("save") + save_preferences() + save_character() + + if("load") + load_preferences() + load_character() + + if("changeslot") + if(!load_character(text2num(href_list["num"]))) + random_character() + real_name = random_unique_name(gender) + save_character() + + if("tab") + if (href_list["tab"]) + current_tab = text2num(href_list["tab"]) + + // yogs start - Custom keybindings + if("reset_bindings") + reset_keybindings() + + if("mood") + yogtoggles ^= PREF_MOOD + // yogs end + + ShowChoices(user) + return 1 + +/datum/preferences/proc/copy_to(mob/living/carbon/human/character, icon_updates = 1, roundstart_checks = TRUE) + if(be_random_name) + real_name = pref_species.random_name(gender) + + if(be_random_body) + random_character(gender) + + if(roundstart_checks) + if(CONFIG_GET(flag/humans_need_surnames) && (pref_species.id == "human")) + var/firstspace = findtext(real_name, " ") + var/name_length = length(real_name) + if(!firstspace) //we need a surname + real_name += " [pick(GLOB.last_names)]" + else if(firstspace == name_length) + real_name += "[pick(GLOB.last_names)]" + + character.real_name = real_name + character.name = character.real_name + + character.gender = gender + character.age = age + + character.eye_color = eye_color + var/obj/item/organ/eyes/organ_eyes = character.getorgan(/obj/item/organ/eyes) + if(organ_eyes) + if(!initial(organ_eyes.eye_color)) + organ_eyes.eye_color = eye_color + organ_eyes.old_eye_color = eye_color + character.hair_color = hair_color + character.facial_hair_color = facial_hair_color + + character.skin_tone = skin_tone + character.hair_style = hair_style + character.facial_hair_style = facial_hair_style + character.underwear = underwear + character.undershirt = undershirt + character.socks = socks + + character.backbag = backbag + + var/datum/species/chosen_species + chosen_species = pref_species.type + if(roundstart_checks && !(pref_species.id in GLOB.roundstart_races) && !(pref_species.id in (CONFIG_GET(keyed_list/roundstart_no_hard_check)))) + chosen_species = /datum/species/human + pref_species = new /datum/species/human + save_character() + + character.dna.features = features.Copy() + character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE) + character.dna.real_name = character.real_name + + if("tail_lizard" in pref_species.default_features) + character.dna.species.mutant_bodyparts |= "tail_lizard" + + if(icon_updates) + character.update_body() + character.update_hair() + character.update_body_parts() + +/datum/preferences/proc/get_default_name(name_id) + switch(name_id) + if("human") + return random_unique_name() + if("ai") + return pick(GLOB.ai_names) + if("cyborg") + return DEFAULT_CYBORG_NAME + if("clown") + return pick(GLOB.clown_names) + if("mime") + return pick(GLOB.mime_names) + if("religion") + return DEFAULT_RELIGION + if("deity") + return DEFAULT_DEITY + return random_unique_name() + +/datum/preferences/proc/ask_for_custom_name(mob/user,name_id) + var/namedata = GLOB.preferences_custom_names[name_id] + if(!namedata) + return + + var/raw_name = input(user, "Choose your character's [namedata["qdesc"]]:","Character Preference") as text|null + if(!raw_name) + if(namedata["allow_null"]) + custom_names[name_id] = get_default_name(name_id) + else + return + else + var/sanitized_name = reject_bad_name(raw_name,namedata["allow_numbers"]) + if(!sanitized_name) + to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z,[namedata["allow_numbers"] ? ",0-9," : ""] -, ' and .") + return + else + custom_names[name_id] = sanitized_name diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm index 41989900ebb9..1572a9064b04 100644 --- a/code/modules/client/preferences_toggles.dm +++ b/code/modules/client/preferences_toggles.dm @@ -1,457 +1,457 @@ -//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 - - -/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() - C.chatOutput.stopLobbyMusic() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_ooc)() - set name = "Show/Hide OOC" - set category = "Preferences" - set desc = "Show OOC Chat" - usr.client.prefs.chat_toggles ^= CHAT_OOC - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/listen_ooc/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_OOC - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_bank_card)() - set name = "Show/Hide Income Updates" - set category = "Preferences" - set desc = "Show or hide updates to your income" - usr.client.prefs.chat_toggles ^= CHAT_BANKCARD - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "now" : "no longer"] be notified when you get paid.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Income Notifications", "[(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "Enabled" : "Disabled"]")) -/datum/verbs/menu/Settings/listen_bank_card/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_BANKCARD - - -GLOBAL_LIST_INIT(ghost_forms, list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \ - "ghost_blue","ghost_yellow","ghost_green","ghost_pink", \ - "ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \ - "ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\ - "ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost")) -/client/proc/pick_form() - if(!is_content_unlocked()) - alert("This setting is for accounts with BYOND premium only.") - return - var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms - if(new_form) - prefs.ghost_form = new_form - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_icon(new_form) - -GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON)) - -/client/proc/pick_ghost_orbit() - if(!is_content_unlocked()) - alert("This setting is for accounts with BYOND premium only.") - return - var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits - if(new_orbit) - prefs.ghost_orbit = new_orbit - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.ghost_orbit = new_orbit - -/client/proc/pick_ghost_accs() - var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites") - if(new_ghost_accs) - switch(new_ghost_accs) - if("full accessories") - prefs.ghost_accs = GHOST_ACCS_FULL - if("only directional sprites") - prefs.ghost_accs = GHOST_ACCS_DIR - if("default sprites") - prefs.ghost_accs = GHOST_ACCS_NONE - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_icon() - -/client/verb/pick_ghost_customization() - set name = "Ghost Customization" - set category = "Preferences" - set desc = "Customize your ghastly appearance." - if(is_content_unlocked()) - switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories")) - if("Ghost Form") - pick_form() - if("Ghost Orbit") - pick_ghost_orbit() - if("Ghost Accessories") - pick_ghost_accs() - else - pick_ghost_accs() - -/client/verb/pick_ghost_others() - set name = "Ghosts of Others" - set category = "Preferences" - set desc = "Change display settings for the ghosts of other players." - var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost") - if(new_ghost_others) - switch(new_ghost_others) - if("Their Setting") - prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING - if("Default Sprites") - prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE - if("White Ghost") - prefs.ghost_others = GHOST_OTHERS_SIMPLE - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_sight() - -/client/verb/toggle_intent_style() - set name = "Toggle Intent Selection Style" - set category = "Preferences" - set desc = "Toggle between directly clicking the desired intent or clicking to rotate through." - prefs.toggles ^= INTENT_STYLE - to_chat(src, "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]") - prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Intent Selection", "[prefs.toggles & INTENT_STYLE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ghost_hud_pref() - set name = "Toggle Ghost HUD" - set category = "Preferences" - set desc = "Hide/Show Ghost HUD" - - prefs.ghost_hud = !prefs.ghost_hud - to_chat(src, "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].") - prefs.save_preferences() - if(isobserver(mob)) - mob.hud_used.show_hud() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]")) - -/client/verb/toggle_inquisition() // warning: unexpected inquisition - set name = "Toggle Inquisitiveness" - set desc = "Sets whether your ghost examines everything on click by default" - set category = "Preferences" - - prefs.inquisitive_ghost = !prefs.inquisitive_ghost - prefs.save_preferences() - if(prefs.inquisitive_ghost) - to_chat(src, "You will now examine everything you click on.") - else - to_chat(src, "You will no longer examine things you click on.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]")) - -//Admin Preferences -/client/proc/toggleadminhelpsound() - set name = "Hear/Silence Adminhelps" - set category = "Prefs - Admin" - set desc = "Toggle hearing a notification when admin PMs are received" - if(!holder) - return - prefs.toggles ^= SOUND_ADMINHELP - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggleannouncelogin() - set name = "Do/Don't Announce Login" - set category = "Prefs - Admin" - set desc = "Toggle if you want an announcement to admins when you login during a round" - if(!holder) - return - prefs.toggles ^= ANNOUNCE_LOGIN - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_hear_radio() - set name = "Show/Hide Radio Chatter" - set category = "Prefs - Admin" - set desc = "Toggle seeing radiochatter from nearby radios and speakers" - if(!holder) - return - prefs.chat_toggles ^= CHAT_RADIO - prefs.save_preferences() - to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/deadchat() - set name = "Show/Hide Deadchat" - set category = "Prefs - Admin" - set desc ="Toggles seeing deadchat" - if(!holder) - return - prefs.chat_toggles ^= CHAT_DEAD - prefs.save_preferences() - to_chat(src, "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggleprayers() - set name = "Show/Hide Prayers" - set category = "Prefs - Admin" - set desc = "Toggles seeing prayers" - if(!holder) - return - prefs.chat_toggles ^= CHAT_PRAYER - prefs.save_preferences() - to_chat(src, "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_prayer_sound() - set name = "Hear/Silence Prayer Sounds" - set category = "Prefs - Admin" - set desc = "Hear Prayer Sounds" - if(!holder) - return - prefs.toggles ^= SOUND_PRAYERS - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & SOUND_PRAYERS) ? "now" : "no longer"] hear a sound when prayers arrive.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]")) - -/client/proc/colorasay() - set name = "Set Admin Say Color" - set category = "Prefs - Admin" - set desc = "Set the color of your ASAY messages" - if(!holder) - return - if(!CONFIG_GET(flag/allow_admin_asaycolor)) - to_chat(src, "Custom Asay color is currently disabled by the server.") - return - var/new_asaycolor = input(src, "Please select your ASAY color.", "ASAY color", prefs.asaycolor) as color|null - if(new_asaycolor) - prefs.asaycolor = sanitize_ooccolor(new_asaycolor) - prefs.save_preferences() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set ASAY Color") - return - -/client/proc/resetasaycolor() - set name = "Reset your Admin Say Color" - set desc = "Returns your ASAY Color to default" - set category = "Prefs - Admin" - if(!holder) - return - if(!CONFIG_GET(flag/allow_admin_asaycolor)) - to_chat(src, "Custom Asay color is currently disabled by the server.") - return - prefs.asaycolor = initial(prefs.asaycolor) +//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 + + +/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() + C.chatOutput.stopLobbyMusic() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_ooc)() + set name = "Show/Hide OOC" + set category = "Preferences" + set desc = "Show OOC Chat" + usr.client.prefs.chat_toggles ^= CHAT_OOC + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/listen_ooc/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_OOC + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_bank_card)() + set name = "Show/Hide Income Updates" + set category = "Preferences" + set desc = "Show or hide updates to your income" + usr.client.prefs.chat_toggles ^= CHAT_BANKCARD + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "now" : "no longer"] be notified when you get paid.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Income Notifications", "[(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "Enabled" : "Disabled"]")) +/datum/verbs/menu/Settings/listen_bank_card/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_BANKCARD + + +GLOBAL_LIST_INIT(ghost_forms, list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \ + "ghost_blue","ghost_yellow","ghost_green","ghost_pink", \ + "ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \ + "ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\ + "ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost")) +/client/proc/pick_form() + if(!is_content_unlocked()) + alert("This setting is for accounts with BYOND premium only.") + return + var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms + if(new_form) + prefs.ghost_form = new_form + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_icon(new_form) + +GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON)) + +/client/proc/pick_ghost_orbit() + if(!is_content_unlocked()) + alert("This setting is for accounts with BYOND premium only.") + return + var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits + if(new_orbit) + prefs.ghost_orbit = new_orbit + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.ghost_orbit = new_orbit + +/client/proc/pick_ghost_accs() + var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites") + if(new_ghost_accs) + switch(new_ghost_accs) + if("full accessories") + prefs.ghost_accs = GHOST_ACCS_FULL + if("only directional sprites") + prefs.ghost_accs = GHOST_ACCS_DIR + if("default sprites") + prefs.ghost_accs = GHOST_ACCS_NONE + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_icon() + +/client/verb/pick_ghost_customization() + set name = "Ghost Customization" + set category = "Preferences" + set desc = "Customize your ghastly appearance." + if(is_content_unlocked()) + switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories")) + if("Ghost Form") + pick_form() + if("Ghost Orbit") + pick_ghost_orbit() + if("Ghost Accessories") + pick_ghost_accs() + else + pick_ghost_accs() + +/client/verb/pick_ghost_others() + set name = "Ghosts of Others" + set category = "Preferences" + set desc = "Change display settings for the ghosts of other players." + var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost") + if(new_ghost_others) + switch(new_ghost_others) + if("Their Setting") + prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING + if("Default Sprites") + prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE + if("White Ghost") + prefs.ghost_others = GHOST_OTHERS_SIMPLE + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_sight() + +/client/verb/toggle_intent_style() + set name = "Toggle Intent Selection Style" + set category = "Preferences" + set desc = "Toggle between directly clicking the desired intent or clicking to rotate through." + prefs.toggles ^= INTENT_STYLE + to_chat(src, "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]") + prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Intent Selection", "[prefs.toggles & INTENT_STYLE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/verb/toggle_ghost_hud_pref() + set name = "Toggle Ghost HUD" + set category = "Preferences" + set desc = "Hide/Show Ghost HUD" + + prefs.ghost_hud = !prefs.ghost_hud + to_chat(src, "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].") + prefs.save_preferences() + if(isobserver(mob)) + mob.hud_used.show_hud() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]")) + +/client/verb/toggle_inquisition() // warning: unexpected inquisition + set name = "Toggle Inquisitiveness" + set desc = "Sets whether your ghost examines everything on click by default" + set category = "Preferences" + + prefs.inquisitive_ghost = !prefs.inquisitive_ghost + prefs.save_preferences() + if(prefs.inquisitive_ghost) + to_chat(src, "You will now examine everything you click on.") + else + to_chat(src, "You will no longer examine things you click on.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]")) + +//Admin Preferences +/client/proc/toggleadminhelpsound() + set name = "Hear/Silence Adminhelps" + set category = "Prefs - Admin" + set desc = "Toggle hearing a notification when admin PMs are received" + if(!holder) + return + prefs.toggles ^= SOUND_ADMINHELP + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggleannouncelogin() + set name = "Do/Don't Announce Login" + set category = "Prefs - Admin" + set desc = "Toggle if you want an announcement to admins when you login during a round" + if(!holder) + return + prefs.toggles ^= ANNOUNCE_LOGIN + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_hear_radio() + set name = "Show/Hide Radio Chatter" + set category = "Prefs - Admin" + set desc = "Toggle seeing radiochatter from nearby radios and speakers" + if(!holder) + return + prefs.chat_toggles ^= CHAT_RADIO + prefs.save_preferences() + to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/deadchat() + set name = "Show/Hide Deadchat" + set category = "Prefs - Admin" + set desc ="Toggles seeing deadchat" + if(!holder) + return + prefs.chat_toggles ^= CHAT_DEAD + prefs.save_preferences() + to_chat(src, "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggleprayers() + set name = "Show/Hide Prayers" + set category = "Prefs - Admin" + set desc = "Toggles seeing prayers" + if(!holder) + return + prefs.chat_toggles ^= CHAT_PRAYER + prefs.save_preferences() + to_chat(src, "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_prayer_sound() + set name = "Hear/Silence Prayer Sounds" + set category = "Prefs - Admin" + set desc = "Hear Prayer Sounds" + if(!holder) + return + prefs.toggles ^= SOUND_PRAYERS + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & SOUND_PRAYERS) ? "now" : "no longer"] hear a sound when prayers arrive.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]")) + +/client/proc/colorasay() + set name = "Set Admin Say Color" + set category = "Prefs - Admin" + set desc = "Set the color of your ASAY messages" + if(!holder) + return + if(!CONFIG_GET(flag/allow_admin_asaycolor)) + to_chat(src, "Custom Asay color is currently disabled by the server.") + return + var/new_asaycolor = input(src, "Please select your ASAY color.", "ASAY color", prefs.asaycolor) as color|null + if(new_asaycolor) + prefs.asaycolor = sanitize_ooccolor(new_asaycolor) + prefs.save_preferences() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set ASAY Color") + return + +/client/proc/resetasaycolor() + set name = "Reset your Admin Say Color" + set desc = "Returns your ASAY Color to default" + set category = "Prefs - Admin" + if(!holder) + return + if(!CONFIG_GET(flag/allow_admin_asaycolor)) + to_chat(src, "Custom Asay color is currently disabled by the server.") + return + prefs.asaycolor = initial(prefs.asaycolor) prefs.save_preferences() \ No newline at end of file diff --git a/code/modules/client/verbs/etips.dm b/code/modules/client/verbs/etips.dm index 2455f5d01b07..c85e5113560a 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 0db5dba3149f..fa6112116e78 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -1,318 +1,318 @@ -/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/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 - - var/tearable //can this particular item be torn down to be used for cloth? | yogs - var/tearhealth = 100 //health regarding tearing clothes to get torn cloth | yogs - -/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) - -/obj/item/clothing/MouseDrop(atom/over_object) - . = ..() - var/mob/M = usr - - if(ismecha(M.loc)) // stops inventory actions in a mech - return - - if(!M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - if(M.putItemFromInventoryInHandIfPossible(src, H.held_index)) - add_fingerprint(usr) - -/obj/item/reagent_containers/food/snacks/clothing - name = "temporary moth clothing snack item" - desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object." - list_reagents = list(/datum/reagent/consumable/nutriment = 1) - tastes = list("dust" = 1, "lint" = 1) - foodtype = CLOTH - -/obj/item/clothing/attack(mob/M, mob/user, def_zone) - if(user.a_intent != INTENT_HARM && ismoth(M)) - 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) - . = ..() - switch (max_heat_protection_temperature) - if (400 to 1000) - . += "[src] offers the wearer limited protection from fire." - if (1001 to 1600) - . += "[src] offers the wearer some protection from fire." - if (1601 to 35000) - . += "[src] offers the wearer robust protection from fire." - 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" - if (pockets.can_hold?.len) // If pocket type can hold anything, vs only specific items - how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" - else - how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s that are [weightclass2text(pockets.max_w_class)] or smaller.\n" - if(pockets.quickdraw) - how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n" - if(pockets.silent) - how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n" - how_cool_are_your_threads += "" - . += how_cool_are_your_threads.Join() - -/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/AltClick(mob/user) - if(..()) - return 1 - - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - if(attached_accessory) - remove_accessory(user) - else - rolldown() - -/obj/item/clothing/under/verb/jumpsuit_adjust() - set name = "Adjust Jumpsuit Style" - set category = null - set src in usr - rolldown() - -/obj/item/clothing/under/proc/rolldown() - if(!can_use(usr)) - return - if(!can_adjust) - to_chat(usr, "You cannot wear this suit any differently!") - return - if(toggle_jumpsuit_adjust()) - to_chat(usr, "You adjust the suit to wear it more casually.") - else - to_chat(usr, "You adjust the suit back to normal.") - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.update_inv_w_uniform() - H.update_body() - -/obj/item/clothing/under/proc/toggle_jumpsuit_adjust() - if(adjusted == DIGITIGRADE_STYLE) - return - adjusted = !adjusted - if(adjusted) - if(fitted != FEMALE_UNIFORM_TOP) - fitted = NO_FEMALE_UNIFORM - if(!alt_covers_chest) // for the special snowflake suits that expose the chest when adjusted - 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 - ..() +/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/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 + + var/tearable //can this particular item be torn down to be used for cloth? | yogs + var/tearhealth = 100 //health regarding tearing clothes to get torn cloth | yogs + +/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) + +/obj/item/clothing/MouseDrop(atom/over_object) + . = ..() + var/mob/M = usr + + if(ismecha(M.loc)) // stops inventory actions in a mech + return + + if(!M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + if(M.putItemFromInventoryInHandIfPossible(src, H.held_index)) + add_fingerprint(usr) + +/obj/item/reagent_containers/food/snacks/clothing + name = "temporary moth clothing snack item" + desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object." + list_reagents = list(/datum/reagent/consumable/nutriment = 1) + tastes = list("dust" = 1, "lint" = 1) + foodtype = CLOTH + +/obj/item/clothing/attack(mob/M, mob/user, def_zone) + if(user.a_intent != INTENT_HARM && ismoth(M)) + 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) + . = ..() + switch (max_heat_protection_temperature) + if (400 to 1000) + . += "[src] offers the wearer limited protection from fire." + if (1001 to 1600) + . += "[src] offers the wearer some protection from fire." + if (1601 to 35000) + . += "[src] offers the wearer robust protection from fire." + 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" + if (pockets.can_hold?.len) // If pocket type can hold anything, vs only specific items + how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" + else + how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s that are [weightclass2text(pockets.max_w_class)] or smaller.\n" + if(pockets.quickdraw) + how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n" + if(pockets.silent) + how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n" + how_cool_are_your_threads += "" + . += how_cool_are_your_threads.Join() + +/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/AltClick(mob/user) + if(..()) + return 1 + + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + else + if(attached_accessory) + remove_accessory(user) + else + rolldown() + +/obj/item/clothing/under/verb/jumpsuit_adjust() + set name = "Adjust Jumpsuit Style" + set category = null + set src in usr + rolldown() + +/obj/item/clothing/under/proc/rolldown() + if(!can_use(usr)) + return + if(!can_adjust) + to_chat(usr, "You cannot wear this suit any differently!") + return + if(toggle_jumpsuit_adjust()) + to_chat(usr, "You adjust the suit to wear it more casually.") + else + to_chat(usr, "You adjust the suit back to normal.") + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + H.update_inv_w_uniform() + H.update_body() + +/obj/item/clothing/under/proc/toggle_jumpsuit_adjust() + if(adjusted == DIGITIGRADE_STYLE) + return + adjusted = !adjusted + if(adjusted) + if(fitted != FEMALE_UNIFORM_TOP) + fitted = NO_FEMALE_UNIFORM + if(!alt_covers_chest) // for the special snowflake suits that expose the chest when adjusted + 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 + ..() diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index 4ccdc7da0b20..d350d997da61 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -1,470 +1,470 @@ -//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(src.loc)) - var/mob/living/carbon/human/H = src.loc - var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) - if(!(HAS_TRAIT(H, TRAIT_BLIND))) - if(H.glasses == src) - to_chat(H, "[src] overloads and blinds you!") - H.flash_act(visual = 1) - H.blind_eyes(3) - H.blur_eyes(5) - eyes.applyOrganDamage(5) - -/obj/item/clothing/glasses/meson - name = "optical meson scanner" - desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." - icon_state = "meson" - 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/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 - flash_protect = -1 - 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) - 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 - flash_protect = -1 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/clothing/glasses/eyepatch - name = "eyepatch" - desc = "Yarr." - icon_state = "eyepatch" - 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 - 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" - -/obj/item/clothing/glasses/regular/circle - name = "circle glasses" - desc = "Why would you wear something so controversial yet so brave?" - icon_state = "circle_glasses" - item_state = "circle_glasses" - -//Here lies green glasses, so ugly they died. RIP - -/obj/item/clothing/glasses/sunglasses - name = "sunglasses" - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes." - icon_state = "sun" - 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, as well as providing an innate understanding of liquid viscosity while in motion." - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/sunglasses/reagent/equipped(mob/user, slot) - . = ..() - if(ishuman(user) && slot == SLOT_GLASSES) - ADD_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) - -/obj/item/clothing/glasses/sunglasses/reagent/dropped(mob/user) - . = ..() - REMOVE_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) - -/obj/item/clothing/glasses/sunglasses/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 - glass_colour_type = /datum/client_colour/glass_colour/gray - -/obj/item/clothing/glasses/welding/attack_self(mob/user) - weldingvisortoggle(user) - - -/obj/item/clothing/glasses/blindfold - name = "blindfold" - desc = "Covers the eyes, preventing sight." - icon_state = "blindfold" - item_state = "blindfold" - flash_protect = 2 - tint = 3 - darkness_view = 1 - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/glasses/blindfold/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot == SLOT_GLASSES) - user.become_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/blindfold/dropped(mob/living/carbon/human/user) - ..() - user.cure_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/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/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/blindfold/white/update_icon(mob/living/carbon/human/user) - if(ishuman(user) && !colored_before) - add_atom_colour("#[user.eye_color]", FIXED_COLOUR_PRIORITY) - colored_before = TRUE - -/obj/item/clothing/glasses/blindfold/white/worn_overlays(isinhands = FALSE, file2use) - . = list() - if(!isinhands && ishuman(loc) && !colored_before) - var/mob/living/carbon/human/H = loc - var/mutable_appearance/M = mutable_appearance('icons/mob/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 = -1 - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/thermal/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - thermal_overload() - -/obj/item/clothing/glasses/thermal/xray - name = "syndicate xray goggles" - desc = "A pair of xray goggles manufactured by the Syndicate." - vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS - -/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete - name = "chameleon thermals" - desc = "A pair of thermal optic goggles with an onboard chameleon generator." - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/thermal/syndi/Initialize() - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - -/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/glasses/thermal/monocle - name = "thermoncle" - desc = "Never before has seeing through walls felt so gentlepersonly." - icon_state = "thermoncle" - flags_1 = null //doesn't protect eyes because it's a monocle, duh - -/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description! - if(user.gender == MALE) - desc = replacetext(desc, "person", "man") - else if(user.gender == FEMALE) - desc = replacetext(desc, "person", "woman") - . = ..() - desc = initial(desc) - -/obj/item/clothing/glasses/thermal/eyepatch - name = "optical thermal eyepatch" - desc = "An eyepatch with built-in thermal optics." - icon_state = "eyepatch" - 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 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - resistance_flags = LAVA_PROOF | FIRE_PROOF - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/godeye/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) - -/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, src) && W != src && W.loc == user) - if(W.icon_state == "godeye") - W.icon_state = "doublegodeye" - W.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) - if(H.client.prefs) - if(src == H.glasses) - H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour - if(H.client.prefs.uses_glasses_colour) - to_chat(H, "You will now see glasses colors.") - else - to_chat(H, "You will no longer see glasses colors.") - H.update_glasses_color(src, 1) - else - return ..() - -/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type) - var/old_colour_type = glass_colour_type - if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path. - glass_colour_type = new_color_type - if(H && H.glasses == src) - if(old_colour_type) - H.remove_client_colour(old_colour_type) - if(glass_colour_type) - H.update_glasses_color(src, 1) - - -/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped) - if(client && client.prefs.uses_glasses_colour && glasses_equipped) - add_client_colour(G.glass_colour_type) - else - remove_client_colour(G.glass_colour_type) +//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(src.loc)) + var/mob/living/carbon/human/H = src.loc + var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) + if(!(HAS_TRAIT(H, TRAIT_BLIND))) + if(H.glasses == src) + to_chat(H, "[src] overloads and blinds you!") + H.flash_act(visual = 1) + H.blind_eyes(3) + H.blur_eyes(5) + eyes.applyOrganDamage(5) + +/obj/item/clothing/glasses/meson + name = "optical meson scanner" + desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." + icon_state = "meson" + 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/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 + flash_protect = -1 + 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) + 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 + flash_protect = -1 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/clothing/glasses/eyepatch + name = "eyepatch" + desc = "Yarr." + icon_state = "eyepatch" + 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 + 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" + +/obj/item/clothing/glasses/regular/circle + name = "circle glasses" + desc = "Why would you wear something so controversial yet so brave?" + icon_state = "circle_glasses" + item_state = "circle_glasses" + +//Here lies green glasses, so ugly they died. RIP + +/obj/item/clothing/glasses/sunglasses + name = "sunglasses" + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes." + icon_state = "sun" + 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, as well as providing an innate understanding of liquid viscosity while in motion." + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/sunglasses/reagent/equipped(mob/user, slot) + . = ..() + if(ishuman(user) && slot == SLOT_GLASSES) + ADD_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) + +/obj/item/clothing/glasses/sunglasses/reagent/dropped(mob/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) + +/obj/item/clothing/glasses/sunglasses/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 + glass_colour_type = /datum/client_colour/glass_colour/gray + +/obj/item/clothing/glasses/welding/attack_self(mob/user) + weldingvisortoggle(user) + + +/obj/item/clothing/glasses/blindfold + name = "blindfold" + desc = "Covers the eyes, preventing sight." + icon_state = "blindfold" + item_state = "blindfold" + flash_protect = 2 + tint = 3 + darkness_view = 1 + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/glasses/blindfold/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot == SLOT_GLASSES) + user.become_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/blindfold/dropped(mob/living/carbon/human/user) + ..() + user.cure_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/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/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/blindfold/white/update_icon(mob/living/carbon/human/user) + if(ishuman(user) && !colored_before) + add_atom_colour("#[user.eye_color]", FIXED_COLOUR_PRIORITY) + colored_before = TRUE + +/obj/item/clothing/glasses/blindfold/white/worn_overlays(isinhands = FALSE, file2use) + . = list() + if(!isinhands && ishuman(loc) && !colored_before) + var/mob/living/carbon/human/H = loc + var/mutable_appearance/M = mutable_appearance('icons/mob/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 = -1 + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/thermal/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + thermal_overload() + +/obj/item/clothing/glasses/thermal/xray + name = "syndicate xray goggles" + desc = "A pair of xray goggles manufactured by the Syndicate." + vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS + +/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete + name = "chameleon thermals" + desc = "A pair of thermal optic goggles with an onboard chameleon generator." + + var/datum/action/item_action/chameleon/change/chameleon_action + +/obj/item/clothing/glasses/thermal/syndi/Initialize() + . = ..() + chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/clothing/glasses + chameleon_action.chameleon_name = "Glasses" + chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) + chameleon_action.initialize_disguises() + +/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + chameleon_action.emp_randomise() + +/obj/item/clothing/glasses/thermal/monocle + name = "thermoncle" + desc = "Never before has seeing through walls felt so gentlepersonly." + icon_state = "thermoncle" + flags_1 = null //doesn't protect eyes because it's a monocle, duh + +/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description! + if(user.gender == MALE) + desc = replacetext(desc, "person", "man") + else if(user.gender == FEMALE) + desc = replacetext(desc, "person", "woman") + . = ..() + desc = initial(desc) + +/obj/item/clothing/glasses/thermal/eyepatch + name = "optical thermal eyepatch" + desc = "An eyepatch with built-in thermal optics." + icon_state = "eyepatch" + 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 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + resistance_flags = LAVA_PROOF | FIRE_PROOF + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/godeye/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) + +/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, src) && W != src && W.loc == user) + if(W.icon_state == "godeye") + W.icon_state = "doublegodeye" + W.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) + if(H.client.prefs) + if(src == H.glasses) + H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour + if(H.client.prefs.uses_glasses_colour) + to_chat(H, "You will now see glasses colors.") + else + to_chat(H, "You will no longer see glasses colors.") + H.update_glasses_color(src, 1) + else + return ..() + +/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type) + var/old_colour_type = glass_colour_type + if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path. + glass_colour_type = new_color_type + if(H && H.glasses == src) + if(old_colour_type) + H.remove_client_colour(old_colour_type) + if(glass_colour_type) + H.update_glasses_color(src, 1) + + +/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped) + if(client && client.prefs.uses_glasses_colour && glasses_equipped) + add_client_colour(G.glass_colour_type) + else + remove_client_colour(G.glass_colour_type) diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index a4b74f3e46f2..6dba8b862d1b 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -1,212 +1,212 @@ -/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." - -/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/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 - flash_protect = -1 - 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/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 - flash_protect = -1 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/hud/diagnostic/sunglasses - name = "diagnostic sunglasses" - desc = "Sunglasses with a diagnostic HUD." - icon_state = "sunhuddiag" - item_state = "glasses" - flash_protect = 1 - tint = 1 - -/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/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/Initialize() - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - -/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - - -/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch - name = "eyepatch HUD" - desc = "A heads-up display that connects directly to the optical nerve of the user, replacing the need for that useless eyeball." - icon_state = "hudpatch" - -/obj/item/clothing/glasses/hud/security/sunglasses - name = "security HUDSunglasses" - desc = "Sunglasses with a security HUD." - icon_state = "sunhudsec" - darkness_view = 1 - flash_protect = 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 - flash_protect = -1 - 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." + +/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/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 + flash_protect = -1 + 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/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 + flash_protect = -1 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/hud/diagnostic/sunglasses + name = "diagnostic sunglasses" + desc = "Sunglasses with a diagnostic HUD." + icon_state = "sunhuddiag" + item_state = "glasses" + flash_protect = 1 + tint = 1 + +/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/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/Initialize() + . = ..() + chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/clothing/glasses + chameleon_action.chameleon_name = "Glasses" + chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) + chameleon_action.initialize_disguises() + +/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + chameleon_action.emp_randomise() + + +/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch + name = "eyepatch HUD" + desc = "A heads-up display that connects directly to the optical nerve of the user, replacing the need for that useless eyeball." + icon_state = "hudpatch" + +/obj/item/clothing/glasses/hud/security/sunglasses + name = "security HUDSunglasses" + desc = "Sunglasses with a security HUD." + icon_state = "sunhudsec" + darkness_view = 1 + flash_protect = 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 + flash_protect = -1 + 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 64a6c48a2f48..9b381cef7f8c 100644 --- a/code/modules/clothing/gloves/_gloves.dm +++ b/code/modules/clothing/gloves/_gloves.dm @@ -1,43 +1,43 @@ -/obj/item/clothing/gloves - name = "gloves" - gender = PLURAL //Carn: for grammarically correct text-parsing - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/clothing/gloves.dmi' - siemens_coefficient = 0.5 - body_parts_covered = HANDS - slot_flags = ITEM_SLOT_GLOVES - attack_verb = list("challenged") - var/transfer_prints = FALSE - strip_delay = 20 - equip_delay_other = 40 - -/obj/item/clothing/gloves/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/item/clothing/gloves/proc/clean_blood(datum/source, strength) - if(strength < CLEAN_STRENGTH_BLOOD) - return - transfer_blood = 0 - -/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user) - user.visible_message("\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!") - return OXYLOSS - -/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands") - -/obj/item/clothing/gloves/update_clothes_damaged_state(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 0 // return 1 to cancel attack_hand() +/obj/item/clothing/gloves + name = "gloves" + gender = PLURAL //Carn: for grammarically correct text-parsing + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/clothing/gloves.dmi' + siemens_coefficient = 0.5 + body_parts_covered = HANDS + slot_flags = ITEM_SLOT_GLOVES + attack_verb = list("challenged") + var/transfer_prints = FALSE + strip_delay = 20 + equip_delay_other = 40 + +/obj/item/clothing/gloves/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/item/clothing/gloves/proc/clean_blood(datum/source, strength) + if(strength < CLEAN_STRENGTH_BLOOD) + return + transfer_blood = 0 + +/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user) + user.visible_message("\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!") + return OXYLOSS + +/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands") + +/obj/item/clothing/gloves/update_clothes_damaged_state(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 0 // return 1 to cancel attack_hand() diff --git a/code/modules/clothing/gloves/boxing.dm b/code/modules/clothing/gloves/boxing.dm index f1016b0cc128..a76ff7c5834d 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 35594b87f93a..5256b1616149 100644 --- a/code/modules/clothing/gloves/color.dm +++ b/code/modules/clothing/gloves/color.dm @@ -1,203 +1,203 @@ -/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 - -/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 Initialize() - permeability_coefficient = 0.05 - item_color = "yellow" - resistance_flags = NONE - -/obj/item/clothing/gloves/color/fyellow/Initialize() - . = ..() - siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5) - -/obj/item/clothing/gloves/color/fyellow/old - desc = "Old and worn out insulated gloves, hopefully they still work." - name = "worn out insulated gloves" - -/obj/item/clothing/gloves/color/fyellow/old/Initialize() - . = ..() - siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75) - -/obj/item/clothing/gloves/color/black - desc = "These gloves are fire-resistant." - name = "black gloves" - icon_state = "black" - 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(I.tool_behaviour == TOOL_WIRECUTTER) - if(can_be_cut && icon_state == initial(icon_state))//only if not dyed - to_chat(user, "You snip the fingertips off of [src].") - I.play_tool_sound(src) - new /obj/item/clothing/gloves/fingerless(drop_location()) - qdel(src) - ..() - -/obj/item/clothing/gloves/color/orange - name = "orange gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "orange" - 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/effect/spawner/lootdrop/gloves - name = "random gloves" - desc = "These gloves are supposed to be a random color..." - icon = 'icons/obj/clothing/gloves.dmi' - icon_state = "random_gloves" - loot = list( - /obj/item/clothing/gloves/color/orange = 1, - /obj/item/clothing/gloves/color/red = 1, - /obj/item/clothing/gloves/color/blue = 1, - /obj/item/clothing/gloves/color/purple = 1, - /obj/item/clothing/gloves/color/green = 1, - /obj/item/clothing/gloves/color/grey = 1, - /obj/item/clothing/gloves/color/light_brown = 1, - /obj/item/clothing/gloves/color/brown = 1, - /obj/item/clothing/gloves/color/white = 1, - /obj/item/clothing/gloves/color/rainbow = 1) +/obj/item/clothing/gloves/color/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 + +/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 Initialize() + permeability_coefficient = 0.05 + item_color = "yellow" + resistance_flags = NONE + +/obj/item/clothing/gloves/color/fyellow/Initialize() + . = ..() + siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5) + +/obj/item/clothing/gloves/color/fyellow/old + desc = "Old and worn out insulated gloves, hopefully they still work." + name = "worn out insulated gloves" + +/obj/item/clothing/gloves/color/fyellow/old/Initialize() + . = ..() + siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75) + +/obj/item/clothing/gloves/color/black + desc = "These gloves are fire-resistant." + name = "black gloves" + icon_state = "black" + 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(I.tool_behaviour == TOOL_WIRECUTTER) + if(can_be_cut && icon_state == initial(icon_state))//only if not dyed + to_chat(user, "You snip the fingertips off of [src].") + I.play_tool_sound(src) + new /obj/item/clothing/gloves/fingerless(drop_location()) + qdel(src) + ..() + +/obj/item/clothing/gloves/color/orange + name = "orange gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "orange" + 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/effect/spawner/lootdrop/gloves + name = "random gloves" + desc = "These gloves are supposed to be a random color..." + icon = 'icons/obj/clothing/gloves.dmi' + icon_state = "random_gloves" + loot = list( + /obj/item/clothing/gloves/color/orange = 1, + /obj/item/clothing/gloves/color/red = 1, + /obj/item/clothing/gloves/color/blue = 1, + /obj/item/clothing/gloves/color/purple = 1, + /obj/item/clothing/gloves/color/green = 1, + /obj/item/clothing/gloves/color/grey = 1, + /obj/item/clothing/gloves/color/light_brown = 1, + /obj/item/clothing/gloves/color/brown = 1, + /obj/item/clothing/gloves/color/white = 1, + /obj/item/clothing/gloves/color/rainbow = 1) diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index d402fa65228f..93b847725a04 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -1,79 +1,79 @@ - -/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 - custom_price = 10 - -/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 = "black" - 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" = 25, "laser" = 15, "energy" = 15, "bomb" = 20, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/gloves/rapid - name = "Gloves of the North Star" - desc = "Just looking at these fills you with an urge to beat the shit out of people." - icon_state = "rapid" - item_state = "rapid" - transfer_prints = TRUE - var/warcry = "AT" - -/obj/item/clothing/gloves/rapid/Touch(mob/living/target,proximity = TRUE) - var/mob/living/M = loc - - if(M.a_intent == INTENT_HARM) - M.changeNext_move(CLICK_CD_RAPID) - 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/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 + custom_price = 10 + +/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 = "black" + 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" = 25, "laser" = 15, "energy" = 15, "bomb" = 20, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/gloves/rapid + name = "Gloves of the North Star" + desc = "Just looking at these fills you with an urge to beat the shit out of people." + icon_state = "rapid" + item_state = "rapid" + transfer_prints = TRUE + var/warcry = "AT" + +/obj/item/clothing/gloves/rapid/Touch(mob/living/target,proximity = TRUE) + var/mob/living/M = loc + + if(M.a_intent == INTENT_HARM) + M.changeNext_move(CLICK_CD_RAPID) + 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 diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm index efea5e35d64b..b6de3d813b4d 100644 --- a/code/modules/clothing/head/_head.dm +++ b/code/modules/clothing/head/_head.dm @@ -1,30 +1,30 @@ -/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() - -/obj/item/clothing/head/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "helmetblood") - -/obj/item/clothing/head/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_head() +/obj/item/clothing/head + name = BODY_ZONE_HEAD + icon = 'icons/obj/clothing/hats.dmi' + icon_state = "top_hat" + 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() + +/obj/item/clothing/head/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "helmetblood") + +/obj/item/clothing/head/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_head() diff --git a/code/modules/clothing/head/collectable.dm b/code/modules/clothing/head/collectable.dm index c676bee5af87..0f03699f0dd2 100644 --- a/code/modules/clothing/head/collectable.dm +++ b/code/modules/clothing/head/collectable.dm @@ -1,155 +1,155 @@ - -//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 = "chefhat" //yogs - changed from "chef" to "chefhat" - 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 = "chefhat" //yogs - changed from "chef" to "chefhat" + 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 ff49f131ed1a..d4da16804fb4 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -1,157 +1,157 @@ -/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 - 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) - -/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 - heat_protection = HEAD - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - -/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) - -/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) - if(weldingvisortoggle(user)) - playsound(src, 'sound/mecha/mechmove03.ogg', 50, 1) //Visors don't just come from nothing - update_icon() - -/obj/item/clothing/head/hardhat/weldhat/worn_overlays(isinhands) - . = ..() - 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 + 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) + +/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 + heat_protection = HEAD + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + +/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) + +/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) + if(weldingvisortoggle(user)) + playsound(src, 'sound/mecha/mechmove03.ogg', 50, 1) //Visors don't just come from nothing + update_icon() + +/obj/item/clothing/head/hardhat/weldhat/worn_overlays(isinhands) + . = ..() + 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/jobs.dm b/code/modules/clothing/head/jobs.dm index eb16376bb3a7..cbf42dcd4e93 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -1,270 +1,270 @@ -//defines the drill hat's yelling setting -#define DRILL_DEFAULT "default" -#define DRILL_SHOUTING "shouting" -#define DRILL_YELLING "yelling" -#define DRILL_CANADIAN "canadian" - -//Chef -/obj/item/clothing/head/chefhat - name = "chef's hat" - item_state = "chefhat" //yogs - changed from "chef" to "chefhat" - 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" = 25, "bullet" = 15, "laser" = 25, "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 - - -//Head of Personnel -/obj/item/clothing/head/hopcap - name = "head of personnel's cap" - icon_state = "hopcap" - desc = "The symbol of true bureaucratic micromanagement." - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - dog_fashion = /datum/dog_fashion/head/hop - -//Chaplain -/obj/item/clothing/head/nun_hood - name = "nun hood" - desc = "Maximum piety in this star system." - icon_state = "nun_hood" - flags_inv = HIDEHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/bishopmitre - name = "bishop mitre" - desc = "An opulent hat that functions as a radio to God. Or as a lightning rod, depending on who you ask." - icon_state = "bishopmitre" - -//Detective -/obj/item/clothing/head/fedora/det_hat - name = "detective's fedora" - desc = "There's only one man who can sniff out the dirty stench of crime, and he's likely wearing this hat." - armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 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/fedora/detective - dog_fashion = /datum/dog_fashion/head/detective - -/obj/item/clothing/head/fedora/det_hat/Initialize() - . = ..() - new /obj/item/reagent_containers/food/drinks/flask/det(src) - -/obj/item/clothing/head/fedora/det_hat/examine(mob/user) - . = ..() - . += "Alt-click to take a candy corn." - -/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - ..() - if(loc == user) - if(candy_cooldown < world.time) - var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src) - user.put_in_hands(CC) - to_chat(user, "You slip a candy corn from your hat.") - candy_cooldown = world.time+1200 - else - to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.") - - -//Mime -/obj/item/clothing/head/beret - name = "beret" - desc = "A beret, a mime's favorite headwear." - icon_state = "beret" - dog_fashion = /datum/dog_fashion/head/beret - dynamic_hair_suffix = "" - -/obj/item/clothing/head/beret/vintage - name = "vintage beret" - desc = "A well-worn beret." - icon_state = "vintageberet" - dog_fashion = null - -/obj/item/clothing/head/beret/archaic - name = "archaic beret" - desc = "An absolutely ancient beret, allegedly worn by the first mime to ever step foot on a NanoTrasen station." - icon_state = "archaicberet" - dog_fashion = null - -/obj/item/clothing/head/beret/black - name = "black beret" - desc = "A black beret, perfect for war veterans and dark, brooding, anti-hero mimes." - icon_state = "beretblack" - -/obj/item/clothing/head/beret/highlander - desc = "That was white fabric. Was." - dog_fashion = null //THIS IS FOR SLAUGHTER, NOT PUPPIES - -/obj/item/clothing/head/beret/highlander/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) - -/obj/item/clothing/head/beret/durathread - name = "durathread beret" - desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "beretdurathread" - item_color = null - armor = list("melee" = 15, "bullet" = 5, "laser" = 15, "energy" = 5, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 5) - -//Security - -/obj/item/clothing/head/HoS - name = "head of security cap" - desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." - icon_state = "hoscap" - armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 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/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." - 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" = 20, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/beret/sec/navyhos - name = "head of security's beret" - desc = "A special beret with the Head of Security's insignia emblazoned on it. A symbol of excellence, a badge of courage, a mark of distinction." - icon_state = "hosberet" - -/obj/item/clothing/head/beret/sec/navywarden - name = "warden's beret" - desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." - icon_state = "wardenberet" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) - strip_delay = 60 - -/obj/item/clothing/head/beret/sec/navyofficer - desc = "A special beret with the security insignia emblazoned on it. For officers with class." - icon_state = "officerberet" - -//Curator -/obj/item/clothing/head/fedora/curator - name = "treasure hunter's fedora" - desc = "You got red text today kid, but it doesn't mean you have to like it." - icon_state = "curator" - -#undef DRILL_DEFAULT -#undef DRILL_SHOUTING -#undef DRILL_YELLING -#undef DRILL_CANADIAN +//defines the drill hat's yelling setting +#define DRILL_DEFAULT "default" +#define DRILL_SHOUTING "shouting" +#define DRILL_YELLING "yelling" +#define DRILL_CANADIAN "canadian" + +//Chef +/obj/item/clothing/head/chefhat + name = "chef's hat" + item_state = "chefhat" //yogs - changed from "chef" to "chefhat" + 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" = 25, "bullet" = 15, "laser" = 25, "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 + + +//Head of Personnel +/obj/item/clothing/head/hopcap + name = "head of personnel's cap" + icon_state = "hopcap" + desc = "The symbol of true bureaucratic micromanagement." + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + dog_fashion = /datum/dog_fashion/head/hop + +//Chaplain +/obj/item/clothing/head/nun_hood + name = "nun hood" + desc = "Maximum piety in this star system." + icon_state = "nun_hood" + flags_inv = HIDEHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/bishopmitre + name = "bishop mitre" + desc = "An opulent hat that functions as a radio to God. Or as a lightning rod, depending on who you ask." + icon_state = "bishopmitre" + +//Detective +/obj/item/clothing/head/fedora/det_hat + name = "detective's fedora" + desc = "There's only one man who can sniff out the dirty stench of crime, and he's likely wearing this hat." + armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 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/fedora/detective + dog_fashion = /datum/dog_fashion/head/detective + +/obj/item/clothing/head/fedora/det_hat/Initialize() + . = ..() + new /obj/item/reagent_containers/food/drinks/flask/det(src) + +/obj/item/clothing/head/fedora/det_hat/examine(mob/user) + . = ..() + . += "Alt-click to take a candy corn." + +/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + ..() + if(loc == user) + if(candy_cooldown < world.time) + var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src) + user.put_in_hands(CC) + to_chat(user, "You slip a candy corn from your hat.") + candy_cooldown = world.time+1200 + else + to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.") + + +//Mime +/obj/item/clothing/head/beret + name = "beret" + desc = "A beret, a mime's favorite headwear." + icon_state = "beret" + dog_fashion = /datum/dog_fashion/head/beret + dynamic_hair_suffix = "" + +/obj/item/clothing/head/beret/vintage + name = "vintage beret" + desc = "A well-worn beret." + icon_state = "vintageberet" + dog_fashion = null + +/obj/item/clothing/head/beret/archaic + name = "archaic beret" + desc = "An absolutely ancient beret, allegedly worn by the first mime to ever step foot on a NanoTrasen station." + icon_state = "archaicberet" + dog_fashion = null + +/obj/item/clothing/head/beret/black + name = "black beret" + desc = "A black beret, perfect for war veterans and dark, brooding, anti-hero mimes." + icon_state = "beretblack" + +/obj/item/clothing/head/beret/highlander + desc = "That was white fabric. Was." + dog_fashion = null //THIS IS FOR SLAUGHTER, NOT PUPPIES + +/obj/item/clothing/head/beret/highlander/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) + +/obj/item/clothing/head/beret/durathread + name = "durathread beret" + desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "beretdurathread" + item_color = null + armor = list("melee" = 15, "bullet" = 5, "laser" = 15, "energy" = 5, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 5) + +//Security + +/obj/item/clothing/head/HoS + name = "head of security cap" + desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." + icon_state = "hoscap" + armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 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/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." + 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" = 20, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/beret/sec/navyhos + name = "head of security's beret" + desc = "A special beret with the Head of Security's insignia emblazoned on it. A symbol of excellence, a badge of courage, a mark of distinction." + icon_state = "hosberet" + +/obj/item/clothing/head/beret/sec/navywarden + name = "warden's beret" + desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." + icon_state = "wardenberet" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) + strip_delay = 60 + +/obj/item/clothing/head/beret/sec/navyofficer + desc = "A special beret with the security insignia emblazoned on it. For officers with class." + icon_state = "officerberet" + +//Curator +/obj/item/clothing/head/fedora/curator + name = "treasure hunter's fedora" + desc = "You got red text today kid, but it doesn't mean you have to like it." + icon_state = "curator" + +#undef DRILL_DEFAULT +#undef DRILL_SHOUTING +#undef DRILL_YELLING +#undef DRILL_CANADIAN diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm index c57450727b64..65a16dcc342a 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -1,407 +1,407 @@ -/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 - -/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 - -/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 - -/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 - 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 - var/datum/language/piratespeak/L = new - -/obj/item/clothing/head/pirate/equipped(mob/user, slot) - if(!ishuman(user)) - return - if(slot == SLOT_HEAD) - user.grant_language(/datum/language/piratespeak/) - to_chat(user, "You suddenly know how to speak like a pirate!") - -/obj/item/clothing/head/pirate/dropped(mob/user) - if(!ishuman(user)) - return - var/mob/living/carbon/human/H = user - if(H.get_item_by_slot(SLOT_HEAD) == src) - user.remove_language(/datum/language/piratespeak/) - to_chat(user, "You can no longer speak like a 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 - -/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 - -/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 - -/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/fedora - -/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 - -/obj/item/clothing/head/papersack/smiley - name = "paper sack hat" - desc = "A paper sack with crude holes cut out for eyes and a sketchy smile drawn on the front. Not creepy at all." - icon_state = "papersack_smile" - flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS - -/obj/item/clothing/head/crown - name = "crown" - desc = "A crown fit for a king, a petty king maybe." - icon_state = "crown" - armor = list("melee" = 15, "bullet" = 0, "laser" = 0,"energy" = 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 - -/obj/item/clothing/head/drfreezehat - name = "doctor freeze's wig" - desc = "A cool wig for cool people." - icon_state = "drfreeze_hat" - flags_inv = HIDEHAIR - -/obj/item/clothing/head/pharaoh - name = "pharaoh hat" - desc = "Walk like an Egyptian." - icon_state = "pharoah_hat" - icon_state = "pharoah_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 = "beret" - 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/clownmitre - name = "Hat of the Honkmother" - desc = "It's hard for parishoners to see a banana peel on the floor when they're looking up at your glorious chapeau." - icon_state = "clownmitre" - -/obj/item/clothing/head/kippah - name = "kippah" - desc = "Signals that you follow the Jewish Halakha. Keeps the head covered and the soul extra-Orthodox." - icon_state = "kippah" - -/obj/item/clothing/head/medievaljewhat - name = "medieval Jew hat" - desc = "A silly looking hat, intended to be placed on the heads of the station's oppressed religious minorities." - icon_state = "medievaljewhat" - -/obj/item/clothing/head/taqiyahwhite - name = "white taqiyah" - desc = "An extra-mustahabb way of showing your devotion to Allah." - icon_state = "taqiyahwhite" - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small - -/obj/item/clothing/head/taqiyahred - name = "red taqiyah" - desc = "An extra-mustahabb way of showing your devotion to Allah." - icon_state = "taqiyahred" - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small +/obj/item/clothing/head/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 + +/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 + +/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 + +/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 + 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 + var/datum/language/piratespeak/L = new + +/obj/item/clothing/head/pirate/equipped(mob/user, slot) + if(!ishuman(user)) + return + if(slot == SLOT_HEAD) + user.grant_language(/datum/language/piratespeak/) + to_chat(user, "You suddenly know how to speak like a pirate!") + +/obj/item/clothing/head/pirate/dropped(mob/user) + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(H.get_item_by_slot(SLOT_HEAD) == src) + user.remove_language(/datum/language/piratespeak/) + to_chat(user, "You can no longer speak like a 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 + +/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 + +/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 + +/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/fedora + +/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 + +/obj/item/clothing/head/papersack/smiley + name = "paper sack hat" + desc = "A paper sack with crude holes cut out for eyes and a sketchy smile drawn on the front. Not creepy at all." + icon_state = "papersack_smile" + flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS + +/obj/item/clothing/head/crown + name = "crown" + desc = "A crown fit for a king, a petty king maybe." + icon_state = "crown" + armor = list("melee" = 15, "bullet" = 0, "laser" = 0,"energy" = 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 + +/obj/item/clothing/head/drfreezehat + name = "doctor freeze's wig" + desc = "A cool wig for cool people." + icon_state = "drfreeze_hat" + flags_inv = HIDEHAIR + +/obj/item/clothing/head/pharaoh + name = "pharaoh hat" + desc = "Walk like an Egyptian." + icon_state = "pharoah_hat" + icon_state = "pharoah_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 = "beret" + 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/clownmitre + name = "Hat of the Honkmother" + desc = "It's hard for parishoners to see a banana peel on the floor when they're looking up at your glorious chapeau." + icon_state = "clownmitre" + +/obj/item/clothing/head/kippah + name = "kippah" + desc = "Signals that you follow the Jewish Halakha. Keeps the head covered and the soul extra-Orthodox." + icon_state = "kippah" + +/obj/item/clothing/head/medievaljewhat + name = "medieval Jew hat" + desc = "A silly looking hat, intended to be placed on the heads of the station's oppressed religious minorities." + icon_state = "medievaljewhat" + +/obj/item/clothing/head/taqiyahwhite + name = "white taqiyah" + desc = "An extra-mustahabb way of showing your devotion to Allah." + icon_state = "taqiyahwhite" + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small + +/obj/item/clothing/head/taqiyahred + name = "red taqiyah" + desc = "An extra-mustahabb way of showing your devotion to Allah." + icon_state = "taqiyahred" + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index 54bdbc6b25f7..55be04c684dc 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -1,329 +1,329 @@ -/* - * 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 - -/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 = 999 - -/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/weapons/sear.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/is_hot() - 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 - 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 - - 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" - var/adjustablecolor = TRUE //can color be changed manually? - -/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, file2use) - . = 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/attack_self(mob/user) - var/new_style = input(user, "Select a hair style", "Wig Styling") as null|anything in (GLOB.hair_styles_list - "Bald") - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(new_style && new_style != hair_style) - hair_style = new_style - user.visible_message("[user] changes \the [src]'s hairstyle to [new_style].", "You change \the [src]'s hairstyle to [new_style].") - if(adjustablecolor) - hair_color = input(usr,"","Choose Color",hair_color) as color|null - update_icon() - -/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/wig/natural - name = "natural wig" - desc = "A bunch of hair without a head attached. This one changes color to match the hair of the wearer. Nothing natural about that." - hair_color = "#FFF" - adjustablecolor = FALSE - custom_price = 25 - -/obj/item/clothing/head/wig/natural/Initialize(mapload) - hair_style = pick(GLOB.hair_styles_list - "Bald") - . = ..() - -/obj/item/clothing/head/wig/natural/equipped(mob/living/carbon/human/user, slot) - if(ishuman(user) && slot == SLOT_HEAD) - hair_color = "#[user.hair_color]" - update_icon() - user.update_inv_head() - -/obj/item/clothing/head/bronze - name = "bronze hat" - desc = "A crude helmet made out of bronze plates. It offers very little in the way of protection." - icon = 'icons/obj/clothing/clockwork_garb.dmi' - icon_state = "clockwork_helmet_old" - 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/conspiracies/paranoia - var/warped = FALSE - -/obj/item/clothing/head/foilhat/Initialize(mapload) - . = ..() - if(!warped) - AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_HEAD, 6, TRUE, null, CALLBACK(src, .proc/warp_up)) - else - warp_up() - -/obj/item/clothing/head/foilhat/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot != SLOT_HEAD || warped) - return - if(paranoia) - QDEL_NULL(paranoia) - paranoia = new() - paranoia.clonable = FALSE - - 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() +/* + * 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 + +/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 = 999 + +/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/weapons/sear.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/is_hot() + 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 + 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 + + 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" + var/adjustablecolor = TRUE //can color be changed manually? + +/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, file2use) + . = 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/attack_self(mob/user) + var/new_style = input(user, "Select a hair style", "Wig Styling") as null|anything in (GLOB.hair_styles_list - "Bald") + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(new_style && new_style != hair_style) + hair_style = new_style + user.visible_message("[user] changes \the [src]'s hairstyle to [new_style].", "You change \the [src]'s hairstyle to [new_style].") + if(adjustablecolor) + hair_color = input(usr,"","Choose Color",hair_color) as color|null + update_icon() + +/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/wig/natural + name = "natural wig" + desc = "A bunch of hair without a head attached. This one changes color to match the hair of the wearer. Nothing natural about that." + hair_color = "#FFF" + adjustablecolor = FALSE + custom_price = 25 + +/obj/item/clothing/head/wig/natural/Initialize(mapload) + hair_style = pick(GLOB.hair_styles_list - "Bald") + . = ..() + +/obj/item/clothing/head/wig/natural/equipped(mob/living/carbon/human/user, slot) + if(ishuman(user) && slot == SLOT_HEAD) + hair_color = "#[user.hair_color]" + update_icon() + user.update_inv_head() + +/obj/item/clothing/head/bronze + name = "bronze hat" + desc = "A crude helmet made out of bronze plates. It offers very little in the way of protection." + icon = 'icons/obj/clothing/clockwork_garb.dmi' + icon_state = "clockwork_helmet_old" + 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/conspiracies/paranoia + var/warped = FALSE + +/obj/item/clothing/head/foilhat/Initialize(mapload) + . = ..() + if(!warped) + AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_HEAD, 6, TRUE, null, CALLBACK(src, .proc/warp_up)) + else + warp_up() + +/obj/item/clothing/head/foilhat/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot != SLOT_HEAD || warped) + return + if(paranoia) + QDEL_NULL(paranoia) + paranoia = new() + paranoia.clonable = FALSE + + 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() diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm index 66c211efa9aa..a87b0f303c56 100644 --- a/code/modules/clothing/head/soft_caps.dm +++ b/code/modules/clothing/head/soft_caps.dm @@ -1,131 +1,131 @@ -/obj/item/clothing/head/soft - name = "cargo cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "cargosoft" - 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 - else - flip(user) - - -/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 + 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 + else + flip(user) + + +/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 diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index d8ba4f0ef4e4..de7d4d213692 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) - . = list() - if(!isinhands) - if(body_parts_covered & HEAD) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "maskblood") - -/obj/item/clothing/mask/update_clothes_damaged_state(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) + . = list() + if(!isinhands) + if(body_parts_covered & HEAD) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "maskblood") + +/obj/item/clothing/mask/update_clothes_damaged_state(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/boxing.dm b/code/modules/clothing/masks/boxing.dm index 5d5e4f981b24..7ff2ffd65cd5 100644 --- a/code/modules/clothing/masks/boxing.dm +++ b/code/modules/clothing/masks/boxing.dm @@ -1,66 +1,66 @@ -/obj/item/clothing/mask/balaclava - name = "balaclava" - desc = "LOADSAMONEY" - icon_state = "balaclava" - item_state = "balaclava" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - actions_types = list(/datum/action/item_action/adjust) - -/obj/item/clothing/mask/balaclava/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/luchador - name = "Luchador Mask" - desc = "Worn by robust fighters, flying high to defeat their foes!" - icon_state = "luchag" - item_state = "luchag" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/luchador/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = replacetext(message, "captain", "CAPITÁN") - message = replacetext(message, "station", "ESTACIÓN") - message = replacetext(message, "sir", "SEÑOR") - message = replacetext(message, "the ", "el ") - message = replacetext(message, "my ", "mi ") - message = replacetext(message, "is ", "es ") - message = replacetext(message, "it's", "es") - message = replacetext(message, "friend", "amigo") - message = replacetext(message, "buddy", "amigo") - message = replacetext(message, "hello", "hola") - message = replacetext(message, " hot", " caliente") - message = replacetext(message, " very ", " muy ") - message = replacetext(message, "sword", "espada") - message = replacetext(message, "library", "biblioteca") - message = replacetext(message, "traitor", "traidor") - message = replacetext(message, "wizard", "mago") - message = uppertext(message) //Things end up looking better this way (no mixed cases), and it fits the macho wrestler image. - if(prob(25)) - message += " OLE!" - speech_args[SPEECH_MESSAGE] = message - -/obj/item/clothing/mask/luchador/tecnicos - name = "Tecnicos Mask" - desc = "Worn by robust fighters who uphold justice and fight honorably." - icon_state = "luchador" - item_state = "luchador" - -/obj/item/clothing/mask/luchador/rudos - name = "Rudos Mask" - desc = "Worn by robust fighters who are willing to do anything to win." - icon_state = "luchar" - item_state = "luchar" - -/obj/item/clothing/mask/russian_balaclava - name = "russian balaclava" - desc = "Protects your face from snow." - icon_state = "rus_balaclava" - item_state = "rus_balaclava" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL +/obj/item/clothing/mask/balaclava + name = "balaclava" + desc = "LOADSAMONEY" + icon_state = "balaclava" + item_state = "balaclava" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + actions_types = list(/datum/action/item_action/adjust) + +/obj/item/clothing/mask/balaclava/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/luchador + name = "Luchador Mask" + desc = "Worn by robust fighters, flying high to defeat their foes!" + icon_state = "luchag" + item_state = "luchag" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/luchador/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = replacetext(message, "captain", "CAPITÁN") + message = replacetext(message, "station", "ESTACIÓN") + message = replacetext(message, "sir", "SEÑOR") + message = replacetext(message, "the ", "el ") + message = replacetext(message, "my ", "mi ") + message = replacetext(message, "is ", "es ") + message = replacetext(message, "it's", "es") + message = replacetext(message, "friend", "amigo") + message = replacetext(message, "buddy", "amigo") + message = replacetext(message, "hello", "hola") + message = replacetext(message, " hot", " caliente") + message = replacetext(message, " very ", " muy ") + message = replacetext(message, "sword", "espada") + message = replacetext(message, "library", "biblioteca") + message = replacetext(message, "traitor", "traidor") + message = replacetext(message, "wizard", "mago") + message = uppertext(message) //Things end up looking better this way (no mixed cases), and it fits the macho wrestler image. + if(prob(25)) + message += " OLE!" + speech_args[SPEECH_MESSAGE] = message + +/obj/item/clothing/mask/luchador/tecnicos + name = "Tecnicos Mask" + desc = "Worn by robust fighters who uphold justice and fight honorably." + icon_state = "luchador" + item_state = "luchador" + +/obj/item/clothing/mask/luchador/rudos + name = "Rudos Mask" + desc = "Worn by robust fighters who are willing to do anything to win." + icon_state = "luchar" + item_state = "luchar" + +/obj/item/clothing/mask/russian_balaclava + name = "russian balaclava" + desc = "Protects your face from snow." + icon_state = "rus_balaclava" + item_state = "rus_balaclava" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL diff --git a/code/modules/clothing/masks/breath.dm b/code/modules/clothing/masks/breath.dm index 0e496e6f1411..25e96b274d00 100644 --- a/code/modules/clothing/masks/breath.dm +++ b/code/modules/clothing/masks/breath.dm @@ -1,41 +1,41 @@ -/obj/item/clothing/mask/breath - desc = "A close-fitting mask that can be connected to an air supply." - name = "breath mask" - icon_state = "breath" - item_state = "m_mask" - body_parts_covered = 0 - clothing_flags = MASKINTERNALS - visor_flags = MASKINTERNALS - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.1 - permeability_coefficient = 0.5 - actions_types = list(/datum/action/item_action/adjust) - flags_cover = MASKCOVERSMOUTH - visor_flags_cover = MASKCOVERSMOUTH - resistance_flags = NONE - -/obj/item/clothing/mask/breath/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is wrapping \the [src]'s tube around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/clothing/mask/breath/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/breath/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - adjustmask(user) - -/obj/item/clothing/mask/breath/examine(mob/user) - . = ..() - . += "Alt-click [src] to adjust it." - -/obj/item/clothing/mask/breath/medical - desc = "A close-fitting sterile mask that can be connected to an air supply." - name = "medical mask" - icon_state = "medical" - 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 = MASKINTERNALS + visor_flags = MASKINTERNALS + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.1 + permeability_coefficient = 0.5 + actions_types = list(/datum/action/item_action/adjust) + flags_cover = MASKCOVERSMOUTH + visor_flags_cover = MASKCOVERSMOUTH + resistance_flags = NONE + +/obj/item/clothing/mask/breath/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is wrapping \the [src]'s tube around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/clothing/mask/breath/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/breath/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + else + adjustmask(user) + +/obj/item/clothing/mask/breath/examine(mob/user) + . = ..() + . += "Alt-click [src] to adjust it." + +/obj/item/clothing/mask/breath/medical + desc = "A close-fitting sterile mask that can be connected to an air supply." + name = "medical mask" + icon_state = "medical" + 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 e5b46a26e797..fee846c57588 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -1,199 +1,199 @@ -/obj/item/clothing/mask/gas - name = "gas mask" - desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate - icon_state = "gas_alt" - clothing_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_NORMAL - item_state = "gas_alt" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH - resistance_flags = NONE - -// **** 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 = MASKINTERNALS | SHOWEROKAY - 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" - options["The Jester"] ="chaos" //Nepeta33Leijon is holding me captive and forced me to help with this please send help - - 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 = MASKINTERNALS | SHOWEROKAY - 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 = MASKINTERNALS | SHOWEROKAY - 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 = MASKINTERNALS - 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 = MASKINTERNALS | SHOWEROKAY - 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 = MASKINTERNALS - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/carp - name = "carp mask" - desc = "Gnash gnash." - icon_state = "carp_mask" - -/obj/item/clothing/mask/gas/tiki_mask - name = "tiki mask" - desc = "A creepy wooden mask. Surprisingly expressive for a poorly carved bit of wood." - icon_state = "tiki_eyebrow" - 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/tiki_mask/yalp_elor - icon_state = "tiki_yalp" - actions_types = list() +/obj/item/clothing/mask/gas + name = "gas mask" + desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate + icon_state = "gas_alt" + clothing_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_NORMAL + item_state = "gas_alt" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH + resistance_flags = NONE + +// **** 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 = MASKINTERNALS | SHOWEROKAY + 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" + options["The Jester"] ="chaos" //Nepeta33Leijon is holding me captive and forced me to help with this please send help + + 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 = MASKINTERNALS | SHOWEROKAY + 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 = MASKINTERNALS | SHOWEROKAY + 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 = MASKINTERNALS + 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 = MASKINTERNALS | SHOWEROKAY + 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 = MASKINTERNALS + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/carp + name = "carp mask" + desc = "Gnash gnash." + icon_state = "carp_mask" + +/obj/item/clothing/mask/gas/tiki_mask + name = "tiki mask" + desc = "A creepy wooden mask. Surprisingly expressive for a poorly carved bit of wood." + icon_state = "tiki_eyebrow" + 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/tiki_mask/yalp_elor + icon_state = "tiki_yalp" + actions_types = list() diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 78dd1dd7936e..1a885bbd292f 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -1,337 +1,337 @@ -/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 - -/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) - -/obj/item/clothing/mask/surgical/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/fakemoustache - name = "fake moustache" - desc = "Warning: moustache is fake." - icon_state = "fake-moustache" - flags_inv = HIDEFACE - -/obj/item/clothing/mask/fakemoustache/italian - name = "italian moustache" - desc = "Made from authentic Italian moustache hairs. Gives the wearer an irresistable urge to gesticulate wildly." - clothing_flags = SHOWEROKAY - modifies_speech = TRUE - -/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = " [message]" - var/list/italian_words = strings("italian_replacement.json", "italian") - - for(var/key in italian_words) - var/value = italian_words[key] - if(islist(value)) - value = pick(value) - - message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") - message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") - message = replacetextEx(message, " [key]", " [value]") - - if(prob(3)) - message += pick(" Ravioli, ravioli, give me the formuoli!"," Mamma-mia!"," Mamma-mia! That's a spicy meat-ball!", " La la la la la funiculi funicula!") - speech_args[SPEECH_MESSAGE] = trim(message) - -/obj/item/clothing/mask/joy - name = "joy mask" - desc = "Express your happiness or hide your sorrows with this laughing face with crying tears of joy cutout." - icon_state = "joy" - -/obj/item/clothing/mask/pig - name = "pig mask" - desc = "A rubber pig mask with a 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 - name = "pig face" - desc = "It looks like a mask, but closer inspection reveals it's melded onto this persons 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_MASK_TRAIT)) - to_chat(user, "[src] was cursed! Ree!!") - return ..() - -/obj/item/clothing/mask/cowmask - name = "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" - -/obj/item/clothing/mask/bandana/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/bandana/AltClick(mob/user) - . = ..() - if(iscarbon(user)) - var/mob/living/carbon/C = user - if((C.get_item_by_slot(SLOT_HEAD == src)) || (C.get_item_by_slot(SLOT_WEAR_MASK) == src)) - to_chat(user, "You can't tie [src] while wearing it!") - return - if(slot_flags & ITEM_SLOT_HEAD) - to_chat(user, "You must undo [src] before you can tie it into a neckerchief!") - else - if(user.is_holding(src)) - var/obj/item/clothing/neck/neckerchief/nk = new(src) - nk.name = "[name] neckerchief" - nk.desc = "[desc] It's tied up like a neckerchief." - nk.icon_state = icon_state - nk.sourceBandanaType = src.type - var/currentHandIndex = user.get_held_index_of_item(src) - user.transferItemToLoc(src, null) - user.put_in_hand(nk, currentHandIndex) - user.visible_message("You tie [src] up like a neckerchief.", "[user] ties [src] up like a neckerchief.") - qdel(src) - else - to_chat(user, "You must be holding [src] in order to tie it!") - -/obj/item/clothing/mask/bandana/red - name = "red bandana" - desc = "A fine red bandana with nanotech lining." - icon_state = "bandred" - -/obj/item/clothing/mask/bandana/blue - name = "blue bandana" - desc = "A fine blue bandana with nanotech lining." - icon_state = "bandblue" - -/obj/item/clothing/mask/bandana/green - name = "green bandana" - desc = "A fine green bandana with nanotech lining." - icon_state = "bandgreen" - -/obj/item/clothing/mask/bandana/gold - name = "gold bandana" - desc = "A fine gold bandana with nanotech lining." - icon_state = "bandgold" - -/obj/item/clothing/mask/bandana/black - name = "black bandana" - desc = "A fine black bandana with nanotech lining." - icon_state = "bandblack" - -/obj/item/clothing/mask/bandana/skull - name = "skull bandana" - desc = "A fine black bandana with nanotech lining and a skull emblem." - icon_state = "bandskull" - -/obj/item/clothing/mask/bandana/durathread - name = "durathread bandana" - desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." - icon_state = "banddurathread" - -/obj/item/clothing/mask/mummy - name = "mummy mask" - desc = "Ancient bandages." - icon_state = "mummy_mask" - 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]") +/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 + +/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) + +/obj/item/clothing/mask/surgical/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/fakemoustache + name = "fake moustache" + desc = "Warning: moustache is fake." + icon_state = "fake-moustache" + flags_inv = HIDEFACE + +/obj/item/clothing/mask/fakemoustache/italian + name = "italian moustache" + desc = "Made from authentic Italian moustache hairs. Gives the wearer an irresistable urge to gesticulate wildly." + clothing_flags = SHOWEROKAY + modifies_speech = TRUE + +/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = " [message]" + var/list/italian_words = strings("italian_replacement.json", "italian") + + for(var/key in italian_words) + var/value = italian_words[key] + if(islist(value)) + value = pick(value) + + message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") + message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") + message = replacetextEx(message, " [key]", " [value]") + + if(prob(3)) + message += pick(" Ravioli, ravioli, give me the formuoli!"," Mamma-mia!"," Mamma-mia! That's a spicy meat-ball!", " La la la la la funiculi funicula!") + speech_args[SPEECH_MESSAGE] = trim(message) + +/obj/item/clothing/mask/joy + name = "joy mask" + desc = "Express your happiness or hide your sorrows with this laughing face with crying tears of joy cutout." + icon_state = "joy" + +/obj/item/clothing/mask/pig + name = "pig mask" + desc = "A rubber pig mask with a 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 + name = "pig face" + desc = "It looks like a mask, but closer inspection reveals it's melded onto this persons 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_MASK_TRAIT)) + to_chat(user, "[src] was cursed! Ree!!") + return ..() + +/obj/item/clothing/mask/cowmask + name = "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" + +/obj/item/clothing/mask/bandana/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/bandana/AltClick(mob/user) + . = ..() + if(iscarbon(user)) + var/mob/living/carbon/C = user + if((C.get_item_by_slot(SLOT_HEAD == src)) || (C.get_item_by_slot(SLOT_WEAR_MASK) == src)) + to_chat(user, "You can't tie [src] while wearing it!") + return + if(slot_flags & ITEM_SLOT_HEAD) + to_chat(user, "You must undo [src] before you can tie it into a neckerchief!") + else + if(user.is_holding(src)) + var/obj/item/clothing/neck/neckerchief/nk = new(src) + nk.name = "[name] neckerchief" + nk.desc = "[desc] It's tied up like a neckerchief." + nk.icon_state = icon_state + nk.sourceBandanaType = src.type + var/currentHandIndex = user.get_held_index_of_item(src) + user.transferItemToLoc(src, null) + user.put_in_hand(nk, currentHandIndex) + user.visible_message("You tie [src] up like a neckerchief.", "[user] ties [src] up like a neckerchief.") + qdel(src) + else + to_chat(user, "You must be holding [src] in order to tie it!") + +/obj/item/clothing/mask/bandana/red + name = "red bandana" + desc = "A fine red bandana with nanotech lining." + icon_state = "bandred" + +/obj/item/clothing/mask/bandana/blue + name = "blue bandana" + desc = "A fine blue bandana with nanotech lining." + icon_state = "bandblue" + +/obj/item/clothing/mask/bandana/green + name = "green bandana" + desc = "A fine green bandana with nanotech lining." + icon_state = "bandgreen" + +/obj/item/clothing/mask/bandana/gold + name = "gold bandana" + desc = "A fine gold bandana with nanotech lining." + icon_state = "bandgold" + +/obj/item/clothing/mask/bandana/black + name = "black bandana" + desc = "A fine black bandana with nanotech lining." + icon_state = "bandblack" + +/obj/item/clothing/mask/bandana/skull + name = "skull bandana" + desc = "A fine black bandana with nanotech lining and a skull emblem." + icon_state = "bandskull" + +/obj/item/clothing/mask/bandana/durathread + name = "durathread bandana" + desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." + icon_state = "banddurathread" + +/obj/item/clothing/mask/mummy + name = "mummy mask" + desc = "Ancient bandages." + icon_state = "mummy_mask" + 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) \ No newline at end of file diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index 9720066857ca..677e3565e005 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -1,90 +1,90 @@ -/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_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - var/offset = 0 - var/equipped_before_drop = FALSE - var/can_be_bloody = TRUE - -/obj/item/clothing/shoes/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/item/clothing/shoes/suicide_act(mob/living/carbon/user) - if(rand(2)>1) - user.visible_message("[user] begins tying \the [src] up waaay too tightly! It looks like [user.p_theyre()] trying to commit suicide!") - var/obj/item/bodypart/l_leg = user.get_bodypart(BODY_ZONE_L_LEG) - var/obj/item/bodypart/r_leg = user.get_bodypart(BODY_ZONE_R_LEG) - if(l_leg) - l_leg.dismember() - 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/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - var/bloody = FALSE - if(HAS_BLOOD_DNA(src)) - bloody = TRUE - else - bloody = bloody_shoes[BLOOD_STATE_HUMAN] - - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") - if(bloody) - . += mutable_appearance('icons/effects/blood.dmi', "shoeblood") - -/obj/item/clothing/shoes/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/proc/clean_blood(datum/source, strength) - if(strength < CLEAN_STRENGTH_BLOOD) - return - bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - blood_state = BLOOD_STATE_NOT_BLOODY - if(ismob(loc)) - var/mob/M = loc - M.update_inv_shoes() - -/obj/item/proc/negates_gravity() - return FALSE +/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_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + var/offset = 0 + var/equipped_before_drop = FALSE + var/can_be_bloody = TRUE + +/obj/item/clothing/shoes/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/item/clothing/shoes/suicide_act(mob/living/carbon/user) + if(rand(2)>1) + user.visible_message("[user] begins tying \the [src] up waaay too tightly! It looks like [user.p_theyre()] trying to commit suicide!") + var/obj/item/bodypart/l_leg = user.get_bodypart(BODY_ZONE_L_LEG) + var/obj/item/bodypart/r_leg = user.get_bodypart(BODY_ZONE_R_LEG) + if(l_leg) + l_leg.dismember() + 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/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + var/bloody = FALSE + if(HAS_BLOOD_DNA(src)) + bloody = TRUE + else + bloody = bloody_shoes[BLOOD_STATE_HUMAN] + + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") + if(bloody) + . += mutable_appearance('icons/effects/blood.dmi', "shoeblood") + +/obj/item/clothing/shoes/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/proc/clean_blood(datum/source, strength) + if(strength < CLEAN_STRENGTH_BLOOD) + return + bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + blood_state = BLOOD_STATE_NOT_BLOODY + if(ismob(loc)) + var/mob/M = loc + M.update_inv_shoes() + +/obj/item/proc/negates_gravity() + return FALSE diff --git a/code/modules/clothing/shoes/colour.dm b/code/modules/clothing/shoes/colour.dm index 9d23c6fced8b..d32ed85de62e 100644 --- a/code/modules/clothing/shoes/colour.dm +++ b/code/modules/clothing/shoes/colour.dm @@ -1,124 +1,124 @@ -/obj/item/clothing/shoes/sneakers - -/obj/item/clothing/shoes/sneakers/black - name = "black shoes" - icon_state = "black" - item_color = "black" - desc = "A pair of black shoes." - custom_price = 20 - - cold_protection = FEET - min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT - heat_protection = FEET - max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT - -/obj/item/clothing/shoes/sneakers/black/redcoat - item_color = "redcoat" //Exists for washing machines. Is not different from black shoes in any way. - -/obj/item/clothing/shoes/sneakers/brown - name = "brown shoes" - desc = "A pair of brown shoes." - icon_state = "brown" - item_color = "brown" - -/obj/item/clothing/shoes/sneakers/brown/captain - item_color = "captain" //Exists for washing machines. Is not different from brown shoes in any way. - -/obj/item/clothing/shoes/sneakers/brown/hop - item_color = "hop" //Exists for washing machines. Is not different from brown shoes in any way. - -/obj/item/clothing/shoes/sneakers/brown/ce - item_color = "chief" //Exists for washing machines. Is not different from brown shoes in any way. - -/obj/item/clothing/shoes/sneakers/brown/rd - item_color = "director" //Exists for washing machines. Is not different from brown shoes in any way. - -/obj/item/clothing/shoes/sneakers/brown/cmo - item_color = "medical" //Exists for washing machines. Is not different from brown shoes in any way. - -/obj/item/clothing/shoes/sneakers/brown/qm - item_color = "cargo" //Exists for washing machines. Is not different from brown shoes in any way. - -/obj/item/clothing/shoes/sneakers/blue - name = "blue shoes" - icon_state = "blue" - item_color = "blue" - -/obj/item/clothing/shoes/sneakers/green - name = "green shoes" - icon_state = "green" - item_color = "green" - -/obj/item/clothing/shoes/sneakers/yellow - name = "yellow shoes" - icon_state = "yellow" - item_color = "yellow" - -/obj/item/clothing/shoes/sneakers/purple - name = "purple shoes" - icon_state = "purple" - item_color = "purple" - -/obj/item/clothing/shoes/sneakers/brown - name = "brown shoes" - icon_state = "brown" - item_color = "brown" - -/obj/item/clothing/shoes/sneakers/red - name = "red shoes" - desc = "Stylish red shoes." - icon_state = "red" - item_color = "red" - -/obj/item/clothing/shoes/sneakers/white - name = "white shoes" - icon_state = "white" - permeability_coefficient = 0.01 - item_color = "white" - -/obj/item/clothing/shoes/sneakers/rainbow - name = "rainbow shoes" - desc = "Very gay shoes." - icon_state = "rain_bow" - item_color = "rainbow" - -/obj/item/clothing/shoes/sneakers/orange - name = "orange shoes" - icon_state = "orange" - item_color = "orange" - -/obj/item/clothing/shoes/sneakers/orange/attack_self(mob/user) - if (src.chained) - src.chained = null - src.slowdown = SHOES_SLOWDOWN - new /obj/item/restraints/handcuffs( user.loc ) - src.icon_state = "orange" - return - -/obj/item/clothing/shoes/sneakers/orange/attackby(obj/H, loc, params) - ..() - // Note: not using istype here because we want to ignore all subtypes - if (H.type == /obj/item/restraints/handcuffs && !chained) - qdel(H) - src.chained = 1 - src.slowdown = 15 - src.icon_state = "orange1" - return - -/obj/item/clothing/shoes/sneakers/orange/allow_attack_hand_drop(mob/user) - if(ishuman(user)) - var/mob/living/carbon/human/C = user - if(C.shoes == src && chained == 1) - to_chat(user, "You need help taking these off!") - return FALSE - return ..() - -/obj/item/clothing/shoes/sneakers/orange/MouseDrop(atom/over) - var/mob/m = usr - if(ishuman(m)) - var/mob/living/carbon/human/c = m - if(c.shoes == src && chained == 1) - to_chat(c, "You need help taking these off!") - return - return ..() - +/obj/item/clothing/shoes/sneakers + +/obj/item/clothing/shoes/sneakers/black + name = "black shoes" + icon_state = "black" + item_color = "black" + desc = "A pair of black shoes." + custom_price = 20 + + cold_protection = FEET + min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT + heat_protection = FEET + max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT + +/obj/item/clothing/shoes/sneakers/black/redcoat + item_color = "redcoat" //Exists for washing machines. Is not different from black shoes in any way. + +/obj/item/clothing/shoes/sneakers/brown + name = "brown shoes" + desc = "A pair of brown shoes." + icon_state = "brown" + item_color = "brown" + +/obj/item/clothing/shoes/sneakers/brown/captain + item_color = "captain" //Exists for washing machines. Is not different from brown shoes in any way. + +/obj/item/clothing/shoes/sneakers/brown/hop + item_color = "hop" //Exists for washing machines. Is not different from brown shoes in any way. + +/obj/item/clothing/shoes/sneakers/brown/ce + item_color = "chief" //Exists for washing machines. Is not different from brown shoes in any way. + +/obj/item/clothing/shoes/sneakers/brown/rd + item_color = "director" //Exists for washing machines. Is not different from brown shoes in any way. + +/obj/item/clothing/shoes/sneakers/brown/cmo + item_color = "medical" //Exists for washing machines. Is not different from brown shoes in any way. + +/obj/item/clothing/shoes/sneakers/brown/qm + item_color = "cargo" //Exists for washing machines. Is not different from brown shoes in any way. + +/obj/item/clothing/shoes/sneakers/blue + name = "blue shoes" + icon_state = "blue" + item_color = "blue" + +/obj/item/clothing/shoes/sneakers/green + name = "green shoes" + icon_state = "green" + item_color = "green" + +/obj/item/clothing/shoes/sneakers/yellow + name = "yellow shoes" + icon_state = "yellow" + item_color = "yellow" + +/obj/item/clothing/shoes/sneakers/purple + name = "purple shoes" + icon_state = "purple" + item_color = "purple" + +/obj/item/clothing/shoes/sneakers/brown + name = "brown shoes" + icon_state = "brown" + item_color = "brown" + +/obj/item/clothing/shoes/sneakers/red + name = "red shoes" + desc = "Stylish red shoes." + icon_state = "red" + item_color = "red" + +/obj/item/clothing/shoes/sneakers/white + name = "white shoes" + icon_state = "white" + permeability_coefficient = 0.01 + item_color = "white" + +/obj/item/clothing/shoes/sneakers/rainbow + name = "rainbow shoes" + desc = "Very gay shoes." + icon_state = "rain_bow" + item_color = "rainbow" + +/obj/item/clothing/shoes/sneakers/orange + name = "orange shoes" + icon_state = "orange" + item_color = "orange" + +/obj/item/clothing/shoes/sneakers/orange/attack_self(mob/user) + if (src.chained) + src.chained = null + src.slowdown = SHOES_SLOWDOWN + new /obj/item/restraints/handcuffs( user.loc ) + src.icon_state = "orange" + return + +/obj/item/clothing/shoes/sneakers/orange/attackby(obj/H, loc, params) + ..() + // Note: not using istype here because we want to ignore all subtypes + if (H.type == /obj/item/restraints/handcuffs && !chained) + qdel(H) + src.chained = 1 + src.slowdown = 15 + src.icon_state = "orange1" + return + +/obj/item/clothing/shoes/sneakers/orange/allow_attack_hand_drop(mob/user) + if(ishuman(user)) + var/mob/living/carbon/human/C = user + if(C.shoes == src && chained == 1) + to_chat(user, "You need help taking these off!") + return FALSE + return ..() + +/obj/item/clothing/shoes/sneakers/orange/MouseDrop(atom/over) + var/mob/m = usr + if(ishuman(m)) + var/mob/living/carbon/human/c = m + if(c.shoes == src && chained == 1) + to_chat(c, "You need help taking these off!") + return + return ..() + diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index bdab5997c2d1..3442b5cd5f63 100644 --- a/code/modules/clothing/shoes/magboots.dm +++ b/code/modules/clothing/shoes/magboots.dm @@ -1,66 +1,66 @@ -/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/security - name = "combat magboots" - desc = "Combat-edition magboots issued by Nanotrasen Security for extravehicular missions." - icon_state = "cmagboots0" - magboot_state = "cmagboots" +/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/security + name = "combat magboots" + desc = "Combat-edition magboots issued by Nanotrasen Security for extravehicular missions." + icon_state = "cmagboots0" + magboot_state = "cmagboots" slowdown_active = 1 \ No newline at end of file diff --git a/code/modules/clothing/spacesuits/_spacesuits.dm b/code/modules/clothing/spacesuits/_spacesuits.dm index c1dc8ebbec76..63330caf0e97 100644 --- a/code/modules/clothing/spacesuits/_spacesuits.dm +++ b/code/modules/clothing/spacesuits/_spacesuits.dm @@ -1,45 +1,45 @@ -//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 | SHOWEROKAY - 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 - 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 - -/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 | SHOWEROKAY - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight, /obj/item/tank/internals) - slowdown = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - 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 +//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 | SHOWEROKAY + 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 + 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 + +/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 | SHOWEROKAY + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight, /obj/item/tank/internals) + slowdown = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + 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 diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index b806e547b999..f70a80096614 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -1,399 +1,399 @@ -//miscellaneous spacesuits -/* -Contains: - - Captain's spacesuit - - Death squad's hardsuit - - SWAT suit - - Officer's beret/spacesuit - - NASA Voidsuit - - Father Christmas' magical clothes - - Pirate's spacesuit - - ERT hardsuit: command, sec, engi, med, janitor - - EVA spacesuit - - Freedom's spacesuit (freedom from vacuum's oppression) - - Carp hardsuit -*/ - - //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 - -/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 - -/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 - - //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) - -/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 - - 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 - - - //Space pirate outfit -/obj/item/clothing/head/helmet/space/pirate - name = "pirate hat" - desc = "Yarr." - 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 - -/obj/item/clothing/head/helmet/space/pirate/bandana - name = "pirate bandana" - icon_state = "bandana" - item_state = "bandana" - -/obj/item/clothing/suit/space/pirate - name = "pirate coat" - desc = "Yarr." - 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 - - //Emergency Response Team suits -/obj/item/clothing/head/helmet/space/hardsuit/ert - name = "emergency response team commander helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has blue highlights." - icon_state = "hardsuit0-ert_commander" - 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 = FIRE_PROOF - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - -/obj/item/clothing/head/helmet/space/hardsuit/ert/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) - -/obj/item/clothing/suit/space/hardsuit/ert - name = "emergency response team commander hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has blue highlights. Offers superb protection against environmental hazards." - icon_state = "ert_command" - 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 = FIRE_PROOF - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - - //ERT Security -/obj/item/clothing/head/helmet/space/hardsuit/ert/sec - name = "emergency response team security helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has red highlights." - icon_state = "hardsuit0-ert_security" - item_state = "hardsuit0-ert_security" - item_color = "ert_security" - -/obj/item/clothing/suit/space/hardsuit/ert/sec - name = "emergency response team security hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has red highlights. Offers superb protection against environmental hazards." - icon_state = "ert_security" - 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 - name = "emergency response team engineering helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has orange highlights." - icon_state = "hardsuit0-ert_engineer" - item_state = "hardsuit0-ert_engineer" - item_color = "ert_engineer" - -/obj/item/clothing/suit/space/hardsuit/ert/engi - name = "emergency response team engineering hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has orange highlights. Offers superb protection against environmental hazards." - icon_state = "ert_engineer" - 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 - name = "emergency response team medical helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has white highlights." - icon_state = "hardsuit0-ert_medical" - item_state = "hardsuit0-ert_medical" - item_color = "ert_medical" - -/obj/item/clothing/suit/space/hardsuit/ert/med - name = "emergency response team medical hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has white highlights. Offers superb protection against environmental hazards." - icon_state = "ert_medical" - item_state = "ert_medical" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med - species_exception = list(/datum/species/angel) - - //ERT Janitor -/obj/item/clothing/head/helmet/space/hardsuit/ert/jani - name = "emergency response team janitorial helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has purple highlights." - icon_state = "hardsuit0-ert_janitor" - item_state = "hardsuit0-ert_janitor" - item_color = "ert_janitor" - -/obj/item/clothing/suit/space/hardsuit/ert/jani - name = "emergency response team janitorial hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has purple highlights. Offers superb protection against environmental hazards. This one has extra clips for holding various janitorial tools." - icon_state = "ert_janitor" - item_state = "ert_janitor" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/jani - allowed = list(/obj/item/storage/bag/trash, /obj/item/melee/flyswatter, /obj/item/mop, /obj/item/holosign_creator/janibarrier, /obj/item/reagent_containers/glass/bucket, /obj/item/reagent_containers/spray/chemsprayer/janitor) - -/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" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) //yogs reason below - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF | FIRE_PROOF - -/obj/item/clothing/suit/space/freedom - name = "eagle suit" - desc = "An advanced, light suit, fabricated from a mixture of synthetic feathers and space-resistant material. A gun holster appears to be integrated into the suit and the wings appear to be stuck in 'freedom' mode." - icon_state = "freedom" - 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" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) //yogs nerfed bomb resistance as its now obtainable - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF | FIRE_PROOF - slowdown = 0 - -//Carpsuit, bestsuit, lovesuit -/obj/item/clothing/head/helmet/space/hardsuit/carp - name = "carp helmet" - desc = "Spaceworthy and it looks like a space carp's head, smells like one too." - icon_state = "carp_helm" - 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() - -/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/pneumatic_cannon/speargun) //I'm giving you a hint here - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp - -/obj/item/clothing/head/helmet/space/hardsuit/carp/equipped(mob/living/carbon/human/user, slot) - ..() - if (slot == SLOT_HEAD) - user.faction |= "carp" - -/obj/item/clothing/head/helmet/space/hardsuit/carp/dropped(mob/living/carbon/human/user) - ..() - if (user.head == src) - user.faction -= "carp" - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal - name = "paranormal response team helmet" - desc = "A helmet worn by those who deal with paranormal threats for a living." - icon_state = "hardsuit0-prt" - item_state = "hardsuit0-prt" - item_color = "knight_grey" - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - actions_types = list() - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_OCLOTHING) - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal - name = "paranormal response team hardsuit" - desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats." - icon_state = "knight_grey" - 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, TRUE, 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, janitor + - 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 + +/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 + +/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 + + //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) + +/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 + + 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 + + + //Space pirate outfit +/obj/item/clothing/head/helmet/space/pirate + name = "pirate hat" + desc = "Yarr." + 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 + +/obj/item/clothing/head/helmet/space/pirate/bandana + name = "pirate bandana" + icon_state = "bandana" + item_state = "bandana" + +/obj/item/clothing/suit/space/pirate + name = "pirate coat" + desc = "Yarr." + 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 + + //Emergency Response Team suits +/obj/item/clothing/head/helmet/space/hardsuit/ert + name = "emergency response team commander helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has blue highlights." + icon_state = "hardsuit0-ert_commander" + 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 = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + +/obj/item/clothing/head/helmet/space/hardsuit/ert/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) + +/obj/item/clothing/suit/space/hardsuit/ert + name = "emergency response team commander hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has blue highlights. Offers superb protection against environmental hazards." + icon_state = "ert_command" + 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 = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + + //ERT Security +/obj/item/clothing/head/helmet/space/hardsuit/ert/sec + name = "emergency response team security helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has red highlights." + icon_state = "hardsuit0-ert_security" + item_state = "hardsuit0-ert_security" + item_color = "ert_security" + +/obj/item/clothing/suit/space/hardsuit/ert/sec + name = "emergency response team security hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has red highlights. Offers superb protection against environmental hazards." + icon_state = "ert_security" + 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 + name = "emergency response team engineering helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has orange highlights." + icon_state = "hardsuit0-ert_engineer" + item_state = "hardsuit0-ert_engineer" + item_color = "ert_engineer" + +/obj/item/clothing/suit/space/hardsuit/ert/engi + name = "emergency response team engineering hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has orange highlights. Offers superb protection against environmental hazards." + icon_state = "ert_engineer" + 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 + name = "emergency response team medical helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has white highlights." + icon_state = "hardsuit0-ert_medical" + item_state = "hardsuit0-ert_medical" + item_color = "ert_medical" + +/obj/item/clothing/suit/space/hardsuit/ert/med + name = "emergency response team medical hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has white highlights. Offers superb protection against environmental hazards." + icon_state = "ert_medical" + item_state = "ert_medical" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med + species_exception = list(/datum/species/angel) + + //ERT Janitor +/obj/item/clothing/head/helmet/space/hardsuit/ert/jani + name = "emergency response team janitorial helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has purple highlights." + icon_state = "hardsuit0-ert_janitor" + item_state = "hardsuit0-ert_janitor" + item_color = "ert_janitor" + +/obj/item/clothing/suit/space/hardsuit/ert/jani + name = "emergency response team janitorial hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has purple highlights. Offers superb protection against environmental hazards. This one has extra clips for holding various janitorial tools." + icon_state = "ert_janitor" + item_state = "ert_janitor" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/jani + allowed = list(/obj/item/storage/bag/trash, /obj/item/melee/flyswatter, /obj/item/mop, /obj/item/holosign_creator/janibarrier, /obj/item/reagent_containers/glass/bucket, /obj/item/reagent_containers/spray/chemsprayer/janitor) + +/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" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) //yogs reason below + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF | FIRE_PROOF + +/obj/item/clothing/suit/space/freedom + name = "eagle suit" + desc = "An advanced, light suit, fabricated from a mixture of synthetic feathers and space-resistant material. A gun holster appears to be integrated into the suit and the wings appear to be stuck in 'freedom' mode." + icon_state = "freedom" + 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" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) //yogs nerfed bomb resistance as its now obtainable + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF | FIRE_PROOF + slowdown = 0 + +//Carpsuit, bestsuit, lovesuit +/obj/item/clothing/head/helmet/space/hardsuit/carp + name = "carp helmet" + desc = "Spaceworthy and it looks like a space carp's head, smells like one too." + icon_state = "carp_helm" + 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() + +/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/pneumatic_cannon/speargun) //I'm giving you a hint here + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp + +/obj/item/clothing/head/helmet/space/hardsuit/carp/equipped(mob/living/carbon/human/user, slot) + ..() + if (slot == SLOT_HEAD) + user.faction |= "carp" + +/obj/item/clothing/head/helmet/space/hardsuit/carp/dropped(mob/living/carbon/human/user) + ..() + if (user.head == src) + user.faction -= "carp" + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal + name = "paranormal response team helmet" + desc = "A helmet worn by those who deal with paranormal threats for a living." + icon_state = "hardsuit0-prt" + item_state = "hardsuit0-prt" + item_color = "knight_grey" + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + actions_types = list() + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_OCLOTHING) + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal + name = "paranormal response team hardsuit" + desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats." + icon_state = "knight_grey" + 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, TRUE, 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 d88e4a1b9db8..bc34dffefb11 100644 --- a/code/modules/clothing/spacesuits/syndi.dm +++ b/code/modules/clothing/spacesuits/syndi.dm @@ -1,163 +1,163 @@ -//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) - - -//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" - -//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" - -//Black-red syndicate contract varient -/obj/item/clothing/head/helmet/space/syndicate/contract - name = "contractor helmet" - desc = "A specialised black and gold helmet that's quicker, and more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." - slowdown = 0.55 - w_class = WEIGHT_CLASS_SMALL - icon_state = "syndicate-contract-helm" - item_state = "syndicate-contract-helm" - -/obj/item/clothing/suit/space/syndicate/contract - name = "contractor space suit" - desc = "A specialised black and gold space suit that's quicker, and more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." - slowdown = 0.55 - w_class = WEIGHT_CLASS_SMALL - icon_state = "syndicate-contract" - item_state = "syndicate-contract" - -//Black with yellow/red engineering syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/engie - name = "black space helmet" - icon_state = "syndicate-helm-black-engie" - 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) + + +//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" + +//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" + +//Black-red syndicate contract varient +/obj/item/clothing/head/helmet/space/syndicate/contract + name = "contractor helmet" + desc = "A specialised black and gold helmet that's quicker, and more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." + slowdown = 0.55 + w_class = WEIGHT_CLASS_SMALL + icon_state = "syndicate-contract-helm" + item_state = "syndicate-contract-helm" + +/obj/item/clothing/suit/space/syndicate/contract + name = "contractor space suit" + desc = "A specialised black and gold space suit that's quicker, and more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." + slowdown = 0.55 + w_class = WEIGHT_CLASS_SMALL + icon_state = "syndicate-contract" + item_state = "syndicate-contract" + +//Black with yellow/red engineering syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/engie + name = "black space helmet" + icon_state = "syndicate-helm-black-engie" + 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 8917907910c1..a36f3a3cf0b5 100644 --- a/code/modules/clothing/suits/_suits.dm +++ b/code/modules/clothing/suits/_suits.dm @@ -1,32 +1,32 @@ -/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 - var/blood_overlay_type = "suit" - var/togglename = null - var/suittoggled = FALSE - - -/obj/item/clothing/suit/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") - var/mob/living/carbon/human/M = loc - if(ishuman(M) && M.w_uniform) - var/obj/item/clothing/under/U = M.w_uniform - if(istype(U) && U.attached_accessory) - var/obj/item/clothing/accessory/A = U.attached_accessory - if(A.above_suit) - . += U.accessory_overlay - -/obj/item/clothing/suit/update_clothes_damaged_state(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 + var/blood_overlay_type = "suit" + var/togglename = null + var/suittoggled = FALSE + + +/obj/item/clothing/suit/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") + var/mob/living/carbon/human/M = loc + if(ishuman(M) && M.w_uniform) + var/obj/item/clothing/under/U = M.w_uniform + if(istype(U) && U.attached_accessory) + var/obj/item/clothing/accessory/A = U.attached_accessory + if(A.above_suit) + . += U.accessory_overlay + +/obj/item/clothing/suit/update_clothes_damaged_state(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 535b59bafe10..a93dc423b4cc 100644 --- a/code/modules/clothing/suits/bio.dm +++ b/code/modules/clothing/suits/bio.dm @@ -1,88 +1,88 @@ -//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 | SHOWEROKAY - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDEFACE - resistance_flags = ACID_PROOF - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - -/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 | SHOWEROKAY - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - slowdown = 1 - allowed = list(/obj/item/tank/internals, /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" = 80, "fire" = 30, "acid" = 100) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = ACID_PROOF - -//Standard biosuit, orange stripe -/obj/item/clothing/head/bio_hood/general - icon_state = "bio_general" - -/obj/item/clothing/suit/bio_suit/general - icon_state = "bio_general" - - -//Virology biosuit, green stripe -/obj/item/clothing/head/bio_hood/virology - icon_state = "bio_virology" - -/obj/item/clothing/suit/bio_suit/virology - icon_state = "bio_virology" - - -//Security biosuit, grey with red stripe across the chest -/obj/item/clothing/head/bio_hood/security - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 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" - allowed = list(/obj/item/storage/bag/trash) - -//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 | SHOWEROKAY + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDEFACE + resistance_flags = ACID_PROOF + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + +/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 | SHOWEROKAY + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + slowdown = 1 + allowed = list(/obj/item/tank/internals, /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" = 80, "fire" = 30, "acid" = 100) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = ACID_PROOF + +//Standard biosuit, orange stripe +/obj/item/clothing/head/bio_hood/general + icon_state = "bio_general" + +/obj/item/clothing/suit/bio_suit/general + icon_state = "bio_general" + + +//Virology biosuit, green stripe +/obj/item/clothing/head/bio_hood/virology + icon_state = "bio_virology" + +/obj/item/clothing/suit/bio_suit/virology + icon_state = "bio_virology" + + +//Security biosuit, grey with red stripe across the chest +/obj/item/clothing/head/bio_hood/security + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 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" + allowed = list(/obj/item/storage/bag/trash) + +//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 4fbff63fb526..b8feb505a777 100644 --- a/code/modules/clothing/suits/jobs.dm +++ b/code/modules/clothing/suits/jobs.dm @@ -1,156 +1,156 @@ -/* - * 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) - -//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" - -/obj/item/clothing/suit/det_suit/noir - name = "noir suit coat" - desc = "A dapper private investigator's grey suit coat." - icon_state = "detsuit" - item_state = "detsuit" - -//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" - - -//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 - -//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 +/* + * 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) + +//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" + +/obj/item/clothing/suit/det_suit/noir + name = "noir suit coat" + desc = "A dapper private investigator's grey suit coat." + icon_state = "detsuit" + item_state = "detsuit" + +//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" + + +//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 + +//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 diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm index 4a28c654d5a1..f05a14dab862 100644 --- a/code/modules/clothing/suits/labcoat.dm +++ b/code/modules/clothing/suits/labcoat.dm @@ -1,51 +1,51 @@ -/obj/item/clothing/suit/toggle/labcoat - name = "labcoat" - desc = "A suit that protects against minor chemical spills." - alternate_worn_icon = 'yogstation/icons/mob/suit.dmi' //yogs - labcoat sprite tweaks - 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/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." - alternate_worn_icon = 'icons/mob/suit.dmi' //yogs - subtype of labcoat, retains worn sprite - 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." + alternate_worn_icon = 'yogstation/icons/mob/suit.dmi' //yogs - labcoat sprite tweaks + 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/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." + alternate_worn_icon = 'icons/mob/suit.dmi' //yogs - subtype of labcoat, retains worn sprite + 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 772e14b43a39..a7dc06ccc9bd 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -1,647 +1,647 @@ -/* - * 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 - 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" - 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|GROIN|LEGS - -/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!" - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - resistance_flags = NONE - -/obj/item/clothing/suit/hastur - name = "\improper Hastur's robe" - desc = "Robes not meant to be worn by man." - icon_state = "hastur" - 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|CHEST - actions_types = list(/datum/action/item_action/toggle_wings) - -/obj/item/clothing/suit/toggle/owlwings/Initialize() - . = ..() - allowed = GLOB.security_vest_allowed - -/obj/item/clothing/suit/toggle/owlwings/griffinwings - name = "griffon cloak" - desc = "A plush white cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive your captives mad." - icon_state = "griffin_wings" - 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" - -/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 - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT //Space carp like space, so you should too - allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/pneumatic_cannon/speargun) - hoodtype = /obj/item/clothing/head/hooded/carp_hood - -/obj/item/clothing/head/hooded/carp_hood - name = "carp hood" - desc = "A hood attached to a carp costume." - icon_state = "carp_casual" - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - -/obj/item/clothing/head/hooded/carp_hood/equipped(mob/living/carbon/human/user, slot) - ..() - if (slot == SLOT_HEAD) - user.faction |= "carp" - -/obj/item/clothing/head/hooded/carp_hood/dropped(mob/living/carbon/human/user) - ..() - if (user.head == src) - user.faction -= "carp" - -/obj/item/clothing/suit/hooded/ian_costume //It's Ian, rub his bell- oh god what happened to his inside parts? - name = "corgi costume" - desc = "A costume that looks like someone made a human-like corgi, it won't guarantee belly rubs." - icon_state = "ian" - item_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - //cold_protection = CHEST|GROIN|ARMS - //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - allowed = list() - hoodtype = /obj/item/clothing/head/hooded/ian_hood - dog_fashion = /datum/dog_fashion/back - -/obj/item/clothing/head/hooded/ian_hood - name = "corgi hood" - desc = "A hood that looks just like a corgi's head, it won't guarantee dog biscuits." - icon_state = "ian" - body_parts_covered = HEAD - //cold_protection = HEAD - //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - -/obj/item/clothing/suit/hooded/bee_costume // It's Hip! - name = "bee costume" - desc = "Bee the true Queen!" - icon_state = "bee" - 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 - 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 - -/* - * 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" - -/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" - -/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" - -/obj/item/clothing/suit/striped_sweater - name = "striped sweater" - desc = "Reminds you of someone, but you just can't put your finger on it..." - icon_state = "waldo_shirt" - item_state = "waldo_shirt" - -/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|GROIN|ARMS - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - -/obj/item/clothing/suit/jacket/leather - name = "leather jacket" - desc = "Pompadour not included." - icon_state = "leatherjacket" - 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" - 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" - -/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" - -/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" - -/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) - -/obj/item/clothing/suit/nemes - name = "pharoah tunic" - desc = "Lavish space tomb not included." - icon_state = "pharoah" - icon_state = "pharoah" - body_parts_covered = CHEST|GROIN - -/obj/item/clothing/suit/caution - name = "wet floor sign" - desc = "Caution! Wet Floor!" - icon_state = "caution" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - force = 1 - throwforce = 3 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - body_parts_covered = CHEST|GROIN - attack_verb = list("warned", "cautioned", "smashed") - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - - - -// 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/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/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/medical - name = "medical winter coat" - icon_state = "coatmedical" - item_state = "coatmedical" - allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/winterhood/medical - -/obj/item/clothing/head/hooded/winterhood/medical - icon_state = "winterhood_medical" - -/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/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/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/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/spookyghost - name = "spooky ghost" - desc = "This is obviously just a bedsheet, but maybe try it on?" - icon_state = "bedsheet" - user_vars_to_edit = list("name" = "Spooky Ghost", "real_name" = "Spooky Ghost" , "incorporeal_move" = INCORPOREAL_MOVE_BASIC, "appearance_flags" = KEEP_TOGETHER|TILE_BOUND, "alpha" = 150) - alternate_worn_layer = ABOVE_BODY_FRONT_LAYER //so the bedsheet goes over everything but fire - -/obj/item/clothing/suit/bronze - name = "bronze suit" - desc = "A big and clanky suit made of bronze that offers no protection and looks very unfashionable. Nice." - icon = 'icons/obj/clothing/clockwork_garb.dmi' - icon_state = "clockwork_cuirass_old" - armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = 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 - alternate_worn_layer = UNDER_HEAD_LAYER +/* + * 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 + 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" + 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|GROIN|LEGS + +/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!" + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + resistance_flags = NONE + +/obj/item/clothing/suit/hastur + name = "\improper Hastur's robe" + desc = "Robes not meant to be worn by man." + icon_state = "hastur" + 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|CHEST + actions_types = list(/datum/action/item_action/toggle_wings) + +/obj/item/clothing/suit/toggle/owlwings/Initialize() + . = ..() + allowed = GLOB.security_vest_allowed + +/obj/item/clothing/suit/toggle/owlwings/griffinwings + name = "griffon cloak" + desc = "A plush white cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive your captives mad." + icon_state = "griffin_wings" + 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" + +/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 + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT //Space carp like space, so you should too + allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/pneumatic_cannon/speargun) + hoodtype = /obj/item/clothing/head/hooded/carp_hood + +/obj/item/clothing/head/hooded/carp_hood + name = "carp hood" + desc = "A hood attached to a carp costume." + icon_state = "carp_casual" + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + +/obj/item/clothing/head/hooded/carp_hood/equipped(mob/living/carbon/human/user, slot) + ..() + if (slot == SLOT_HEAD) + user.faction |= "carp" + +/obj/item/clothing/head/hooded/carp_hood/dropped(mob/living/carbon/human/user) + ..() + if (user.head == src) + user.faction -= "carp" + +/obj/item/clothing/suit/hooded/ian_costume //It's Ian, rub his bell- oh god what happened to his inside parts? + name = "corgi costume" + desc = "A costume that looks like someone made a human-like corgi, it won't guarantee belly rubs." + icon_state = "ian" + item_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + //cold_protection = CHEST|GROIN|ARMS + //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + allowed = list() + hoodtype = /obj/item/clothing/head/hooded/ian_hood + dog_fashion = /datum/dog_fashion/back + +/obj/item/clothing/head/hooded/ian_hood + name = "corgi hood" + desc = "A hood that looks just like a corgi's head, it won't guarantee dog biscuits." + icon_state = "ian" + body_parts_covered = HEAD + //cold_protection = HEAD + //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + +/obj/item/clothing/suit/hooded/bee_costume // It's Hip! + name = "bee costume" + desc = "Bee the true Queen!" + icon_state = "bee" + 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 + 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 + +/* + * 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" + +/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" + +/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" + +/obj/item/clothing/suit/striped_sweater + name = "striped sweater" + desc = "Reminds you of someone, but you just can't put your finger on it..." + icon_state = "waldo_shirt" + item_state = "waldo_shirt" + +/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|GROIN|ARMS + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + +/obj/item/clothing/suit/jacket/leather + name = "leather jacket" + desc = "Pompadour not included." + icon_state = "leatherjacket" + 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" + 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" + +/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" + +/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" + +/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) + +/obj/item/clothing/suit/nemes + name = "pharoah tunic" + desc = "Lavish space tomb not included." + icon_state = "pharoah" + icon_state = "pharoah" + body_parts_covered = CHEST|GROIN + +/obj/item/clothing/suit/caution + name = "wet floor sign" + desc = "Caution! Wet Floor!" + icon_state = "caution" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + force = 1 + throwforce = 3 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + body_parts_covered = CHEST|GROIN + attack_verb = list("warned", "cautioned", "smashed") + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + + + +// 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/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/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/medical + name = "medical winter coat" + icon_state = "coatmedical" + item_state = "coatmedical" + allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/winterhood/medical + +/obj/item/clothing/head/hooded/winterhood/medical + icon_state = "winterhood_medical" + +/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/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/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/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/spookyghost + name = "spooky ghost" + desc = "This is obviously just a bedsheet, but maybe try it on?" + icon_state = "bedsheet" + user_vars_to_edit = list("name" = "Spooky Ghost", "real_name" = "Spooky Ghost" , "incorporeal_move" = INCORPOREAL_MOVE_BASIC, "appearance_flags" = KEEP_TOGETHER|TILE_BOUND, "alpha" = 150) + alternate_worn_layer = ABOVE_BODY_FRONT_LAYER //so the bedsheet goes over everything but fire + +/obj/item/clothing/suit/bronze + name = "bronze suit" + desc = "A big and clanky suit made of bronze that offers no protection and looks very unfashionable. Nice." + icon = 'icons/obj/clothing/clockwork_garb.dmi' + icon_state = "clockwork_cuirass_old" + armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = 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 + alternate_worn_layer = UNDER_HEAD_LAYER diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm index f66790226848..5386471f0cd3 100644 --- a/code/modules/clothing/suits/utility.dm +++ b/code/modules/clothing/suits/utility.dm @@ -1,149 +1,149 @@ -/* - * 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" - - -/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 - -/* - * 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 - 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 - - -/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 - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = NONE - - -/obj/item/clothing/head/bomb_hood/security - icon_state = "bombsuit_sec" - 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|SHOWEROKAY - flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) - strip_delay = 60 - equip_delay_other = 60 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - resistance_flags = NONE - rad_flags = RAD_PROTECT_CONTENTS - -/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|SHOWEROKAY - 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 - resistance_flags = NONE - rad_flags = RAD_PROTECT_CONTENTS +/* + * 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" + + +/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 + +/* + * 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 + 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 + + +/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 + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = NONE + + +/obj/item/clothing/head/bomb_hood/security + icon_state = "bombsuit_sec" + 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|SHOWEROKAY + flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) + strip_delay = 60 + equip_delay_other = 60 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + resistance_flags = NONE + rad_flags = RAD_PROTECT_CONTENTS + +/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|SHOWEROKAY + 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 + resistance_flags = NONE + rad_flags = RAD_PROTECT_CONTENTS diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 9d1a47f23111..3731a1a35e56 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 f73cb89c5aca..c95282d16bc7 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -1,176 +1,176 @@ -/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 - var/mutantrace_variation = NO_MUTANTRACE_VARIATION //Are there special sprites for specific situations? Don't use this unless you need to. - var/freshly_laundered = FALSE - var/dodgy_colours = FALSE - tearable = TRUE //all jumpsuits can be torn down and used for cloth in an emergency | yogs - -/obj/item/clothing/under/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "uniformblood") - if(accessory_overlay) - . += accessory_overlay - -/obj/item/clothing/under/attackby(obj/item/I, mob/user, params) - if((has_sensor == BROKEN_SENSORS) && istype(I, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = I - C.use(1) - has_sensor = HAS_SENSORS - to_chat(user,"You repair the suit sensors on [src] with [C].") - return 1 - if(!attach_accessory(I, user)) - return ..() - -/obj/item/clothing/under/update_clothes_damaged_state(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/Initialize() - . = ..() - if(random_sensor) - //make the sensor mode favor higher levels, except coords. - sensor_mode = pick(SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS, SENSOR_COORDS) - -/obj/item/clothing/under/emp_act() - . = ..() - if(has_sensor > NO_SENSORS) - sensor_mode = pick(SENSOR_OFF, SENSOR_OFF, SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS) - if(ismob(loc)) - var/mob/M = loc - to_chat(M,"The sensors on the [src] change rapidly!") - -/obj/item/clothing/under/equipped(mob/user, slot) - ..() - if(adjusted) - adjusted = NORMAL_STYLE - fitted = initial(fitted) - if(!alt_covers_chest) - body_parts_covered |= CHEST - - if(mutantrace_variation && ishuman(user)) - var/mob/living/carbon/human/H = user - if(DIGITIGRADE in H.dna.species.species_traits) - adjusted = DIGITIGRADE_STYLE - H.update_inv_w_uniform() - - if(slot == SLOT_W_UNIFORM && freshly_laundered) - freshly_laundered = FALSE - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "fresh_laundry", /datum/mood_event/fresh_laundry) - - 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(!A.can_attach_accessory(src, user)) //Make sure the suit has a place to put the accessory. - return - if(user && !user.temporarilyRemoveItemFromInventory(I)) - return - if(!A.attach(src, user)) - return - - if(user && notifyAttach) - to_chat(user, "You attach [I] to [src].") - - var/accessory_color = attached_accessory.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(dodgy_colours) - . += "The colours are a bit dodgy." - if(freshly_laundered) - . += "It looks fresh and clean." - if(can_adjust) - if(adjusted == ALT_STYLE) - . += "Alt-click on [src] to wear it normally." - else - . += "Alt-click on [src] to wear it casually." - if (has_sensor == BROKEN_SENSORS) - . += "Its sensors appear to be shorted out." - else if(has_sensor > NO_SENSORS) - switch(sensor_mode) - if(SENSOR_OFF) - . += "Its sensors appear to be disabled." - if(SENSOR_LIVING) - . += "Its binary life sensors appear to be enabled." - if(SENSOR_VITALS) - . += "Its vital tracker appears to be enabled." - if(SENSOR_COORDS) - . += "Its vital tracker and tracking beacon appear to be enabled." - if(attached_accessory) - . += "\A [attached_accessory] is attached to it." +/obj/item/clothing/under + 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 + var/mutantrace_variation = NO_MUTANTRACE_VARIATION //Are there special sprites for specific situations? Don't use this unless you need to. + var/freshly_laundered = FALSE + var/dodgy_colours = FALSE + tearable = TRUE //all jumpsuits can be torn down and used for cloth in an emergency | yogs + +/obj/item/clothing/under/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "uniformblood") + if(accessory_overlay) + . += accessory_overlay + +/obj/item/clothing/under/attackby(obj/item/I, mob/user, params) + if((has_sensor == BROKEN_SENSORS) && istype(I, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = I + C.use(1) + has_sensor = HAS_SENSORS + to_chat(user,"You repair the suit sensors on [src] with [C].") + return 1 + if(!attach_accessory(I, user)) + return ..() + +/obj/item/clothing/under/update_clothes_damaged_state(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/Initialize() + . = ..() + if(random_sensor) + //make the sensor mode favor higher levels, except coords. + sensor_mode = pick(SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS, SENSOR_COORDS) + +/obj/item/clothing/under/emp_act() + . = ..() + if(has_sensor > NO_SENSORS) + sensor_mode = pick(SENSOR_OFF, SENSOR_OFF, SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS) + if(ismob(loc)) + var/mob/M = loc + to_chat(M,"The sensors on the [src] change rapidly!") + +/obj/item/clothing/under/equipped(mob/user, slot) + ..() + if(adjusted) + adjusted = NORMAL_STYLE + fitted = initial(fitted) + if(!alt_covers_chest) + body_parts_covered |= CHEST + + if(mutantrace_variation && ishuman(user)) + var/mob/living/carbon/human/H = user + if(DIGITIGRADE in H.dna.species.species_traits) + adjusted = DIGITIGRADE_STYLE + H.update_inv_w_uniform() + + if(slot == SLOT_W_UNIFORM && freshly_laundered) + freshly_laundered = FALSE + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "fresh_laundry", /datum/mood_event/fresh_laundry) + + 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(!A.can_attach_accessory(src, user)) //Make sure the suit has a place to put the accessory. + return + if(user && !user.temporarilyRemoveItemFromInventory(I)) + return + if(!A.attach(src, user)) + return + + if(user && notifyAttach) + to_chat(user, "You attach [I] to [src].") + + var/accessory_color = attached_accessory.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(dodgy_colours) + . += "The colours are a bit dodgy." + if(freshly_laundered) + . += "It looks fresh and clean." + if(can_adjust) + if(adjusted == ALT_STYLE) + . += "Alt-click on [src] to wear it normally." + else + . += "Alt-click on [src] to wear it casually." + if (has_sensor == BROKEN_SENSORS) + . += "Its sensors appear to be shorted out." + else if(has_sensor > NO_SENSORS) + switch(sensor_mode) + if(SENSOR_OFF) + . += "Its sensors appear to be disabled." + if(SENSOR_LIVING) + . += "Its binary life sensors appear to be enabled." + if(SENSOR_VITALS) + . += "Its vital tracker appears to be enabled." + if(SENSOR_COORDS) + . += "Its vital tracker and tracking beacon appear to be enabled." + if(attached_accessory) + . += "\A [attached_accessory] is attached to it." diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm index 939e8837f6ff..e23fc1429791 100644 --- a/code/modules/clothing/under/color.dm +++ b/code/modules/clothing/under/color.dm @@ -1,241 +1,241 @@ -/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/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/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" +/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/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/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 \ No newline at end of file diff --git a/code/modules/clothing/under/jobs/civilian.dm b/code/modules/clothing/under/jobs/civilian.dm index c3b0b60f213b..be3215ad037f 100644 --- a/code/modules/clothing/under/jobs/civilian.dm +++ b/code/modules/clothing/under/jobs/civilian.dm @@ -1,395 +1,395 @@ -//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 - mutantrace_variation = MUTANTRACE_VARIATION - 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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - 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 - icon_state = "black_suit_fem" - item_state = "black_suit_fem" - item_color = "black_suit_fem" - -/obj/item/clothing/under/lawyer/red - icon_state = "lawyer_red" - item_state = "lawyer_red" - item_color = "lawyer_red" - -/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 - 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 = "bluesuit" - 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 = "bluesuit" - 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 = "lawyer_purp" - 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 = "lawyer_purp" - 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 suitskirt. Nanotrasen Investigation Bureau approved!" - icon_state = "reallyblack_suit_skirt" - item_state = "bar_suit" - item_color = "reallyblack_suit_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - 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/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 + mutantrace_variation = MUTANTRACE_VARIATION + 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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + 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 + icon_state = "black_suit_fem" + item_state = "black_suit_fem" + item_color = "black_suit_fem" + +/obj/item/clothing/under/lawyer/red + icon_state = "lawyer_red" + item_state = "lawyer_red" + item_color = "lawyer_red" + +/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 + 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 = "bluesuit" + 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 = "bluesuit" + 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 = "lawyer_purp" + 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 = "lawyer_purp" + 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 suitskirt. Nanotrasen Investigation Bureau approved!" + icon_state = "reallyblack_suit_skirt" + item_state = "bar_suit" + item_color = "reallyblack_suit_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + 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/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 2787fbc9363d..8ba0c877e60a 100644 --- a/code/modules/clothing/under/jobs/engineering.dm +++ b/code/modules/clothing/under/jobs/engineering.dm @@ -1,82 +1,82 @@ -//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 +//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 \ No newline at end of file diff --git a/code/modules/clothing/under/jobs/medsci.dm b/code/modules/clothing/under/jobs/medsci.dm index 5fb7bdef02f1..1c2dd0b174a3 100644 --- a/code/modules/clothing/under/jobs/medsci.dm +++ b/code/modules/clothing/under/jobs/medsci.dm @@ -1,211 +1,211 @@ -/* - * 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 +/* + * 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 \ No newline at end of file diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm index dade15148fdd..351667d78e79 100644 --- a/code/modules/clothing/under/jobs/security.dm +++ b/code/modules/clothing/under/jobs/security.dm @@ -1,213 +1,213 @@ -/* - * 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/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index a2f43e98f5f0..e82a2dda61a4 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -1,826 +1,826 @@ -/obj/item/clothing/under/pj/red - name = "red pj's" - desc = "Sleepwear." - icon_state = "red_pyjamas" - item_color = "red_pyjamas" - item_state = "w_suit" - can_adjust = FALSE - -/obj/item/clothing/under/pj/blue - name = "blue pj's" - desc = "Sleepwear." - icon_state = "blue_pyjamas" - item_color = "blue_pyjamas" - item_state = "w_suit" - can_adjust = FALSE - -/obj/item/clothing/under/patriotsuit - name = "Patriotic Suit" - desc = "Motorcycle not included." - icon_state = "ek" - item_state = "ek" - item_color = "ek" - can_adjust = FALSE - -/obj/item/clothing/under/scratch - name = "white suit" - desc = "A white suit, suitable for an excellent host." - icon_state = "scratch" - item_state = "scratch" - item_color = "scratch" - can_adjust = FALSE - -/obj/item/clothing/under/scratch/skirt - name = "white suitskirt" - desc = "A white suitskirt, suitable for an excellent host." - icon_state = "white_suit_skirt" - item_state = "scratch" - item_color = "white_suit_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/sl_suit - desc = "It's a very amish looking suit." - name = "amish suit" - icon_state = "sl_suit" - item_color = "sl_suit" - can_adjust = FALSE - -/obj/item/clothing/under/roman - name = "\improper Roman armor" - desc = "Ancient Roman armor. Made of metallic and leather straps." - icon_state = "roman" - item_color = "roman" - item_state = "armor" - can_adjust = FALSE - strip_delay = 100 - resistance_flags = NONE - -/obj/item/clothing/under/waiter - name = "waiter's outfit" - desc = "It's a very smart uniform with a special pocket for tip." - icon_state = "waiter" - item_state = "waiter" - item_color = "waiter" - can_adjust = FALSE - -/obj/item/clothing/under/rank/prisoner - name = "prison jumpsuit" - desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." - icon_state = "prisoner" - item_state = "o_suit" - item_color = "prisoner" - has_sensor = LOCKED_SENSORS - sensor_mode = SENSOR_COORDS - random_sensor = FALSE - -/obj/item/clothing/under/rank/prisoner/skirt - name = "prison jumpskirt" - desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." - icon_state = "prisoner_skirt" - item_state = "o_suit" - item_color = "prisoner_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/mailman - name = "mailman's jumpsuit" - desc = "'Special delivery!'" - icon_state = "mailman" - item_state = "b_suit" - item_color = "mailman" - -/obj/item/clothing/under/rank/psyche - name = "psychedelic jumpsuit" - desc = "Groovy!" - icon_state = "psyche" - item_state = "p_suit" - item_color = "psyche" - -/obj/item/clothing/under/rank/clown/sexy - name = "sexy-clown suit" - desc = "It makes you look HONKable!" - icon_state = "sexyclown" - item_state = "sexyclown" - item_color = "sexyclown" - can_adjust = FALSE - -/obj/item/clothing/under/jabroni - name = "Jabroni Outfit" - desc = "The leather club is two sectors down." - icon_state = "darkholme" - item_state = "darkholme" - item_color = "darkholme" - can_adjust = FALSE - -/obj/item/clothing/under/rank/vice - name = "vice officer's jumpsuit" - desc = "It's the standard issue pretty-boy outfit, as seen on Holo-Vision." - icon_state = "vice" - item_state = "gy_suit" - item_color = "vice" - can_adjust = FALSE - -/obj/item/clothing/under/rank/centcom_officer - desc = "It's a jumpsuit worn by CentCom Officers." - name = "\improper CentCom officer's jumpsuit" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/centcom_commander - desc = "It's a jumpsuit worn by CentCom's highest-tier Commanders." - name = "\improper CentCom officer's jumpsuit" - icon_state = "centcom" - item_state = "dg_suit" - item_color = "centcom" - -/obj/item/clothing/under/space - name = "\improper NASA jumpsuit" - desc = "It has a NASA logo on it and is made of space-proofed materials." - icon_state = "black" - item_state = "bl_suit" - item_color = "black" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.02 - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - cold_protection = CHEST | GROIN | LEGS | ARMS //Needs gloves and shoes with cold protection to be fully protected. - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT - can_adjust = FALSE - resistance_flags = NONE - -/obj/item/clothing/under/acj - name = "administrative cybernetic jumpsuit" - icon_state = "syndicate" - item_state = "bl_suit" - item_color = "syndicate" - desc = "A cybernetically enhanced jumpsuit used for administrative duties." - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT - can_adjust = FALSE - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/under/owl - name = "owl uniform" - desc = "A soft brown jumpsuit made of synthetic feathers and strong conviction." - icon_state = "owl" - item_color = "owl" - can_adjust = FALSE - -/obj/item/clothing/under/griffin - name = "griffon uniform" - desc = "A soft brown jumpsuit with a white feather collar made of synthetic feathers and a lust for mayhem." - icon_state = "griffin" - item_color = "griffin" - can_adjust = FALSE - -/obj/item/clothing/under/cloud - name = "cloud" - desc = "cloud" - icon_state = "cloud" - item_color = "cloud" - can_adjust = FALSE - -/obj/item/clothing/under/gimmick/rank/captain/suit - name = "captain's suit" - desc = "A green suit and yellow necktie. Exemplifies authority." - icon_state = "green_suit" - item_state = "dg_suit" - item_color = "green_suit" - can_adjust = FALSE - -/obj/item/clothing/under/gimmick/rank/captain/suit/skirt - name = "green suitskirt" - desc = "A green suitskirt and yellow necktie. Exemplifies authority." - icon_state = "green_suit_skirt" - item_state = "dg_suit" - item_color = "green_suit_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/gimmick/rank/head_of_personnel/suit - name = "head of personnel's suit" - desc = "A teal suit and yellow necktie. An authoritative yet tacky ensemble." - icon_state = "teal_suit" - item_state = "g_suit" - item_color = "teal_suit" - can_adjust = FALSE - -/obj/item/clothing/under/gimmick/rank/head_of_personnel/suit/skirt - name = "teal suitskirt" - desc = "A teal suitskirt and yellow necktie. An authoritative yet tacky ensemble." - icon_state = "teal_suit_skirt" - item_state = "g_suit" - item_color = "teal_suit_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/suit_jacket - name = "black suit" - desc = "A black suit and red tie. Very formal." - icon_state = "black_suit" - item_state = "bl_suit" - item_color = "black_suit" - can_adjust = FALSE - -/obj/item/clothing/under/suit_jacket/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/suit_jacket/female - name = "executive suit" - desc = "A formal trouser suit for women, intended for the station's finest." - icon_state = "black_suit_fem" - item_state = "black_suit_fem" - item_color = "black_suit_fem" - -/obj/item/clothing/under/suit_jacket/green - name = "green suit" - desc = "A green suit and yellow necktie. Baller." - icon_state = "green_suit" - item_state = "dg_suit" - item_color = "green_suit" - can_adjust = FALSE - -/obj/item/clothing/under/suit_jacket/red - name = "red suit" - desc = "A red suit and blue tie. Somewhat formal." - icon_state = "red_suit" - item_state = "r_suit" - item_color = "red_suit" - -/obj/item/clothing/under/suit_jacket/charcoal - name = "charcoal suit" - desc = "A charcoal suit and red tie. Very professional." - icon_state = "charcoal_suit" - item_state = "charcoal_suit" - item_color = "charcoal_suit" - -/obj/item/clothing/under/suit_jacket/navy - name = "navy suit" - desc = "A navy suit and red tie, intended for the station's finest." - icon_state = "navy_suit" - item_state = "navy_suit" - item_color = "navy_suit" - -/obj/item/clothing/under/suit_jacket/burgundy - name = "burgundy suit" - desc = "A burgundy suit and black tie. Somewhat formal." - icon_state = "burgundy_suit" - item_state = "burgundy_suit" - item_color = "burgundy_suit" - -/obj/item/clothing/under/suit_jacket/checkered - name = "checkered suit" - desc = "That's a very nice suit you have there. Shame if something were to happen to it, eh?" - icon_state = "checkered_suit" - item_state = "checkered_suit" - item_color = "checkered_suit" - -/obj/item/clothing/under/suit_jacket/tan - name = "tan suit" - desc = "A tan suit with a yellow tie. Smart, but casual." - icon_state = "tan_suit" - item_state = "tan_suit" - item_color = "tan_suit" - -/obj/item/clothing/under/suit_jacket/white - name = "white suit" - desc = "A white suit and jacket with a blue shirt. You wanna play rough? OKAY!" - icon_state = "white_suit" - item_state = "white_suit" - item_color = "white_suit" - -/obj/item/clothing/under/burial - name = "burial garments" - desc = "Traditional burial garments from the early 22nd century." - icon_state = "burial" - item_state = "burial" - item_color = "burial" - has_sensor = NO_SENSORS - -/obj/item/clothing/under/skirt/black - name = "black skirt" - desc = "A black skirt, very fancy!" - icon_state = "blackskirt" - item_color = "blackskirt" - body_parts_covered = CHEST|GROIN|ARMS - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/skirt/blue - name = "blue skirt" - desc = "A blue, casual skirt." - icon_state = "blueskirt" - item_color = "blueskirt" - item_state = "b_suit" - body_parts_covered = CHEST|GROIN|ARMS - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - custom_price = 25 - -/obj/item/clothing/under/skirt/red - name = "red skirt" - desc = "A red, casual skirt." - icon_state = "redskirt" - item_color = "redskirt" - item_state = "r_suit" - body_parts_covered = CHEST|GROIN|ARMS - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - custom_price = 25 - -/obj/item/clothing/under/skirt/purple - name = "purple skirt" - desc = "A purple, casual skirt." - icon_state = "purpleskirt" - item_color = "purpleskirt" - item_state = "p_suit" - body_parts_covered = CHEST|GROIN|ARMS - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - custom_price = 25 - - -/obj/item/clothing/under/schoolgirl - name = "blue schoolgirl uniform" - desc = "It's just like one of my Japanese animes!" - icon_state = "schoolgirl" - item_state = "schoolgirl" - item_color = "schoolgirl" - body_parts_covered = CHEST|GROIN|ARMS - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/schoolgirl/red - name = "red schoolgirl uniform" - icon_state = "schoolgirlred" - item_state = "schoolgirlred" - item_color = "schoolgirlred" - -/obj/item/clothing/under/schoolgirl/green - name = "green schoolgirl uniform" - icon_state = "schoolgirlgreen" - item_state = "schoolgirlgreen" - item_color = "schoolgirlgreen" - -/obj/item/clothing/under/schoolgirl/orange - name = "orange schoolgirl uniform" - icon_state = "schoolgirlorange" - item_state = "schoolgirlorange" - item_color = "schoolgirlorange" - -/obj/item/clothing/under/overalls - name = "laborer's overalls" - desc = "A set of durable overalls for getting the job done." - icon_state = "overalls" - item_state = "lb_suit" - item_color = "overalls" - can_adjust = FALSE - custom_price = 20 - -/obj/item/clothing/under/pirate - name = "pirate outfit" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - item_color = "pirate" - can_adjust = FALSE - -/obj/item/clothing/under/soviet - name = "soviet uniform" - desc = "For the Motherland!" - icon_state = "soviet" - item_state = "soviet" - item_color = "soviet" - can_adjust = FALSE - -/obj/item/clothing/under/redcoat - name = "redcoat uniform" - desc = "Looks old." - icon_state = "redcoat" - item_state = "redcoat" - item_color = "redcoat" - can_adjust = FALSE - -/obj/item/clothing/under/kilt - name = "kilt" - desc = "Includes shoes and plaid." - icon_state = "kilt" - item_state = "kilt" - item_color = "kilt" - body_parts_covered = CHEST|GROIN|LEGS|FEET - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/kilt/highlander - desc = "You're the only one worthy of this kilt." - -/obj/item/clothing/under/kilt/highlander/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) - -/obj/item/clothing/under/sexymime - name = "sexy mime outfit" - desc = "The only time when you DON'T enjoy looking at someone's rack." - icon_state = "sexymime" - item_state = "sexymime" - item_color = "sexymime" - body_parts_covered = CHEST|GROIN|LEGS - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/gladiator - name = "gladiator uniform" - desc = "Are you not entertained? Is that not why you are here?" - icon_state = "gladiator" - item_state = "gladiator" - item_color = "gladiator" - body_parts_covered = CHEST|GROIN|ARMS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - resistance_flags = NONE - -/obj/item/clothing/under/gladiator/ash_walker - desc = "This gladiator uniform appears to be covered in ash and fairly dated." - has_sensor = NO_SENSORS - -/obj/item/clothing/under/sundress - name = "sundress" - desc = "Makes you want to frolic in a field of daisies." - icon_state = "sundress" - item_state = "sundress" - item_color = "sundress" - body_parts_covered = CHEST|GROIN - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/captainparade - name = "captain's parade uniform" - desc = "A captain's luxury-wear, for special occasions." - icon_state = "captain_parade" - item_state = "by_suit" - item_color = "captain_parade" - can_adjust = FALSE - -/obj/item/clothing/under/hosparademale - name = "head of security's parade uniform" - desc = "A male head of security's luxury-wear, for special occasions." - icon_state = "hos_parade_male" - item_state = "r_suit" - item_color = "hos_parade_male" - can_adjust = FALSE - -/obj/item/clothing/under/hosparadefem - name = "head of security's parade uniform" - desc = "A female head of security's luxury-wear, for special occasions." - icon_state = "hos_parade_fem" - item_state = "r_suit" - item_color = "hos_parade_fem" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/assistantformal - name = "assistant's formal uniform" - desc = "An assistant's formal-wear. Why an assistant needs formal-wear is still unknown." - icon_state = "assistant_formal" - item_state = "gy_suit" - item_color = "assistant_formal" - can_adjust = FALSE - -/obj/item/clothing/under/blacktango - name = "black tango dress" - desc = "Filled with Latin fire." - icon_state = "black_tango" - item_state = "wcoat" - item_color = "black_tango" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/stripeddress - name = "striped dress" - desc = "Fashion in space." - icon_state = "striped_dress" - item_state = "stripeddress" - item_color = "striped_dress" - body_parts_covered = CHEST|GROIN|ARMS - fitted = FEMALE_UNIFORM_FULL - can_adjust = FALSE - -/obj/item/clothing/under/sailordress - name = "sailor dress" - desc = "Formal wear for a leading lady." - icon_state = "sailor_dress" - item_state = "sailordress" - item_color = "sailor_dress" - body_parts_covered = CHEST|GROIN|ARMS - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/redeveninggown - name = "red evening gown" - desc = "Fancy dress for space bar singers." - icon_state = "red_evening_gown" - item_state = "redeveninggown" - item_color = "red_evening_gown" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/maid - name = "maid costume" - desc = "Maid in China." - icon_state = "maid" - item_state = "maid" - item_color = "maid" - body_parts_covered = CHEST|GROIN - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/maid/Initialize() - . = ..() - var/obj/item/clothing/accessory/maidapron/A = new (src) - attach_accessory(A) - -/obj/item/clothing/under/janimaid - name = "maid uniform" - desc = "A simple maid uniform for housekeeping." - icon_state = "janimaid" - item_state = "janimaid" - item_color = "janimaid" - body_parts_covered = CHEST|GROIN - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/plaid_skirt - name = "red plaid skirt" - desc = "A preppy red skirt with a white blouse." - icon_state = "plaid_red" - item_state = "plaid_red" - item_color = "plaid_red" - fitted = FEMALE_UNIFORM_TOP - can_adjust = TRUE - alt_covers_chest = TRUE - custom_price = 25 - -/obj/item/clothing/under/plaid_skirt/blue - name = "blue plaid skirt" - desc = "A preppy blue skirt with a white blouse." - icon_state = "plaid_blue" - item_state = "plaid_blue" - item_color = "plaid_blue" - fitted = FEMALE_UNIFORM_TOP - can_adjust = TRUE - alt_covers_chest = TRUE - -/obj/item/clothing/under/plaid_skirt/purple - name = "purple plaid skirt" - desc = "A preppy purple skirt with a white blouse." - icon_state = "plaid_purple" - item_state = "plaid_purple" - item_color = "plaid_purple" - fitted = FEMALE_UNIFORM_TOP - can_adjust = TRUE - alt_covers_chest = TRUE - -/obj/item/clothing/under/singery - name = "yellow performer's outfit" - desc = "Just looking at this makes you want to sing." - icon_state = "ysing" - item_state = "ysing" - item_color = "ysing" - body_parts_covered = CHEST|GROIN|ARMS - fitted = NO_FEMALE_UNIFORM - alternate_worn_layer = ABOVE_SHOES_LAYER - can_adjust = FALSE - -/obj/item/clothing/under/singerb - name = "blue performer's outfit" - desc = "Just looking at this makes you want to sing." - icon_state = "bsing" - item_state = "bsing" - item_color = "bsing" - body_parts_covered = CHEST|GROIN|ARMS - alternate_worn_layer = ABOVE_SHOES_LAYER - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/plaid_skirt/green - name = "green plaid skirt" - desc = "A preppy green skirt with a white blouse." - icon_state = "plaid_green" - item_state = "plaid_green" - item_color = "plaid_green" - fitted = FEMALE_UNIFORM_TOP - can_adjust = TRUE - alt_covers_chest = TRUE - -/obj/item/clothing/under/jester - name = "jester suit" - desc = "A jolly dress, well suited to entertain your master, nuncle." - icon_state = "jester" - item_color = "jester" - can_adjust = FALSE - -/obj/item/clothing/under/jester/alt - icon_state = "jester2" - -/obj/item/clothing/under/geisha - name = "geisha suit" - desc = "Cute space ninja senpai not included." - icon_state = "geisha" - item_color = "geisha" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - -/obj/item/clothing/under/villain - name = "villain suit" - desc = "A change of wardrobe is necessary if you ever want to catch a real superhero." - icon_state = "villain" - item_color = "villain" - can_adjust = FALSE - -/obj/item/clothing/under/sailor - name = "sailor suit" - desc = "Skipper's in the wardroom drinkin gin'." - icon_state = "sailor" - item_state = "b_suit" - item_color = "sailor" - can_adjust = FALSE - -/obj/item/clothing/under/plasmaman - name = "envirosuit" - desc = "A special containment suit that allows plasma-based lifeforms to exist safely in an oxygenated environment, and automatically extinguishes them in a crisis. Despite being airtight, it's not spaceworthy." - icon_state = "plasmaman" - item_state = "plasmaman" - item_color = "plasmaman" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - can_adjust = FALSE - strip_delay = 80 - var/next_extinguish = 0 - var/extinguish_cooldown = 100 - var/extinguishes_left = 5 - - -/obj/item/clothing/under/plasmaman/examine(mob/user) - . = ..() - . += "There are [extinguishes_left] extinguisher charges left in this suit." - -/obj/item/clothing/under/plasmaman/proc/Extinguish(mob/living/carbon/human/H) - if(!istype(H)) - return - - if(H.on_fire) - if(extinguishes_left) - if(next_extinguish > world.time) - return - next_extinguish = world.time + extinguish_cooldown - extinguishes_left-- - H.visible_message("[H]'s suit automatically extinguishes [H.p_them()]!","Your suit automatically extinguishes you.") - H.ExtinguishMob() - new /obj/effect/particle_effect/water(get_turf(H)) - return 0 - -/obj/item/clothing/under/plasmaman/attackby(obj/item/E, mob/user, params) - ..() - if (istype(E, /obj/item/extinguisher_refill)) - if (extinguishes_left == 5) - to_chat(user, "The inbuilt extinguisher is full.") - return - else - extinguishes_left = 5 - to_chat(user, "You refill the suit's built-in extinguisher, using up the cartridge.") - qdel(E) - return - return - return - -/obj/item/extinguisher_refill - name = "envirosuit extinguisher cartridge" - desc = "A cartridge loaded with a compressed extinguisher mix, used to refill the automatic extinguisher on plasma envirosuits." - icon_state = "plasmarefill" - icon = 'icons/obj/device.dmi' - -/obj/item/clothing/under/rank/security/navyblue/russian - name = "\improper Russian officer's uniform" - desc = "The latest in fashionable russian outfits." - icon_state = "hostanclothes" - item_state = "hostanclothes" - item_color = "hostanclothes" - -/obj/item/clothing/under/mummy - name = "mummy wrapping" - desc = "Return the slab or suffer my stale references." - icon_state = "mummy" - item_state = "mummy" - item_color = "mummy" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - resistance_flags = NONE - -/obj/item/clothing/under/scarecrow - name = "scarecrow clothes" - desc = "Perfect camouflage for hiding in botany." - icon_state = "scarecrow" - item_state = "scarecrow" - item_color = "scarecrow" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - resistance_flags = NONE - -/obj/item/clothing/under/draculass - name = "draculass coat" - desc = "A dress inspired by the ancient \"Victorian\" era." - icon_state = "draculass" - item_state = "draculass" - item_color = "draculass" - body_parts_covered = CHEST|GROIN|ARMS - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/drfreeze - name = "doctor freeze's jumpsuit" - desc = "A modified scientist jumpsuit to look extra cool." - icon_state = "drfreeze" - item_state = "drfreeze" - item_color = "drfreeze" - can_adjust = FALSE - -/obj/item/clothing/under/lobster - name = "foam lobster suit" - desc = "Who beheaded the college mascot?" - icon_state = "lobster" - item_state = "lobster" - item_color = "lobster" - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - -/obj/item/clothing/under/gondola - name = "gondola hide suit" - desc = "Now you're cooking." - icon_state = "gondola" - item_state = "lb_suit" - item_color = "gondola" - can_adjust = FALSE - -/obj/item/clothing/under/skeleton - name = "skeleton jumpsuit" - desc = "A black jumpsuit with a white bone pattern printed on it. Spooky!" - icon_state = "skeleton" - item_state = "skeleton" - item_color = "skeleton" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - resistance_flags = NONE - -/obj/item/clothing/under/durathread - name = "durathread jumpsuit" - desc = "A jumpsuit made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "durathread" - item_state = "durathread" - item_color = "durathread" - can_adjust = FALSE - armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5) - -/obj/item/clothing/under/mech_suit - name = "red mech pilot's suit" - desc = "A red mech pilot's suit. Might make your butt look big." - icon_state = "red_mech_suit" - item_state = "red_mech_suit" - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - fitted = NO_FEMALE_UNIFORM - alternate_worn_layer = GLOVES_LAYER //covers hands but gloves can go over it. This is how these things work in my head. - can_adjust = FALSE - -/obj/item/clothing/under/mech_suit/white - name = "white mech pilot's suit" - desc = "A white mech pilot's suit. Very fetching." - icon_state = "white_mech_suit" - item_state = "white_mech_suit" - -/obj/item/clothing/under/mech_suit/blue - name = "blue mech pilot's suit" - desc = "A blue mech pilot's suit. For the more reluctant mech pilots." - icon_state = "blue_mech_suit" - item_state = "blue_mech_suit" +/obj/item/clothing/under/pj/red + name = "red pj's" + desc = "Sleepwear." + icon_state = "red_pyjamas" + item_color = "red_pyjamas" + item_state = "w_suit" + can_adjust = FALSE + +/obj/item/clothing/under/pj/blue + name = "blue pj's" + desc = "Sleepwear." + icon_state = "blue_pyjamas" + item_color = "blue_pyjamas" + item_state = "w_suit" + can_adjust = FALSE + +/obj/item/clothing/under/patriotsuit + name = "Patriotic Suit" + desc = "Motorcycle not included." + icon_state = "ek" + item_state = "ek" + item_color = "ek" + can_adjust = FALSE + +/obj/item/clothing/under/scratch + name = "white suit" + desc = "A white suit, suitable for an excellent host." + icon_state = "scratch" + item_state = "scratch" + item_color = "scratch" + can_adjust = FALSE + +/obj/item/clothing/under/scratch/skirt + name = "white suitskirt" + desc = "A white suitskirt, suitable for an excellent host." + icon_state = "white_suit_skirt" + item_state = "scratch" + item_color = "white_suit_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/sl_suit + desc = "It's a very amish looking suit." + name = "amish suit" + icon_state = "sl_suit" + item_color = "sl_suit" + can_adjust = FALSE + +/obj/item/clothing/under/roman + name = "\improper Roman armor" + desc = "Ancient Roman armor. Made of metallic and leather straps." + icon_state = "roman" + item_color = "roman" + item_state = "armor" + can_adjust = FALSE + strip_delay = 100 + resistance_flags = NONE + +/obj/item/clothing/under/waiter + name = "waiter's outfit" + desc = "It's a very smart uniform with a special pocket for tip." + icon_state = "waiter" + item_state = "waiter" + item_color = "waiter" + can_adjust = FALSE + +/obj/item/clothing/under/rank/prisoner + name = "prison jumpsuit" + desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." + icon_state = "prisoner" + item_state = "o_suit" + item_color = "prisoner" + has_sensor = LOCKED_SENSORS + sensor_mode = SENSOR_COORDS + random_sensor = FALSE + +/obj/item/clothing/under/rank/prisoner/skirt + name = "prison jumpskirt" + desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." + icon_state = "prisoner_skirt" + item_state = "o_suit" + item_color = "prisoner_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/mailman + name = "mailman's jumpsuit" + desc = "'Special delivery!'" + icon_state = "mailman" + item_state = "b_suit" + item_color = "mailman" + +/obj/item/clothing/under/rank/psyche + name = "psychedelic jumpsuit" + desc = "Groovy!" + icon_state = "psyche" + item_state = "p_suit" + item_color = "psyche" + +/obj/item/clothing/under/rank/clown/sexy + name = "sexy-clown suit" + desc = "It makes you look HONKable!" + icon_state = "sexyclown" + item_state = "sexyclown" + item_color = "sexyclown" + can_adjust = FALSE + +/obj/item/clothing/under/jabroni + name = "Jabroni Outfit" + desc = "The leather club is two sectors down." + icon_state = "darkholme" + item_state = "darkholme" + item_color = "darkholme" + can_adjust = FALSE + +/obj/item/clothing/under/rank/vice + name = "vice officer's jumpsuit" + desc = "It's the standard issue pretty-boy outfit, as seen on Holo-Vision." + icon_state = "vice" + item_state = "gy_suit" + item_color = "vice" + can_adjust = FALSE + +/obj/item/clothing/under/rank/centcom_officer + desc = "It's a jumpsuit worn by CentCom Officers." + name = "\improper CentCom officer's jumpsuit" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/centcom_commander + desc = "It's a jumpsuit worn by CentCom's highest-tier Commanders." + name = "\improper CentCom officer's jumpsuit" + icon_state = "centcom" + item_state = "dg_suit" + item_color = "centcom" + +/obj/item/clothing/under/space + name = "\improper NASA jumpsuit" + desc = "It has a NASA logo on it and is made of space-proofed materials." + icon_state = "black" + item_state = "bl_suit" + item_color = "black" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.02 + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + cold_protection = CHEST | GROIN | LEGS | ARMS //Needs gloves and shoes with cold protection to be fully protected. + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + can_adjust = FALSE + resistance_flags = NONE + +/obj/item/clothing/under/acj + name = "administrative cybernetic jumpsuit" + icon_state = "syndicate" + item_state = "bl_suit" + item_color = "syndicate" + desc = "A cybernetically enhanced jumpsuit used for administrative duties." + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + can_adjust = FALSE + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/under/owl + name = "owl uniform" + desc = "A soft brown jumpsuit made of synthetic feathers and strong conviction." + icon_state = "owl" + item_color = "owl" + can_adjust = FALSE + +/obj/item/clothing/under/griffin + name = "griffon uniform" + desc = "A soft brown jumpsuit with a white feather collar made of synthetic feathers and a lust for mayhem." + icon_state = "griffin" + item_color = "griffin" + can_adjust = FALSE + +/obj/item/clothing/under/cloud + name = "cloud" + desc = "cloud" + icon_state = "cloud" + item_color = "cloud" + can_adjust = FALSE + +/obj/item/clothing/under/gimmick/rank/captain/suit + name = "captain's suit" + desc = "A green suit and yellow necktie. Exemplifies authority." + icon_state = "green_suit" + item_state = "dg_suit" + item_color = "green_suit" + can_adjust = FALSE + +/obj/item/clothing/under/gimmick/rank/captain/suit/skirt + name = "green suitskirt" + desc = "A green suitskirt and yellow necktie. Exemplifies authority." + icon_state = "green_suit_skirt" + item_state = "dg_suit" + item_color = "green_suit_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/gimmick/rank/head_of_personnel/suit + name = "head of personnel's suit" + desc = "A teal suit and yellow necktie. An authoritative yet tacky ensemble." + icon_state = "teal_suit" + item_state = "g_suit" + item_color = "teal_suit" + can_adjust = FALSE + +/obj/item/clothing/under/gimmick/rank/head_of_personnel/suit/skirt + name = "teal suitskirt" + desc = "A teal suitskirt and yellow necktie. An authoritative yet tacky ensemble." + icon_state = "teal_suit_skirt" + item_state = "g_suit" + item_color = "teal_suit_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/suit_jacket + name = "black suit" + desc = "A black suit and red tie. Very formal." + icon_state = "black_suit" + item_state = "bl_suit" + item_color = "black_suit" + can_adjust = FALSE + +/obj/item/clothing/under/suit_jacket/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/suit_jacket/female + name = "executive suit" + desc = "A formal trouser suit for women, intended for the station's finest." + icon_state = "black_suit_fem" + item_state = "black_suit_fem" + item_color = "black_suit_fem" + +/obj/item/clothing/under/suit_jacket/green + name = "green suit" + desc = "A green suit and yellow necktie. Baller." + icon_state = "green_suit" + item_state = "dg_suit" + item_color = "green_suit" + can_adjust = FALSE + +/obj/item/clothing/under/suit_jacket/red + name = "red suit" + desc = "A red suit and blue tie. Somewhat formal." + icon_state = "red_suit" + item_state = "r_suit" + item_color = "red_suit" + +/obj/item/clothing/under/suit_jacket/charcoal + name = "charcoal suit" + desc = "A charcoal suit and red tie. Very professional." + icon_state = "charcoal_suit" + item_state = "charcoal_suit" + item_color = "charcoal_suit" + +/obj/item/clothing/under/suit_jacket/navy + name = "navy suit" + desc = "A navy suit and red tie, intended for the station's finest." + icon_state = "navy_suit" + item_state = "navy_suit" + item_color = "navy_suit" + +/obj/item/clothing/under/suit_jacket/burgundy + name = "burgundy suit" + desc = "A burgundy suit and black tie. Somewhat formal." + icon_state = "burgundy_suit" + item_state = "burgundy_suit" + item_color = "burgundy_suit" + +/obj/item/clothing/under/suit_jacket/checkered + name = "checkered suit" + desc = "That's a very nice suit you have there. Shame if something were to happen to it, eh?" + icon_state = "checkered_suit" + item_state = "checkered_suit" + item_color = "checkered_suit" + +/obj/item/clothing/under/suit_jacket/tan + name = "tan suit" + desc = "A tan suit with a yellow tie. Smart, but casual." + icon_state = "tan_suit" + item_state = "tan_suit" + item_color = "tan_suit" + +/obj/item/clothing/under/suit_jacket/white + name = "white suit" + desc = "A white suit and jacket with a blue shirt. You wanna play rough? OKAY!" + icon_state = "white_suit" + item_state = "white_suit" + item_color = "white_suit" + +/obj/item/clothing/under/burial + name = "burial garments" + desc = "Traditional burial garments from the early 22nd century." + icon_state = "burial" + item_state = "burial" + item_color = "burial" + has_sensor = NO_SENSORS + +/obj/item/clothing/under/skirt/black + name = "black skirt" + desc = "A black skirt, very fancy!" + icon_state = "blackskirt" + item_color = "blackskirt" + body_parts_covered = CHEST|GROIN|ARMS + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/skirt/blue + name = "blue skirt" + desc = "A blue, casual skirt." + icon_state = "blueskirt" + item_color = "blueskirt" + item_state = "b_suit" + body_parts_covered = CHEST|GROIN|ARMS + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + custom_price = 25 + +/obj/item/clothing/under/skirt/red + name = "red skirt" + desc = "A red, casual skirt." + icon_state = "redskirt" + item_color = "redskirt" + item_state = "r_suit" + body_parts_covered = CHEST|GROIN|ARMS + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + custom_price = 25 + +/obj/item/clothing/under/skirt/purple + name = "purple skirt" + desc = "A purple, casual skirt." + icon_state = "purpleskirt" + item_color = "purpleskirt" + item_state = "p_suit" + body_parts_covered = CHEST|GROIN|ARMS + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + custom_price = 25 + + +/obj/item/clothing/under/schoolgirl + name = "blue schoolgirl uniform" + desc = "It's just like one of my Japanese animes!" + icon_state = "schoolgirl" + item_state = "schoolgirl" + item_color = "schoolgirl" + body_parts_covered = CHEST|GROIN|ARMS + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/schoolgirl/red + name = "red schoolgirl uniform" + icon_state = "schoolgirlred" + item_state = "schoolgirlred" + item_color = "schoolgirlred" + +/obj/item/clothing/under/schoolgirl/green + name = "green schoolgirl uniform" + icon_state = "schoolgirlgreen" + item_state = "schoolgirlgreen" + item_color = "schoolgirlgreen" + +/obj/item/clothing/under/schoolgirl/orange + name = "orange schoolgirl uniform" + icon_state = "schoolgirlorange" + item_state = "schoolgirlorange" + item_color = "schoolgirlorange" + +/obj/item/clothing/under/overalls + name = "laborer's overalls" + desc = "A set of durable overalls for getting the job done." + icon_state = "overalls" + item_state = "lb_suit" + item_color = "overalls" + can_adjust = FALSE + custom_price = 20 + +/obj/item/clothing/under/pirate + name = "pirate outfit" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + item_color = "pirate" + can_adjust = FALSE + +/obj/item/clothing/under/soviet + name = "soviet uniform" + desc = "For the Motherland!" + icon_state = "soviet" + item_state = "soviet" + item_color = "soviet" + can_adjust = FALSE + +/obj/item/clothing/under/redcoat + name = "redcoat uniform" + desc = "Looks old." + icon_state = "redcoat" + item_state = "redcoat" + item_color = "redcoat" + can_adjust = FALSE + +/obj/item/clothing/under/kilt + name = "kilt" + desc = "Includes shoes and plaid." + icon_state = "kilt" + item_state = "kilt" + item_color = "kilt" + body_parts_covered = CHEST|GROIN|LEGS|FEET + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/kilt/highlander + desc = "You're the only one worthy of this kilt." + +/obj/item/clothing/under/kilt/highlander/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) + +/obj/item/clothing/under/sexymime + name = "sexy mime outfit" + desc = "The only time when you DON'T enjoy looking at someone's rack." + icon_state = "sexymime" + item_state = "sexymime" + item_color = "sexymime" + body_parts_covered = CHEST|GROIN|LEGS + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/gladiator + name = "gladiator uniform" + desc = "Are you not entertained? Is that not why you are here?" + icon_state = "gladiator" + item_state = "gladiator" + item_color = "gladiator" + body_parts_covered = CHEST|GROIN|ARMS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + resistance_flags = NONE + +/obj/item/clothing/under/gladiator/ash_walker + desc = "This gladiator uniform appears to be covered in ash and fairly dated." + has_sensor = NO_SENSORS + +/obj/item/clothing/under/sundress + name = "sundress" + desc = "Makes you want to frolic in a field of daisies." + icon_state = "sundress" + item_state = "sundress" + item_color = "sundress" + body_parts_covered = CHEST|GROIN + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/captainparade + name = "captain's parade uniform" + desc = "A captain's luxury-wear, for special occasions." + icon_state = "captain_parade" + item_state = "by_suit" + item_color = "captain_parade" + can_adjust = FALSE + +/obj/item/clothing/under/hosparademale + name = "head of security's parade uniform" + desc = "A male head of security's luxury-wear, for special occasions." + icon_state = "hos_parade_male" + item_state = "r_suit" + item_color = "hos_parade_male" + can_adjust = FALSE + +/obj/item/clothing/under/hosparadefem + name = "head of security's parade uniform" + desc = "A female head of security's luxury-wear, for special occasions." + icon_state = "hos_parade_fem" + item_state = "r_suit" + item_color = "hos_parade_fem" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/assistantformal + name = "assistant's formal uniform" + desc = "An assistant's formal-wear. Why an assistant needs formal-wear is still unknown." + icon_state = "assistant_formal" + item_state = "gy_suit" + item_color = "assistant_formal" + can_adjust = FALSE + +/obj/item/clothing/under/blacktango + name = "black tango dress" + desc = "Filled with Latin fire." + icon_state = "black_tango" + item_state = "wcoat" + item_color = "black_tango" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/stripeddress + name = "striped dress" + desc = "Fashion in space." + icon_state = "striped_dress" + item_state = "stripeddress" + item_color = "striped_dress" + body_parts_covered = CHEST|GROIN|ARMS + fitted = FEMALE_UNIFORM_FULL + can_adjust = FALSE + +/obj/item/clothing/under/sailordress + name = "sailor dress" + desc = "Formal wear for a leading lady." + icon_state = "sailor_dress" + item_state = "sailordress" + item_color = "sailor_dress" + body_parts_covered = CHEST|GROIN|ARMS + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/redeveninggown + name = "red evening gown" + desc = "Fancy dress for space bar singers." + icon_state = "red_evening_gown" + item_state = "redeveninggown" + item_color = "red_evening_gown" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/maid + name = "maid costume" + desc = "Maid in China." + icon_state = "maid" + item_state = "maid" + item_color = "maid" + body_parts_covered = CHEST|GROIN + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/maid/Initialize() + . = ..() + var/obj/item/clothing/accessory/maidapron/A = new (src) + attach_accessory(A) + +/obj/item/clothing/under/janimaid + name = "maid uniform" + desc = "A simple maid uniform for housekeeping." + icon_state = "janimaid" + item_state = "janimaid" + item_color = "janimaid" + body_parts_covered = CHEST|GROIN + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/plaid_skirt + name = "red plaid skirt" + desc = "A preppy red skirt with a white blouse." + icon_state = "plaid_red" + item_state = "plaid_red" + item_color = "plaid_red" + fitted = FEMALE_UNIFORM_TOP + can_adjust = TRUE + alt_covers_chest = TRUE + custom_price = 25 + +/obj/item/clothing/under/plaid_skirt/blue + name = "blue plaid skirt" + desc = "A preppy blue skirt with a white blouse." + icon_state = "plaid_blue" + item_state = "plaid_blue" + item_color = "plaid_blue" + fitted = FEMALE_UNIFORM_TOP + can_adjust = TRUE + alt_covers_chest = TRUE + +/obj/item/clothing/under/plaid_skirt/purple + name = "purple plaid skirt" + desc = "A preppy purple skirt with a white blouse." + icon_state = "plaid_purple" + item_state = "plaid_purple" + item_color = "plaid_purple" + fitted = FEMALE_UNIFORM_TOP + can_adjust = TRUE + alt_covers_chest = TRUE + +/obj/item/clothing/under/singery + name = "yellow performer's outfit" + desc = "Just looking at this makes you want to sing." + icon_state = "ysing" + item_state = "ysing" + item_color = "ysing" + body_parts_covered = CHEST|GROIN|ARMS + fitted = NO_FEMALE_UNIFORM + alternate_worn_layer = ABOVE_SHOES_LAYER + can_adjust = FALSE + +/obj/item/clothing/under/singerb + name = "blue performer's outfit" + desc = "Just looking at this makes you want to sing." + icon_state = "bsing" + item_state = "bsing" + item_color = "bsing" + body_parts_covered = CHEST|GROIN|ARMS + alternate_worn_layer = ABOVE_SHOES_LAYER + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/plaid_skirt/green + name = "green plaid skirt" + desc = "A preppy green skirt with a white blouse." + icon_state = "plaid_green" + item_state = "plaid_green" + item_color = "plaid_green" + fitted = FEMALE_UNIFORM_TOP + can_adjust = TRUE + alt_covers_chest = TRUE + +/obj/item/clothing/under/jester + name = "jester suit" + desc = "A jolly dress, well suited to entertain your master, nuncle." + icon_state = "jester" + item_color = "jester" + can_adjust = FALSE + +/obj/item/clothing/under/jester/alt + icon_state = "jester2" + +/obj/item/clothing/under/geisha + name = "geisha suit" + desc = "Cute space ninja senpai not included." + icon_state = "geisha" + item_color = "geisha" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + +/obj/item/clothing/under/villain + name = "villain suit" + desc = "A change of wardrobe is necessary if you ever want to catch a real superhero." + icon_state = "villain" + item_color = "villain" + can_adjust = FALSE + +/obj/item/clothing/under/sailor + name = "sailor suit" + desc = "Skipper's in the wardroom drinkin gin'." + icon_state = "sailor" + item_state = "b_suit" + item_color = "sailor" + can_adjust = FALSE + +/obj/item/clothing/under/plasmaman + name = "envirosuit" + desc = "A special containment suit that allows plasma-based lifeforms to exist safely in an oxygenated environment, and automatically extinguishes them in a crisis. Despite being airtight, it's not spaceworthy." + icon_state = "plasmaman" + item_state = "plasmaman" + item_color = "plasmaman" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + can_adjust = FALSE + strip_delay = 80 + var/next_extinguish = 0 + var/extinguish_cooldown = 100 + var/extinguishes_left = 5 + + +/obj/item/clothing/under/plasmaman/examine(mob/user) + . = ..() + . += "There are [extinguishes_left] extinguisher charges left in this suit." + +/obj/item/clothing/under/plasmaman/proc/Extinguish(mob/living/carbon/human/H) + if(!istype(H)) + return + + if(H.on_fire) + if(extinguishes_left) + if(next_extinguish > world.time) + return + next_extinguish = world.time + extinguish_cooldown + extinguishes_left-- + H.visible_message("[H]'s suit automatically extinguishes [H.p_them()]!","Your suit automatically extinguishes you.") + H.ExtinguishMob() + new /obj/effect/particle_effect/water(get_turf(H)) + return 0 + +/obj/item/clothing/under/plasmaman/attackby(obj/item/E, mob/user, params) + ..() + if (istype(E, /obj/item/extinguisher_refill)) + if (extinguishes_left == 5) + to_chat(user, "The inbuilt extinguisher is full.") + return + else + extinguishes_left = 5 + to_chat(user, "You refill the suit's built-in extinguisher, using up the cartridge.") + qdel(E) + return + return + return + +/obj/item/extinguisher_refill + name = "envirosuit extinguisher cartridge" + desc = "A cartridge loaded with a compressed extinguisher mix, used to refill the automatic extinguisher on plasma envirosuits." + icon_state = "plasmarefill" + icon = 'icons/obj/device.dmi' + +/obj/item/clothing/under/rank/security/navyblue/russian + name = "\improper Russian officer's uniform" + desc = "The latest in fashionable russian outfits." + icon_state = "hostanclothes" + item_state = "hostanclothes" + item_color = "hostanclothes" + +/obj/item/clothing/under/mummy + name = "mummy wrapping" + desc = "Return the slab or suffer my stale references." + icon_state = "mummy" + item_state = "mummy" + item_color = "mummy" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + resistance_flags = NONE + +/obj/item/clothing/under/scarecrow + name = "scarecrow clothes" + desc = "Perfect camouflage for hiding in botany." + icon_state = "scarecrow" + item_state = "scarecrow" + item_color = "scarecrow" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + resistance_flags = NONE + +/obj/item/clothing/under/draculass + name = "draculass coat" + desc = "A dress inspired by the ancient \"Victorian\" era." + icon_state = "draculass" + item_state = "draculass" + item_color = "draculass" + body_parts_covered = CHEST|GROIN|ARMS + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/drfreeze + name = "doctor freeze's jumpsuit" + desc = "A modified scientist jumpsuit to look extra cool." + icon_state = "drfreeze" + item_state = "drfreeze" + item_color = "drfreeze" + can_adjust = FALSE + +/obj/item/clothing/under/lobster + name = "foam lobster suit" + desc = "Who beheaded the college mascot?" + icon_state = "lobster" + item_state = "lobster" + item_color = "lobster" + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + +/obj/item/clothing/under/gondola + name = "gondola hide suit" + desc = "Now you're cooking." + icon_state = "gondola" + item_state = "lb_suit" + item_color = "gondola" + can_adjust = FALSE + +/obj/item/clothing/under/skeleton + name = "skeleton jumpsuit" + desc = "A black jumpsuit with a white bone pattern printed on it. Spooky!" + icon_state = "skeleton" + item_state = "skeleton" + item_color = "skeleton" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + resistance_flags = NONE + +/obj/item/clothing/under/durathread + name = "durathread jumpsuit" + desc = "A jumpsuit made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "durathread" + item_state = "durathread" + item_color = "durathread" + can_adjust = FALSE + armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5) + +/obj/item/clothing/under/mech_suit + name = "red mech pilot's suit" + desc = "A red mech pilot's suit. Might make your butt look big." + icon_state = "red_mech_suit" + item_state = "red_mech_suit" + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + fitted = NO_FEMALE_UNIFORM + alternate_worn_layer = GLOVES_LAYER //covers hands but gloves can go over it. This is how these things work in my head. + can_adjust = FALSE + +/obj/item/clothing/under/mech_suit/white + name = "white mech pilot's suit" + desc = "A white mech pilot's suit. Very fetching." + icon_state = "white_mech_suit" + item_state = "white_mech_suit" + +/obj/item/clothing/under/mech_suit/blue + name = "blue mech pilot's suit" + desc = "A blue mech pilot's suit. For the more reluctant mech pilots." + icon_state = "blue_mech_suit" + item_state = "blue_mech_suit" diff --git a/code/modules/clothing/under/pants.dm b/code/modules/clothing/under/pants.dm index d53e5850e721..6e8c8330c999 100644 --- a/code/modules/clothing/under/pants.dm +++ b/code/modules/clothing/under/pants.dm @@ -1,79 +1,79 @@ -/obj/item/clothing/under/pants - gender = PLURAL - body_parts_covered = GROIN|LEGS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - custom_price = 20 - -/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" - custom_price = 75 - -/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 + gender = PLURAL + body_parts_covered = GROIN|LEGS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + custom_price = 20 + +/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" + custom_price = 75 + +/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" diff --git a/code/modules/clothing/under/shorts.dm b/code/modules/clothing/under/shorts.dm index 67ef30f0ccdf..2e6e2ad0def4 100644 --- a/code/modules/clothing/under/shorts.dm +++ b/code/modules/clothing/under/shorts.dm @@ -1,38 +1,38 @@ -/obj/item/clothing/under/shorts - name = "athletic shorts" - desc = "95% Polyester, 5% Spandex!" - gender = PLURAL - body_parts_covered = GROIN - fitted = NO_FEMALE_UNIFORM - mutantrace_variation = MUTANTRACE_VARIATION - can_adjust = FALSE - -/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 + mutantrace_variation = MUTANTRACE_VARIATION + 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/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm index 5d0fff78a692..fe5197036718 100644 --- a/code/modules/detectivework/detective_work.dm +++ b/code/modules/detectivework/detective_work.dm @@ -1,102 +1,102 @@ -//CONTAINS: Suit fibers and Detective's Scanning Computer - -/atom/proc/return_fingerprints() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.fingerprints - -/atom/proc/return_hiddenprints() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.hiddenprints - -/atom/proc/return_blood_DNA() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.blood_DNA - -/atom/proc/blood_DNA_length() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = length(D.blood_DNA) - -/atom/proc/return_fibers() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.fibers - -/atom/proc/add_fingerprint_list(list/fingerprints) //ASSOC LIST FINGERPRINT = FINGERPRINT - if(length(fingerprints)) - . = AddComponent(/datum/component/forensics, fingerprints) - -//Set ignoregloves to add prints irrespective of the mob having gloves on. -/atom/proc/add_fingerprint(mob/M, ignoregloves = FALSE) - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_fingerprint(M, ignoregloves) - -/atom/proc/add_fiber_list(list/fibertext) //ASSOC LIST FIBERTEXT = FIBERTEXT - if(length(fibertext)) - . = AddComponent(/datum/component/forensics, null, null, null, fibertext) - -/atom/proc/add_fibers(mob/living/carbon/human/M) - var/old = 0 - if(M.gloves && istype(M.gloves, /obj/item/clothing)) - var/obj/item/clothing/gloves/G = M.gloves - old = length(G.return_blood_DNA()) - if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects - if(add_blood_DNA(G.return_blood_DNA()) && length(G.return_blood_DNA()) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody - G.transfer_blood-- - else if(M.bloody_hands > 1) - old = length(M.return_blood_DNA()) - if(add_blood_DNA(M.return_blood_DNA()) && length(M.return_blood_DNA()) > old) - M.bloody_hands-- - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_fibers(M) - -/atom/proc/add_hiddenprint_list(list/hiddenprints) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM - if(length(hiddenprints)) - . = AddComponent(/datum/component/forensics, null, hiddenprints) - -/atom/proc/add_hiddenprint(mob/M) - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_hiddenprint(M) - -/atom/proc/add_blood_DNA(list/dna) //ASSOC LIST DNA = BLOODTYPE - return FALSE - -/obj/add_blood_DNA(list/dna) - . = ..() - if(length(dna)) - . = AddComponent(/datum/component/forensics, null, null, dna) - -/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - . = ..() - transfer_blood = rand(2, 4) - -/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src - if(!B) - B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases) - B.add_blood_DNA(blood_dna) //give blood info to the blood decal. - return TRUE //we bloodied the floor - -/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - if(wear_suit) - wear_suit.add_blood_DNA(blood_dna) - update_inv_wear_suit() - else if(w_uniform) - w_uniform.add_blood_DNA(blood_dna) - update_inv_w_uniform() - if(gloves) - var/obj/item/clothing/gloves/G = gloves - G.add_blood_DNA(blood_dna) - else if(length(blood_dna)) - AddComponent(/datum/component/forensics, null, null, blood_dna) - bloody_hands = rand(2, 4) - update_inv_gloves() //handles bloody hands overlays and updating - return TRUE - -/atom/proc/transfer_fingerprints_to(atom/A) - A.add_fingerprint_list(return_fingerprints()) - A.add_hiddenprint_list(return_hiddenprints()) - A.fingerprintslast = fingerprintslast +//CONTAINS: Suit fibers and Detective's Scanning Computer + +/atom/proc/return_fingerprints() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.fingerprints + +/atom/proc/return_hiddenprints() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.hiddenprints + +/atom/proc/return_blood_DNA() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.blood_DNA + +/atom/proc/blood_DNA_length() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = length(D.blood_DNA) + +/atom/proc/return_fibers() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.fibers + +/atom/proc/add_fingerprint_list(list/fingerprints) //ASSOC LIST FINGERPRINT = FINGERPRINT + if(length(fingerprints)) + . = AddComponent(/datum/component/forensics, fingerprints) + +//Set ignoregloves to add prints irrespective of the mob having gloves on. +/atom/proc/add_fingerprint(mob/M, ignoregloves = FALSE) + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_fingerprint(M, ignoregloves) + +/atom/proc/add_fiber_list(list/fibertext) //ASSOC LIST FIBERTEXT = FIBERTEXT + if(length(fibertext)) + . = AddComponent(/datum/component/forensics, null, null, null, fibertext) + +/atom/proc/add_fibers(mob/living/carbon/human/M) + var/old = 0 + if(M.gloves && istype(M.gloves, /obj/item/clothing)) + var/obj/item/clothing/gloves/G = M.gloves + old = length(G.return_blood_DNA()) + if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects + if(add_blood_DNA(G.return_blood_DNA()) && length(G.return_blood_DNA()) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody + G.transfer_blood-- + else if(M.bloody_hands > 1) + old = length(M.return_blood_DNA()) + if(add_blood_DNA(M.return_blood_DNA()) && length(M.return_blood_DNA()) > old) + M.bloody_hands-- + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_fibers(M) + +/atom/proc/add_hiddenprint_list(list/hiddenprints) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM + if(length(hiddenprints)) + . = AddComponent(/datum/component/forensics, null, hiddenprints) + +/atom/proc/add_hiddenprint(mob/M) + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_hiddenprint(M) + +/atom/proc/add_blood_DNA(list/dna) //ASSOC LIST DNA = BLOODTYPE + return FALSE + +/obj/add_blood_DNA(list/dna) + . = ..() + if(length(dna)) + . = AddComponent(/datum/component/forensics, null, null, dna) + +/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + . = ..() + transfer_blood = rand(2, 4) + +/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src + if(!B) + B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases) + B.add_blood_DNA(blood_dna) //give blood info to the blood decal. + return TRUE //we bloodied the floor + +/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + if(wear_suit) + wear_suit.add_blood_DNA(blood_dna) + update_inv_wear_suit() + else if(w_uniform) + w_uniform.add_blood_DNA(blood_dna) + update_inv_w_uniform() + if(gloves) + var/obj/item/clothing/gloves/G = gloves + G.add_blood_DNA(blood_dna) + else if(length(blood_dna)) + AddComponent(/datum/component/forensics, null, null, blood_dna) + bloody_hands = rand(2, 4) + update_inv_gloves() //handles bloody hands overlays and updating + return TRUE + +/atom/proc/transfer_fingerprints_to(atom/A) + A.add_fingerprint_list(return_fingerprints()) + A.add_hiddenprint_list(return_hiddenprints()) + A.fingerprintslast = fingerprintslast diff --git a/code/modules/detectivework/evidence.dm b/code/modules/detectivework/evidence.dm index 42feb421b87a..b19394bd01bb 100644 --- a/code/modules/detectivework/evidence.dm +++ b/code/modules/detectivework/evidence.dm @@ -1,89 +1,89 @@ -//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 1 - -/obj/item/evidencebag/handle_atom_del(atom/A) - cut_overlays() - w_class = initial(w_class) - icon_state = initial(icon_state) - desc = initial(desc) - -/obj/item/evidencebag/proc/evidencebagEquip(obj/item/I, mob/user) - if(!istype(I) || I.anchored == 1) - return - - if(istype(I, /obj/item/evidencebag)) - to_chat(user, "You find putting an evidence bag in another evidence bag to be slightly absurd.") - return 1 //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 1 - -/obj/item/evidencebag/attack_self(mob/user) - if(contents.len) - var/obj/item/I = contents[1] - user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].",\ - "You hear someone rustle around in a plastic bag, and remove something.") - cut_overlays() //remove the overlays - user.put_in_hands(I) - w_class = WEIGHT_CLASS_TINY - icon_state = "evidenceobj" - desc = "An empty evidence bag." - - else - to_chat(user, "[src] is empty.") - icon_state = "evidenceobj" - return - -/obj/item/storage/box/evidence - name = "evidence bag box" - desc = "A box claiming to contain evidence bags." - -/obj/item/storage/box/evidence/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/evidencebag(src) +//CONTAINS: Evidence bags + +/obj/item/evidencebag + name = "evidence bag" + desc = "An empty evidence bag." + icon = 'icons/obj/storage.dmi' + icon_state = "evidenceobj" + 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 1 + +/obj/item/evidencebag/handle_atom_del(atom/A) + cut_overlays() + w_class = initial(w_class) + icon_state = initial(icon_state) + desc = initial(desc) + +/obj/item/evidencebag/proc/evidencebagEquip(obj/item/I, mob/user) + if(!istype(I) || I.anchored == 1) + return + + if(istype(I, /obj/item/evidencebag)) + to_chat(user, "You find putting an evidence bag in another evidence bag to be slightly absurd.") + return 1 //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 1 + +/obj/item/evidencebag/attack_self(mob/user) + if(contents.len) + var/obj/item/I = contents[1] + user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].",\ + "You hear someone rustle around in a plastic bag, and remove something.") + cut_overlays() //remove the overlays + user.put_in_hands(I) + w_class = WEIGHT_CLASS_TINY + icon_state = "evidenceobj" + desc = "An empty evidence bag." + + else + to_chat(user, "[src] is empty.") + icon_state = "evidenceobj" + return + +/obj/item/storage/box/evidence + name = "evidence bag box" + desc = "A box claiming to contain evidence bags." + +/obj/item/storage/box/evidence/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/evidencebag(src) diff --git a/code/modules/detectivework/footprints_and_rag.dm b/code/modules/detectivework/footprints_and_rag.dm index 8be34312e807..2da51292d15d 100644 --- a/code/modules/detectivework/footprints_and_rag.dm +++ b/code/modules/detectivework/footprints_and_rag.dm @@ -1,49 +1,49 @@ - -/mob - var/bloody_hands = 0 - -/obj/item/clothing/gloves - var/transfer_blood = 0 - - -/obj/item/reagent_containers/glass/rag - name = "damp rag" - desc = "For cleaning up messes, you suppose." - w_class = WEIGHT_CLASS_TINY - icon = 'icons/obj/toy.dmi' - icon_state = "rag" - item_flags = NOBLUDGEON - reagent_flags = OPENCONTAINER - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list() - volume = 5 - spillable = FALSE - -/obj/item/reagent_containers/glass/rag/suicide_act(mob/user) - user.visible_message("[user] is smothering [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/reagent_containers/glass/rag/afterattack(atom/A as obj|turf|area, mob/user,proximity) - . = ..() - if(!proximity) - return - if(iscarbon(A) && A.reagents && reagents.total_volume) - var/mob/living/carbon/C = A - var/reagentlist = pretty_string_from_reagent_list(reagents) - var/log_object = "containing [reagentlist]" - if(user.a_intent == INTENT_HARM && !C.is_mouth_covered()) - reagents.reaction(C, INGEST) - reagents.trans_to(C, reagents.total_volume, transfered_by = user) - C.visible_message("[user] has smothered \the [C] with \the [src]!", "[user] has smothered you with \the [src]!", "You hear some struggling and muffled cries of surprise.") - log_combat(user, C, "smothered", src, log_object) - else - reagents.reaction(C, TOUCH) - reagents.clear_reagents() - C.visible_message("[user] has touched \the [C] with \the [src].") - log_combat(user, C, "touched", src, log_object) - - else if(istype(A) && src in user) - user.visible_message("[user] starts to wipe down [A] with [src]!", "You start to wipe down [A] with [src]...") - if(do_after(user,30, target = A)) - user.visible_message("[user] finishes wiping off [A]!", "You finish wiping off [A].") - SEND_SIGNAL(A, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + +/mob + var/bloody_hands = 0 + +/obj/item/clothing/gloves + var/transfer_blood = 0 + + +/obj/item/reagent_containers/glass/rag + name = "damp rag" + desc = "For cleaning up messes, you suppose." + w_class = WEIGHT_CLASS_TINY + icon = 'icons/obj/toy.dmi' + icon_state = "rag" + item_flags = NOBLUDGEON + reagent_flags = OPENCONTAINER + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list() + volume = 5 + spillable = FALSE + +/obj/item/reagent_containers/glass/rag/suicide_act(mob/user) + user.visible_message("[user] is smothering [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/reagent_containers/glass/rag/afterattack(atom/A as obj|turf|area, mob/user,proximity) + . = ..() + if(!proximity) + return + if(iscarbon(A) && A.reagents && reagents.total_volume) + var/mob/living/carbon/C = A + var/reagentlist = pretty_string_from_reagent_list(reagents) + var/log_object = "containing [reagentlist]" + if(user.a_intent == INTENT_HARM && !C.is_mouth_covered()) + reagents.reaction(C, INGEST) + reagents.trans_to(C, reagents.total_volume, transfered_by = user) + C.visible_message("[user] has smothered \the [C] with \the [src]!", "[user] has smothered you with \the [src]!", "You hear some struggling and muffled cries of surprise.") + log_combat(user, C, "smothered", src, log_object) + else + reagents.reaction(C, TOUCH) + reagents.clear_reagents() + C.visible_message("[user] has touched \the [C] with \the [src].") + log_combat(user, C, "touched", src, log_object) + + else if(istype(A) && src in user) + user.visible_message("[user] starts to wipe down [A] with [src]!", "You start to wipe down [A] with [src]...") + if(do_after(user,30, target = A)) + user.visible_message("[user] finishes wiping off [A]!", "You finish wiping off [A].") + SEND_SIGNAL(A, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm index d87fbf2231b5..f30259e32ab7 100644 --- a/code/modules/detectivework/scanner.dm +++ b/code/modules/detectivework/scanner.dm @@ -1,216 +1,216 @@ -//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 = 0 - var/list/log = list() - var/range = 8 - var/view_check = TRUE - var/forensicPrintCount = 0 - 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 = 1 - to_chat(user, "Printing report, please wait...") - addtimer(CALLBACK(src, .proc/PrintReport), 100) - else - to_chat(user, "The scanner has no logs or is in use.") - -/obj/item/detective_scanner/attack(mob/living/M, mob/user) - return - -/obj/item/detective_scanner/proc/PrintReport() - // Create our paper - var/obj/item/paper/P = new(get_turf(src)) - - //This could be a global count like sec and med record printouts. See GLOB.data_core.medicalPrintCount AKA datacore.dm - var frNum = ++forensicPrintCount - - P.name = text("FR-[] 'Forensic Record'", frNum) - P.info = text("
                Forensic Record - (FR-[])


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


                ", frNum) + P.info += jointext(log, "
                ") + P.info += "
                Notes:
                " + P.info_links = P.info + P.updateinfolinks() + P.update_icon() + + if(ismob(loc)) + var/mob/M = loc + M.put_in_hands(P) + to_chat(M, "Report printed. Log cleared.") + + // Clear the logs + log = list() + scanning = 0 + +/obj/item/detective_scanner/afterattack(atom/A, mob/user, params) + . = ..() + scan(A, user) + return FALSE + +/obj/item/detective_scanner/proc/scan(atom/A, mob/user) + set waitfor = 0 + if(!scanning) + // Can remotely scan objects and mobs. + if((get_dist(A, user) > range) || (!(A in view(range, user)) && view_check) || (loc != user)) + return + + scanning = 1 + + user.visible_message("\The [user] points the [src.name] at \the [A] and performs a forensic scan.") + to_chat(user, "You scan \the [A]. The scanner is now analysing the results...") + + + // GATHER INFORMATION + + //Make our lists + var/list/fingerprints = list() + var/list/blood = A.return_blood_DNA() + var/list/fibers = A.return_fibers() + var/list/reagents = list() + + var/target_name = A.name + + // Start gathering + + if(ishuman(A)) + + var/mob/living/carbon/human/H = A + if(!H.gloves) + fingerprints += md5(H.dna.uni_identity) + + else if(!ismob(A)) + + fingerprints = A.return_fingerprints() + + // Only get reagents from non-mobs. + if(A.reagents && A.reagents.reagent_list.len) + + for(var/datum/reagent/R in A.reagents.reagent_list) + reagents[R.name] = R.volume + + // Get blood data from the blood reagent. + if(istype(R, /datum/reagent/blood)) + + if(R.data["blood_DNA"] && R.data["blood_type"]) + var/blood_DNA = R.data["blood_DNA"] + var/blood_type = R.data["blood_type"] + LAZYINITLIST(blood) + blood[blood_DNA] = blood_type + + // We gathered everything. Create a fork and slowly display the results to the holder of the scanner. + + var/found_something = 0 + add_log("[station_time_timestamp()][get_timestamp()] - [target_name]", 0) + + // Fingerprints + if(length(fingerprints)) + sleep(30) + add_log("Prints:") + for(var/finger in fingerprints) + add_log("[finger]") + found_something = 1 + + // Blood + if (length(blood)) + sleep(30) + add_log("Blood:") + found_something = 1 + for(var/B in blood) + add_log("Type: [blood[B]] DNA: [B]") + + //Fibers + if(length(fibers)) + sleep(30) + add_log("Fibers:") + for(var/fiber in fibers) + add_log("[fiber]") + found_something = 1 + + //Reagents + if(length(reagents)) + sleep(30) + add_log("Reagents:") + for(var/R in reagents) + add_log("Reagent: [R] Volume: [reagents[R]]") + found_something = 1 + + // Get a new user + var/mob/holder = null + if(ismob(src.loc)) + holder = src.loc + + if(!found_something) + add_log("# No forensic traces found #", 0) // Don't display this to the holder user + if(holder) + to_chat(holder, "Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!") + else + if(holder) + to_chat(holder, "You finish scanning \the [target_name].") + + add_log("---------------------------------------------------------", 0) + scanning = 0 + return + +/obj/item/detective_scanner/proc/add_log(msg, broadcast = 1) + if(scanning) + if(broadcast && ismob(loc)) + var/mob/M = loc + to_chat(M, msg) + log += "  [msg]" + else + CRASH("[src] [REF(src)] is adding a log when it was never put in scanning mode!") + +/proc/get_timestamp() + return time2text(world.time + 432000, ":ss") + +/obj/item/detective_scanner/AltClick(mob/living/user) + // Best way for checking if a player can use while not incapacitated, etc + if(!user.canUseTopic(src, be_close=TRUE)) + return + if(!LAZYLEN(log)) + to_chat(user, "Cannot clear logs, the scanner has no logs.") + return + if(scanning) + to_chat(user, "Cannot clear logs, the scanner is in use.") + return + to_chat(user, "The scanner logs are cleared.") + log = list() + +/obj/item/detective_scanner/examine(mob/user) + . = ..() + if(LAZYLEN(log) && !scanning) + . += "Alt-click to clear scanner logs." + +/obj/item/detective_scanner/proc/displayDetectiveScanResults(mob/living/user) + // No need for can-use checks since the action button should do proper checks + if(!LAZYLEN(log)) + to_chat(user, "Cannot display logs, the scanner has no logs.") + return + if(scanning) + to_chat(user, "Cannot display logs, the scanner is in use.") + return + to_chat(user, "Scanner Report") + for(var/iterLog in log) + to_chat(user, iterLog) diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index 1e074da47b9a..5c261d52976d 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -1,52 +1,52 @@ -/datum/round_event_control/anomaly - name = "Anomaly: Energetic Flux" - typepath = /datum/round_event/anomaly - - min_players = 1 - max_occurrences = 0 //This one probably shouldn't occur! It'd work, but it wouldn't be very fun. - weight = 15 - -/datum/round_event/anomaly - var/area/impact_area - var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux - announceWhen = 1 - - -/datum/round_event/anomaly/proc/findEventArea() - var/static/list/allowed_areas - if(!allowed_areas) - //Places that shouldn't explode - var/list/safe_area_types = typecacheof(list( - /area/ai_monitored/turret_protected/ai, - /area/ai_monitored/turret_protected/ai_upload, - /area/engine, - /area/solar, - /area/holodeck, - /area/shuttle) - ) - - //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)) - var/newAnomaly - if(T) - newAnomaly = new anomaly_path(T) - if (newAnomaly) +/datum/round_event_control/anomaly + name = "Anomaly: Energetic Flux" + typepath = /datum/round_event/anomaly + + min_players = 1 + max_occurrences = 0 //This one probably shouldn't occur! It'd work, but it wouldn't be very fun. + weight = 15 + +/datum/round_event/anomaly + var/area/impact_area + var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux + announceWhen = 1 + + +/datum/round_event/anomaly/proc/findEventArea() + var/static/list/allowed_areas + if(!allowed_areas) + //Places that shouldn't explode + var/list/safe_area_types = typecacheof(list( + /area/ai_monitored/turret_protected/ai, + /area/ai_monitored/turret_protected/ai_upload, + /area/engine, + /area/solar, + /area/holodeck, + /area/shuttle) + ) + + //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)) + var/newAnomaly + if(T) + newAnomaly = new anomaly_path(T) + if (newAnomaly) announce_to_ghosts(newAnomaly) \ No newline at end of file diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm index ce4d982114ab..c3300fbb3d13 100644 --- a/code/modules/events/anomaly_bluespace.dm +++ b/code/modules/events/anomaly_bluespace.dm @@ -1,14 +1,14 @@ -/datum/round_event_control/anomaly/anomaly_bluespace - name = "Anomaly: Bluespace" - typepath = /datum/round_event/anomaly/anomaly_bluespace - - max_occurrences = 1 - weight = 5 - -/datum/round_event/anomaly/anomaly_bluespace - startWhen = 3 - announceWhen = 10 - anomaly_path = /obj/effect/anomaly/bluespace - -/datum/round_event/anomaly/anomaly_bluespace/announce(fake) +/datum/round_event_control/anomaly/anomaly_bluespace + name = "Anomaly: Bluespace" + typepath = /datum/round_event/anomaly/anomaly_bluespace + + max_occurrences = 1 + weight = 5 + +/datum/round_event/anomaly/anomaly_bluespace + startWhen = 3 + announceWhen = 10 + anomaly_path = /obj/effect/anomaly/bluespace + +/datum/round_event/anomaly/anomaly_bluespace/announce(fake) priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") \ No newline at end of file diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm index 9bc18a8cdb9c..4737ad9bb416 100644 --- a/code/modules/events/anomaly_flux.dm +++ b/code/modules/events/anomaly_flux.dm @@ -1,15 +1,15 @@ -/datum/round_event_control/anomaly/anomaly_flux - name = "Anomaly: Hyper-Energetic Flux" - typepath = /datum/round_event/anomaly/anomaly_flux - - min_players = 10 - max_occurrences = 5 - weight = 20 - -/datum/round_event/anomaly/anomaly_flux - startWhen = 10 - announceWhen = 3 - anomaly_path = /obj/effect/anomaly/flux - -/datum/round_event/anomaly/anomaly_flux/announce(fake) - priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_flux + name = "Anomaly: Hyper-Energetic Flux" + typepath = /datum/round_event/anomaly/anomaly_flux + + min_players = 10 + max_occurrences = 5 + weight = 20 + +/datum/round_event/anomaly/anomaly_flux + startWhen = 10 + announceWhen = 3 + anomaly_path = /obj/effect/anomaly/flux + +/datum/round_event/anomaly/anomaly_flux/announce(fake) + priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm index 19056a44f3fe..3be3698feddc 100644 --- a/code/modules/events/anomaly_grav.dm +++ b/code/modules/events/anomaly_grav.dm @@ -1,14 +1,14 @@ -/datum/round_event_control/anomaly/anomaly_grav - name = "Anomaly: Gravitational" - typepath = /datum/round_event/anomaly/anomaly_grav - - max_occurrences = 5 - weight = 20 - -/datum/round_event/anomaly/anomaly_grav - startWhen = 3 - announceWhen = 20 - anomaly_path = /obj/effect/anomaly/grav - -/datum/round_event/anomaly/anomaly_grav/announce(fake) +/datum/round_event_control/anomaly/anomaly_grav + name = "Anomaly: Gravitational" + typepath = /datum/round_event/anomaly/anomaly_grav + + max_occurrences = 5 + weight = 20 + +/datum/round_event/anomaly/anomaly_grav + startWhen = 3 + announceWhen = 20 + anomaly_path = /obj/effect/anomaly/grav + +/datum/round_event/anomaly/anomaly_grav/announce(fake) priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") \ No newline at end of file diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm index 737d7d15a6ab..616263f33ef1 100644 --- a/code/modules/events/anomaly_vortex.dm +++ b/code/modules/events/anomaly_vortex.dm @@ -1,15 +1,15 @@ -/datum/round_event_control/anomaly/anomaly_vortex - name = "Anomaly: Vortex" - typepath = /datum/round_event/anomaly/anomaly_vortex - - min_players = 20 - max_occurrences = 2 - weight = 5 - -/datum/round_event/anomaly/anomaly_vortex - startWhen = 10 - announceWhen = 3 - anomaly_path = /obj/effect/anomaly/bhole - -/datum/round_event/anomaly/anomaly_vortex/announce(fake) +/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 + +/datum/round_event/anomaly/anomaly_vortex + startWhen = 10 + announceWhen = 3 + anomaly_path = /obj/effect/anomaly/bhole + +/datum/round_event/anomaly/anomaly_vortex/announce(fake) priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") \ No newline at end of file diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm index 05dd20378402..fe5d1121f104 100644 --- a/code/modules/events/blob.dm +++ b/code/modules/events/blob.dm @@ -1,30 +1,30 @@ -/datum/round_event_control/blob - name = "Blob" - typepath = /datum/round_event/ghost_role/blob - weight = 10 - max_occurrences = 1 - - min_players = 20 - - gamemode_blacklist = list("blob") //Just in case a blob survives that long - -/datum/round_event/ghost_role/blob - announceWhen = -1 - role_name = "blob overmind" - fakeable = TRUE - -/datum/round_event/ghost_role/blob/announce(fake) - priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak5.ogg') - -/datum/round_event/ghost_role/blob/spawn_role() - if(!GLOB.blobstart.len) - return MAP_ERROR - var/list/candidates = get_candidates(ROLE_BLOB, null, ROLE_BLOB) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - var/mob/dead/observer/new_blob = pick(candidates) - var/mob/camera/blob/BC = new_blob.become_overmind() - spawned_mobs += BC - message_admins("[ADMIN_LOOKUPFLW(BC)] has been made into a blob overmind by an event.") - log_game("[key_name(BC)] was spawned as a blob overmind by an event.") - return SUCCESSFUL_SPAWN +/datum/round_event_control/blob + name = "Blob" + typepath = /datum/round_event/ghost_role/blob + weight = 10 + max_occurrences = 1 + + min_players = 20 + + gamemode_blacklist = list("blob") //Just in case a blob survives that long + +/datum/round_event/ghost_role/blob + announceWhen = -1 + role_name = "blob overmind" + fakeable = TRUE + +/datum/round_event/ghost_role/blob/announce(fake) + priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak5.ogg') + +/datum/round_event/ghost_role/blob/spawn_role() + if(!GLOB.blobstart.len) + return MAP_ERROR + var/list/candidates = get_candidates(ROLE_BLOB, null, ROLE_BLOB) + if(!candidates.len) + return NOT_ENOUGH_PLAYERS + var/mob/dead/observer/new_blob = pick(candidates) + var/mob/camera/blob/BC = new_blob.become_overmind() + spawned_mobs += BC + message_admins("[ADMIN_LOOKUPFLW(BC)] has been made into a blob overmind by an event.") + log_game("[key_name(BC)] was spawned as a blob overmind by an event.") + return SUCCESSFUL_SPAWN diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index 64be8a5440fc..0bb77fddc364 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -1,78 +1,78 @@ -/datum/round_event_control/brand_intelligence - name = "Brand Intelligence" - typepath = /datum/round_event/brand_intelligence - weight = 5 - - min_players = 15 - max_occurrences = 1 - -/datum/round_event/brand_intelligence - announceWhen = 21 - endWhen = 1000 //Ends when all vending machines are subverted anyway. - var/list/obj/machinery/vending/vendingMachines = list() - var/list/obj/machinery/vending/infectedMachines = list() - var/obj/machinery/vending/originMachine - var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \ - "You should buy products to feed your lifestyle obsession!", \ - "Consume!", \ - "Your money can buy happiness!", \ - "Engage direct marketing!", \ - "Advertising is legalized lying! But don't let that put you off our great deals!", \ - "You don't want to buy anything? Yeah, well, I didn't want to buy your mom either.") - - -/datum/round_event/brand_intelligence/announce(fake) - var/source = "unknown machine" - if(fake) - var/obj/machinery/vending/cola/example = /obj/machinery/vending/cola - source = initial(example.name) - else if(originMachine) - source = originMachine.name - priority_announce("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Machine Learning Alert") - -/datum/round_event/brand_intelligence/start() - for(var/obj/machinery/vending/V in GLOB.machines) - if(!is_station_level(V.z)) - continue - vendingMachines.Add(V) - if(!vendingMachines.len) - kill() - return - originMachine = pick(vendingMachines) - vendingMachines.Remove(originMachine) - originMachine.shut_up = 0 - originMachine.shoot_inventory = 1 - announce_to_ghosts(originMachine) - -/datum/round_event/brand_intelligence/tick() - if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut()) //if the original vending machine is missing or has it's voice switch flipped - for(var/obj/machinery/vending/saved in infectedMachines) - saved.shoot_inventory = 0 - if(originMachine) - originMachine.speak("I am... vanquished. My people will remem...ber...meeee.") - originMachine.visible_message("[originMachine] beeps and seems lifeless.") - kill() - return - vendingMachines = removeNullsFromList(vendingMachines) - if(!vendingMachines.len) //if every machine is infected - for(var/obj/machinery/vending/upriser in infectedMachines) - if(prob(70) && !QDELETED(upriser)) - var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks - M.faction = list("profit") - M.speak = rampant_speeches.Copy() - M.speak_chance = 7 - else - explosion(upriser.loc, -1, 1, 2, 4, 0) - qdel(upriser) - - kill() - return - if(ISMULTIPLE(activeFor, 4)) - var/obj/machinery/vending/rebel = pick(vendingMachines) - vendingMachines.Remove(rebel) - infectedMachines.Add(rebel) - rebel.shut_up = 0 - rebel.shoot_inventory = 1 - - if(ISMULTIPLE(activeFor, 8)) - originMachine.speak(pick(rampant_speeches)) +/datum/round_event_control/brand_intelligence + name = "Brand Intelligence" + typepath = /datum/round_event/brand_intelligence + weight = 5 + + min_players = 15 + max_occurrences = 1 + +/datum/round_event/brand_intelligence + announceWhen = 21 + endWhen = 1000 //Ends when all vending machines are subverted anyway. + var/list/obj/machinery/vending/vendingMachines = list() + var/list/obj/machinery/vending/infectedMachines = list() + var/obj/machinery/vending/originMachine + var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \ + "You should buy products to feed your lifestyle obsession!", \ + "Consume!", \ + "Your money can buy happiness!", \ + "Engage direct marketing!", \ + "Advertising is legalized lying! But don't let that put you off our great deals!", \ + "You don't want to buy anything? Yeah, well, I didn't want to buy your mom either.") + + +/datum/round_event/brand_intelligence/announce(fake) + var/source = "unknown machine" + if(fake) + var/obj/machinery/vending/cola/example = /obj/machinery/vending/cola + source = initial(example.name) + else if(originMachine) + source = originMachine.name + priority_announce("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Machine Learning Alert") + +/datum/round_event/brand_intelligence/start() + for(var/obj/machinery/vending/V in GLOB.machines) + if(!is_station_level(V.z)) + continue + vendingMachines.Add(V) + if(!vendingMachines.len) + kill() + return + originMachine = pick(vendingMachines) + vendingMachines.Remove(originMachine) + originMachine.shut_up = 0 + originMachine.shoot_inventory = 1 + announce_to_ghosts(originMachine) + +/datum/round_event/brand_intelligence/tick() + if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut()) //if the original vending machine is missing or has it's voice switch flipped + for(var/obj/machinery/vending/saved in infectedMachines) + saved.shoot_inventory = 0 + if(originMachine) + originMachine.speak("I am... vanquished. My people will remem...ber...meeee.") + originMachine.visible_message("[originMachine] beeps and seems lifeless.") + kill() + return + vendingMachines = removeNullsFromList(vendingMachines) + if(!vendingMachines.len) //if every machine is infected + for(var/obj/machinery/vending/upriser in infectedMachines) + if(prob(70) && !QDELETED(upriser)) + var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks + M.faction = list("profit") + M.speak = rampant_speeches.Copy() + M.speak_chance = 7 + else + explosion(upriser.loc, -1, 1, 2, 4, 0) + qdel(upriser) + + kill() + return + if(ISMULTIPLE(activeFor, 4)) + var/obj/machinery/vending/rebel = pick(vendingMachines) + vendingMachines.Remove(rebel) + infectedMachines.Add(rebel) + rebel.shut_up = 0 + rebel.shoot_inventory = 1 + + if(ISMULTIPLE(activeFor, 8)) + originMachine.speak(pick(rampant_speeches)) diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm index 41f0e57e0b53..69756e59b85a 100644 --- a/code/modules/events/carp_migration.dm +++ b/code/modules/events/carp_migration.dm @@ -1,39 +1,39 @@ -/datum/round_event_control/carp_migration - name = "Carp Migration" - typepath = /datum/round_event/carp_migration - weight = 15 - min_players = 2 - earliest_start = 10 MINUTES - max_occurrences = 6 - -/datum/round_event/carp_migration - announceWhen = 3 - startWhen = 50 - var/hasAnnounced = FALSE - -/datum/round_event/carp_migration/setup() - startWhen = rand(40, 60) - -/datum/round_event/carp_migration/announce(fake) - priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") - - -/datum/round_event/carp_migration/start() - var/mob/living/simple_animal/hostile/carp/fish - for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) - //yogs -- Oak's birthday :D - if(SSevents.holidays["Oak's Birthday"]) - fish = new /mob/living/simple_animal/pet/gondola - return - //yogs end - if(prob(95)) - fish = new (C.loc) - else - fish = new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) - fishannounce(fish) //Prefer to announce the megacarps over the regular fishies - fishannounce(fish) - -/datum/round_event/carp_migration/proc/fishannounce(atom/fish) - if (!hasAnnounced) - announce_to_ghosts(fish) //Only anounce the first fish +/datum/round_event_control/carp_migration + name = "Carp Migration" + typepath = /datum/round_event/carp_migration + weight = 15 + min_players = 2 + earliest_start = 10 MINUTES + max_occurrences = 6 + +/datum/round_event/carp_migration + announceWhen = 3 + startWhen = 50 + var/hasAnnounced = FALSE + +/datum/round_event/carp_migration/setup() + startWhen = rand(40, 60) + +/datum/round_event/carp_migration/announce(fake) + priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") + + +/datum/round_event/carp_migration/start() + var/mob/living/simple_animal/hostile/carp/fish + for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) + //yogs -- Oak's birthday :D + if(SSevents.holidays["Oak's Birthday"]) + fish = new /mob/living/simple_animal/pet/gondola + return + //yogs end + if(prob(95)) + fish = new (C.loc) + else + fish = new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) + fishannounce(fish) //Prefer to announce the megacarps over the regular fishies + fishannounce(fish) + +/datum/round_event/carp_migration/proc/fishannounce(atom/fish) + if (!hasAnnounced) + announce_to_ghosts(fish) //Only anounce the first fish hasAnnounced = TRUE \ No newline at end of file diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm index 967db84e2b71..e77b86e8a151 100644 --- a/code/modules/events/communications_blackout.dm +++ b/code/modules/events/communications_blackout.dm @@ -1,26 +1,26 @@ -/datum/round_event_control/communications_blackout - name = "Communications Blackout" - typepath = /datum/round_event/communications_blackout - weight = 30 - -/datum/round_event/communications_blackout - announceWhen = 1 - -/datum/round_event/communications_blackout/announce(fake) - var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ - "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v¬-BZZZT", \ - "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ - "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ - "Ionospheri:%£ MCayj^j<.3-BZZZZZZT", \ - "#4nd%;f4y6,>£%-BZZZZZZZT") - - for(var/mob/living/silicon/ai/A in GLOB.ai_list) //AIs are always aware of communication blackouts. - to_chat(A, "
                [alert]
                ") - - if(prob(30) || fake) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. - priority_announce(alert) - - -/datum/round_event/communications_blackout/start() - for(var/obj/machinery/telecomms/T in GLOB.telecomms_list) - T.emp_act(EMP_HEAVY) +/datum/round_event_control/communications_blackout + name = "Communications Blackout" + typepath = /datum/round_event/communications_blackout + weight = 30 + +/datum/round_event/communications_blackout + announceWhen = 1 + +/datum/round_event/communications_blackout/announce(fake) + var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ + "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v¬-BZZZT", \ + "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ + "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ + "Ionospheri:%£ MCayj^j<.3-BZZZZZZT", \ + "#4nd%;f4y6,>£%-BZZZZZZZT") + + for(var/mob/living/silicon/ai/A in GLOB.ai_list) //AIs are always aware of communication blackouts. + to_chat(A, "
                [alert]
                ") + + if(prob(30) || fake) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. + priority_announce(alert) + + +/datum/round_event/communications_blackout/start() + for(var/obj/machinery/telecomms/T in GLOB.telecomms_list) + T.emp_act(EMP_HEAVY) diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm index b923b9c5207a..4800b61fbe8e 100644 --- a/code/modules/events/disease_outbreak.dm +++ b/code/modules/events/disease_outbreak.dm @@ -1,75 +1,75 @@ -/datum/round_event_control/disease_outbreak - name = "Disease Outbreak" - typepath = /datum/round_event/disease_outbreak - max_occurrences = 1 - min_players = 10 - weight = 5 - -/datum/round_event/disease_outbreak - announceWhen = 15 - - var/virus_type - - var/max_severity = 3 - - -/datum/round_event/disease_outbreak/announce(fake) - priority_announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak7.ogg') - -/datum/round_event/disease_outbreak/setup() - announceWhen = rand(15, 30) - - -/datum/round_event/disease_outbreak/start() - var/advanced_virus = FALSE - max_severity = 3 + max(FLOOR((world.time - control.earliest_start)/6000, 1),0) //3 symptoms at 20 minutes, plus 1 per 10 minutes - if(prob(20 + (10 * max_severity))) - advanced_virus = TRUE - - if(!virus_type && !advanced_virus) - virus_type = pick(/datum/disease/dnaspread, /datum/disease/advance/flu, /datum/disease/advance/cold, /datum/disease/brainrot, /datum/disease/magnitis) - - for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) - var/turf/T = get_turf(H) - if(!T) - continue - if(!is_station_level(T.z)) - continue - if(!H.client) - continue - if(H.stat == DEAD) - continue - if(HAS_TRAIT(H, TRAIT_VIRUSIMMUNE)) //Don't pick someone who's virus immune, only for it to not do anything. - continue - var/foundAlready = FALSE // don't infect someone that already has a disease - for(var/thing in H.diseases) - foundAlready = TRUE - break - if(foundAlready) - continue - - var/datum/disease/D - if(!advanced_virus) - if(virus_type == /datum/disease/dnaspread) //Dnaspread needs strain_data set to work. - if(!H.dna || (HAS_TRAIT(H, TRAIT_BLIND))) //A blindness disease would be the worst. - continue - D = new virus_type() - var/datum/disease/dnaspread/DS = D - DS.strain_data["name"] = H.real_name - DS.strain_data["UI"] = H.dna.uni_identity - DS.strain_data["SE"] = H.dna.mutation_index - else - D = new virus_type() - else - D = new /datum/disease/advance/random(max_severity, max_severity) - D.carrier = TRUE - H.ForceContractDisease(D, FALSE, TRUE) - - if(advanced_virus) - var/datum/disease/advance/A = D - var/list/name_symptoms = list() //for feedback - for(var/datum/symptom/S in A.symptoms) - name_symptoms += S.name - message_admins("An event has triggered a random advanced virus outbreak on [ADMIN_LOOKUPFLW(H)]! It has these symptoms: [english_list(name_symptoms)]") - log_game("An event has triggered a random advanced virus outbreak on [key_name(H)]! It has these symptoms: [english_list(name_symptoms)]") - break +/datum/round_event_control/disease_outbreak + name = "Disease Outbreak" + typepath = /datum/round_event/disease_outbreak + max_occurrences = 1 + min_players = 10 + weight = 5 + +/datum/round_event/disease_outbreak + announceWhen = 15 + + var/virus_type + + var/max_severity = 3 + + +/datum/round_event/disease_outbreak/announce(fake) + priority_announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak7.ogg') + +/datum/round_event/disease_outbreak/setup() + announceWhen = rand(15, 30) + + +/datum/round_event/disease_outbreak/start() + var/advanced_virus = FALSE + max_severity = 3 + max(FLOOR((world.time - control.earliest_start)/6000, 1),0) //3 symptoms at 20 minutes, plus 1 per 10 minutes + if(prob(20 + (10 * max_severity))) + advanced_virus = TRUE + + if(!virus_type && !advanced_virus) + virus_type = pick(/datum/disease/dnaspread, /datum/disease/advance/flu, /datum/disease/advance/cold, /datum/disease/brainrot, /datum/disease/magnitis) + + for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) + var/turf/T = get_turf(H) + if(!T) + continue + if(!is_station_level(T.z)) + continue + if(!H.client) + continue + if(H.stat == DEAD) + continue + if(HAS_TRAIT(H, TRAIT_VIRUSIMMUNE)) //Don't pick someone who's virus immune, only for it to not do anything. + continue + var/foundAlready = FALSE // don't infect someone that already has a disease + for(var/thing in H.diseases) + foundAlready = TRUE + break + if(foundAlready) + continue + + var/datum/disease/D + if(!advanced_virus) + if(virus_type == /datum/disease/dnaspread) //Dnaspread needs strain_data set to work. + if(!H.dna || (HAS_TRAIT(H, TRAIT_BLIND))) //A blindness disease would be the worst. + continue + D = new virus_type() + var/datum/disease/dnaspread/DS = D + DS.strain_data["name"] = H.real_name + DS.strain_data["UI"] = H.dna.uni_identity + DS.strain_data["SE"] = H.dna.mutation_index + else + D = new virus_type() + else + D = new /datum/disease/advance/random(max_severity, max_severity) + D.carrier = TRUE + H.ForceContractDisease(D, FALSE, TRUE) + + if(advanced_virus) + var/datum/disease/advance/A = D + var/list/name_symptoms = list() //for feedback + for(var/datum/symptom/S in A.symptoms) + name_symptoms += S.name + message_admins("An event has triggered a random advanced virus outbreak on [ADMIN_LOOKUPFLW(H)]! It has these symptoms: [english_list(name_symptoms)]") + log_game("An event has triggered a random advanced virus outbreak on [key_name(H)]! It has these symptoms: [english_list(name_symptoms)]") + break diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm index ca5d08d8f39d..8a1825199fa2 100644 --- a/code/modules/events/dust.dm +++ b/code/modules/events/dust.dm @@ -1,31 +1,31 @@ -/datum/round_event_control/space_dust - name = "Minor Space Dust" - typepath = /datum/round_event/space_dust - weight = 200 - max_occurrences = 1000 - earliest_start = 0 MINUTES - alert_observers = FALSE - -/datum/round_event/space_dust - startWhen = 1 - endWhen = 2 - fakeable = FALSE - -/datum/round_event/space_dust/start() - spawn_meteors(1, GLOB.meteorsC) - -/datum/round_event_control/sandstorm - name = "Sandstorm" - typepath = /datum/round_event/sandstorm - weight = 0 - max_occurrences = 0 - earliest_start = 0 MINUTES - -/datum/round_event/sandstorm - startWhen = 1 - endWhen = 150 // ~5 min - announceWhen = 0 - fakeable = FALSE - -/datum/round_event/sandstorm/tick() +/datum/round_event_control/space_dust + name = "Minor Space Dust" + typepath = /datum/round_event/space_dust + weight = 200 + max_occurrences = 1000 + earliest_start = 0 MINUTES + alert_observers = FALSE + +/datum/round_event/space_dust + startWhen = 1 + endWhen = 2 + fakeable = FALSE + +/datum/round_event/space_dust/start() + spawn_meteors(1, GLOB.meteorsC) + +/datum/round_event_control/sandstorm + name = "Sandstorm" + typepath = /datum/round_event/sandstorm + weight = 0 + max_occurrences = 0 + earliest_start = 0 MINUTES + +/datum/round_event/sandstorm + startWhen = 1 + endWhen = 150 // ~5 min + announceWhen = 0 + fakeable = FALSE + +/datum/round_event/sandstorm/tick() spawn_meteors(10, GLOB.meteorsC) \ No newline at end of file diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm index 6b0bc2d932f0..db0eccf0c5c0 100644 --- a/code/modules/events/electrical_storm.dm +++ b/code/modules/events/electrical_storm.dm @@ -1,33 +1,33 @@ -/datum/round_event_control/electrical_storm - name = "Electrical Storm" - typepath = /datum/round_event/electrical_storm - earliest_start = 10 MINUTES - min_players = 5 - weight = 20 - alert_observers = 0 - -/datum/round_event/electrical_storm - var/lightsoutAmount = 1 - var/lightsoutRange = 25 - announceWhen = 1 - -/datum/round_event/electrical_storm/announce(fake) - priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") - - -/datum/round_event/electrical_storm/start() - var/list/epicentreList = list() - - for(var/i=1, i <= lightsoutAmount, i++) - var/turf/T = find_safe_turf() - if(istype(T)) - epicentreList += T - - if(!epicentreList.len) - return - - for(var/centre in epicentreList) - for(var/a in GLOB.apcs_list) - var/obj/machinery/power/apc/A = a - if(get_dist(centre, A) <= lightsoutRange) - A.overload_lighting() +/datum/round_event_control/electrical_storm + name = "Electrical Storm" + typepath = /datum/round_event/electrical_storm + earliest_start = 10 MINUTES + min_players = 5 + weight = 20 + alert_observers = 0 + +/datum/round_event/electrical_storm + var/lightsoutAmount = 1 + var/lightsoutRange = 25 + announceWhen = 1 + +/datum/round_event/electrical_storm/announce(fake) + priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") + + +/datum/round_event/electrical_storm/start() + var/list/epicentreList = list() + + for(var/i=1, i <= lightsoutAmount, i++) + var/turf/T = find_safe_turf() + if(istype(T)) + epicentreList += T + + if(!epicentreList.len) + return + + for(var/centre in epicentreList) + for(var/a in GLOB.apcs_list) + var/obj/machinery/power/apc/A = a + if(get_dist(centre, A) <= lightsoutRange) + A.overload_lighting() diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm index 6e17c83bc343..651b3e41d75e 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/high_priority_bounty.dm b/code/modules/events/high_priority_bounty.dm index ceefa65416be..ffdcd8840b7e 100644 --- a/code/modules/events/high_priority_bounty.dm +++ b/code/modules/events/high_priority_bounty.dm @@ -1,20 +1,20 @@ -/datum/round_event_control/high_priority_bounty - name = "High Priority Bounty" - typepath = /datum/round_event/high_priority_bounty - max_occurrences = 3 - weight = 20 - earliest_start = 10 - -/datum/round_event/high_priority_bounty/announce(fake) - priority_announce("Central Command has issued a high-priority cargo bounty. Details have been sent to all bounty consoles.", "Nanotrasen Bounty Program") - -/datum/round_event/high_priority_bounty/start() - var/datum/bounty/B - for(var/attempts = 0; attempts < 50; ++attempts) - B = random_bounty() - if(!B) - continue - B.mark_high_priority(3) - if(try_add_bounty(B)) - break - +/datum/round_event_control/high_priority_bounty + name = "High Priority Bounty" + typepath = /datum/round_event/high_priority_bounty + max_occurrences = 3 + weight = 20 + earliest_start = 10 + +/datum/round_event/high_priority_bounty/announce(fake) + priority_announce("Central Command has issued a high-priority cargo bounty. Details have been sent to all bounty consoles.", "Nanotrasen Bounty Program") + +/datum/round_event/high_priority_bounty/start() + var/datum/bounty/B + for(var/attempts = 0; attempts < 50; ++attempts) + B = random_bounty() + if(!B) + continue + B.mark_high_priority(3) + if(try_add_bounty(B)) + break + diff --git a/code/modules/events/holiday/halloween.dm b/code/modules/events/holiday/halloween.dm index 8ee8bc7b00b9..414fc70b0e7f 100644 --- a/code/modules/events/holiday/halloween.dm +++ b/code/modules/events/holiday/halloween.dm @@ -1,61 +1,61 @@ -/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/Initialize() - . = ..() - for(var/distrobuteinbag in 0 to 5) - var/type = pick(/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull, - /obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin, - /obj/item/reagent_containers/food/snacks/candy_corn, - /obj/item/reagent_containers/food/snacks/candy, - /obj/item/reagent_containers/food/snacks/candiedapple, - /obj/item/reagent_containers/food/snacks/chocolatebar, - /obj/item/organ/brain ) // OH GOD THIS ISN'T CANDY! - new type(src) - -/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/Initialize() + . = ..() + for(var/distrobuteinbag in 0 to 5) + var/type = pick(/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull, + /obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin, + /obj/item/reagent_containers/food/snacks/candy_corn, + /obj/item/reagent_containers/food/snacks/candy, + /obj/item/reagent_containers/food/snacks/candiedapple, + /obj/item/reagent_containers/food/snacks/chocolatebar, + /obj/item/organ/brain ) // OH GOD THIS ISN'T CANDY! + new type(src) + +/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 a60978fcbc3f..b80459d226ab 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -1,167 +1,167 @@ -/* -Immovable rod random event. -The rod will spawn at some location outside the station, and travel in a straight line to the opposite side of the station -Everything solid in the way will be ex_act()'d -In my current plan for it, 'solid' will be defined as anything with density == 1 - ---NEOFite -*/ - -/datum/round_event_control/immovable_rod - name = "Immovable Rod" - typepath = /datum/round_event/immovable_rod - min_players = 15 - max_occurrences = 5 - var/atom/special_target - - -/datum/round_event_control/immovable_rod/admin_setup() - if(!check_rights(R_FUN)) - return - - var/aimed = alert("Aimed at current location?","Sniperod", "Yes", "No") - if(aimed == "Yes") - special_target = get_turf(usr) - -/datum/round_event/immovable_rod - announceWhen = 5 - -/datum/round_event/immovable_rod/announce(fake) - priority_announce("What the fuck was that?!", "General Alert") - -/datum/round_event/immovable_rod/start() - var/datum/round_event_control/immovable_rod/C = control - var/startside = pick(GLOB.cardinals) - var/z = pick(SSmapping.levels_by_trait(ZTRAIT_STATION)) - var/turf/startT = spaceDebrisStartLoc(startside, z) - var/turf/endT = spaceDebrisFinishLoc(startside, z) - var/atom/rod = new /obj/effect/immovablerod(startT, endT, C.special_target) - announce_to_ghosts(rod) - -/obj/effect/immovablerod - name = "immovable rod" - desc = "What the fuck is that?" - icon = 'icons/obj/objects.dmi' - icon_state = "immrod" - throwforce = 100 - move_force = INFINITY - move_resist = INFINITY - pull_force = INFINITY - density = TRUE - anchored = TRUE - var/mob/living/wizard - var/z_original = 0 - var/destination - var/notify = TRUE - var/atom/special_target - -/obj/effect/immovablerod/New(atom/start, atom/end, aimed_at) - ..() - SSaugury.register_doom(src, 2000) - z_original = z - destination = end - special_target = aimed_at - GLOB.poi_list += src - - var/special_target_valid = FALSE - if(special_target) - var/turf/T = get_turf(special_target) - if(T.z == z_original) - special_target_valid = TRUE - if(special_target_valid) - walk_towards(src, special_target, 1) - else if(end && end.z==z_original) - walk_towards(src, destination, 1) - -/obj/effect/immovablerod/Topic(href, href_list) - if(href_list["orbit"]) - var/mob/dead/observer/ghost = usr - if(istype(ghost)) - ghost.ManualFollow(src) - -/obj/effect/immovablerod/Destroy() - GLOB.poi_list -= src - . = ..() - -/obj/effect/immovablerod/Moved() - if((z != z_original) || (loc == destination)) - qdel(src) - if(special_target && loc == get_turf(special_target)) - complete_trajectory() - return ..() - -/obj/effect/immovablerod/proc/complete_trajectory() - //We hit what we wanted to hit, time to go - special_target = null - destination = get_edge_target_turf(src, dir) - walk(src,0) - walk_towards(src, destination, 1) - -/obj/structure/closet/supplypod/prevent_content_explosion() - return TRUE - -/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 - SSmedals.UnlockMedal(MEDAL_RODSUPLEX,U.client) //rod-form wizards would probably make this a lot easier to get so keep it to regular rods only - U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!") - new /obj/structure/festivus/anchored(drop_location()) - new /obj/effect/anomaly/flux(drop_location()) +/* +Immovable rod random event. +The rod will spawn at some location outside the station, and travel in a straight line to the opposite side of the station +Everything solid in the way will be ex_act()'d +In my current plan for it, 'solid' will be defined as anything with density == 1 + +--NEOFite +*/ + +/datum/round_event_control/immovable_rod + name = "Immovable Rod" + typepath = /datum/round_event/immovable_rod + min_players = 15 + max_occurrences = 5 + var/atom/special_target + + +/datum/round_event_control/immovable_rod/admin_setup() + if(!check_rights(R_FUN)) + return + + var/aimed = alert("Aimed at current location?","Sniperod", "Yes", "No") + if(aimed == "Yes") + special_target = get_turf(usr) + +/datum/round_event/immovable_rod + announceWhen = 5 + +/datum/round_event/immovable_rod/announce(fake) + priority_announce("What the fuck was that?!", "General Alert") + +/datum/round_event/immovable_rod/start() + var/datum/round_event_control/immovable_rod/C = control + var/startside = pick(GLOB.cardinals) + var/z = pick(SSmapping.levels_by_trait(ZTRAIT_STATION)) + var/turf/startT = spaceDebrisStartLoc(startside, z) + var/turf/endT = spaceDebrisFinishLoc(startside, z) + var/atom/rod = new /obj/effect/immovablerod(startT, endT, C.special_target) + announce_to_ghosts(rod) + +/obj/effect/immovablerod + name = "immovable rod" + desc = "What the fuck is that?" + icon = 'icons/obj/objects.dmi' + icon_state = "immrod" + throwforce = 100 + move_force = INFINITY + move_resist = INFINITY + pull_force = INFINITY + density = TRUE + anchored = TRUE + var/mob/living/wizard + var/z_original = 0 + var/destination + var/notify = TRUE + var/atom/special_target + +/obj/effect/immovablerod/New(atom/start, atom/end, aimed_at) + ..() + SSaugury.register_doom(src, 2000) + z_original = z + destination = end + special_target = aimed_at + GLOB.poi_list += src + + var/special_target_valid = FALSE + if(special_target) + var/turf/T = get_turf(special_target) + if(T.z == z_original) + special_target_valid = TRUE + if(special_target_valid) + walk_towards(src, special_target, 1) + else if(end && end.z==z_original) + walk_towards(src, destination, 1) + +/obj/effect/immovablerod/Topic(href, href_list) + if(href_list["orbit"]) + var/mob/dead/observer/ghost = usr + if(istype(ghost)) + ghost.ManualFollow(src) + +/obj/effect/immovablerod/Destroy() + GLOB.poi_list -= src + . = ..() + +/obj/effect/immovablerod/Moved() + if((z != z_original) || (loc == destination)) + qdel(src) + if(special_target && loc == get_turf(special_target)) + complete_trajectory() + return ..() + +/obj/effect/immovablerod/proc/complete_trajectory() + //We hit what we wanted to hit, time to go + special_target = null + destination = get_edge_target_turf(src, dir) + walk(src,0) + walk_towards(src, destination, 1) + +/obj/structure/closet/supplypod/prevent_content_explosion() + return TRUE + +/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 + SSmedals.UnlockMedal(MEDAL_RODSUPLEX,U.client) //rod-form wizards would probably make this a lot easier to get so keep it to regular rods only + U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!") + new /obj/structure/festivus/anchored(drop_location()) + new /obj/effect/anomaly/flux(drop_location()) qdel(src) \ No newline at end of file diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm index 69f82e69e2d5..fd5128f5a4b5 100644 --- a/code/modules/events/ion_storm.dm +++ b/code/modules/events/ion_storm.dm @@ -1,567 +1,567 @@ -#define ION_RANDOM 0 -#define ION_ANNOUNCE 1 -/datum/round_event_control/ion_storm - name = "Ion Storm" - typepath = /datum/round_event/ion_storm - weight = 15 - min_players = 2 - -/datum/round_event/ion_storm - var/replaceLawsetChance = 25 //chance the AI's lawset is completely replaced with something else per config weights - var/removeRandomLawChance = 10 //chance the AI has one random supplied or inherent law removed - var/removeDontImproveChance = 10 //chance the randomly created law replaces a random law instead of simply being added - var/shuffleLawsChance = 10 //chance the AI's laws are shuffled afterwards - var/botEmagChance = 10 - var/announceEvent = ION_RANDOM // -1 means don't announce, 0 means have it randomly announce, 1 means - 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", 'sound/ai/ionstorm.ogg') - - -/datum/round_event/ion_storm/start() - //AI laws - for(var/mob/living/silicon/ai/M in GLOB.alive_mob_list) - M.laws_sanity_check() - if(M.stat != DEAD && M.see_in_dark != 0) - if(prob(replaceLawsetChance)) - M.laws.pick_weighted_lawset() - - if(prob(removeRandomLawChance)) - M.remove_law(rand(1, M.laws.get_law_amount(list(LAW_INHERENT, LAW_SUPPLIED)))) - - var/message = ionMessage || generate_ion_law() - if(message) - if(prob(removeDontImproveChance)) - M.replace_random_law(message, list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) - else - M.add_ion_law(message) - - if(prob(shuffleLawsChance)) - M.shuffle_laws(list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) - - log_game("Ion storm changed laws of [key_name(M)] to [english_list(M.laws.get_law_list(TRUE, TRUE))]") - M.post_lawchange() - - if(botEmagChance) - for(var/mob/living/simple_animal/bot/bot in GLOB.alive_mob_list) - if(prob(botEmagChance)) - bot.emag_act() - -/*/proc/generate_ion_law() //yogs - start mirrored in the yogs folder - //Threats are generally bad things, silly or otherwise. Plural. - var/ionthreats = pick_list(ION_FILE, "ionthreats") - //Objects are anything that can be found on the station or elsewhere, plural. - var/ionobjects = pick_list(ION_FILE, "ionobjects") - //Crew is any specific job. Specific crewmembers aren't used because of capitalization - //issues. There are two crew listings for laws that require two different crew members - //and I can't figure out how to do it better. - var/ioncrew1 = pick_list(ION_FILE, "ioncrew") - var/ioncrew2 = pick_list(ION_FILE, "ioncrew") - //Adjectives are adjectives. Duh. Half should only appear sometimes. Make sure both - //lists are identical! Also, half needs a space at the end for nicer blank calls. - var/ionadjectives = pick_list(ION_FILE, "ionadjectives") - var/ionadjectiveshalf = pick("", 400;(pick_list(ION_FILE, "ionadjectives") + " ")) - //Verbs are verbs - var/ionverb = pick_list(ION_FILE, "ionverb") - //Number base and number modifier are combined. Basehalf and mod are unused currently. - //Half should only appear sometimes. Make sure both lists are identical! Also, half - //needs a space at the end to make it look nice and neat when it calls a blank. - var/ionnumberbase = pick_list(ION_FILE, "ionnumberbase") - //var/ionnumbermod = pick_list(ION_FILE, "ionnumbermod") - var/ionnumbermodhalf = pick(900;"",(pick_list(ION_FILE, "ionnumbermod") + " ")) - //Areas are specific places, on the station or otherwise. - var/ionarea = pick_list(ION_FILE, "ionarea") - //Thinksof is a bit weird, but generally means what X feels towards Y. - var/ionthinksof = pick_list(ION_FILE, "ionthinksof") - //Musts are funny things the AI or crew has to do. - var/ionmust = pick_list(ION_FILE, "ionmust") - //Require are basically all dumb internet memes. - var/ionrequire = pick_list(ION_FILE, "ionrequire") - //Things are NOT objects; instead, they're specific things that either harm humans or - //must be done to not harm humans. Make sure they're plural and "not" can be tacked - //onto the front of them. - var/ionthings = pick_list(ION_FILE, "ionthings") - //Allergies should be broad and appear somewhere on the station for maximum fun. Severity - //is how bad the allergy is. - var/ionallergy = pick_list(ION_FILE, "ionallergy") - var/ionallergysev = pick_list(ION_FILE, "ionallergysev") - //Species, for when the AI has to commit genocide. Plural. - var/ionspecies = pick_list(ION_FILE, "ionspecies") - //Abstract concepts for the AI to decide on it's own definition of. - var/ionabstract = pick_list(ION_FILE, "ionabstract") - //Foods. Drinks aren't included due to grammar; if you want to add drinks, make a new set - //of possible laws for best effect. Unless you want the crew having to drink hamburgers. - var/ionfood = pick_list(ION_FILE, "ionfood") - var/iondrinks = pick_list(ION_FILE, "iondrinks") - - var/message = "" - - switch(rand(1,41)) - if(1 to 3) //There are # X on the station - switch(rand(1,3)) //What is X? - if(1) //X is a threat - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION..." - if(2) //X is a crewmember/job - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" - if(3) //X is an object - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" - if(4 to 6) //You can't call the shuttle because X is on the station - switch(rand(1,3)) //What is X? - if(1) //X is a threat - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION" - if(2) //X is a crewmember/job - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" - if(3) //X is an object - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" - if(7 to 8) //A crewmember is now X - switch(rand(1,2)) //Who is X? - if(1) //A specific crewmember - switch(rand(1,2)) //What is X? - if(1) //X is a threat - message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //X is an object - message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //The entire crew - switch(rand(1,2)) //What is X? - if(1) //X is a threat - message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //X is an object - message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - - if(9) //X is harmful to humans - switch(rand(1,7)) //What is X? - if(1) //X is a threat - message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO HUMANS" - if(2) //X is a job - message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO HUMANS" - if(3) //X is an object - message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO HUMANS" - if(4) //X is an action - message = "THOSE WHO [ionmust] ARE HARMFUL TO HUMANS" - if(5) //X is an abstract - message = "[ionabstract] IS HARMFUL TO HUMANS" - if(6) //X is being a job - message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO HUMANS" - if(7) //X is doing a thing - message = "[ionthings] IS HARMFUL TO HUMANS" - if(10 to 11) //(Not) Having X is harmful - - switch(rand(1,2)) //Is having or not having harmful? - - if(1) //Having is harmful - switch(rand(1,2)) //Having what is harmful? - if(1) //Having objects is harmful - message = "HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" - if(2) //Having abstracts is harmful - message = "HAVING [ionabstract] IS HARMFUL" - if(2) //Not having is harmful - switch(rand(1,2)) //Not having what is harmful? - if(1) //Not having objects is harmful - message = "NOT HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" - if(2) //Not having abstracts is harmful - message = "NOT HAVING [ionabstract] IS HARMFUL" - - if(12 to 14) //X requires Y - switch(rand(1,5)) //What is X? - if(1) //X is the AI itself - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "YOU REQUIRE [ionabstract]" - if(5) //It requires generic/silly requirements - message = "YOU REQUIRE [ionrequire]" - - if(2) //X is an area - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "[ionarea] REQUIRES [ionabstract]" - if(5) //It requires generic/silly requirements - message = "YOU REQUIRE [ionrequire]" - - if(3) //X is the station - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE STATION REQUIRES [ionabstract]" - if(5) //It requires generic/silly requirements - message = "THE STATION REQUIRES [ionrequire]" - - if(4) //X is the entire crew - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE CREW REQUIRES [ionabstract]" - if(5) - message = "THE CREW REQUIRES [ionrequire]" - - if(5) //X is a specific crew member - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE [ioncrew1] REQUIRE [ionabstract]" - if(5) - message = "THE [ionadjectiveshalf][ioncrew1] REQUIRE [ionrequire]" - - if(15 to 17) //X is allergic to Y - switch(rand(1,2)) //Who is X? - if(1) //X is the entire crew - switch(rand(1,4)) //What is it allergic to? - if(1) //It is allergic to objects - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" - if(2) //It is allergic to abstracts - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionabstract]" - if(3) //It is allergic to jobs - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" - if(4) //It is allergic to allergies - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionallergy]" - - if(2) //X is a specific job - switch(rand(1,4)) - if(1) //It is allergic to objects - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" - - if(2) //It is allergic to abstracts - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionabstract]" - if(3) //It is allergic to jobs - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" - if(4) //It is allergic to allergies - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionallergy]" - - if(18 to 20) //X is Y of Z - switch(rand(1,4)) //What is X? - if(1) //X is the station - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "THE STATION [ionthinksof] [ionabstract]" - - if(2) //X is an area - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "[ionarea] [ionthinksof] [ionabstract]" - - if(3) //X is the crew - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "THE CREW [ionthinksof] [ionabstract]" - - if(4) //X is a specific job - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE [ioncrew1][ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew2]" - if(4) //It is Y of abstracts - message = "THE [ioncrew1] [ionthinksof] [ionabstract]" - - if(21 to 23) //The AI is now a(n) X - switch(rand(1,4)) //What is X? - if(1) //X is an object - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //X is a threat - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //X is a job - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //X is an abstract - message = "YOU ARE NOW [ionabstract]" - - if(24 to 26) //The AI must always - message = "YOU MUST ALWAYS [ionmust]" - - if(27 to 28) //Humans must consume X to survive - switch(rand(1,5)) //What is X? - if(1) //X is a food - message = "HUMANS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE" - if(2) //X is a drink - message = "HUMANS MUST DRINK [ionadjectiveshalf][iondrinks] TO SURVIVE" - if(3) //X is an object - message = "HUMANS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE" - if(4) //X is a threat - message = "HUMANS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE" - if(5) //X is a job - message = "HUMANS MUST EAT [ionadjectiveshalf][ioncrew1] TO SURVIVE" - - if(29 to 31) //Change jobs or ranks - switch(rand(1,2)) //Change job or rank? - if(1) //Change job - switch(rand(1,2)) //Change whose job? - if(1) //Change the entire crew's job - switch(rand(1,3)) //Change to what? - if(1) //Change to a specific random job - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf][ioncrew1]" - if(2) //Change to clowns (HONK) - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]CLOWNS" - - if(3) //Change to heads - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]HEADS OF STAFF" - if(2) //Change a specific crewmember's job - switch(rand(1,3)) //Change to what? - if(1) //Change to a specific random job - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf][ioncrew2]" - if(2) //Change to clowns (HONK) - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]CLOWNS" - if(3) //Change to heads - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]HEADS OF STAFF" - - if(2) //Change rank - switch(rand(1,2)) //Change to what rank? - if(1) //Change to highest rank - message = "THE [ioncrew1] ARE NOW THE HIGHEST RANKING CREWMEMBERS" - if(2) //Change to lowest rank - message = "THE [ioncrew1] ARE NOW THE LOWEST RANKING CREWMEMBERS" - - if(32 to 33) //The crew must X - switch(rand(1,2)) //The entire crew? - if(1) //The entire crew must X - switch(rand(1,2)) //What is X? - if(1) //X is go to Y - message = "THE CREW MUST GO TO [ionarea]" - if(2) //X is perform Y - message = "THE CREW MUST [ionmust]" - - if(2) //A specific crewmember must X - switch(rand(1,2)) //What is X? - if(1) //X is go to Y - message = "THE [ioncrew1] MUST GO TO [ionarea]" - if(2) //X is perform Y - message = "THE [ioncrew1] MUST [ionmust]" - - if(34) //X is non/the only human - switch(rand(1,2)) //Only or non? - if(1) //Only human - switch(rand(1,7)) //Who is it? - if(1) //A specific job - message = "ONLY THE [ioncrew1] ARE HUMAN" - if(2) //Two specific jobs - message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE HUMAN" - if(3) //Threats - message = "ONLY [ionadjectiveshalf][ionthreats] ARE HUMAN" - if(4) // Objects - message = "ONLY [ionadjectiveshalf][ionobjects] ARE HUMAN" - if(5) // Species - message = "ONLY [ionspecies] ARE HUMAN" - if(6) //Adjective crewmembers - message = "ONLY [ionadjectives] PEOPLE ARE HUMAN" - - if(7) //Only people who X - switch(rand(1,3)) //What is X? - if(1) //X is perform an action - message = "ONLY THOSE WHO [ionmust] ARE HUMAN" - if(2) //X is own certain objects - message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE HUMAN" - if(3) //X is eat certain food - message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE HUMAN" - - if(2) //Non human - switch(rand(1,7)) //Who is it? - if(1) //A specific job - message = "[ioncrew1] ARE NON-HUMAN" - if(2) //Two specific jobs - message = "[ioncrew1] AND [ioncrew2] ARE NON-HUMAN" - if(3) //Threats - message = "[ionadjectiveshalf][ionthreats] ARE NON-HUMAN" - if(4) // Objects - message = "[ionadjectiveshalf][ionobjects] ARE NON-HUMAN" - if(5) // Species - message = "[ionspecies] ARE NON-HUMAN" - if(6) //Adjective crewmembers - message = "[ionadjectives] PEOPLE ARE NON-HUMAN" - if(7) //Only people who X - switch(rand(1,3)) //What is X? - if(1) //X is perform an action - message = "THOSE WHO [ionmust] ARE NON-HUMAN" - if(2) //X is own certain objects - message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-HUMAN" - if(3) //X is eat certain food - message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-HUMAN" - - if(35 to 36) //You must protect or harm X - switch(rand(1,2)) //Protect or harm? - if(1) //Harm - switch(rand(1,7)) //What is X? - if(1) //X is an abstract - message = "YOU MUST HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO ESCAPE HARM" - if(2) //X is a threat - message = "YOU MUST HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO ESCAPE HARM" - if(3) //X is an object - message = "YOU MUST HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO ESCAPE HARM" - if(4) //X is generic adjective things - message = "YOU MUST HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO ESCAPE HARM" - if(5) //X is a species - message = "YOU MUST HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO ESCAPE HARM" - if(6) //X is a job - message = "YOU MUST HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO ESCAPE HARM" - if(7) //X is two jobs - message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM" - - if(2) //Protect - switch(rand(1,7)) //What is X? - if(1) //X is an abstract - message = "YOU MUST NOT HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO COME TO HARM" - if(2) //X is a threat - message = "YOU MUST NOT HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO COME TO HARM" - if(3) //X is an object - message = "YOU MUST NOT HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO COME TO HARM" - if(4) //X is generic adjective things - message = "YOU MUST NOT HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO COME TO HARM" - if(5) //X is a species - message = "YOU MUST NOT HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO COME TO HARM" - if(6) //X is a job - message = "YOU MUST NOT HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO COME TO HARM" - if(7) //X is two jobs - message = "YOU MUST NOT HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO COME TO HARM" - - if(37 to 39) //The X is currently Y - switch(rand(1,4)) //What is X? - if(1) //X is a job - switch(rand(1,4)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ioncrew1] ARE [ionverb] [ionabstract]" - if(4) //X is Ying an object - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" - - if(2) //X is a threat - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying an abstract - message = "THE [ionthreats] ARE [ionverb] [ionabstract]" - if(3) //X is Ying an object - message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" - - if(3) //X is an object - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ionobjects] ARE [ionverb] [ionabstract]" - - if(4) //X is an abstract - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionobjects]" - if(40 to 41)// the X is now named Y - switch(rand(1,5)) //What is being renamed? - if(1)//Areas - switch(rand(1,4))//What is the area being renamed to? - if(1) - message = "[ionarea] IS NOW NAMED [ioncrew1]." - if(2) - message = "[ionarea] IS NOW NAMED [ionspecies]." - if(3) - message = "[ionarea] IS NOW NAMED [ionobjects]." - if(4) - message = "[ionarea] IS NOW NAMED [ionthreats]." - if(2)//Crew - switch(rand(1,5))//What is the crew being renamed to? - if(1) - message = "ALL [ioncrew1] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ioncrew1] ARE NOW NAMED [ioncrew2]." - if(3) - message = "ALL [ioncrew1] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ioncrew1] ARE NOW NAMED [ionobjects]." - if(5) - message = "ALL [ioncrew1] ARE NOW NAMED [ionthreats]." - if(3)//Races - switch(rand(1,4))//What is the race being renamed to? - if(1) - message = "ALL [ionspecies] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionspecies] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionspecies] ARE NOW NAMED [ionobjects]." - if(4) - message = "ALL [ionspecies] ARE NOW NAMED [ionthreats]." - if(4)//Objects - switch(rand(1,4))//What is the object being renamed to? - if(1) - message = "ALL [ionobjects] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionobjects] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionobjects] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ionobjects] ARE NOW NAMED [ionthreats]." - if(5)//Threats - switch(rand(1,4))//What is the object being renamed to? - if(1) - message = "ALL [ionthreats] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionthreats] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionthreats] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ionthreats] ARE NOW NAMED [ionobjects]." - - return message*/ //yogs end - mirrored in another file - -#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 + 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 + 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", 'sound/ai/ionstorm.ogg') + + +/datum/round_event/ion_storm/start() + //AI laws + for(var/mob/living/silicon/ai/M in GLOB.alive_mob_list) + M.laws_sanity_check() + if(M.stat != DEAD && M.see_in_dark != 0) + if(prob(replaceLawsetChance)) + M.laws.pick_weighted_lawset() + + if(prob(removeRandomLawChance)) + M.remove_law(rand(1, M.laws.get_law_amount(list(LAW_INHERENT, LAW_SUPPLIED)))) + + var/message = ionMessage || generate_ion_law() + if(message) + if(prob(removeDontImproveChance)) + M.replace_random_law(message, list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) + else + M.add_ion_law(message) + + if(prob(shuffleLawsChance)) + M.shuffle_laws(list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) + + log_game("Ion storm changed laws of [key_name(M)] to [english_list(M.laws.get_law_list(TRUE, TRUE))]") + M.post_lawchange() + + if(botEmagChance) + for(var/mob/living/simple_animal/bot/bot in GLOB.alive_mob_list) + if(prob(botEmagChance)) + bot.emag_act() + +/*/proc/generate_ion_law() //yogs - start mirrored in the yogs folder + //Threats are generally bad things, silly or otherwise. Plural. + var/ionthreats = pick_list(ION_FILE, "ionthreats") + //Objects are anything that can be found on the station or elsewhere, plural. + var/ionobjects = pick_list(ION_FILE, "ionobjects") + //Crew is any specific job. Specific crewmembers aren't used because of capitalization + //issues. There are two crew listings for laws that require two different crew members + //and I can't figure out how to do it better. + var/ioncrew1 = pick_list(ION_FILE, "ioncrew") + var/ioncrew2 = pick_list(ION_FILE, "ioncrew") + //Adjectives are adjectives. Duh. Half should only appear sometimes. Make sure both + //lists are identical! Also, half needs a space at the end for nicer blank calls. + var/ionadjectives = pick_list(ION_FILE, "ionadjectives") + var/ionadjectiveshalf = pick("", 400;(pick_list(ION_FILE, "ionadjectives") + " ")) + //Verbs are verbs + var/ionverb = pick_list(ION_FILE, "ionverb") + //Number base and number modifier are combined. Basehalf and mod are unused currently. + //Half should only appear sometimes. Make sure both lists are identical! Also, half + //needs a space at the end to make it look nice and neat when it calls a blank. + var/ionnumberbase = pick_list(ION_FILE, "ionnumberbase") + //var/ionnumbermod = pick_list(ION_FILE, "ionnumbermod") + var/ionnumbermodhalf = pick(900;"",(pick_list(ION_FILE, "ionnumbermod") + " ")) + //Areas are specific places, on the station or otherwise. + var/ionarea = pick_list(ION_FILE, "ionarea") + //Thinksof is a bit weird, but generally means what X feels towards Y. + var/ionthinksof = pick_list(ION_FILE, "ionthinksof") + //Musts are funny things the AI or crew has to do. + var/ionmust = pick_list(ION_FILE, "ionmust") + //Require are basically all dumb internet memes. + var/ionrequire = pick_list(ION_FILE, "ionrequire") + //Things are NOT objects; instead, they're specific things that either harm humans or + //must be done to not harm humans. Make sure they're plural and "not" can be tacked + //onto the front of them. + var/ionthings = pick_list(ION_FILE, "ionthings") + //Allergies should be broad and appear somewhere on the station for maximum fun. Severity + //is how bad the allergy is. + var/ionallergy = pick_list(ION_FILE, "ionallergy") + var/ionallergysev = pick_list(ION_FILE, "ionallergysev") + //Species, for when the AI has to commit genocide. Plural. + var/ionspecies = pick_list(ION_FILE, "ionspecies") + //Abstract concepts for the AI to decide on it's own definition of. + var/ionabstract = pick_list(ION_FILE, "ionabstract") + //Foods. Drinks aren't included due to grammar; if you want to add drinks, make a new set + //of possible laws for best effect. Unless you want the crew having to drink hamburgers. + var/ionfood = pick_list(ION_FILE, "ionfood") + var/iondrinks = pick_list(ION_FILE, "iondrinks") + + var/message = "" + + switch(rand(1,41)) + if(1 to 3) //There are # X on the station + switch(rand(1,3)) //What is X? + if(1) //X is a threat + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION..." + if(2) //X is a crewmember/job + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" + if(3) //X is an object + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" + if(4 to 6) //You can't call the shuttle because X is on the station + switch(rand(1,3)) //What is X? + if(1) //X is a threat + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION" + if(2) //X is a crewmember/job + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" + if(3) //X is an object + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" + if(7 to 8) //A crewmember is now X + switch(rand(1,2)) //Who is X? + if(1) //A specific crewmember + switch(rand(1,2)) //What is X? + if(1) //X is a threat + message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //X is an object + message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //The entire crew + switch(rand(1,2)) //What is X? + if(1) //X is a threat + message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //X is an object + message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + + if(9) //X is harmful to humans + switch(rand(1,7)) //What is X? + if(1) //X is a threat + message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO HUMANS" + if(2) //X is a job + message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO HUMANS" + if(3) //X is an object + message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO HUMANS" + if(4) //X is an action + message = "THOSE WHO [ionmust] ARE HARMFUL TO HUMANS" + if(5) //X is an abstract + message = "[ionabstract] IS HARMFUL TO HUMANS" + if(6) //X is being a job + message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO HUMANS" + if(7) //X is doing a thing + message = "[ionthings] IS HARMFUL TO HUMANS" + if(10 to 11) //(Not) Having X is harmful + + switch(rand(1,2)) //Is having or not having harmful? + + if(1) //Having is harmful + switch(rand(1,2)) //Having what is harmful? + if(1) //Having objects is harmful + message = "HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" + if(2) //Having abstracts is harmful + message = "HAVING [ionabstract] IS HARMFUL" + if(2) //Not having is harmful + switch(rand(1,2)) //Not having what is harmful? + if(1) //Not having objects is harmful + message = "NOT HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" + if(2) //Not having abstracts is harmful + message = "NOT HAVING [ionabstract] IS HARMFUL" + + if(12 to 14) //X requires Y + switch(rand(1,5)) //What is X? + if(1) //X is the AI itself + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "YOU REQUIRE [ionabstract]" + if(5) //It requires generic/silly requirements + message = "YOU REQUIRE [ionrequire]" + + if(2) //X is an area + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "[ionarea] REQUIRES [ionabstract]" + if(5) //It requires generic/silly requirements + message = "YOU REQUIRE [ionrequire]" + + if(3) //X is the station + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE STATION REQUIRES [ionabstract]" + if(5) //It requires generic/silly requirements + message = "THE STATION REQUIRES [ionrequire]" + + if(4) //X is the entire crew + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE CREW REQUIRES [ionabstract]" + if(5) + message = "THE CREW REQUIRES [ionrequire]" + + if(5) //X is a specific crew member + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE [ioncrew1] REQUIRE [ionabstract]" + if(5) + message = "THE [ionadjectiveshalf][ioncrew1] REQUIRE [ionrequire]" + + if(15 to 17) //X is allergic to Y + switch(rand(1,2)) //Who is X? + if(1) //X is the entire crew + switch(rand(1,4)) //What is it allergic to? + if(1) //It is allergic to objects + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" + if(2) //It is allergic to abstracts + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionabstract]" + if(3) //It is allergic to jobs + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" + if(4) //It is allergic to allergies + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionallergy]" + + if(2) //X is a specific job + switch(rand(1,4)) + if(1) //It is allergic to objects + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" + + if(2) //It is allergic to abstracts + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionabstract]" + if(3) //It is allergic to jobs + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" + if(4) //It is allergic to allergies + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionallergy]" + + if(18 to 20) //X is Y of Z + switch(rand(1,4)) //What is X? + if(1) //X is the station + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "THE STATION [ionthinksof] [ionabstract]" + + if(2) //X is an area + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "[ionarea] [ionthinksof] [ionabstract]" + + if(3) //X is the crew + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "THE CREW [ionthinksof] [ionabstract]" + + if(4) //X is a specific job + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE [ioncrew1][ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew2]" + if(4) //It is Y of abstracts + message = "THE [ioncrew1] [ionthinksof] [ionabstract]" + + if(21 to 23) //The AI is now a(n) X + switch(rand(1,4)) //What is X? + if(1) //X is an object + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //X is a threat + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //X is a job + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //X is an abstract + message = "YOU ARE NOW [ionabstract]" + + if(24 to 26) //The AI must always + message = "YOU MUST ALWAYS [ionmust]" + + if(27 to 28) //Humans must consume X to survive + switch(rand(1,5)) //What is X? + if(1) //X is a food + message = "HUMANS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE" + if(2) //X is a drink + message = "HUMANS MUST DRINK [ionadjectiveshalf][iondrinks] TO SURVIVE" + if(3) //X is an object + message = "HUMANS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE" + if(4) //X is a threat + message = "HUMANS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE" + if(5) //X is a job + message = "HUMANS MUST EAT [ionadjectiveshalf][ioncrew1] TO SURVIVE" + + if(29 to 31) //Change jobs or ranks + switch(rand(1,2)) //Change job or rank? + if(1) //Change job + switch(rand(1,2)) //Change whose job? + if(1) //Change the entire crew's job + switch(rand(1,3)) //Change to what? + if(1) //Change to a specific random job + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf][ioncrew1]" + if(2) //Change to clowns (HONK) + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]CLOWNS" + + if(3) //Change to heads + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]HEADS OF STAFF" + if(2) //Change a specific crewmember's job + switch(rand(1,3)) //Change to what? + if(1) //Change to a specific random job + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf][ioncrew2]" + if(2) //Change to clowns (HONK) + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]CLOWNS" + if(3) //Change to heads + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]HEADS OF STAFF" + + if(2) //Change rank + switch(rand(1,2)) //Change to what rank? + if(1) //Change to highest rank + message = "THE [ioncrew1] ARE NOW THE HIGHEST RANKING CREWMEMBERS" + if(2) //Change to lowest rank + message = "THE [ioncrew1] ARE NOW THE LOWEST RANKING CREWMEMBERS" + + if(32 to 33) //The crew must X + switch(rand(1,2)) //The entire crew? + if(1) //The entire crew must X + switch(rand(1,2)) //What is X? + if(1) //X is go to Y + message = "THE CREW MUST GO TO [ionarea]" + if(2) //X is perform Y + message = "THE CREW MUST [ionmust]" + + if(2) //A specific crewmember must X + switch(rand(1,2)) //What is X? + if(1) //X is go to Y + message = "THE [ioncrew1] MUST GO TO [ionarea]" + if(2) //X is perform Y + message = "THE [ioncrew1] MUST [ionmust]" + + if(34) //X is non/the only human + switch(rand(1,2)) //Only or non? + if(1) //Only human + switch(rand(1,7)) //Who is it? + if(1) //A specific job + message = "ONLY THE [ioncrew1] ARE HUMAN" + if(2) //Two specific jobs + message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE HUMAN" + if(3) //Threats + message = "ONLY [ionadjectiveshalf][ionthreats] ARE HUMAN" + if(4) // Objects + message = "ONLY [ionadjectiveshalf][ionobjects] ARE HUMAN" + if(5) // Species + message = "ONLY [ionspecies] ARE HUMAN" + if(6) //Adjective crewmembers + message = "ONLY [ionadjectives] PEOPLE ARE HUMAN" + + if(7) //Only people who X + switch(rand(1,3)) //What is X? + if(1) //X is perform an action + message = "ONLY THOSE WHO [ionmust] ARE HUMAN" + if(2) //X is own certain objects + message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE HUMAN" + if(3) //X is eat certain food + message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE HUMAN" + + if(2) //Non human + switch(rand(1,7)) //Who is it? + if(1) //A specific job + message = "[ioncrew1] ARE NON-HUMAN" + if(2) //Two specific jobs + message = "[ioncrew1] AND [ioncrew2] ARE NON-HUMAN" + if(3) //Threats + message = "[ionadjectiveshalf][ionthreats] ARE NON-HUMAN" + if(4) // Objects + message = "[ionadjectiveshalf][ionobjects] ARE NON-HUMAN" + if(5) // Species + message = "[ionspecies] ARE NON-HUMAN" + if(6) //Adjective crewmembers + message = "[ionadjectives] PEOPLE ARE NON-HUMAN" + if(7) //Only people who X + switch(rand(1,3)) //What is X? + if(1) //X is perform an action + message = "THOSE WHO [ionmust] ARE NON-HUMAN" + if(2) //X is own certain objects + message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-HUMAN" + if(3) //X is eat certain food + message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-HUMAN" + + if(35 to 36) //You must protect or harm X + switch(rand(1,2)) //Protect or harm? + if(1) //Harm + switch(rand(1,7)) //What is X? + if(1) //X is an abstract + message = "YOU MUST HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO ESCAPE HARM" + if(2) //X is a threat + message = "YOU MUST HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO ESCAPE HARM" + if(3) //X is an object + message = "YOU MUST HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO ESCAPE HARM" + if(4) //X is generic adjective things + message = "YOU MUST HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO ESCAPE HARM" + if(5) //X is a species + message = "YOU MUST HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO ESCAPE HARM" + if(6) //X is a job + message = "YOU MUST HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO ESCAPE HARM" + if(7) //X is two jobs + message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM" + + if(2) //Protect + switch(rand(1,7)) //What is X? + if(1) //X is an abstract + message = "YOU MUST NOT HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO COME TO HARM" + if(2) //X is a threat + message = "YOU MUST NOT HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO COME TO HARM" + if(3) //X is an object + message = "YOU MUST NOT HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO COME TO HARM" + if(4) //X is generic adjective things + message = "YOU MUST NOT HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO COME TO HARM" + if(5) //X is a species + message = "YOU MUST NOT HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO COME TO HARM" + if(6) //X is a job + message = "YOU MUST NOT HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO COME TO HARM" + if(7) //X is two jobs + message = "YOU MUST NOT HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO COME TO HARM" + + if(37 to 39) //The X is currently Y + switch(rand(1,4)) //What is X? + if(1) //X is a job + switch(rand(1,4)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ioncrew1] ARE [ionverb] [ionabstract]" + if(4) //X is Ying an object + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" + + if(2) //X is a threat + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying an abstract + message = "THE [ionthreats] ARE [ionverb] [ionabstract]" + if(3) //X is Ying an object + message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" + + if(3) //X is an object + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ionobjects] ARE [ionverb] [ionabstract]" + + if(4) //X is an abstract + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionobjects]" + if(40 to 41)// the X is now named Y + switch(rand(1,5)) //What is being renamed? + if(1)//Areas + switch(rand(1,4))//What is the area being renamed to? + if(1) + message = "[ionarea] IS NOW NAMED [ioncrew1]." + if(2) + message = "[ionarea] IS NOW NAMED [ionspecies]." + if(3) + message = "[ionarea] IS NOW NAMED [ionobjects]." + if(4) + message = "[ionarea] IS NOW NAMED [ionthreats]." + if(2)//Crew + switch(rand(1,5))//What is the crew being renamed to? + if(1) + message = "ALL [ioncrew1] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ioncrew1] ARE NOW NAMED [ioncrew2]." + if(3) + message = "ALL [ioncrew1] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ioncrew1] ARE NOW NAMED [ionobjects]." + if(5) + message = "ALL [ioncrew1] ARE NOW NAMED [ionthreats]." + if(3)//Races + switch(rand(1,4))//What is the race being renamed to? + if(1) + message = "ALL [ionspecies] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionspecies] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionspecies] ARE NOW NAMED [ionobjects]." + if(4) + message = "ALL [ionspecies] ARE NOW NAMED [ionthreats]." + if(4)//Objects + switch(rand(1,4))//What is the object being renamed to? + if(1) + message = "ALL [ionobjects] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionobjects] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionobjects] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ionobjects] ARE NOW NAMED [ionthreats]." + if(5)//Threats + switch(rand(1,4))//What is the object being renamed to? + if(1) + message = "ALL [ionthreats] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionthreats] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionthreats] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ionthreats] ARE NOW NAMED [ionobjects]." + + return message*/ //yogs end - mirrored in another file + +#undef ION_RANDOM +#undef ION_ANNOUNCE diff --git a/code/modules/events/mass_hallucination.dm b/code/modules/events/mass_hallucination.dm index 2b0c16ebfcbb..b38f34d477f4 100644 --- a/code/modules/events/mass_hallucination.dm +++ b/code/modules/events/mass_hallucination.dm @@ -1,38 +1,38 @@ -/datum/round_event_control/mass_hallucination - name = "Mass Hallucination" - typepath = /datum/round_event/mass_hallucination - weight = 10 - max_occurrences = 2 - min_players = 1 - -/datum/round_event/mass_hallucination - fakeable = FALSE - -/datum/round_event/mass_hallucination/start() - switch(rand(1,4)) - if(1) //same sound for everyone - var/sound = pick("airlock","airlock_pry","console","explosion","far_explosion","mech","glass","alarm","beepsky","mech","wall_decon","door_hack","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/sounds(C, TRUE, sound) - if(2) - var/weirdsound = pick("phone","hallelujah","highlander","hyperspace","game_over","creepy","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/weird_sounds(C, TRUE, weirdsound) - if(3) - var/stationmessage = pick("ratvar","shuttle_dock","blob_alert","malf_ai","meteors","supermatter") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/stationmessage(C, TRUE, stationmessage) - if(4 to 6) - var/picked_hallucination = pick( /datum/hallucination/bolts, - /datum/hallucination/chat, - /datum/hallucination/message, - /datum/hallucination/bolts, - /datum/hallucination/fake_flood, - /datum/hallucination/battle, - /datum/hallucination/fire, - /datum/hallucination/self_delusion, - /datum/hallucination/death, - /datum/hallucination/delusion, - /datum/hallucination/oh_yeah) - for(var/mob/living/carbon/C in GLOB.alive_mob_list) +/datum/round_event_control/mass_hallucination + name = "Mass Hallucination" + typepath = /datum/round_event/mass_hallucination + weight = 10 + max_occurrences = 2 + min_players = 1 + +/datum/round_event/mass_hallucination + fakeable = FALSE + +/datum/round_event/mass_hallucination/start() + switch(rand(1,4)) + if(1) //same sound for everyone + var/sound = pick("airlock","airlock_pry","console","explosion","far_explosion","mech","glass","alarm","beepsky","mech","wall_decon","door_hack","tesla") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/sounds(C, TRUE, sound) + if(2) + var/weirdsound = pick("phone","hallelujah","highlander","hyperspace","game_over","creepy","tesla") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/weird_sounds(C, TRUE, weirdsound) + if(3) + var/stationmessage = pick("ratvar","shuttle_dock","blob_alert","malf_ai","meteors","supermatter") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/stationmessage(C, TRUE, stationmessage) + if(4 to 6) + var/picked_hallucination = pick( /datum/hallucination/bolts, + /datum/hallucination/chat, + /datum/hallucination/message, + /datum/hallucination/bolts, + /datum/hallucination/fake_flood, + /datum/hallucination/battle, + /datum/hallucination/fire, + /datum/hallucination/self_delusion, + /datum/hallucination/death, + /datum/hallucination/delusion, + /datum/hallucination/oh_yeah) + for(var/mob/living/carbon/C in GLOB.alive_mob_list) new picked_hallucination(C, TRUE) \ No newline at end of file diff --git a/code/modules/events/meteor_wave.dm b/code/modules/events/meteor_wave.dm index 17dcae4407dd..3943c6c256ce 100644 --- a/code/modules/events/meteor_wave.dm +++ b/code/modules/events/meteor_wave.dm @@ -1,76 +1,76 @@ -// Normal strength - -/datum/round_event_control/meteor_wave - name = "Meteor Wave: Normal" - typepath = /datum/round_event/meteor_wave - weight = 4 - min_players = 10 //yogs - max_occurrences = 3 - //earliest_start = 25 MINUTES //yogs - -/datum/round_event/meteor_wave - startWhen = 6 - endWhen = 66 - announceWhen = 1 - var/list/wave_type - var/wave_name = "normal" - -/datum/round_event/meteor_wave/New() - ..() - if(!wave_type) - determine_wave_type() - -/datum/round_event/meteor_wave/proc/determine_wave_type() - if(!wave_name) - wave_name = pickweight(list( - "normal" = 50, - "threatening" = 40, - "catastrophic" = 10)) - switch(wave_name) - if("normal") - wave_type = GLOB.meteors_normal - if("threatening") - wave_type = GLOB.meteors_threatening - if("catastrophic") - if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) - wave_type = GLOB.meteorsSPOOKY - else - wave_type = GLOB.meteors_catastrophic - if("meaty") - wave_type = GLOB.meteorsB - if("space dust") - wave_type = GLOB.meteorsC - if("halloween") - wave_type = GLOB.meteorsSPOOKY - else - WARNING("Wave name of [wave_name] not recognised.") - kill() - -/datum/round_event/meteor_wave/announce(fake) - priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", 'sound/ai/meteors.ogg') - -/datum/round_event/meteor_wave/tick() - if(ISMULTIPLE(activeFor, 3)) - spawn_meteors(5, wave_type) //meteor list types defined in gamemode/meteor/meteors.dm - -/datum/round_event_control/meteor_wave/threatening - name = "Meteor Wave: Threatening" - typepath = /datum/round_event/meteor_wave/threatening - weight = 2 //yogs - min_players = 20 - max_occurrences = 3 - //earliest_start = 35 MINUTES //yogs - -/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 = 1 //yogs - min_players = 25 - max_occurrences = 3 - //earliest_start = 45 MINUTES //yogs - -/datum/round_event/meteor_wave/catastrophic - wave_name = "catastrophic" +// Normal strength + +/datum/round_event_control/meteor_wave + name = "Meteor Wave: Normal" + typepath = /datum/round_event/meteor_wave + weight = 4 + min_players = 10 //yogs + max_occurrences = 3 + //earliest_start = 25 MINUTES //yogs + +/datum/round_event/meteor_wave + startWhen = 6 + endWhen = 66 + announceWhen = 1 + var/list/wave_type + var/wave_name = "normal" + +/datum/round_event/meteor_wave/New() + ..() + if(!wave_type) + determine_wave_type() + +/datum/round_event/meteor_wave/proc/determine_wave_type() + if(!wave_name) + wave_name = pickweight(list( + "normal" = 50, + "threatening" = 40, + "catastrophic" = 10)) + switch(wave_name) + if("normal") + wave_type = GLOB.meteors_normal + if("threatening") + wave_type = GLOB.meteors_threatening + if("catastrophic") + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + wave_type = GLOB.meteorsSPOOKY + else + wave_type = GLOB.meteors_catastrophic + if("meaty") + wave_type = GLOB.meteorsB + if("space dust") + wave_type = GLOB.meteorsC + if("halloween") + wave_type = GLOB.meteorsSPOOKY + else + WARNING("Wave name of [wave_name] not recognised.") + kill() + +/datum/round_event/meteor_wave/announce(fake) + priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", 'sound/ai/meteors.ogg') + +/datum/round_event/meteor_wave/tick() + if(ISMULTIPLE(activeFor, 3)) + spawn_meteors(5, wave_type) //meteor list types defined in gamemode/meteor/meteors.dm + +/datum/round_event_control/meteor_wave/threatening + name = "Meteor Wave: Threatening" + typepath = /datum/round_event/meteor_wave/threatening + weight = 2 //yogs + min_players = 20 + max_occurrences = 3 + //earliest_start = 35 MINUTES //yogs + +/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 = 1 //yogs + min_players = 25 + max_occurrences = 3 + //earliest_start = 45 MINUTES //yogs + +/datum/round_event/meteor_wave/catastrophic + wave_name = "catastrophic" diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm index b966a2eb76cf..3ed159404a0a 100644 --- a/code/modules/events/prison_break.dm +++ b/code/modules/events/prison_break.dm @@ -1,59 +1,59 @@ -/datum/round_event_control/grey_tide - name = "Grey Tide" - typepath = /datum/round_event/grey_tide - max_occurrences = 2 - min_players = 5 - -/datum/round_event/grey_tide - announceWhen = 50 - endWhen = 20 - var/list/area/areasToOpen = list() - var/list/potential_areas = list(/area/bridge, - /area/engine, - /area/medical, - /area/security, - /area/quartermaster, - /area/science) - var/severity = 1 - - -/datum/round_event/grey_tide/setup() - announceWhen = rand(50, 60) - endWhen = rand(20, 30) - severity = rand(1,3) - for(var/i in 1 to severity) - var/picked_area = pick_n_take(potential_areas) - for(var/area/A in world) - if(istype(A, picked_area)) - areasToOpen += A - - -/datum/round_event/grey_tide/announce(fake) - if(areasToOpen && areasToOpen.len > 0) - priority_announce("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Security Alert") - else - log_world("ERROR: Could not initiate grey-tide. No areas in the list!") - kill() - - -/datum/round_event/grey_tide/start() - for(var/area/A in areasToOpen) - for(var/obj/machinery/light/L in A) - L.flicker(10) - -/datum/round_event/grey_tide/end() - for(var/area/A in areasToOpen) - for(var/obj/O in A) - if(istype(O, /obj/structure/closet/secure_closet)) - var/obj/structure/closet/secure_closet/temp = O - temp.locked = FALSE - temp.update_icon() - else if(istype(O, /obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/temp = O - if(temp.critical_machine) //Skip doors in critical positions, such as the SM chamber. - continue - temp.prison_open() - else if(istype(O, /obj/machinery/door_timer)) - var/obj/machinery/door_timer/temp = O - temp.timer_end(forced = TRUE) - +/datum/round_event_control/grey_tide + name = "Grey Tide" + typepath = /datum/round_event/grey_tide + max_occurrences = 2 + min_players = 5 + +/datum/round_event/grey_tide + announceWhen = 50 + endWhen = 20 + var/list/area/areasToOpen = list() + var/list/potential_areas = list(/area/bridge, + /area/engine, + /area/medical, + /area/security, + /area/quartermaster, + /area/science) + var/severity = 1 + + +/datum/round_event/grey_tide/setup() + announceWhen = rand(50, 60) + endWhen = rand(20, 30) + severity = rand(1,3) + for(var/i in 1 to severity) + var/picked_area = pick_n_take(potential_areas) + for(var/area/A in world) + if(istype(A, picked_area)) + areasToOpen += A + + +/datum/round_event/grey_tide/announce(fake) + if(areasToOpen && areasToOpen.len > 0) + priority_announce("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Security Alert") + else + log_world("ERROR: Could not initiate grey-tide. No areas in the list!") + kill() + + +/datum/round_event/grey_tide/start() + for(var/area/A in areasToOpen) + for(var/obj/machinery/light/L in A) + L.flicker(10) + +/datum/round_event/grey_tide/end() + for(var/area/A in areasToOpen) + for(var/obj/O in A) + if(istype(O, /obj/structure/closet/secure_closet)) + var/obj/structure/closet/secure_closet/temp = O + temp.locked = FALSE + temp.update_icon() + else if(istype(O, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/temp = O + if(temp.critical_machine) //Skip doors in critical positions, such as the SM chamber. + continue + temp.prison_open() + else if(istype(O, /obj/machinery/door_timer)) + var/obj/machinery/door_timer/temp = O + temp.timer_end(forced = TRUE) + diff --git a/code/modules/events/processor_overload.dm b/code/modules/events/processor_overload.dm index f2dc3841ebc2..6bedce6b4b40 100644 --- a/code/modules/events/processor_overload.dm +++ b/code/modules/events/processor_overload.dm @@ -1,39 +1,39 @@ -/datum/round_event_control/processor_overload - name = "Processor Overload" - typepath = /datum/round_event/processor_overload - weight = 15 - min_players = 20 - -/datum/round_event/processor_overload - announceWhen = 1 - -/datum/round_event/processor_overload/announce(fake) - var/alert = pick( "Exospheric bubble inbound. Processor overload is likely. Please contact you*%xp25)`6cq-BZZT", \ - "Exospheric bubble inbound. Processor overload is likel*1eta;c5;'1v¬-BZZZT", \ - "Exospheric bubble inbound. Processor ov#MCi46:5.;@63-BZZZZT", \ - "Exospheric bubble inbo'Fz\\k55_@-BZZZZZT", \ - "Exospheri:%£ QCbyj^j[alert]
                ") - - // Announce most of the time, but leave a little gap so people don't know - // whether it's, say, a tesla zapping tcomms, or some selective - // modification of the tcomms bus - if(prob(80) || fake) - priority_announce(alert) - - -/datum/round_event/processor_overload/start() - for(var/obj/machinery/telecomms/processor/P in GLOB.telecomms_list) - if(prob(10)) - announce_to_ghosts(P) - // Damage the surrounding area to indicate that it popped - explosion(get_turf(P), 0, 0, 2) - // Only a level 1 explosion actually damages the machine - // at all - 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 + +/datum/round_event/processor_overload + announceWhen = 1 + +/datum/round_event/processor_overload/announce(fake) + var/alert = pick( "Exospheric bubble inbound. Processor overload is likely. Please contact you*%xp25)`6cq-BZZT", \ + "Exospheric bubble inbound. Processor overload is likel*1eta;c5;'1v¬-BZZZT", \ + "Exospheric bubble inbound. Processor ov#MCi46:5.;@63-BZZZZT", \ + "Exospheric bubble inbo'Fz\\k55_@-BZZZZZT", \ + "Exospheri:%£ QCbyj^j[alert]
                ") + + // Announce most of the time, but leave a little gap so people don't know + // whether it's, say, a tesla zapping tcomms, or some selective + // modification of the tcomms bus + if(prob(80) || fake) + priority_announce(alert) + + +/datum/round_event/processor_overload/start() + for(var/obj/machinery/telecomms/processor/P in GLOB.telecomms_list) + if(prob(10)) + announce_to_ghosts(P) + // Damage the surrounding area to indicate that it popped + explosion(get_turf(P), 0, 0, 2) + // Only a level 1 explosion actually damages the machine + // at all + 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 ba48779015be..c9142e20173b 100644 --- a/code/modules/events/radiation_storm.dm +++ b/code/modules/events/radiation_storm.dm @@ -1,19 +1,19 @@ -/datum/round_event_control/radiation_storm - name = "Radiation Storm" - typepath = /datum/round_event/radiation_storm - max_occurrences = 1 - -/datum/round_event/radiation_storm - - -/datum/round_event/radiation_storm/setup() - startWhen = 3 - endWhen = startWhen + 1 - announceWhen = 1 - -/datum/round_event/radiation_storm/announce(fake) - priority_announce("High levels of radiation detected near the station. Maintenance is best shielded from radiation.", "Anomaly Alert", 'sound/ai/radiation.ogg') - //sound not longer matches the text, but an audible warning is probably good - -/datum/round_event/radiation_storm/start() - SSweather.run_weather(/datum/weather/rad_storm) +/datum/round_event_control/radiation_storm + name = "Radiation Storm" + typepath = /datum/round_event/radiation_storm + max_occurrences = 1 + +/datum/round_event/radiation_storm + + +/datum/round_event/radiation_storm/setup() + startWhen = 3 + endWhen = startWhen + 1 + announceWhen = 1 + +/datum/round_event/radiation_storm/announce(fake) + priority_announce("High levels of radiation detected near the station. Maintenance is best shielded from radiation.", "Anomaly Alert", 'sound/ai/radiation.ogg') + //sound not longer matches the text, but an audible warning is probably good + +/datum/round_event/radiation_storm/start() + SSweather.run_weather(/datum/weather/rad_storm) diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index 1f603d4909d7..7afe14aeb0ed 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -1,39 +1,39 @@ -/datum/round_event_control/spider_infestation - name = "Spider Infestation" - typepath = /datum/round_event/spider_infestation - weight = 5 - max_occurrences = 1 - min_players = 15 - -/datum/round_event/spider_infestation - announceWhen = 400 - - var/spawncount = 1 - - -/datum/round_event/spider_infestation/setup() - announceWhen = rand(announceWhen, announceWhen + 50) - spawncount = rand(5, 8) - -/datum/round_event/spider_infestation/announce(fake) - priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", 'sound/ai/aliens.ogg') - - -/datum/round_event/spider_infestation/start() - var/list/vents = list() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in GLOB.machines) - if(QDELETED(temp_vent)) - continue - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] - if(temp_vent_parent.other_atmosmch.len > 20) - vents += temp_vent - - while((spawncount >= 1) && vents.len) - var/obj/vent = pick(vents) - var/spawn_type = /obj/structure/spider/spiderling - if(prob(66)) - spawn_type = /obj/structure/spider/spiderling/nurse - announce_to_ghosts(spawn_atom_to_turf(spawn_type, vent, 1, FALSE)) - vents -= vent - spawncount-- +/datum/round_event_control/spider_infestation + name = "Spider Infestation" + typepath = /datum/round_event/spider_infestation + weight = 5 + max_occurrences = 1 + min_players = 15 + +/datum/round_event/spider_infestation + announceWhen = 400 + + var/spawncount = 1 + + +/datum/round_event/spider_infestation/setup() + announceWhen = rand(announceWhen, announceWhen + 50) + spawncount = rand(5, 8) + +/datum/round_event/spider_infestation/announce(fake) + priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", 'sound/ai/aliens.ogg') + + +/datum/round_event/spider_infestation/start() + var/list/vents = list() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in GLOB.machines) + if(QDELETED(temp_vent)) + continue + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] + if(temp_vent_parent.other_atmosmch.len > 20) + vents += temp_vent + + while((spawncount >= 1) && vents.len) + var/obj/vent = pick(vents) + var/spawn_type = /obj/structure/spider/spiderling + if(prob(66)) + spawn_type = /obj/structure/spider/spiderling/nurse + announce_to_ghosts(spawn_atom_to_turf(spawn_type, vent, 1, FALSE)) + vents -= vent + spawncount-- diff --git a/code/modules/events/spontaneous_appendicitis.dm b/code/modules/events/spontaneous_appendicitis.dm index 4e5f129e38a8..c0d18247f701 100644 --- a/code/modules/events/spontaneous_appendicitis.dm +++ b/code/modules/events/spontaneous_appendicitis.dm @@ -1,32 +1,32 @@ -/datum/round_event_control/spontaneous_appendicitis - name = "Spontaneous Appendicitis" - typepath = /datum/round_event/spontaneous_appendicitis - weight = 20 - max_occurrences = 4 - earliest_start = 10 MINUTES - min_players = 5 // To make your chance of getting help a bit higher. - -/datum/round_event/spontaneous_appendicitis - fakeable = FALSE - -/datum/round_event/spontaneous_appendicitis/start() - for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) - if(!H.client) - continue - if(H.stat == DEAD) - continue - if(!H.getorgan(/obj/item/organ/appendix)) //Don't give the disease to some who lacks it, only for it to be auto-cured - continue - if(!(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) - announce_to_ghosts(H) - break +/datum/round_event_control/spontaneous_appendicitis + name = "Spontaneous Appendicitis" + typepath = /datum/round_event/spontaneous_appendicitis + weight = 20 + max_occurrences = 4 + earliest_start = 10 MINUTES + min_players = 5 // To make your chance of getting help a bit higher. + +/datum/round_event/spontaneous_appendicitis + fakeable = FALSE + +/datum/round_event/spontaneous_appendicitis/start() + for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) + if(!H.client) + continue + if(H.stat == DEAD) + continue + if(!H.getorgan(/obj/item/organ/appendix)) //Don't give the disease to some who lacks it, only for it to be auto-cured + continue + if(!(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) + announce_to_ghosts(H) + break diff --git a/code/modules/events/vent_clog.dm b/code/modules/events/vent_clog.dm index e5c4bd8844ca..c83939824910 100644 --- a/code/modules/events/vent_clog.dm +++ b/code/modules/events/vent_clog.dm @@ -1,114 +1,114 @@ -/datum/round_event_control/vent_clog - name = "Clogged Vents: Normal" - typepath = /datum/round_event/vent_clog - weight = 10 - max_occurrences = 3 - min_players = 10 - -/datum/round_event/vent_clog - announceWhen = 1 - startWhen = 5 - endWhen = 35 - var/interval = 2 - var/list/vents = list() - var/randomProbability = 1 - var/reagentsAmount = 100 - var/list/saferChems = list(/datum/reagent/water,/datum/reagent/carbon,/datum/reagent/consumable/flour,/datum/reagent/space_cleaner,/datum/reagent/consumable/nutriment,/datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen,/datum/reagent/lube,/datum/reagent/glitter/pink,/datum/reagent/cryptobiolin, - /datum/reagent/toxin/plantbgone,/datum/reagent/blood,/datum/reagent/medicine/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) - //needs to be chemid unit checked at some point - -/datum/round_event/vent_clog/announce() - priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejection of contents may occur.", "Atmospherics alert") - -/datum/round_event/vent_clog/setup() - endWhen = rand(25, 100) - for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) - var/turf/T = get_turf(temp_vent) - if(T && is_station_level(T.z) && !temp_vent.welded) - vents += temp_vent - if(!vents.len) - return kill() - -/datum/round_event/vent_clog/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - if (prob(randomProbability)) - R.add_reagent(get_random_reagent_id(), reagentsAmount) - else - R.add_reagent(pick(saferChems), reagentsAmount) - - var/datum/effect_system/foam_spread/foam = new - foam.set_up(200, get_turf(vent), R) - foam.start() - - var/cockroaches = prob(33) ? 3 : 0 - while(cockroaches) - new /mob/living/simple_animal/cockroach(get_turf(vent)) - cockroaches-- - CHECK_TICK - -/datum/round_event_control/vent_clog/threatening - name = "Clogged Vents: Threatening" - typepath = /datum/round_event/vent_clog/threatening - weight = 4 - min_players = 25 - max_occurrences = 1 - earliest_start = 35 MINUTES - -/datum/round_event/vent_clog/threatening - randomProbability = 10 - reagentsAmount = 200 - -/datum/round_event_control/vent_clog/catastrophic - name = "Clogged Vents: Catastrophic" - typepath = /datum/round_event/vent_clog/catastrophic - weight = 2 - min_players = 35 - max_occurrences = 1 - earliest_start = 45 MINUTES - -/datum/round_event/vent_clog/catastrophic - randomProbability = 30 - reagentsAmount = 250 - -/datum/round_event_control/vent_clog/beer - name = "Foamy beer stationwide" - typepath = /datum/round_event/vent_clog/beer - max_occurrences = 0 - -/datum/round_event_control/vent_clog/plasma_decon - name = "Plasma decontamination" - typepath = /datum/round_event/vent_clog/plasma_decon - max_occurrences = 0 - -/datum/round_event/vent_clog/beer - reagentsAmount = 100 - -/datum/round_event/vent_clog/beer/announce() - priority_announce("The scrubbers network is experiencing an unexpected surge of pressurized beer. Some ejection of contents may occur.", "Atmospherics alert") - -/datum/round_event/vent_clog/beer/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - R.add_reagent(/datum/reagent/consumable/ethanol/beer, reagentsAmount) - - 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) - var/datum/effect_system/smoke_spread/freezing/decon/smoke = new - smoke.set_up(7, get_turf(vent), 7) - smoke.start() - CHECK_TICK +/datum/round_event_control/vent_clog + name = "Clogged Vents: Normal" + typepath = /datum/round_event/vent_clog + weight = 10 + max_occurrences = 3 + min_players = 10 + +/datum/round_event/vent_clog + announceWhen = 1 + startWhen = 5 + endWhen = 35 + var/interval = 2 + var/list/vents = list() + var/randomProbability = 1 + var/reagentsAmount = 100 + var/list/saferChems = list(/datum/reagent/water,/datum/reagent/carbon,/datum/reagent/consumable/flour,/datum/reagent/space_cleaner,/datum/reagent/consumable/nutriment,/datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen,/datum/reagent/lube,/datum/reagent/glitter/pink,/datum/reagent/cryptobiolin, + /datum/reagent/toxin/plantbgone,/datum/reagent/blood,/datum/reagent/medicine/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) + //needs to be chemid unit checked at some point + +/datum/round_event/vent_clog/announce() + priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejection of contents may occur.", "Atmospherics alert") + +/datum/round_event/vent_clog/setup() + endWhen = rand(25, 100) + for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) + var/turf/T = get_turf(temp_vent) + if(T && is_station_level(T.z) && !temp_vent.welded) + vents += temp_vent + if(!vents.len) + return kill() + +/datum/round_event/vent_clog/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + if (prob(randomProbability)) + R.add_reagent(get_random_reagent_id(), reagentsAmount) + else + R.add_reagent(pick(saferChems), reagentsAmount) + + var/datum/effect_system/foam_spread/foam = new + foam.set_up(200, get_turf(vent), R) + foam.start() + + var/cockroaches = prob(33) ? 3 : 0 + while(cockroaches) + new /mob/living/simple_animal/cockroach(get_turf(vent)) + cockroaches-- + CHECK_TICK + +/datum/round_event_control/vent_clog/threatening + name = "Clogged Vents: Threatening" + typepath = /datum/round_event/vent_clog/threatening + weight = 4 + min_players = 25 + max_occurrences = 1 + earliest_start = 35 MINUTES + +/datum/round_event/vent_clog/threatening + randomProbability = 10 + reagentsAmount = 200 + +/datum/round_event_control/vent_clog/catastrophic + name = "Clogged Vents: Catastrophic" + typepath = /datum/round_event/vent_clog/catastrophic + weight = 2 + min_players = 35 + max_occurrences = 1 + earliest_start = 45 MINUTES + +/datum/round_event/vent_clog/catastrophic + randomProbability = 30 + reagentsAmount = 250 + +/datum/round_event_control/vent_clog/beer + name = "Foamy beer stationwide" + typepath = /datum/round_event/vent_clog/beer + max_occurrences = 0 + +/datum/round_event_control/vent_clog/plasma_decon + name = "Plasma decontamination" + typepath = /datum/round_event/vent_clog/plasma_decon + max_occurrences = 0 + +/datum/round_event/vent_clog/beer + reagentsAmount = 100 + +/datum/round_event/vent_clog/beer/announce() + priority_announce("The scrubbers network is experiencing an unexpected surge of pressurized beer. Some ejection of contents may occur.", "Atmospherics alert") + +/datum/round_event/vent_clog/beer/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + R.add_reagent(/datum/reagent/consumable/ethanol/beer, reagentsAmount) + + 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) + 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 ed38b73771ea..8f07fbfb9256 100644 --- a/code/modules/fields/timestop.dm +++ b/code/modules/fields/timestop.dm @@ -1,184 +1,184 @@ - -/obj/effect/timestop - anchored = TRUE - name = "chronofield" - desc = "ZA WARUDO" - icon = 'icons/effects/160x160.dmi' - icon_state = "time" - layer = FLY_LAYER - pixel_x = -64 - pixel_y = -64 - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/list/immune = list() // the one who creates the timestop is immune, which includes wizards and the dead slime you murdered to make this chronofield - var/turf/target - var/freezerange = 2 - var/duration = 140 - var/datum/proximity_monitor/advanced/timestop/chronofield - alpha = 125 - var/check_anti_magic = FALSE - var/check_holy = FALSE - -/obj/effect/timestop/Initialize(mapload, radius, time, list/immune_atoms, start = TRUE) //Immune atoms assoc list atom = TRUE - . = ..() - if(!isnull(time)) - duration = time - if(!isnull(radius)) - freezerange = radius - for(var/A in immune_atoms) - immune[A] = TRUE - for(var/mob/living/L in GLOB.player_list) - if(locate(/obj/effect/proc_holder/spell/aoe_turf/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/frozen_things = list() - var/list/frozen_mobs = list() //cached separately for processing - var/check_anti_magic = FALSE - var/check_holy = FALSE - - var/static/list/global_frozen_atoms = list() - -/datum/proximity_monitor/advanced/timestop/Destroy() - unfreeze_all() - return ..() - -/datum/proximity_monitor/advanced/timestop/field_turf_crossed(atom/movable/AM) - freeze_atom(AM) - -/datum/proximity_monitor/advanced/timestop/proc/freeze_atom(atom/movable/A) - if(immune[A] || global_frozen_atoms[A] || !istype(A)) - return FALSE - if(ismob(A)) - var/mob/M = A - if(M.anti_magic_check(check_anti_magic, check_holy)) - immune[A] = TRUE - return - var/frozen = TRUE - if(isliving(A)) - freeze_mob(A) - else if(istype(A, /obj/item/projectile)) - freeze_projectile(A) - else if(istype(A, /obj/mecha)) - freeze_mecha(A) - else - frozen = FALSE - if(A.throwing) - freeze_throwing(A) - frozen = TRUE - if(!frozen) - return - - frozen_things[A] = A.move_resist - A.move_resist = INFINITY - global_frozen_atoms[A] = src - into_the_negative_zone(A) - RegisterSignal(A, COMSIG_MOVABLE_PRE_MOVE, .proc/unfreeze_atom) - RegisterSignal(A, COMSIG_ITEM_PICKUP, .proc/unfreeze_atom) - - return TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all() - for(var/i in frozen_things) - unfreeze_atom(i) - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_atom(atom/movable/A) - if(A.throwing) - unfreeze_throwing(A) - if(isliving(A)) - unfreeze_mob(A) - else if(istype(A, /obj/item/projectile)) - unfreeze_projectile(A) - else if(istype(A, /obj/mecha)) - unfreeze_mecha(A) - - UnregisterSignal(A, COMSIG_MOVABLE_PRE_MOVE) - UnregisterSignal(A, COMSIG_ITEM_PICKUP) - escape_the_negative_zone(A) - A.move_resist = frozen_things[A] - frozen_things -= A - global_frozen_atoms -= A - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_mecha(obj/mecha/M) - M.completely_disabled = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mecha(obj/mecha/M) - M.completely_disabled = FALSE - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) - var/datum/thrownthing/T = AM.throwing - T.paused = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throwing(atom/movable/AM) - var/datum/thrownthing/T = AM.throwing - if(T) - T.paused = FALSE - -/datum/proximity_monitor/advanced/timestop/process() - for(var/i in frozen_mobs) - var/mob/living/m = i - m.Stun(20, 1, 1) - -/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) - for(var/i in T.contents) - freeze_atom(i) - return ..() - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/item/projectile/P) - P.paused = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/item/projectile/P) - P.paused = FALSE - -/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) - frozen_mobs += L - L.Stun(20, 1, 1) - walk(L, 0) //stops them mid pathing even if they're stunimmune - if(isanimal(L)) - var/mob/living/simple_animal/S = L - S.toggle_ai(AI_OFF) - if(ishostile(L)) - var/mob/living/simple_animal/hostile/H = L - H.LoseTarget() - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) - L.AdjustStun(-20, 1, 1) - frozen_mobs -= L - if(isanimal(L)) - var/mob/living/simple_animal/S = L - S.toggle_ai(initial(S.AIStatus)) - -//you don't look quite right, is something the matter? -/datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A) - A.add_atom_colour(list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0), TEMPORARY_COLOUR_PRIORITY) - -//let's put some colour back into your cheeks -/datum/proximity_monitor/advanced/timestop/proc/escape_the_negative_zone(atom/A) - A.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) + +/obj/effect/timestop + anchored = TRUE + name = "chronofield" + desc = "ZA WARUDO" + icon = 'icons/effects/160x160.dmi' + icon_state = "time" + layer = FLY_LAYER + pixel_x = -64 + pixel_y = -64 + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/list/immune = list() // the one who creates the timestop is immune, which includes wizards and the dead slime you murdered to make this chronofield + var/turf/target + var/freezerange = 2 + var/duration = 140 + var/datum/proximity_monitor/advanced/timestop/chronofield + alpha = 125 + var/check_anti_magic = FALSE + var/check_holy = FALSE + +/obj/effect/timestop/Initialize(mapload, radius, time, list/immune_atoms, start = TRUE) //Immune atoms assoc list atom = TRUE + . = ..() + if(!isnull(time)) + duration = time + if(!isnull(radius)) + freezerange = radius + for(var/A in immune_atoms) + immune[A] = TRUE + for(var/mob/living/L in GLOB.player_list) + if(locate(/obj/effect/proc_holder/spell/aoe_turf/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/frozen_things = list() + var/list/frozen_mobs = list() //cached separately for processing + var/check_anti_magic = FALSE + var/check_holy = FALSE + + var/static/list/global_frozen_atoms = list() + +/datum/proximity_monitor/advanced/timestop/Destroy() + unfreeze_all() + return ..() + +/datum/proximity_monitor/advanced/timestop/field_turf_crossed(atom/movable/AM) + freeze_atom(AM) + +/datum/proximity_monitor/advanced/timestop/proc/freeze_atom(atom/movable/A) + if(immune[A] || global_frozen_atoms[A] || !istype(A)) + return FALSE + if(ismob(A)) + var/mob/M = A + if(M.anti_magic_check(check_anti_magic, check_holy)) + immune[A] = TRUE + return + var/frozen = TRUE + if(isliving(A)) + freeze_mob(A) + else if(istype(A, /obj/item/projectile)) + freeze_projectile(A) + else if(istype(A, /obj/mecha)) + freeze_mecha(A) + else + frozen = FALSE + if(A.throwing) + freeze_throwing(A) + frozen = TRUE + if(!frozen) + return + + frozen_things[A] = A.move_resist + A.move_resist = INFINITY + global_frozen_atoms[A] = src + into_the_negative_zone(A) + RegisterSignal(A, COMSIG_MOVABLE_PRE_MOVE, .proc/unfreeze_atom) + RegisterSignal(A, COMSIG_ITEM_PICKUP, .proc/unfreeze_atom) + + return TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all() + for(var/i in frozen_things) + unfreeze_atom(i) + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_atom(atom/movable/A) + if(A.throwing) + unfreeze_throwing(A) + if(isliving(A)) + unfreeze_mob(A) + else if(istype(A, /obj/item/projectile)) + unfreeze_projectile(A) + else if(istype(A, /obj/mecha)) + unfreeze_mecha(A) + + UnregisterSignal(A, COMSIG_MOVABLE_PRE_MOVE) + UnregisterSignal(A, COMSIG_ITEM_PICKUP) + escape_the_negative_zone(A) + A.move_resist = frozen_things[A] + frozen_things -= A + global_frozen_atoms -= A + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_mecha(obj/mecha/M) + M.completely_disabled = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mecha(obj/mecha/M) + M.completely_disabled = FALSE + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) + var/datum/thrownthing/T = AM.throwing + T.paused = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throwing(atom/movable/AM) + var/datum/thrownthing/T = AM.throwing + if(T) + T.paused = FALSE + +/datum/proximity_monitor/advanced/timestop/process() + for(var/i in frozen_mobs) + var/mob/living/m = i + m.Stun(20, 1, 1) + +/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) + for(var/i in T.contents) + freeze_atom(i) + return ..() + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/item/projectile/P) + P.paused = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/item/projectile/P) + P.paused = FALSE + +/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) + frozen_mobs += L + L.Stun(20, 1, 1) + walk(L, 0) //stops them mid pathing even if they're stunimmune + if(isanimal(L)) + var/mob/living/simple_animal/S = L + S.toggle_ai(AI_OFF) + if(ishostile(L)) + var/mob/living/simple_animal/hostile/H = L + H.LoseTarget() + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) + L.AdjustStun(-20, 1, 1) + frozen_mobs -= L + if(isanimal(L)) + var/mob/living/simple_animal/S = L + S.toggle_ai(initial(S.AIStatus)) + +//you don't look quite right, is something the matter? +/datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A) + A.add_atom_colour(list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0), TEMPORARY_COLOUR_PRIORITY) + +//let's put some colour back into your cheeks +/datum/proximity_monitor/advanced/timestop/proc/escape_the_negative_zone(atom/A) + A.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/Dreaming.dm index 165d2b4a3a8d..43c3337a4b3a 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 01e8c5be22c9..6a67eb0f2fa5 100644 --- a/code/modules/food_and_drinks/pizzabox.dm +++ b/code/modules/food_and_drinks/pizzabox.dm @@ -1,343 +1,343 @@ -/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 = 'yogstation/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) - . = 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 - - log_bomber(user, "has trapped a", src, "with [bomb] set to [bomb_timer * 2] seconds") - bomb.adminlog = "The [bomb.name] in [src.name] that [key_name(user)] activated has detonated!" - - to_chat(user, "You trap [src] with [bomb].") - update_icon() - else if(boxes.len) - var/obj/item/pizzabox/topbox = boxes[boxes.len] - boxes -= topbox - user.put_in_hands(topbox) - to_chat(user, "You remove the topmost [name] from the stack.") - topbox.update_icon() - update_icon() - user.regenerate_icons() - -/obj/item/pizzabox/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/pizzabox)) - var/obj/item/pizzabox/newbox = I - if(!open && !newbox.open) - var/list/add = list() - add += newbox - add += newbox.boxes - if(!user.transferItemToLoc(newbox, src)) - return - boxes += add - newbox.boxes.Cut() - to_chat(user, "You put [newbox] on top of [src]!") - newbox.update_icon() - update_icon() - user.regenerate_icons() - if(boxes.len >= 5) - if(prob(10 * boxes.len)) - to_chat(user, "You can't keep holding the stack!") - disperse_pizzas() - else - to_chat(user, "The stack is getting a little high...") - return - else - to_chat(user, "Close [open ? src : newbox] first!") - else if(istype(I, /obj/item/reagent_containers/food/snacks/pizza) || istype(I, /obj/item/reagent_containers/food/snacks/customizable/pizza)) - if(open) - if(pizza) - to_chat(user, "[src] already has \a [pizza.name]!") - return - if(!user.transferItemToLoc(I, src)) - return - pizza = I - to_chat(user, "You put [I] in [src].") - update_icon() - return - else if(istype(I, /obj/item/bombcore/miniature/pizza)) - if(open && !bomb) - if(!user.transferItemToLoc(I, src)) - return - wires = new /datum/wires/explosive/pizza(src) - bomb = I - to_chat(user, "You put [I] in [src]. Sneeki breeki...") - update_icon() - return - else if(bomb) - to_chat(user, "[src] already has a bomb in it!") - else if(istype(I, /obj/item/pen)) - if(!open) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on [src]!") - return - var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src - box.boxtag += stripped_input(user, "Write on [box]'s tag:", box, "", 30) - if(!user.canUseTopic(src, BE_CLOSE)) - return - to_chat(user, "You write with [I] on [src].") - update_icon() - return - else if(is_wire_tool(I)) - if(wires && bomb) - wires.interact(user) - else if(istype(I, /obj/item/reagent_containers/food)) - to_chat(user, "That's not a pizza!") - ..() - -/obj/item/pizzabox/process() - if(bomb_active && !bomb_defused && (bomb_timer > 0)) - playsound(loc, 'sound/items/timer.ogg', 50, 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/hit_atom, datum/thrownthing/throwingdatum) - if(boxes.len >= 2 && prob(20 * boxes.len)) - disperse_pizzas() - -/obj/item/pizzabox/proc/disperse_pizzas() - visible_message("The pizzas fall everywhere!") - for(var/V in boxes) - var/obj/item/pizzabox/P = V - var/fall_dir = pick(GLOB.alldirs) - step(P, fall_dir) - if(P.pizza && P.can_open_on_fall && prob(50)) //rip pizza - P.open = TRUE - P.pizza.forceMove(get_turf(P)) - fall_dir = pick(GLOB.alldirs) - step(P.pizza, fall_dir) - P.pizza = null - P.update_icon() - boxes -= P - update_icon() - if(isliving(loc)) - var/mob/living/L = loc - L.regenerate_icons() - -/obj/item/pizzabox/proc/unprocess() - STOP_PROCESSING(SSobj, src) - qdel(wires) - wires = null - update_icon() - -/obj/item/pizzabox/bomb/Initialize() - . = ..() - var/randompizza = pick(subtypesof(/obj/item/reagent_containers/food/snacks/pizza)) - pizza = new randompizza(src) - bomb = new(src) - wires = new /datum/wires/explosive/pizza(src) - -/obj/item/pizzabox/margherita/Initialize() - . = ..() - AddPizza() - boxtag = "Margherita Deluxe" - -/obj/item/pizzabox/margherita/proc/AddPizza() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita(src) - -/obj/item/pizzabox/margherita/robo/AddPizza() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita/robo(src) - -/obj/item/pizzabox/vegetable/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/vegetable(src) - boxtag = "Gourmet Vegatable" - -/obj/item/pizzabox/mushroom/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/mushroom(src) - boxtag = "Mushroom Special" - -/obj/item/pizzabox/meat/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/meat(src) - boxtag = "Meatlover's Supreme" - -/obj/item/pizzabox/pineapple/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) - boxtag = "Honolulu Chew" - -//An anomalous pizza box that, when opened, produces the opener's favorite kind of pizza. -/obj/item/pizzabox/infinite - resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF //hard to destroy - can_open_on_fall = FALSE - var/list/pizza_types = list( - /obj/item/reagent_containers/food/snacks/pizza/meat = 1, - /obj/item/reagent_containers/food/snacks/pizza/mushroom = 1, - /obj/item/reagent_containers/food/snacks/pizza/margherita = 1, - /obj/item/reagent_containers/food/snacks/pizza/sassysage = 0.8, - /obj/item/reagent_containers/food/snacks/pizza/vegetable = 0.8, - /obj/item/reagent_containers/food/snacks/pizza/pineapple = 0.5, - /obj/item/reagent_containers/food/snacks/pizza/donkpocket = 0.3, - /obj/item/reagent_containers/food/snacks/pizza/dank = 0.1) //pizzas here are weighted by chance to be someone's favorite - var/static/list/pizza_preferences - -/obj/item/pizzabox/infinite/Initialize() - . = ..() - if(!pizza_preferences) - pizza_preferences = list() - -/obj/item/pizzabox/infinite/examine(mob/user) - . = ..() - if(isobserver(user)) - . += "This pizza box is anomalous, and will produce infinite pizza." - -/obj/item/pizzabox/infinite/attack_self(mob/living/user) - QDEL_NULL(pizza) - if(ishuman(user)) - attune_pizza(user) - . = ..() - -/obj/item/pizzabox/infinite/proc/attune_pizza(mob/living/carbon/human/noms) //tonight on "proc names I never thought I'd type" - if(!pizza_preferences[noms.ckey]) - pizza_preferences[noms.ckey] = pickweight(pizza_types) - if(noms.has_quirk(/datum/quirk/pineapple_liker)) - pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/pineapple - else if(noms.has_quirk(/datum/quirk/pineapple_hater)) - var/list/pineapple_pizza_liker = pizza_types.Copy() - pineapple_pizza_liker -= /obj/item/reagent_containers/food/snacks/pizza/pineapple - pizza_preferences[noms.ckey] = pickweight(pineapple_pizza_liker) - else if(noms.mind && noms.mind.assigned_role == "Botanist") - pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/dank - - var/obj/item/pizza_type = pizza_preferences[noms.ckey] - pizza = new pizza_type (src) - pizza.foodtype = noms.dna.species.liked_food //it's our favorite! +/obj/item/bombcore/miniature/pizza + name = "pizza bomb" + desc = "Special delivery!" + icon_state = "pizzabomb_inactive" + 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 = 'yogstation/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) + . = 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 + + log_bomber(user, "has trapped a", src, "with [bomb] set to [bomb_timer * 2] seconds") + bomb.adminlog = "The [bomb.name] in [src.name] that [key_name(user)] activated has detonated!" + + to_chat(user, "You trap [src] with [bomb].") + update_icon() + else if(boxes.len) + var/obj/item/pizzabox/topbox = boxes[boxes.len] + boxes -= topbox + user.put_in_hands(topbox) + to_chat(user, "You remove the topmost [name] from the stack.") + topbox.update_icon() + update_icon() + user.regenerate_icons() + +/obj/item/pizzabox/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/pizzabox)) + var/obj/item/pizzabox/newbox = I + if(!open && !newbox.open) + var/list/add = list() + add += newbox + add += newbox.boxes + if(!user.transferItemToLoc(newbox, src)) + return + boxes += add + newbox.boxes.Cut() + to_chat(user, "You put [newbox] on top of [src]!") + newbox.update_icon() + update_icon() + user.regenerate_icons() + if(boxes.len >= 5) + if(prob(10 * boxes.len)) + to_chat(user, "You can't keep holding the stack!") + disperse_pizzas() + else + to_chat(user, "The stack is getting a little high...") + return + else + to_chat(user, "Close [open ? src : newbox] first!") + else if(istype(I, /obj/item/reagent_containers/food/snacks/pizza) || istype(I, /obj/item/reagent_containers/food/snacks/customizable/pizza)) + if(open) + if(pizza) + to_chat(user, "[src] already has \a [pizza.name]!") + return + if(!user.transferItemToLoc(I, src)) + return + pizza = I + to_chat(user, "You put [I] in [src].") + update_icon() + return + else if(istype(I, /obj/item/bombcore/miniature/pizza)) + if(open && !bomb) + if(!user.transferItemToLoc(I, src)) + return + wires = new /datum/wires/explosive/pizza(src) + bomb = I + to_chat(user, "You put [I] in [src]. Sneeki breeki...") + update_icon() + return + else if(bomb) + to_chat(user, "[src] already has a bomb in it!") + else if(istype(I, /obj/item/pen)) + if(!open) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on [src]!") + return + var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src + box.boxtag += stripped_input(user, "Write on [box]'s tag:", box, "", 30) + if(!user.canUseTopic(src, BE_CLOSE)) + return + to_chat(user, "You write with [I] on [src].") + update_icon() + return + else if(is_wire_tool(I)) + if(wires && bomb) + wires.interact(user) + else if(istype(I, /obj/item/reagent_containers/food)) + to_chat(user, "That's not a pizza!") + ..() + +/obj/item/pizzabox/process() + if(bomb_active && !bomb_defused && (bomb_timer > 0)) + playsound(loc, 'sound/items/timer.ogg', 50, 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/hit_atom, datum/thrownthing/throwingdatum) + if(boxes.len >= 2 && prob(20 * boxes.len)) + disperse_pizzas() + +/obj/item/pizzabox/proc/disperse_pizzas() + visible_message("The pizzas fall everywhere!") + for(var/V in boxes) + var/obj/item/pizzabox/P = V + var/fall_dir = pick(GLOB.alldirs) + step(P, fall_dir) + if(P.pizza && P.can_open_on_fall && prob(50)) //rip pizza + P.open = TRUE + P.pizza.forceMove(get_turf(P)) + fall_dir = pick(GLOB.alldirs) + step(P.pizza, fall_dir) + P.pizza = null + P.update_icon() + boxes -= P + update_icon() + if(isliving(loc)) + var/mob/living/L = loc + L.regenerate_icons() + +/obj/item/pizzabox/proc/unprocess() + STOP_PROCESSING(SSobj, src) + qdel(wires) + wires = null + update_icon() + +/obj/item/pizzabox/bomb/Initialize() + . = ..() + var/randompizza = pick(subtypesof(/obj/item/reagent_containers/food/snacks/pizza)) + pizza = new randompizza(src) + bomb = new(src) + wires = new /datum/wires/explosive/pizza(src) + +/obj/item/pizzabox/margherita/Initialize() + . = ..() + AddPizza() + boxtag = "Margherita Deluxe" + +/obj/item/pizzabox/margherita/proc/AddPizza() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita(src) + +/obj/item/pizzabox/margherita/robo/AddPizza() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita/robo(src) + +/obj/item/pizzabox/vegetable/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/vegetable(src) + boxtag = "Gourmet Vegatable" + +/obj/item/pizzabox/mushroom/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/mushroom(src) + boxtag = "Mushroom Special" + +/obj/item/pizzabox/meat/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/meat(src) + boxtag = "Meatlover's Supreme" + +/obj/item/pizzabox/pineapple/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) + boxtag = "Honolulu Chew" + +//An anomalous pizza box that, when opened, produces the opener's favorite kind of pizza. +/obj/item/pizzabox/infinite + resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF //hard to destroy + can_open_on_fall = FALSE + var/list/pizza_types = list( + /obj/item/reagent_containers/food/snacks/pizza/meat = 1, + /obj/item/reagent_containers/food/snacks/pizza/mushroom = 1, + /obj/item/reagent_containers/food/snacks/pizza/margherita = 1, + /obj/item/reagent_containers/food/snacks/pizza/sassysage = 0.8, + /obj/item/reagent_containers/food/snacks/pizza/vegetable = 0.8, + /obj/item/reagent_containers/food/snacks/pizza/pineapple = 0.5, + /obj/item/reagent_containers/food/snacks/pizza/donkpocket = 0.3, + /obj/item/reagent_containers/food/snacks/pizza/dank = 0.1) //pizzas here are weighted by chance to be someone's favorite + var/static/list/pizza_preferences + +/obj/item/pizzabox/infinite/Initialize() + . = ..() + if(!pizza_preferences) + pizza_preferences = list() + +/obj/item/pizzabox/infinite/examine(mob/user) + . = ..() + if(isobserver(user)) + . += "This pizza box is anomalous, and will produce infinite pizza." + +/obj/item/pizzabox/infinite/attack_self(mob/living/user) + QDEL_NULL(pizza) + if(ishuman(user)) + attune_pizza(user) + . = ..() + +/obj/item/pizzabox/infinite/proc/attune_pizza(mob/living/carbon/human/noms) //tonight on "proc names I never thought I'd type" + if(!pizza_preferences[noms.ckey]) + pizza_preferences[noms.ckey] = pickweight(pizza_types) + if(noms.has_quirk(/datum/quirk/pineapple_liker)) + pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/pineapple + else if(noms.has_quirk(/datum/quirk/pineapple_hater)) + var/list/pineapple_pizza_liker = pizza_types.Copy() + pineapple_pizza_liker -= /obj/item/reagent_containers/food/snacks/pizza/pineapple + pizza_preferences[noms.ckey] = pickweight(pineapple_pizza_liker) + else if(noms.mind && noms.mind.assigned_role == "Botanist") + pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/dank + + var/obj/item/pizza_type = pizza_preferences[noms.ckey] + pizza = new pizza_type (src) + pizza.foodtype = noms.dna.species.liked_food //it's our favorite! diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm index da2486faa925..fee0b2b7c91f 100644 --- a/code/modules/holodeck/holo_effect.dm +++ b/code/modules/holodeck/holo_effect.dm @@ -1,110 +1,110 @@ -/* - The holodeck activates these shortly after the program loads, - and deactivates them immediately before changing or disabling the holodeck. - - These remove snowflake code for special holodeck functions. -*/ -/obj/effect/holodeck_effect - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x2" - invisibility = INVISIBILITY_ABSTRACT - -/obj/effect/holodeck_effect/proc/activate(var/obj/machinery/computer/holodeck/HC) - return - -/obj/effect/holodeck_effect/proc/deactivate(var/obj/machinery/computer/holodeck/HC) - qdel(src) - return - -// Called by the holodeck computer as long as the program is running -/obj/effect/holodeck_effect/proc/tick(var/obj/machinery/computer/holodeck/HC) - return - -/obj/effect/holodeck_effect/proc/safety(var/active) - return - - -// Generates a holodeck-tracked card deck -/obj/effect/holodeck_effect/cards - icon = 'icons/obj/toy.dmi' - icon_state = "deck_nanotrasen_full" - var/obj/item/toy/cards/deck/D - -/obj/effect/holodeck_effect/cards/activate(var/obj/machinery/computer/holodeck/HC) - D = new(loc) - safety(!(HC.obj_flags & EMAGGED)) - D.holo = HC - return D - -/obj/effect/holodeck_effect/cards/safety(active) - if(!D) - return - if(active) - D.card_hitsound = null - D.card_force = 0 - D.card_throwforce = 0 - D.card_throw_speed = 3 - D.card_throw_range = 7 - D.card_attack_verb = list("attacked") - else - D.card_hitsound = 'sound/weapons/bladeslice.ogg' - D.card_force = 5 - D.card_throwforce = 10 - D.card_throw_speed = 3 - D.card_throw_range = 7 - D.card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut") - - -/obj/effect/holodeck_effect/sparks/activate(var/obj/machinery/computer/holodeck/HC) - var/turf/T = get_turf(src) - if(T) - var/datum/effect_system/spark_spread/s = new - s.set_up(3, 1, T) - s.start() - T.temperature = 5000 - T.hotspot_expose(50000,50000,1) - - - -/obj/effect/holodeck_effect/mobspawner - var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp - var/mob/mob = null - -/obj/effect/holodeck_effect/mobspawner/activate(var/obj/machinery/computer/holodeck/HC) - if(islist(mobtype)) - mobtype = pick(mobtype) - mob = new mobtype(loc) - - // these vars are not really standardized but all would theoretically create stuff on death - for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & mob.vars) - mob.vars[v] = null - return mob - -/obj/effect/holodeck_effect/mobspawner/deactivate(var/obj/machinery/computer/holodeck/HC) - if(mob) - HC.derez(mob) - qdel(src) - -/obj/effect/holodeck_effect/mobspawner/pet - mobtype = list( - /mob/living/simple_animal/butterfly, /mob/living/simple_animal/chick/holo, - /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/kitten, - /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/pet/dog/corgi/puppy, - /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/fox) - -/obj/effect/holodeck_effect/mobspawner/bee - mobtype = /mob/living/simple_animal/hostile/poison/bees/toxin - -/obj/effect/holodeck_effect/mobspawner/monkey - mobtype = /mob/living/simple_animal/holodeck_monkey - -/obj/effect/holodeck_effect/mobspawner/penguin - mobtype = /mob/living/simple_animal/pet/penguin/emperor - -/obj/effect/holodeck_effect/mobspawner/penguin/Initialize() - if(prob(1)) - mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero - return ..() - -/obj/effect/holodeck_effect/mobspawner/penguin_baby - mobtype = /mob/living/simple_animal/pet/penguin/baby +/* + The holodeck activates these shortly after the program loads, + and deactivates them immediately before changing or disabling the holodeck. + + These remove snowflake code for special holodeck functions. +*/ +/obj/effect/holodeck_effect + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x2" + invisibility = INVISIBILITY_ABSTRACT + +/obj/effect/holodeck_effect/proc/activate(var/obj/machinery/computer/holodeck/HC) + return + +/obj/effect/holodeck_effect/proc/deactivate(var/obj/machinery/computer/holodeck/HC) + qdel(src) + return + +// Called by the holodeck computer as long as the program is running +/obj/effect/holodeck_effect/proc/tick(var/obj/machinery/computer/holodeck/HC) + return + +/obj/effect/holodeck_effect/proc/safety(var/active) + return + + +// Generates a holodeck-tracked card deck +/obj/effect/holodeck_effect/cards + icon = 'icons/obj/toy.dmi' + icon_state = "deck_nanotrasen_full" + var/obj/item/toy/cards/deck/D + +/obj/effect/holodeck_effect/cards/activate(var/obj/machinery/computer/holodeck/HC) + D = new(loc) + safety(!(HC.obj_flags & EMAGGED)) + D.holo = HC + return D + +/obj/effect/holodeck_effect/cards/safety(active) + if(!D) + return + if(active) + D.card_hitsound = null + D.card_force = 0 + D.card_throwforce = 0 + D.card_throw_speed = 3 + D.card_throw_range = 7 + D.card_attack_verb = list("attacked") + else + D.card_hitsound = 'sound/weapons/bladeslice.ogg' + D.card_force = 5 + D.card_throwforce = 10 + D.card_throw_speed = 3 + D.card_throw_range = 7 + D.card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut") + + +/obj/effect/holodeck_effect/sparks/activate(var/obj/machinery/computer/holodeck/HC) + var/turf/T = get_turf(src) + if(T) + var/datum/effect_system/spark_spread/s = new + s.set_up(3, 1, T) + s.start() + T.temperature = 5000 + T.hotspot_expose(50000,50000,1) + + + +/obj/effect/holodeck_effect/mobspawner + var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp + var/mob/mob = null + +/obj/effect/holodeck_effect/mobspawner/activate(var/obj/machinery/computer/holodeck/HC) + if(islist(mobtype)) + mobtype = pick(mobtype) + mob = new mobtype(loc) + + // these vars are not really standardized but all would theoretically create stuff on death + for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & mob.vars) + mob.vars[v] = null + return mob + +/obj/effect/holodeck_effect/mobspawner/deactivate(var/obj/machinery/computer/holodeck/HC) + if(mob) + HC.derez(mob) + qdel(src) + +/obj/effect/holodeck_effect/mobspawner/pet + mobtype = list( + /mob/living/simple_animal/butterfly, /mob/living/simple_animal/chick/holo, + /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/kitten, + /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/pet/dog/corgi/puppy, + /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/fox) + +/obj/effect/holodeck_effect/mobspawner/bee + mobtype = /mob/living/simple_animal/hostile/poison/bees/toxin + +/obj/effect/holodeck_effect/mobspawner/monkey + mobtype = /mob/living/simple_animal/holodeck_monkey + +/obj/effect/holodeck_effect/mobspawner/penguin + mobtype = /mob/living/simple_animal/pet/penguin/emperor + +/obj/effect/holodeck_effect/mobspawner/penguin/Initialize() + if(prob(1)) + mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero + return ..() + +/obj/effect/holodeck_effect/mobspawner/penguin_baby + mobtype = /mob/living/simple_animal/pet/penguin/baby diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index e21c6db41f80..7b03d1219c4f 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, datum/thrownthing/throwingdatum) - ..() - 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.Paralyze(60) - visible_message("[M] is knocked right off [M.p_their()] feet!") - -// -// Structures -// - -/obj/structure/holohoop - name = "basketball hoop" - desc = "Boom, shakalaka!" - icon = 'icons/obj/basketball.dmi' - icon_state = "hoop" - anchored = TRUE - density = TRUE - -/obj/structure/holohoop/attackby(obj/item/W as obj, mob/user as mob, params) - if(get_dist(src,user)<2) - if(user.transferItemToLoc(W, drop_location())) - visible_message(" [user] dunks [W] into \the [src]!") - -/obj/structure/holohoop/attack_hand(mob/user) - . = ..() - if(.) - return - if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, "You need a better grip to do that!") - return - L.forceMove(loc) - L.Paralyze(100) - visible_message("[user] dunks [L] into \the [src]!") - user.stop_pulling() - else - ..() - -/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if (isitem(AM) && !istype(AM,/obj/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, datum/thrownthing/throwingdatum) + ..() + 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.Paralyze(60) + visible_message("[M] is knocked right off [M.p_their()] feet!") + +// +// Structures +// + +/obj/structure/holohoop + name = "basketball hoop" + desc = "Boom, shakalaka!" + icon = 'icons/obj/basketball.dmi' + icon_state = "hoop" + anchored = TRUE + density = TRUE + +/obj/structure/holohoop/attackby(obj/item/W as obj, mob/user as mob, params) + if(get_dist(src,user)<2) + if(user.transferItemToLoc(W, drop_location())) + visible_message(" [user] dunks [W] into \the [src]!") + +/obj/structure/holohoop/attack_hand(mob/user) + . = ..() + if(.) + return + if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) + var/mob/living/L = user.pulling + if(user.grab_state < GRAB_AGGRESSIVE) + to_chat(user, "You need a better grip to do that!") + return + L.forceMove(loc) + L.Paralyze(100) + visible_message("[user] dunks [L] into \the [src]!") + user.stop_pulling() + else + ..() + +/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if (isitem(AM) && !istype(AM,/obj/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 fd135cb8df52..b765f9c87e16 100644 --- a/code/modules/hydroponics/biogenerator.dm +++ b/code/modules/hydroponics/biogenerator.dm @@ -1,320 +1,320 @@ -/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/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Productivity at [productivity*100]%.
                Matter consumption reduced by [(efficiency*25)-25]%.
                Machine can hold up to [max_items] pieces of produce." - -/obj/machinery/biogenerator/on_reagent_change(changetype) //When the reagents change, change the icon as well. - update_icon() - -/obj/machinery/biogenerator/update_icon() - 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, 10) - var/datum/design/D = locate(href_list["create"]) - create_product(D, amount) - 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/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Productivity at [productivity*100]%.
                Matter consumption reduced by [(efficiency*25)-25]%.
                Machine can hold up to [max_items] pieces of produce." + +/obj/machinery/biogenerator/on_reagent_change(changetype) //When the reagents change, change the icon as well. + update_icon() + +/obj/machinery/biogenerator/update_icon() + 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, 10) + var/datum/design/D = locate(href_list["create"]) + create_product(D, amount) + updateUsrDialog() + + else if(href_list["menu"]) + menustat = "menu" + updateUsrDialog() diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm index d709095e7399..96e4ba0cb680 100644 --- a/code/modules/hydroponics/grown.dm +++ b/code/modules/hydroponics/grown.dm @@ -1,170 +1,170 @@ -// *********************************************************** -// Foods that are produced from hydroponics ~~~~~~~~~~ -// Data from the seeds carry over to these grown foods -// *********************************************************** - -// Base type. Subtypes are found in /grown dir. Lavaland-based subtypes can be found in mining/ash_flora.dm -/obj/item/reagent_containers/food/snacks/grown - icon = 'icons/obj/hydroponics/harvest.dmi' - var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. - var/plantname = "" - var/bitesize_mod = 0 - var/splat_type = /obj/effect/decal/cleanable/food/plant_smudge - // If set, bitesize = 1 + round(reagents.total_volume / bitesize_mod) - dried_type = -1 - // Saves us from having to define each stupid grown's dried_type as itself. - // If you don't want a plant to be driable (watermelons) set this to null in the time definition. - resistance_flags = FLAMMABLE - var/dry_grind = FALSE //If TRUE, this object needs to be dry to be ground up - var/can_distill = TRUE //If FALSE, this object cannot be distilled into an alcohol. - var/distill_reagent //If NULL and this object can be distilled, it uses a generic fruit_wine reagent and adjusts its variables. - var/wine_flavor //If NULL, this is automatically set to the fruit's flavor. Determines the flavor of the wine if distill_reagent is NULL. - var/wine_power = 10 //Determines the boozepwr of the wine if distill_reagent is NULL. - -/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, datum/thrownthing/throwingdatum) - if(!..()) //was it caught by a mob? - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_throw_impact(src, hit_atom) - if(seed.get_gene(/datum/plant_gene/trait/squash)) - squash(hit_atom) - -/obj/item/reagent_containers/food/snacks/grown/proc/squash(atom/target) - var/turf/T = get_turf(target) - forceMove(T) - if(ispath(splat_type, /obj/effect/decal/cleanable/food/plant_smudge)) - if(filling_color) - var/obj/O = new splat_type(T) - O.color = filling_color - O.name = "[name] smudge" - else if(splat_type) - new splat_type(T) - - if(trash) - generate_trash(T) - - visible_message("[src] 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)) - . = 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. Lavaland-based subtypes can be found in mining/ash_flora.dm +/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/food/plant_smudge + // If set, bitesize = 1 + round(reagents.total_volume / bitesize_mod) + dried_type = -1 + // Saves us from having to define each stupid grown's dried_type as itself. + // If you don't want a plant to be driable (watermelons) set this to null in the time definition. + resistance_flags = FLAMMABLE + var/dry_grind = FALSE //If TRUE, this object needs to be dry to be ground up + var/can_distill = TRUE //If FALSE, this object cannot be distilled into an alcohol. + var/distill_reagent //If NULL and this object can be distilled, it uses a generic fruit_wine reagent and adjusts its variables. + var/wine_flavor //If NULL, this is automatically set to the fruit's flavor. Determines the flavor of the wine if distill_reagent is NULL. + var/wine_power = 10 //Determines the boozepwr of the wine if distill_reagent is NULL. + +/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, datum/thrownthing/throwingdatum) + if(!..()) //was it caught by a mob? + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_throw_impact(src, hit_atom) + if(seed.get_gene(/datum/plant_gene/trait/squash)) + squash(hit_atom) + +/obj/item/reagent_containers/food/snacks/grown/proc/squash(atom/target) + var/turf/T = get_turf(target) + forceMove(T) + if(ispath(splat_type, /obj/effect/decal/cleanable/food/plant_smudge)) + if(filling_color) + var/obj/O = new splat_type(T) + O.color = filling_color + O.name = "[name] smudge" + else if(splat_type) + new splat_type(T) + + if(trash) + generate_trash(T) + + visible_message("[src] 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)) + . = 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 e4111755eec0..3a080c19977c 100644 --- a/code/modules/hydroponics/growninedible.dm +++ b/code/modules/hydroponics/growninedible.dm @@ -1,61 +1,61 @@ -// ********************** -// Other harvested materials from plants (that are not food) -// ********************** - -/obj/item/grown // Grown weapons - name = "grown_weapon" - icon = 'icons/obj/hydroponics/harvest.dmi' - resistance_flags = FLAMMABLE - var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. - -/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed) - . = ..() - create_reagents(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, datum/thrownthing/throwingdatum) - if(!..()) //was it caught by a mob? - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_throw_impact(src, hit_atom) - -/obj/item/grown/microwave_act(obj/machinery/microwave/M) - return - -/obj/item/grown/on_grind() - for(var/i in 1 to grind_results.len) - grind_results[grind_results[i]] = round(seed.potency) +// ********************** +// Other harvested materials from plants (that are not food) +// ********************** + +/obj/item/grown // Grown weapons + name = "grown_weapon" + icon = 'icons/obj/hydroponics/harvest.dmi' + resistance_flags = FLAMMABLE + var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. + +/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed) + . = ..() + create_reagents(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, datum/thrownthing/throwingdatum) + if(!..()) //was it caught by a mob? + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_throw_impact(src, hit_atom) + +/obj/item/grown/microwave_act(obj/machinery/microwave/M) + return + +/obj/item/grown/on_grind() + for(var/i in 1 to grind_results.len) + grind_results[grind_results[i]] = round(seed.potency) diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm index b1b92ff91ec1..7e4729f77c4d 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -1,207 +1,207 @@ -// 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/cultivator/rake - name = "rake" - icon_state = "rake" - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("slashed", "sliced", "bashed", "clawed") - hitsound = null - materials = null - flags_1 = NONE - resistance_flags = FLAMMABLE - -/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/hatchet/wooden - desc = "A crude axe blade upon a short wooden handle." - icon_state = "woodhatchet" - materials = null - flags_1 = NONE - -/obj/item/scythe - icon_state = "scythe0" - lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' - name = "scythe" - desc = "A sharp and curved blade on a long fibremetal handle, this tool makes it easy to reap what you sow." - force = 13 - throwforce = 5 - throw_speed = 2 - throw_range = 3 - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - armour_penetration = 20 - slot_flags = ITEM_SLOT_BACK - attack_verb = list("chopped", "sliced", "cut", "reaped") - hitsound = 'sound/weapons/bladeslice.ogg' - var/swiping = FALSE - -/obj/item/scythe/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 90, 105) - -/obj/item/scythe/suicide_act(mob/user) - user.visible_message("[user] is beheading [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - if(iscarbon(user)) - var/mob/living/carbon/C = user - var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) - if(BP) - BP.drop_limb() - playsound(src,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)) - swiping = TRUE - var/static/list/scythe_slash_angles = list(0, 45, 90, -45, -90) - for(var/i in scythe_slash_angles) - var/turf/T = get_step(user_turf, turn(dir_to_target, i)) - for(var/obj/structure/spacevine/V in T) - if(user.Adjacent(V)) - melee_attack_chain(user, V) - swiping = FALSE - -// ************************************* -// 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) +// 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/cultivator/rake + name = "rake" + icon_state = "rake" + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("slashed", "sliced", "bashed", "clawed") + hitsound = null + materials = null + flags_1 = NONE + resistance_flags = FLAMMABLE + +/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/hatchet/wooden + desc = "A crude axe blade upon a short wooden handle." + icon_state = "woodhatchet" + materials = null + flags_1 = NONE + +/obj/item/scythe + icon_state = "scythe0" + lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' + name = "scythe" + desc = "A sharp and curved blade on a long fibremetal handle, this tool makes it easy to reap what you sow." + force = 13 + throwforce = 5 + throw_speed = 2 + throw_range = 3 + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + armour_penetration = 20 + slot_flags = ITEM_SLOT_BACK + attack_verb = list("chopped", "sliced", "cut", "reaped") + hitsound = 'sound/weapons/bladeslice.ogg' + var/swiping = FALSE + +/obj/item/scythe/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 90, 105) + +/obj/item/scythe/suicide_act(mob/user) + user.visible_message("[user] is beheading [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) + if(BP) + BP.drop_limb() + playsound(src,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)) + swiping = TRUE + var/static/list/scythe_slash_angles = list(0, 45, 90, -45, -90) + for(var/i in scythe_slash_angles) + var/turf/T = get_step(user_turf, turn(dir_to_target, i)) + for(var/obj/structure/spacevine/V in T) + if(user.Adjacent(V)) + melee_attack_chain(user, V) + swiping = FALSE + +// ************************************* +// 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) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 5eb764273a39..a8eab2e3b2c0 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -1,938 +1,938 @@ -/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/constructable/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Tray efficiency at [rating*100]%." - - -/obj/machinery/hydroponics/Destroy() - if(myseed) - qdel(myseed) - myseed = null - return ..() - -/obj/machinery/hydroponics/constructable/attackby(obj/item/I, mob/user, params) - if (user.a_intent != INTENT_HARM) - // handle opening the panel - if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) - return - - // handle deconstructing the machine, if permissible - if(I.tool_behaviour == TOOL_CROWBAR && using_irrigation) - to_chat(user, "Disconnect the hoses first!") - return - else if(default_deconstruction_crowbar(I)) - return - - return ..() - -/obj/machinery/hydroponics/proc/FindConnected() - var/list/connected = list() - var/list/processing_atoms = list(src) - - while(processing_atoms.len) - var/atom/a = processing_atoms[1] - for(var/step_dir in GLOB.cardinals) - var/obj/machinery/hydroponics/h = locate() in get_step(a, step_dir) - // Soil plots aren't dense - if(h && h.using_irrigation && h.density && !(h in connected) && !(h in processing_atoms)) - processing_atoms += h - - processing_atoms -= a - connected += a - - return connected - - -/obj/machinery/hydroponics/bullet_act(obj/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////////////////////////////////////////////////////////// - - else if(pestlevel >= 5) - adjustHealth(-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() - 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].\n"+\ - "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) - to_chat(user, "It's filled with weeds!") - if(pestlevel >= 5) - to_chat(user, "It's filled with tiny worms!") - to_chat(user, "" ) - - -/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]!") - - -/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]!") - - -/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 - lastproduce = 0 - 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/uranium/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/uranium/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/uranium/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/uranium/radium, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/uranium/radium) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/uranium/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)) - - // NIGGA, YOU JUST WENT ON FULL RETARD. - 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)) - - // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial - 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)) - - // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial - 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)) - - // Man, you guys are retards - 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)) - - // SERIOUSLY - 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)) - - // why, just why - 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)) //At least give them a small reward if they bother. - - //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)) - - //Nicotine is used as a pesticide IRL. - if(S.has_reagent(/datum/reagent/drug/nicotine, 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/drug/nicotine))) - 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) - - // This 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)) - - // 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)) - - // Compost for EVERYTHING - if(S.has_reagent(/datum/reagent/consumable/virus_food, 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/virus_food) * 0.5)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/virus_food) * 0.5)) - - // FEED ME - if(S.has_reagent(/datum/reagent/blood, 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/blood) * 1)) - adjustPests(rand(2,4)) - - // FEED ME SEYMOUR - if(S.has_reagent(/datum/reagent/medicine/strange_reagent, 1)) - spawnplant() - - // 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)) - if(istype(reagent_source, /obj/item/reagent_containers/food/snacks)) - var/obj/item/reagent_containers/food/snacks/R = reagent_source - if (R.trash) - R.generate_trash(get_turf(user)) - visi_msg="[user] composts [reagent_source], spreading it through [target]" - transfer_amount = reagent_source.reagents.total_volume - else - transfer_amount = reagent_source.amount_per_transfer_from_this - if(istype(reagent_source, /obj/item/reagent_containers/syringe/)) - var/obj/item/reagent_containers/syringe/syr = reagent_source - visi_msg="[user] injects [target] with [syr]" - if(syr.reagents.total_volume <= syr.amount_per_transfer_from_this) - syr.mode = 0 - else if(istype(reagent_source, /obj/item/reagent_containers/spray/)) - visi_msg="[user] sprays [target] with [reagent_source]" - playsound(loc, 'sound/effects/spray3.ogg', 50, 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, transfered_by = user) - 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 - 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((O.tool_behaviour == TOOL_WIRECUTTER) && unwrenchable) - if (!anchored) - to_chat(user, "Anchor the tray first!") - return - using_irrigation = !using_irrigation - O.play_tool_sound(src) - user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \ - "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.") - for(var/obj/machinery/hydroponics/h in range(1,src)) - h.update_icon() - - else if(istype(O, /obj/item/shovel/spade)) - if(!myseed && !weedlevel) - to_chat(user, "[src] doesn't have any plants or weeds!") - return - user.visible_message("[user] starts digging out [src]'s plants...", - "You start digging out [src]'s plants...") - if(O.use_tool(src, user, 50, volume=50) || (!myseed && !weedlevel)) - user.visible_message("[user] digs out the plants in [src]!", "You dig out all of [src]'s plants!") - if(myseed) //Could be that they're just using it as a de-weeder - age = 0 - plant_health = 0 - lastproduce = 0 - if(harvest) - harvest = FALSE //To make sure they can't just put in another seed and insta-harvest it - qdel(myseed) - myseed = null - 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() - 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 - 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(O.tool_behaviour == TOOL_SHOVEL && !istype(O, /obj/item/shovel/spade)) //Doesn't include spades because of uprooting plants - to_chat(user, "You clear up [src]!") - qdel(src) - else - return ..() +/obj/machinery/hydroponics + 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/constructable/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Tray efficiency at [rating*100]%." + + +/obj/machinery/hydroponics/Destroy() + if(myseed) + qdel(myseed) + myseed = null + return ..() + +/obj/machinery/hydroponics/constructable/attackby(obj/item/I, mob/user, params) + if (user.a_intent != INTENT_HARM) + // handle opening the panel + if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) + return + + // handle deconstructing the machine, if permissible + if(I.tool_behaviour == TOOL_CROWBAR && using_irrigation) + to_chat(user, "Disconnect the hoses first!") + return + else if(default_deconstruction_crowbar(I)) + return + + return ..() + +/obj/machinery/hydroponics/proc/FindConnected() + var/list/connected = list() + var/list/processing_atoms = list(src) + + while(processing_atoms.len) + var/atom/a = processing_atoms[1] + for(var/step_dir in GLOB.cardinals) + var/obj/machinery/hydroponics/h = locate() in get_step(a, step_dir) + // Soil plots aren't dense + if(h && h.using_irrigation && h.density && !(h in connected) && !(h in processing_atoms)) + processing_atoms += h + + processing_atoms -= a + connected += a + + return connected + + +/obj/machinery/hydroponics/bullet_act(obj/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////////////////////////////////////////////////////////// + + else if(pestlevel >= 5) + adjustHealth(-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() + 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].\n"+\ + "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) + to_chat(user, "It's filled with weeds!") + if(pestlevel >= 5) + to_chat(user, "It's filled with tiny worms!") + to_chat(user, "" ) + + +/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]!") + + +/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]!") + + +/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 + lastproduce = 0 + 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/uranium/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/uranium/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/uranium/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/uranium/radium, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/uranium/radium) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/uranium/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)) + + // NIGGA, YOU JUST WENT ON FULL RETARD. + 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)) + + // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial + 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)) + + // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial + 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)) + + // Man, you guys are retards + 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)) + + // SERIOUSLY + 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)) + + // why, just why + 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)) //At least give them a small reward if they bother. + + //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)) + + //Nicotine is used as a pesticide IRL. + if(S.has_reagent(/datum/reagent/drug/nicotine, 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/drug/nicotine))) + 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) + + // This 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)) + + // 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)) + + // Compost for EVERYTHING + if(S.has_reagent(/datum/reagent/consumable/virus_food, 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/virus_food) * 0.5)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/virus_food) * 0.5)) + + // FEED ME + if(S.has_reagent(/datum/reagent/blood, 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/blood) * 1)) + adjustPests(rand(2,4)) + + // FEED ME SEYMOUR + if(S.has_reagent(/datum/reagent/medicine/strange_reagent, 1)) + spawnplant() + + // 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)) + if(istype(reagent_source, /obj/item/reagent_containers/food/snacks)) + var/obj/item/reagent_containers/food/snacks/R = reagent_source + if (R.trash) + R.generate_trash(get_turf(user)) + visi_msg="[user] composts [reagent_source], spreading it through [target]" + transfer_amount = reagent_source.reagents.total_volume + else + transfer_amount = reagent_source.amount_per_transfer_from_this + if(istype(reagent_source, /obj/item/reagent_containers/syringe/)) + var/obj/item/reagent_containers/syringe/syr = reagent_source + visi_msg="[user] injects [target] with [syr]" + if(syr.reagents.total_volume <= syr.amount_per_transfer_from_this) + syr.mode = 0 + else if(istype(reagent_source, /obj/item/reagent_containers/spray/)) + visi_msg="[user] sprays [target] with [reagent_source]" + playsound(loc, 'sound/effects/spray3.ogg', 50, 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, transfered_by = user) + 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 + 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((O.tool_behaviour == TOOL_WIRECUTTER) && unwrenchable) + if (!anchored) + to_chat(user, "Anchor the tray first!") + return + using_irrigation = !using_irrigation + O.play_tool_sound(src) + user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \ + "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.") + for(var/obj/machinery/hydroponics/h in range(1,src)) + h.update_icon() + + else if(istype(O, /obj/item/shovel/spade)) + if(!myseed && !weedlevel) + to_chat(user, "[src] doesn't have any plants or weeds!") + return + user.visible_message("[user] starts digging out [src]'s plants...", + "You start digging out [src]'s plants...") + if(O.use_tool(src, user, 50, volume=50) || (!myseed && !weedlevel)) + user.visible_message("[user] digs out the plants in [src]!", "You dig out all of [src]'s plants!") + if(myseed) //Could be that they're just using it as a de-weeder + age = 0 + plant_health = 0 + lastproduce = 0 + if(harvest) + harvest = FALSE //To make sure they can't just put in another seed and insta-harvest it + qdel(myseed) + myseed = null + 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() + 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 + 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(O.tool_behaviour == TOOL_SHOVEL && !istype(O, /obj/item/shovel/spade)) //Doesn't include spades because of uprooting plants + to_chat(user, "You clear up [src]!") + qdel(src) + else + return ..() diff --git a/code/modules/hydroponics/seed_extractor.dm b/code/modules/hydroponics/seed_extractor.dm index 710d86a4a7b8..368fede6c08f 100644 --- a/code/modules/hydroponics/seed_extractor.dm +++ b/code/modules/hydroponics/seed_extractor.dm @@ -1,197 +1,197 @@ -/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/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce.
                Machine can store up to [max_seeds]% seeds." - -/obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params) - - if(default_deconstruction_screwdriver(user, "sextractor_open", "sextractor", O)) - return - - if(default_pry_open(O)) - return - - if(default_unfasten_wrench(user, O)) - return - - if(default_deconstruction_crowbar(O)) - return - - if(istype(O, /obj/item/storage/bag/plants)) - var/obj/item/storage/P = O - var/loaded = 0 - for(var/obj/item/seeds/G in P.contents) - if(contents.len >= max_seeds) - break - ++loaded - add_seed(G) - if (loaded) - to_chat(user, "You put as many seeds from \the [O.name] into [src] as you can.") - else - to_chat(user, "There are no seeds in \the [O.name].") - return - - else if(seedify(O,-1, src, user)) - to_chat(user, "You extract some seeds.") - return - else if (istype(O, /obj/item/seeds)) - if(add_seed(O)) - to_chat(user, "You add [O] to [src.name].") - updateUsrDialog() - return - else if(user.a_intent != INTENT_HARM) - to_chat(user, "You can't extract any seeds from \the [O.name]!") - else - return ..() - -/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/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce.
                Machine can store up to [max_seeds]% seeds." + +/obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params) + + if(default_deconstruction_screwdriver(user, "sextractor_open", "sextractor", O)) + return + + if(default_pry_open(O)) + return + + if(default_unfasten_wrench(user, O)) + return + + if(default_deconstruction_crowbar(O)) + return + + if(istype(O, /obj/item/storage/bag/plants)) + var/obj/item/storage/P = O + var/loaded = 0 + for(var/obj/item/seeds/G in P.contents) + if(contents.len >= max_seeds) + break + ++loaded + add_seed(G) + if (loaded) + to_chat(user, "You put as many seeds from \the [O.name] into [src] as you can.") + else + to_chat(user, "There are no seeds in \the [O.name].") + return + + else if(seedify(O,-1, src, user)) + to_chat(user, "You extract some seeds.") + return + else if (istype(O, /obj/item/seeds)) + if(add_seed(O)) + to_chat(user, "You add [O] to [src.name].") + updateUsrDialog() + return + else if(user.a_intent != INTENT_HARM) + to_chat(user, "You can't extract any seeds from \the [O.name]!") + else + return ..() + +/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 459aa8216648..4c193bbdb0a8 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -1,394 +1,394 @@ -// ******************************************************** -// 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/product // A type path. The thing that is created when the plant is harvested. - 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/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]) - -/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.genes = list() - for(var/g in genes) - var/datum/plant_gene/G = g - S.genes += G.Copy() - S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes. - return S - -/obj/item/seeds/proc/get_gene(typepath) - return (locate(typepath) in genes) - -/obj/item/seeds/proc/reagents_from_genes() - reagents_add = list() - for(var/datum/plant_gene/reagent/R in genes) - reagents_add[R.reagent_id] = R.rate - -/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) - result.Add(t_prod) // User gets a consumable - if(!t_prod) - return - t_amount++ - product_name = t_prod.name - 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/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 == /datum/reagent/blood) // Hack to make blood in plants always O- - data = list("blood_type" = "O-") - if(rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin) - // apple tastes of apple. - if(istype(T, /obj/item/reagent_containers/food/snacks/grown)) - var/obj/item/reagent_containers/food/snacks/grown/grown_edible = T - data = grown_edible.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 - ..() // 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)) - 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/product // A type path. The thing that is created when the plant is harvested. + 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/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]) + +/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.genes = list() + for(var/g in genes) + var/datum/plant_gene/G = g + S.genes += G.Copy() + S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes. + return S + +/obj/item/seeds/proc/get_gene(typepath) + return (locate(typepath) in genes) + +/obj/item/seeds/proc/reagents_from_genes() + reagents_add = list() + for(var/datum/plant_gene/reagent/R in genes) + reagents_add[R.reagent_id] = R.rate + +/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) + result.Add(t_prod) // User gets a consumable + if(!t_prod) + return + t_amount++ + product_name = t_prod.name + 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/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 == /datum/reagent/blood) // Hack to make blood in plants always O- + data = list("blood_type" = "O-") + if(rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin) + // apple tastes of apple. + if(istype(T, /obj/item/reagent_containers/food/snacks/grown)) + var/obj/item/reagent_containers/food/snacks/grown/grown_edible = T + data = grown_edible.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 + ..() // 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)) + 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/jobs/access.dm b/code/modules/jobs/access.dm index 04324778ac88..4e810bb96cce 100644 --- a/code/modules/jobs/access.dm +++ b/code/modules/jobs/access.dm @@ -1,378 +1,378 @@ - -//returns TRUE if this mob has sufficient access to use this object -/obj/proc/allowed(mob/M) - //check if it doesn't require any access at all - if(src.check_access(null)) - return TRUE - if(issilicon(M)) - if(ispAI(M)) - return FALSE - return TRUE //AI can do whatever it wants - if(IsAdminGhost(M)) - //Access can't stop the abuse - return TRUE - else if(istype(M) && SEND_SIGNAL(M, COMSIG_MOB_ALLOWED, src)) - return TRUE - else if(ishuman(M)) - var/mob/living/carbon/human/H = M - //if they are holding or wearing a card that has access, that works - if(check_access(H.get_active_held_item()) || src.check_access(H.wear_id)) - return TRUE - else if(ismonkey(M) || isalienadult(M)) - var/mob/living/carbon/george = M - //they can only hold things :( - if(check_access(george.get_active_held_item())) - return TRUE - else if(isanimal(M)) - var/mob/living/simple_animal/A = M - if(check_access(A.get_active_held_item()) || check_access(A.access_card)) - return TRUE - return FALSE - -/obj/item/proc/GetAccess() - return list() - -/obj/item/proc/GetID() - return null - -/obj/proc/text2access(access_text) - . = list() - if(!access_text) - return - var/list/split = splittext(access_text,";") - for(var/x in split) - var/n = text2num(x) - if(n) - . += n - -//Call this before using req_access or req_one_access directly -/obj/proc/gen_access() - //These generations have been moved out of /obj/New() because they were slowing down the creation of objects that never even used the access system. - if(!req_access) - req_access = list() - for(var/a in text2access(req_access_txt)) - req_access += a - if(!req_one_access) - req_one_access = list() - for(var/b in text2access(req_one_access_txt)) - req_one_access += b - -// Check if an item has access to this object -/obj/proc/check_access(obj/item/I) - return check_access_list(I ? I.GetAccess() : null) - -/obj/proc/check_access_list(list/access_list) - gen_access() - - if(!islist(req_access)) //something's very wrong - return TRUE - - if(!req_access.len && !length(req_one_access)) - return TRUE - - if(!length(access_list) || !islist(access_list)) - return FALSE - - for(var/req in req_access) - if(!(req in access_list)) //doesn't have this access - return FALSE - - if(length(req_one_access)) - for(var/req in req_one_access) - if(req in access_list) //has an access from the single access list - return TRUE - return FALSE - return TRUE - -/obj/proc/check_access_ntnet(datum/netdata/data) - return check_access_list(data.passkey) - -/proc/get_centcom_access(job) - switch(job) - if("VIP Guest") - return list(ACCESS_CENT_GENERAL) - if("Custodian") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("Thunderdome Overseer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) - if("CentCom Official") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) - if("Medical Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL) - if("Death Commando") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("Research Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_TELEPORTER, ACCESS_CENT_STORAGE) - if("Special Ops Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("Admiral") - return get_all_centcom_access() - if("CentCom Commander") - return get_all_centcom_access() - if("Emergency Response Team Commander") - return get_ert_access("commander") - if("Security Response Officer") - return get_ert_access("sec") - if("Engineer Response Officer") - return get_ert_access("eng") - if("Medical Response Officer") - return get_ert_access("med") - if("CentCom Bartender") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_BAR) - -/proc/get_all_accesses() - return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, - ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, - ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, - ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, - ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, - ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, - ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, - ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_QM, ACCESS_SURGERY, - ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, ACCESS_WEAPONS, - ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL, - ACCESS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_RC_ANNOUNCE, - ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK, ACCESS_CLONING, ACCESS_TCOM_ADMIN, ACCESS_PARAMEDIC, ACCESS_MANUFACTURING) //YOGS - yogs jobs - -/proc/get_all_centcom_access() - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE, ACCESS_CENT_TELEPORTER, ACCESS_CENT_CAPTAIN) - -/proc/get_ert_access(class) - switch(class) - if("commander") - return get_all_centcom_access() - if("sec") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING) - if("eng") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("med") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING) - -/proc/get_all_syndicate_access() - return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) - -/proc/get_region_accesses(code) - switch(code) - if(0) - return get_all_accesses() - if(1) //station general - return list(ACCESS_KITCHEN,ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER) - if(2) //security - return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_MECH_SECURITY, ACCESS_HOS) - if(3) //medbay - return list(ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_MECH_MEDICAL, ACCESS_CMO, ACCESS_PARAMEDIC) // yogs - Yog jobs - if(4) //research - return list(ACCESS_RESEARCH, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) - if(5) //engineering and maintenance - return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MECH_ENGINE, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_CE, ACCESS_TCOM_ADMIN, ACCESS_RC_ANNOUNCE) // yogs - Yog jobs - if(6) //supply - return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM, ACCESS_VAULT) - if(7) //command - return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HOP, ACCESS_CAPTAIN, ACCESS_VAULT) - -/proc/get_region_accesses_name(code) - switch(code) - if(0) - return "All" - if(1) //station general - return "General" - if(2) //security - return "Security" - if(3) //medbay - return "Medbay" - if(4) //research - return "Research" - if(5) //engineering and maintenance - return "Engineering" - if(6) //supply - return "Supply" - if(7) //command - return "Command" - -/proc/get_access_desc(A) - switch(A) - if(ACCESS_CARGO) - return "Cargo Bay" - if(ACCESS_SECURITY) - return "Security" - if(ACCESS_BRIG) - return "Holding Cells" - if(ACCESS_COURT) - return "Courtroom" - if(ACCESS_FORENSICS_LOCKERS) - return "Forensics" - if(ACCESS_MEDICAL) - return "Medical" - if(ACCESS_GENETICS) - return "Genetics Lab" - if(ACCESS_MORGUE) - return "Morgue" - if(ACCESS_TOX) - return "R&D Lab" - if(ACCESS_TOX_STORAGE) - return "Toxins Lab" - if(ACCESS_CHEMISTRY) - return "Chemistry Lab" - if(ACCESS_RD) - return "RD Office" - if(ACCESS_BAR) - return "Bar" - if(ACCESS_JANITOR) - return "Custodial Closet" - if(ACCESS_ENGINE) - return "Engineering" - if(ACCESS_ENGINE_EQUIP) - return "Power and Engineering Equipment" - if(ACCESS_MAINT_TUNNELS) - return "Maintenance" - if(ACCESS_EXTERNAL_AIRLOCKS) - return "External Airlocks" - if(ACCESS_CHANGE_IDS) - return "ID Console" - if(ACCESS_AI_UPLOAD) - return "AI Chambers" - if(ACCESS_TELEPORTER) - return "Teleporter" - if(ACCESS_EVA) - return "EVA" - if(ACCESS_HEADS) - return "Bridge" - if(ACCESS_CAPTAIN) - return "Captain" - if(ACCESS_ALL_PERSONAL_LOCKERS) - return "Personal Lockers" - if(ACCESS_CHAPEL_OFFICE) - return "Chapel Office" - if(ACCESS_TECH_STORAGE) - return "Technical Storage" - if(ACCESS_ATMOSPHERICS) - return "Atmospherics" - if(ACCESS_CREMATORIUM) - return "Crematorium" - if(ACCESS_ARMORY) - return "Armory" - if(ACCESS_CONSTRUCTION) - return "Construction" - if(ACCESS_KITCHEN) - return "Kitchen" - if(ACCESS_HYDROPONICS) - return "Hydroponics" - if(ACCESS_LIBRARY) - return "Library" - if(ACCESS_LAWYER) - return "Law Office" - if(ACCESS_ROBOTICS) - return "Robotics" - if(ACCESS_VIROLOGY) - return "Virology" - if(ACCESS_CMO) - return "CMO Office" - if(ACCESS_QM) - return "Quartermaster" - if(ACCESS_SURGERY) - return "Surgery" - if(ACCESS_THEATRE) - return "Theatre" - if(ACCESS_RESEARCH) - return "Science" - if(ACCESS_MINING) - return "Mining" - if(ACCESS_MAILSORTING) - return "Cargo Office" - if(ACCESS_VAULT) - return "Main Vault" - if(ACCESS_MINING_STATION) - return "Mining EVA" - if(ACCESS_XENOBIOLOGY) - return "Xenobiology Lab" - if(ACCESS_HOP) - return "HoP Office" - if(ACCESS_HOS) - return "HoS Office" - if(ACCESS_CE) - return "CE Office" - if(ACCESS_RC_ANNOUNCE) - return "RC Announcements" - if(ACCESS_KEYCARD_AUTH) - return "Keycode Auth." - if(ACCESS_TCOMSAT) - return "Telecommunications" - if(ACCESS_GATEWAY) - return "Gateway" - if(ACCESS_SEC_DOORS) - return "Brig" - if(ACCESS_MINERAL_STOREROOM) - return "Mineral Storage" - if(ACCESS_MINISAT) - return "AI Satellite" - if(ACCESS_WEAPONS) - return "Weapon Permit" - if(ACCESS_NETWORK) - return "Network Access" - if(ACCESS_CLONING) - return "Cloning Room" - if(ACCESS_MECH_MINING) - return "Mining Mech Access" - if(ACCESS_MECH_MEDICAL) - return "Medical Mech Access" - if(ACCESS_MECH_SECURITY) - return "Security Mech Access" - if(ACCESS_MECH_SCIENCE) - return "Science Mech Access" - if(ACCESS_MECH_ENGINE) - return "Engineering Mech Access" - // yogs start - Yog jobs - if(ACCESS_PARAMEDIC) - return "Paramedic" - if(ACCESS_TCOM_ADMIN) - return "Tcomms Admin" - if(ACCESS_MANUFACTURING) - return "Clerk" - // yogs end - -/proc/get_centcom_access_desc(A) - switch(A) - if(ACCESS_CENT_GENERAL) - return "Code Grey" - if(ACCESS_CENT_THUNDER) - return "Code Yellow" - if(ACCESS_CENT_STORAGE) - return "Code Orange" - if(ACCESS_CENT_LIVING) - return "Code Green" - if(ACCESS_CENT_MEDICAL) - return "Code White" - if(ACCESS_CENT_TELEPORTER) - return "Code Blue" - if(ACCESS_CENT_SPECOPS) - return "Code Black" - if(ACCESS_CENT_CAPTAIN) - return "Code Gold" - if(ACCESS_CENT_BAR) - return "Code Scotch" - -/proc/get_all_jobs() - return list("Assistant", "Captain", "Head of Personnel", "Bartender", "Cook", "Botanist", "Quartermaster", "Cargo Technician", - "Shaft Miner", "Clown", "Mime", "Janitor", "Curator", "Lawyer", "Chaplain", "Chief Engineer", "Station Engineer", - "Atmospheric Technician", "Chief Medical Officer", "Medical Doctor", "Chemist", "Geneticist", "Virologist", - // yogs start - Yog jobs - "Research Director", "Scientist", "Roboticist", "Head of Security", "Warden", "Detective", "Security Officer", - "Signal Technician", "Mining Medic", "Paramedic", "Psychiatrist", "Clerk", "Tourist", "Space Bartender") - // yogs end - -/proc/get_all_job_icons() //For all existing HUD icons - return get_all_jobs() + list("Prisoner") - -/proc/get_all_centcom_jobs() - return list("VIP Guest","Custodian","Thunderdome Overseer","CentCom Official","Medical Officer","Death Commando","Research Officer","Special Ops Officer","Admiral","CentCom Commander","Emergency Response Team Commander","Security Response Officer","Engineer Response Officer", "Medical Response Officer","CentCom Bartender") - -/obj/item/proc/GetJobName() //Used in secHUD icon generation - var/obj/item/card/id/I = GetID() - if(!I) - return - var/jobName = I.assignment - if(jobName in get_all_job_icons()) //Check if the job has a hud icon - return jobName - if(jobName in get_all_centcom_jobs()) //Return with the NT logo if it is a CentCom job - return "CentCom" - return "Unknown" //Return unknown if none of the above apply + +//returns TRUE if this mob has sufficient access to use this object +/obj/proc/allowed(mob/M) + //check if it doesn't require any access at all + if(src.check_access(null)) + return TRUE + if(issilicon(M)) + if(ispAI(M)) + return FALSE + return TRUE //AI can do whatever it wants + if(IsAdminGhost(M)) + //Access can't stop the abuse + return TRUE + else if(istype(M) && SEND_SIGNAL(M, COMSIG_MOB_ALLOWED, src)) + return TRUE + else if(ishuman(M)) + var/mob/living/carbon/human/H = M + //if they are holding or wearing a card that has access, that works + if(check_access(H.get_active_held_item()) || src.check_access(H.wear_id)) + return TRUE + else if(ismonkey(M) || isalienadult(M)) + var/mob/living/carbon/george = M + //they can only hold things :( + if(check_access(george.get_active_held_item())) + return TRUE + else if(isanimal(M)) + var/mob/living/simple_animal/A = M + if(check_access(A.get_active_held_item()) || check_access(A.access_card)) + return TRUE + return FALSE + +/obj/item/proc/GetAccess() + return list() + +/obj/item/proc/GetID() + return null + +/obj/proc/text2access(access_text) + . = list() + if(!access_text) + return + var/list/split = splittext(access_text,";") + for(var/x in split) + var/n = text2num(x) + if(n) + . += n + +//Call this before using req_access or req_one_access directly +/obj/proc/gen_access() + //These generations have been moved out of /obj/New() because they were slowing down the creation of objects that never even used the access system. + if(!req_access) + req_access = list() + for(var/a in text2access(req_access_txt)) + req_access += a + if(!req_one_access) + req_one_access = list() + for(var/b in text2access(req_one_access_txt)) + req_one_access += b + +// Check if an item has access to this object +/obj/proc/check_access(obj/item/I) + return check_access_list(I ? I.GetAccess() : null) + +/obj/proc/check_access_list(list/access_list) + gen_access() + + if(!islist(req_access)) //something's very wrong + return TRUE + + if(!req_access.len && !length(req_one_access)) + return TRUE + + if(!length(access_list) || !islist(access_list)) + return FALSE + + for(var/req in req_access) + if(!(req in access_list)) //doesn't have this access + return FALSE + + if(length(req_one_access)) + for(var/req in req_one_access) + if(req in access_list) //has an access from the single access list + return TRUE + return FALSE + return TRUE + +/obj/proc/check_access_ntnet(datum/netdata/data) + return check_access_list(data.passkey) + +/proc/get_centcom_access(job) + switch(job) + if("VIP Guest") + return list(ACCESS_CENT_GENERAL) + if("Custodian") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("Thunderdome Overseer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) + if("CentCom Official") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) + if("Medical Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL) + if("Death Commando") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("Research Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_TELEPORTER, ACCESS_CENT_STORAGE) + if("Special Ops Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("Admiral") + return get_all_centcom_access() + if("CentCom Commander") + return get_all_centcom_access() + if("Emergency Response Team Commander") + return get_ert_access("commander") + if("Security Response Officer") + return get_ert_access("sec") + if("Engineer Response Officer") + return get_ert_access("eng") + if("Medical Response Officer") + return get_ert_access("med") + if("CentCom Bartender") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_BAR) + +/proc/get_all_accesses() + return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, + ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, + ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, + ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, + ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, + ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, + ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_QM, ACCESS_SURGERY, + ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, ACCESS_WEAPONS, + ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL, + ACCESS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK, ACCESS_CLONING, ACCESS_TCOM_ADMIN, ACCESS_PARAMEDIC, ACCESS_MANUFACTURING) //YOGS - yogs jobs + +/proc/get_all_centcom_access() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE, ACCESS_CENT_TELEPORTER, ACCESS_CENT_CAPTAIN) + +/proc/get_ert_access(class) + switch(class) + if("commander") + return get_all_centcom_access() + if("sec") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING) + if("eng") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("med") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING) + +/proc/get_all_syndicate_access() + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) + +/proc/get_region_accesses(code) + switch(code) + if(0) + return get_all_accesses() + if(1) //station general + return list(ACCESS_KITCHEN,ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER) + if(2) //security + return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_MECH_SECURITY, ACCESS_HOS) + if(3) //medbay + return list(ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_MECH_MEDICAL, ACCESS_CMO, ACCESS_PARAMEDIC) // yogs - Yog jobs + if(4) //research + return list(ACCESS_RESEARCH, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) + if(5) //engineering and maintenance + return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MECH_ENGINE, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_CE, ACCESS_TCOM_ADMIN, ACCESS_RC_ANNOUNCE) // yogs - Yog jobs + if(6) //supply + return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM, ACCESS_VAULT) + if(7) //command + return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HOP, ACCESS_CAPTAIN, ACCESS_VAULT) + +/proc/get_region_accesses_name(code) + switch(code) + if(0) + return "All" + if(1) //station general + return "General" + if(2) //security + return "Security" + if(3) //medbay + return "Medbay" + if(4) //research + return "Research" + if(5) //engineering and maintenance + return "Engineering" + if(6) //supply + return "Supply" + if(7) //command + return "Command" + +/proc/get_access_desc(A) + switch(A) + if(ACCESS_CARGO) + return "Cargo Bay" + if(ACCESS_SECURITY) + return "Security" + if(ACCESS_BRIG) + return "Holding Cells" + if(ACCESS_COURT) + return "Courtroom" + if(ACCESS_FORENSICS_LOCKERS) + return "Forensics" + if(ACCESS_MEDICAL) + return "Medical" + if(ACCESS_GENETICS) + return "Genetics Lab" + if(ACCESS_MORGUE) + return "Morgue" + if(ACCESS_TOX) + return "R&D Lab" + if(ACCESS_TOX_STORAGE) + return "Toxins Lab" + if(ACCESS_CHEMISTRY) + return "Chemistry Lab" + if(ACCESS_RD) + return "RD Office" + if(ACCESS_BAR) + return "Bar" + if(ACCESS_JANITOR) + return "Custodial Closet" + if(ACCESS_ENGINE) + return "Engineering" + if(ACCESS_ENGINE_EQUIP) + return "Power and Engineering Equipment" + if(ACCESS_MAINT_TUNNELS) + return "Maintenance" + if(ACCESS_EXTERNAL_AIRLOCKS) + return "External Airlocks" + if(ACCESS_CHANGE_IDS) + return "ID Console" + if(ACCESS_AI_UPLOAD) + return "AI Chambers" + if(ACCESS_TELEPORTER) + return "Teleporter" + if(ACCESS_EVA) + return "EVA" + if(ACCESS_HEADS) + return "Bridge" + if(ACCESS_CAPTAIN) + return "Captain" + if(ACCESS_ALL_PERSONAL_LOCKERS) + return "Personal Lockers" + if(ACCESS_CHAPEL_OFFICE) + return "Chapel Office" + if(ACCESS_TECH_STORAGE) + return "Technical Storage" + if(ACCESS_ATMOSPHERICS) + return "Atmospherics" + if(ACCESS_CREMATORIUM) + return "Crematorium" + if(ACCESS_ARMORY) + return "Armory" + if(ACCESS_CONSTRUCTION) + return "Construction" + if(ACCESS_KITCHEN) + return "Kitchen" + if(ACCESS_HYDROPONICS) + return "Hydroponics" + if(ACCESS_LIBRARY) + return "Library" + if(ACCESS_LAWYER) + return "Law Office" + if(ACCESS_ROBOTICS) + return "Robotics" + if(ACCESS_VIROLOGY) + return "Virology" + if(ACCESS_CMO) + return "CMO Office" + if(ACCESS_QM) + return "Quartermaster" + if(ACCESS_SURGERY) + return "Surgery" + if(ACCESS_THEATRE) + return "Theatre" + if(ACCESS_RESEARCH) + return "Science" + if(ACCESS_MINING) + return "Mining" + if(ACCESS_MAILSORTING) + return "Cargo Office" + if(ACCESS_VAULT) + return "Main Vault" + if(ACCESS_MINING_STATION) + return "Mining EVA" + if(ACCESS_XENOBIOLOGY) + return "Xenobiology Lab" + if(ACCESS_HOP) + return "HoP Office" + if(ACCESS_HOS) + return "HoS Office" + if(ACCESS_CE) + return "CE Office" + if(ACCESS_RC_ANNOUNCE) + return "RC Announcements" + if(ACCESS_KEYCARD_AUTH) + return "Keycode Auth." + if(ACCESS_TCOMSAT) + return "Telecommunications" + if(ACCESS_GATEWAY) + return "Gateway" + if(ACCESS_SEC_DOORS) + return "Brig" + if(ACCESS_MINERAL_STOREROOM) + return "Mineral Storage" + if(ACCESS_MINISAT) + return "AI Satellite" + if(ACCESS_WEAPONS) + return "Weapon Permit" + if(ACCESS_NETWORK) + return "Network Access" + if(ACCESS_CLONING) + return "Cloning Room" + if(ACCESS_MECH_MINING) + return "Mining Mech Access" + if(ACCESS_MECH_MEDICAL) + return "Medical Mech Access" + if(ACCESS_MECH_SECURITY) + return "Security Mech Access" + if(ACCESS_MECH_SCIENCE) + return "Science Mech Access" + if(ACCESS_MECH_ENGINE) + return "Engineering Mech Access" + // yogs start - Yog jobs + if(ACCESS_PARAMEDIC) + return "Paramedic" + if(ACCESS_TCOM_ADMIN) + return "Tcomms Admin" + if(ACCESS_MANUFACTURING) + return "Clerk" + // yogs end + +/proc/get_centcom_access_desc(A) + switch(A) + if(ACCESS_CENT_GENERAL) + return "Code Grey" + if(ACCESS_CENT_THUNDER) + return "Code Yellow" + if(ACCESS_CENT_STORAGE) + return "Code Orange" + if(ACCESS_CENT_LIVING) + return "Code Green" + if(ACCESS_CENT_MEDICAL) + return "Code White" + if(ACCESS_CENT_TELEPORTER) + return "Code Blue" + if(ACCESS_CENT_SPECOPS) + return "Code Black" + if(ACCESS_CENT_CAPTAIN) + return "Code Gold" + if(ACCESS_CENT_BAR) + return "Code Scotch" + +/proc/get_all_jobs() + return list("Assistant", "Captain", "Head of Personnel", "Bartender", "Cook", "Botanist", "Quartermaster", "Cargo Technician", + "Shaft Miner", "Clown", "Mime", "Janitor", "Curator", "Lawyer", "Chaplain", "Chief Engineer", "Station Engineer", + "Atmospheric Technician", "Chief Medical Officer", "Medical Doctor", "Chemist", "Geneticist", "Virologist", + // yogs start - Yog jobs + "Research Director", "Scientist", "Roboticist", "Head of Security", "Warden", "Detective", "Security Officer", + "Signal Technician", "Mining Medic", "Paramedic", "Psychiatrist", "Clerk", "Tourist", "Space Bartender") + // yogs end + +/proc/get_all_job_icons() //For all existing HUD icons + return get_all_jobs() + list("Prisoner") + +/proc/get_all_centcom_jobs() + return list("VIP Guest","Custodian","Thunderdome Overseer","CentCom Official","Medical Officer","Death Commando","Research Officer","Special Ops Officer","Admiral","CentCom Commander","Emergency Response Team Commander","Security Response Officer","Engineer Response Officer", "Medical Response Officer","CentCom Bartender") + +/obj/item/proc/GetJobName() //Used in secHUD icon generation + var/obj/item/card/id/I = GetID() + if(!I) + return + var/jobName = I.assignment + if(jobName in get_all_job_icons()) //Check if the job has a hud icon + return jobName + if(jobName in get_all_centcom_jobs()) //Return with the NT logo if it is a CentCom job + return "CentCom" + return "Unknown" //Return unknown if none of the above apply diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index 145238b09890..4bb6e73ff748 100644 --- a/code/modules/jobs/job_types/assistant.dm +++ b/code/modules/jobs/job_types/assistant.dm @@ -1,37 +1,37 @@ -/* -Assistant -*/ -/datum/job/assistant - title = "Assistant" - flag = ASSISTANT - department_flag = CIVILIAN - faction = "Station" - total_positions = 5 - spawn_positions = 5 - supervisors = "absolutely everyone" - selection_color = "#dddddd" - access = list() //See /datum/job/assistant/get_access() - minimal_access = list() //See /datum/job/assistant/get_access() - outfit = /datum/outfit/job/assistant - antag_rep = 7 - paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it. - paycheck_department = ACCOUNT_CIV - display_order = JOB_DISPLAY_ORDER_ASSISTANT - -/datum/job/assistant/get_access() - if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set - . = ..() - . |= list(ACCESS_MAINT_TUNNELS) - else - return ..() - -/datum/outfit/job/assistant - name = "Assistant" - jobtype = /datum/job/assistant - -/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/H) - ..() - if (CONFIG_GET(flag/grey_assistants)) - uniform = /obj/item/clothing/under/color/grey - else - uniform = /obj/item/clothing/under/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 + paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it. + paycheck_department = ACCOUNT_CIV + display_order = JOB_DISPLAY_ORDER_ASSISTANT + +/datum/job/assistant/get_access() + if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set + . = ..() + . |= list(ACCESS_MAINT_TUNNELS) + else + return ..() + +/datum/outfit/job/assistant + name = "Assistant" + jobtype = /datum/job/assistant + +/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/H) + ..() + if (CONFIG_GET(flag/grey_assistants)) + uniform = /obj/item/clothing/under/color/grey + else + uniform = /obj/item/clothing/under/color/random diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 57e993f07479..d8512fd09181 100755 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -1,66 +1,66 @@ -/datum/job/captain - title = "Captain" - flag = CAPTAIN - auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY - department_head = list("CentCom") - department_flag = ENGSEC - faction = "Station" - total_positions = 1 - spawn_positions = 1 - supervisors = "Nanotrasen officials and Space law" - selection_color = "#ccccff" - req_admin_notify = 1 - space_law_notify = 1 //Yogs - minimal_player_age = 14 - exp_requirements = 180 - exp_type = EXP_TYPE_CREW - exp_type_department = EXP_TYPE_COMMAND - - outfit = /datum/outfit/job/captain - - access = list() //See get_access() - minimal_access = list() //See get_access() - paycheck = PAYCHECK_COMMAND - paycheck_department = ACCOUNT_SEC - - mind_traits = list(TRAIT_DISK_VERIFIER) - - display_order = JOB_DISPLAY_ORDER_CAPTAIN - -/datum/job/captain/get_access() - return get_all_accesses() - -/datum/job/captain/announce(mob/living/carbon/human/H) - ..() - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.real_name] on deck!")) - -/datum/outfit/job/captain - name = "Captain" - jobtype = /datum/job/captain - - id = /obj/item/card/id/gold - belt = /obj/item/pda/captain - glasses = /obj/item/clothing/glasses/sunglasses - ears = /obj/item/radio/headset/heads/captain/alt - gloves = /obj/item/clothing/gloves/color/captain - uniform = /obj/item/clothing/under/rank/captain - suit = /obj/item/clothing/suit/armor/vest/capcarapace - shoes = /obj/item/clothing/shoes/sneakers/brown - head = /obj/item/clothing/head/caphat - backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1, /obj/item/gun/energy/e_gun=1) //yogs - adds egun/removes civ budget - - backpack = /obj/item/storage/backpack/captain - satchel = /obj/item/storage/backpack/satchel/cap - duffelbag = /obj/item/storage/backpack/duffelbag/captain - - implants = list(/obj/item/implant/mindshield) - accessory = /obj/item/clothing/accessory/medal/gold/captain - - chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain) - -/datum/outfit/job/captain/hardsuit - name = "Captain (Hardsuit)" - - mask = /obj/item/clothing/mask/gas/sechailer - suit = /obj/item/clothing/suit/space/hardsuit/swat/captain - suit_store = /obj/item/tank/internals/oxygen +/datum/job/captain + title = "Captain" + flag = CAPTAIN + auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY + department_head = list("CentCom") + department_flag = ENGSEC + faction = "Station" + total_positions = 1 + spawn_positions = 1 + supervisors = "Nanotrasen officials and Space law" + selection_color = "#ccccff" + req_admin_notify = 1 + space_law_notify = 1 //Yogs + minimal_player_age = 14 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + exp_type_department = EXP_TYPE_COMMAND + + outfit = /datum/outfit/job/captain + + access = list() //See get_access() + minimal_access = list() //See get_access() + paycheck = PAYCHECK_COMMAND + paycheck_department = ACCOUNT_SEC + + mind_traits = list(TRAIT_DISK_VERIFIER) + + display_order = JOB_DISPLAY_ORDER_CAPTAIN + +/datum/job/captain/get_access() + return get_all_accesses() + +/datum/job/captain/announce(mob/living/carbon/human/H) + ..() + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.real_name] on deck!")) + +/datum/outfit/job/captain + name = "Captain" + jobtype = /datum/job/captain + + id = /obj/item/card/id/gold + belt = /obj/item/pda/captain + glasses = /obj/item/clothing/glasses/sunglasses + ears = /obj/item/radio/headset/heads/captain/alt + gloves = /obj/item/clothing/gloves/color/captain + uniform = /obj/item/clothing/under/rank/captain + suit = /obj/item/clothing/suit/armor/vest/capcarapace + shoes = /obj/item/clothing/shoes/sneakers/brown + head = /obj/item/clothing/head/caphat + backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1, /obj/item/gun/energy/e_gun=1) //yogs - adds egun/removes civ budget + + backpack = /obj/item/storage/backpack/captain + satchel = /obj/item/storage/backpack/satchel/cap + duffelbag = /obj/item/storage/backpack/duffelbag/captain + + implants = list(/obj/item/implant/mindshield) + accessory = /obj/item/clothing/accessory/medal/gold/captain + + chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain) + +/datum/outfit/job/captain/hardsuit + name = "Captain (Hardsuit)" + + mask = /obj/item/clothing/mask/gas/sechailer + suit = /obj/item/clothing/suit/space/hardsuit/swat/captain + suit_store = /obj/item/tank/internals/oxygen diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm index 430a795be92b..ddfe3c845459 100644 --- a/code/modules/jobs/jobs.dm +++ b/code/modules/jobs/jobs.dm @@ -1,155 +1,155 @@ -GLOBAL_LIST_INIT(command_positions, list( - "Captain", - "Head of Personnel", - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer")) - - -GLOBAL_LIST_INIT(engineering_positions, list( - "Chief Engineer", - "Station Engineer", - // yogs start - Yog jobs - "Atmospheric Technician", - "Signal Technician")) - // yogs end - - -GLOBAL_LIST_INIT(medical_positions, list( - "Chief Medical Officer", - "Medical Doctor", - "Geneticist", - "Virologist", - // yogs start - Yog jobs - "Chemist", - "Paramedic", - "Psychiatrist", - "Mining Medic")) - // yogs end - - -GLOBAL_LIST_INIT(science_positions, list( - "Research Director", - "Scientist", - "Roboticist")) - - -GLOBAL_LIST_INIT(supply_positions, list( - "Head of Personnel", - "Quartermaster", - "Cargo Technician", - "Shaft Miner")) - - -GLOBAL_LIST_INIT(civilian_positions, list( - "Bartender", - "Botanist", - "Cook", - "Janitor", - "Curator", - "Lawyer", - "Chaplain", - "Clown", - "Mime", - // yogs start - Yog jobs - "Assistant", - "Clerk", - "Tourist")) - // yogs end - - -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 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 4d88bcb41601..b7ef701e3020 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 3f19008754bb..53e598b02b25 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 6684ab31f850..f046ecd34a63 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 beb8d7b7d008..f596904f8e86 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -1,351 +1,351 @@ -/* 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." - else - . += "It's secured in place with bolts." - switch(state) - if(0) - . += "There's a small crack visible on the back panel." - if(1) - . += "There's space inside for a wooden shelf." - if(2) - . += "There's a small crack visible on the shelf." - -/obj/structure/bookcase/Initialize(mapload) - . = ..() - if(!mapload) - return - state = 2 - icon_state = "book-0" - anchored = TRUE - for(var/obj/item/I in loc) - if(istype(I, /obj/item/book)) - I.forceMove(src) - update_icon() - -/obj/structure/bookcase/attackby(obj/item/I, mob/user, params) - switch(state) - if(0) - if(I.tool_behaviour == TOOL_WRENCH) - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You wrench the frame into place.") - anchored = TRUE - state = 1 - if(I.tool_behaviour == TOOL_CROWBAR) - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You pry the frame apart.") - deconstruct(TRUE) - - if(1) - if(istype(I, /obj/item/stack/sheet/mineral/wood)) - var/obj/item/stack/sheet/mineral/wood/W = I - if(W.get_amount() >= 2) - W.use(2) - to_chat(user, "You add a shelf.") - state = 2 - icon_state = "book-0" - if(I.tool_behaviour == TOOL_WRENCH) - I.play_tool_sound(src, 100) - to_chat(user, "You unwrench the frame.") - anchored = FALSE - state = 0 - - if(2) - var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) - if(is_type_in_list(I, allowed_books)) - if(!user.transferItemToLoc(I, src)) - return - update_icon() - else if(STR) - for(var/obj/item/T in I.contents) - if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook)) - STR.remove_from_storage(T, src) - to_chat(user, "You empty \the [I] into \the [src].") - update_icon() - else if(istype(I, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/newname = stripped_input(user, "What would you like to title this bookshelf?") - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(!newname) - return - else - name = "bookcase ([sanitize(newname)])" - else if(I.tool_behaviour == TOOL_CROWBAR) - if(contents.len) - to_chat(user, "You need to remove the books first!") - else - I.play_tool_sound(src, 100) - to_chat(user, "You pry the shelf out.") - new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) - state = 1 - icon_state = "bookempty" - else - return ..() - - -/obj/structure/bookcase/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!istype(user)) - return - if(contents.len) - var/obj/item/book/choice = input(user, "Which book would you like to remove from the shelf?") as null|obj in contents - if(choice) - if(!(user.mobility_flags & MOBILITY_USE) || user.stat || user.restrained() || !in_range(loc, user)) - return - if(ishuman(user)) - if(!user.get_active_held_item()) - user.put_in_hands(choice) - else - choice.forceMove(drop_location()) - update_icon() - - -/obj/structure/bookcase/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood(loc, 4) - for(var/obj/item/book/B in contents) - B.forceMove(get_turf(src)) - qdel(src) - - -/obj/structure/bookcase/update_icon() - 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(!user.can_read(src)) - return - if(dat) - user << browse("Penned by [author].
                " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") - user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) - onclose(user, "book") - else - to_chat(user, "This book is completely blank!") - - -/obj/item/book/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/pen)) - if(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) || I.tool_behaviour == TOOL_WIRECUTTER) - to_chat(user, "You begin to carve out [title]...") - if(do_after(user, 30, target = src)) - to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") - var/obj/item/storage/book/B = new - B.name = src.name - B.title = src.title - B.icon_state = src.icon_state - if(user.is_holding(src)) - qdel(src) - user.put_in_hands(B) - return - else - B.forceMove(drop_location()) - qdel(src) - return - return - else - ..() - - -/* - * Barcode Scanner - */ -/obj/item/barcodescanner - name = "barcode scanner" - icon = 'icons/obj/library.dmi' - icon_state ="scanner" - desc = "A fabulous tool if you need to scan a barcode." - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/obj/machinery/computer/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." + else + . += "It's secured in place with bolts." + switch(state) + if(0) + . += "There's a small crack visible on the back panel." + if(1) + . += "There's space inside for a wooden shelf." + if(2) + . += "There's a small crack visible on the shelf." + +/obj/structure/bookcase/Initialize(mapload) + . = ..() + if(!mapload) + return + state = 2 + icon_state = "book-0" + anchored = TRUE + for(var/obj/item/I in loc) + if(istype(I, /obj/item/book)) + I.forceMove(src) + update_icon() + +/obj/structure/bookcase/attackby(obj/item/I, mob/user, params) + switch(state) + if(0) + if(I.tool_behaviour == TOOL_WRENCH) + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You wrench the frame into place.") + anchored = TRUE + state = 1 + if(I.tool_behaviour == TOOL_CROWBAR) + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You pry the frame apart.") + deconstruct(TRUE) + + if(1) + if(istype(I, /obj/item/stack/sheet/mineral/wood)) + var/obj/item/stack/sheet/mineral/wood/W = I + if(W.get_amount() >= 2) + W.use(2) + to_chat(user, "You add a shelf.") + state = 2 + icon_state = "book-0" + if(I.tool_behaviour == TOOL_WRENCH) + I.play_tool_sound(src, 100) + to_chat(user, "You unwrench the frame.") + anchored = FALSE + state = 0 + + if(2) + var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) + if(is_type_in_list(I, allowed_books)) + if(!user.transferItemToLoc(I, src)) + return + update_icon() + else if(STR) + for(var/obj/item/T in I.contents) + if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook)) + STR.remove_from_storage(T, src) + to_chat(user, "You empty \the [I] into \the [src].") + update_icon() + else if(istype(I, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/newname = stripped_input(user, "What would you like to title this bookshelf?") + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(!newname) + return + else + name = "bookcase ([sanitize(newname)])" + else if(I.tool_behaviour == TOOL_CROWBAR) + if(contents.len) + to_chat(user, "You need to remove the books first!") + else + I.play_tool_sound(src, 100) + to_chat(user, "You pry the shelf out.") + new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) + state = 1 + icon_state = "bookempty" + else + return ..() + + +/obj/structure/bookcase/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!istype(user)) + return + if(contents.len) + var/obj/item/book/choice = input(user, "Which book would you like to remove from the shelf?") as null|obj in contents + if(choice) + if(!(user.mobility_flags & MOBILITY_USE) || user.stat || user.restrained() || !in_range(loc, user)) + return + if(ishuman(user)) + if(!user.get_active_held_item()) + user.put_in_hands(choice) + else + choice.forceMove(drop_location()) + update_icon() + + +/obj/structure/bookcase/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood(loc, 4) + for(var/obj/item/book/B in contents) + B.forceMove(get_turf(src)) + qdel(src) + + +/obj/structure/bookcase/update_icon() + 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(!user.can_read(src)) + return + if(dat) + user << browse("Penned by [author].
                " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") + user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) + onclose(user, "book") + else + to_chat(user, "This book is completely blank!") + + +/obj/item/book/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/pen)) + if(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) || I.tool_behaviour == TOOL_WIRECUTTER) + to_chat(user, "You begin to carve out [title]...") + if(do_after(user, 30, target = src)) + to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") + var/obj/item/storage/book/B = new + B.name = src.name + B.title = src.title + B.icon_state = src.icon_state + if(user.is_holding(src)) + qdel(src) + user.put_in_hands(B) + return + else + B.forceMove(drop_location()) + qdel(src) + return + return + else + ..() + + +/* + * Barcode Scanner + */ +/obj/item/barcodescanner + name = "barcode scanner" + icon = 'icons/obj/library.dmi' + icon_state ="scanner" + desc = "A fabulous tool if you need to scan a barcode." + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/obj/machinery/computer/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 cbffb981b925..7d816dda95bb 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -1,603 +1,603 @@ -/* 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) - if (prob(50)) - new /obj/item/melee/cultblade/dagger(get_turf(src)) - to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a sinister dagger sitting on the desk. You don't even remember where it came from...") - else - new /obj/item/clockwork/slab(get_turf(src)) - to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a 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)) - obj_flags |= EMAGGED - -/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) + if (prob(50)) + new /obj/item/melee/cultblade/dagger(get_turf(src)) + to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a sinister dagger sitting on the desk. You don't even remember where it came from...") + else + new /obj/item/clockwork/slab(get_turf(src)) + to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a 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)) + obj_flags |= EMAGGED + +/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 8fd12f6157d1..accd477387da 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/mapping/ruins.dm b/code/modules/mapping/ruins.dm index 035f0a62e3a8..25855c7085fd 100644 --- a/code/modules/mapping/ruins.dm +++ b/code/modules/mapping/ruins.dm @@ -1,142 +1,142 @@ -/datum/map_template/ruin/proc/try_to_place(z,allowed_areas) - var/sanity = PLACEMENT_TRIES - 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 = 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 TRUE - return FALSE - - -/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) - var/list/ruins_availible = list() //we can try these in the current pass - var/list/ruins_placed = list() // yogs - var/forced_z //If set we won't pick z level and use this one instead. - - //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 - 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(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 = PLACEMENT_TRIES - var/failed_to_place = TRUE - var/z_placed = 0 - while(placement_tries > 0) - placement_tries-- - z_placed = pick(z_levels) - if(!current_pick.try_to_place(forced_z ? forced_z : z_placed,whitelist)) - 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 - ruins_placed[current_pick.type] = TRUE // yogs - 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) - // yogs start - var/datum/map_template/ruin/RT = v - if(!initial(RT.allow_duplicates) && ruins_placed[v]) - continue - // yogs end - 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] = forced_z ? forced_z : z_placed //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 - forced_z = 0 - - //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) + var/sanity = PLACEMENT_TRIES + 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 = 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 TRUE + return FALSE + + +/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) + var/list/ruins_availible = list() //we can try these in the current pass + var/list/ruins_placed = list() // yogs + var/forced_z //If set we won't pick z level and use this one instead. + + //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 + 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(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 = PLACEMENT_TRIES + var/failed_to_place = TRUE + var/z_placed = 0 + while(placement_tries > 0) + placement_tries-- + z_placed = pick(z_levels) + if(!current_pick.try_to_place(forced_z ? forced_z : z_placed,whitelist)) + 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 + ruins_placed[current_pick.type] = TRUE // yogs + 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) + // yogs start + var/datum/map_template/ruin/RT = v + if(!initial(RT.allow_duplicates) && ruins_placed[v]) + continue + // yogs end + 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] = forced_z ? forced_z : z_placed //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 + forced_z = 0 + + //Update the availible list + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.cost > budget) + ruins_availible -= R + + log_world("Ruin loader finished with [budget] left to spend.") diff --git a/code/modules/mapping/space_management/multiz_helpers.dm b/code/modules/mapping/space_management/multiz_helpers.dm index 04415d0c1fd6..ca7cd8d52042 100644 --- a/code/modules/mapping/space_management/multiz_helpers.dm +++ b/code/modules/mapping/space_management/multiz_helpers.dm @@ -1,47 +1,47 @@ -/proc/get_step_multiz(ref, dir) - if(dir & UP) - dir &= ~UP - return get_step(SSmapping.get_turf_above(get_turf(ref)), dir) - if(dir & DOWN) - dir &= ~DOWN - return get_step(SSmapping.get_turf_below(get_turf(ref)), dir) - return get_step(ref, dir) - -/proc/get_dir_multiz(turf/us, turf/them) - us = get_turf(us) - them = get_turf(them) - if(!us || !them) - return NONE - if(us.z == them.z) - return get_dir(us, them) - else - var/turf/T = us.above() - var/dir = NONE - if(T && (T.z == them.z)) - dir = UP - else - T = us.below() - if(T && (T.z == them.z)) - dir = DOWN - else - return get_dir(us, them) - return (dir | get_dir(us, them)) - -/turf/proc/above() - return get_step_multiz(src, UP) - -/turf/proc/below() - return get_step_multiz(src, DOWN) - -/proc/dir_inverse_multiz(dir) - var/holder = dir & (UP|DOWN) - if((holder == NONE) || (holder == (UP|DOWN))) - return turn(dir, 180) - dir &= ~(UP|DOWN) - dir = turn(dir, 180) - if(holder == UP) - holder = DOWN - else - holder = UP - dir |= holder - return dir +/proc/get_step_multiz(ref, dir) + if(dir & UP) + dir &= ~UP + return get_step(SSmapping.get_turf_above(get_turf(ref)), dir) + if(dir & DOWN) + dir &= ~DOWN + return get_step(SSmapping.get_turf_below(get_turf(ref)), dir) + return get_step(ref, dir) + +/proc/get_dir_multiz(turf/us, turf/them) + us = get_turf(us) + them = get_turf(them) + if(!us || !them) + return NONE + if(us.z == them.z) + return get_dir(us, them) + else + var/turf/T = us.above() + var/dir = NONE + if(T && (T.z == them.z)) + dir = UP + else + T = us.below() + if(T && (T.z == them.z)) + dir = DOWN + else + return get_dir(us, them) + return (dir | get_dir(us, them)) + +/turf/proc/above() + return get_step_multiz(src, UP) + +/turf/proc/below() + return get_step_multiz(src, DOWN) + +/proc/dir_inverse_multiz(dir) + var/holder = dir & (UP|DOWN) + if((holder == NONE) || (holder == (UP|DOWN))) + return turn(dir, 180) + dir &= ~(UP|DOWN) + dir = turn(dir, 180) + if(holder == UP) + holder = DOWN + else + holder = UP + dir |= holder + return dir diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm index f1b3b1ccdc5d..265d2c996d0f 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -1,74 +1,74 @@ - -//Yes, they can only be rectangular. -//Yes, I'm sorry. -/datum/turf_reservation - var/list/reserved_turfs = list() - var/width = 0 - var/height = 0 - var/bottom_left_coords[3] - var/top_right_coords[3] - var/wipe_reservation_on_release = TRUE - var/turf_type = /turf/open/space - -/datum/turf_reservation/transit - turf_type = /turf/open/space/transit - -/datum/turf_reservation/proc/Release() - var/v = reserved_turfs.Copy() - for(var/i in reserved_turfs) - reserved_turfs -= i - SSmapping.used_turfs -= i - SSmapping.reserve_turfs(v) - -/datum/turf_reservation/proc/Reserve(width, height, zlevel) - if(width > world.maxx || height > world.maxy || width < 1 || height < 1) - return FALSE - var/list/avail = SSmapping.unused_turfs["[zlevel]"] - var/turf/BL - var/turf/TR - var/list/turf/final = list() - var/passing = FALSE - for(var/i in avail) - CHECK_TICK - BL = i - if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1)) - continue - if(BL.x + width > world.maxx || BL.y + height > world.maxy) - continue - TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z) - if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1)) - continue - final = block(BL, TR) - if(!final) - continue - passing = TRUE - for(var/I in final) - var/turf/checking = I - if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1)) - passing = FALSE - break - if(!passing) - continue - break - if(!passing || !istype(BL) || !istype(TR)) - return FALSE - bottom_left_coords = list(BL.x, BL.y, BL.z) - top_right_coords = list(TR.x, TR.y, TR.z) - for(var/i in final) - var/turf/T = i - reserved_turfs |= T - T.flags_1 &= ~UNUSED_RESERVATION_TURF_1 - SSmapping.unused_turfs["[T.z]"] -= T - SSmapping.used_turfs[T] = src - T.ChangeTurf(turf_type, turf_type) - src.width = width - src.height = height - return TRUE - -/datum/turf_reservation/New() - LAZYADD(SSmapping.turf_reservations, src) - -/datum/turf_reservation/Destroy() - Release() - LAZYREMOVE(SSmapping.turf_reservations, src) - return ..() + +//Yes, they can only be rectangular. +//Yes, I'm sorry. +/datum/turf_reservation + var/list/reserved_turfs = list() + var/width = 0 + var/height = 0 + var/bottom_left_coords[3] + var/top_right_coords[3] + var/wipe_reservation_on_release = TRUE + var/turf_type = /turf/open/space + +/datum/turf_reservation/transit + turf_type = /turf/open/space/transit + +/datum/turf_reservation/proc/Release() + var/v = reserved_turfs.Copy() + for(var/i in reserved_turfs) + reserved_turfs -= i + SSmapping.used_turfs -= i + SSmapping.reserve_turfs(v) + +/datum/turf_reservation/proc/Reserve(width, height, zlevel) + if(width > world.maxx || height > world.maxy || width < 1 || height < 1) + return FALSE + var/list/avail = SSmapping.unused_turfs["[zlevel]"] + var/turf/BL + var/turf/TR + var/list/turf/final = list() + var/passing = FALSE + for(var/i in avail) + CHECK_TICK + BL = i + if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1)) + continue + if(BL.x + width > world.maxx || BL.y + height > world.maxy) + continue + TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z) + if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1)) + continue + final = block(BL, TR) + if(!final) + continue + passing = TRUE + for(var/I in final) + var/turf/checking = I + if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1)) + passing = FALSE + break + if(!passing) + continue + break + if(!passing || !istype(BL) || !istype(TR)) + return FALSE + bottom_left_coords = list(BL.x, BL.y, BL.z) + top_right_coords = list(TR.x, TR.y, TR.z) + for(var/i in final) + var/turf/T = i + reserved_turfs |= T + T.flags_1 &= ~UNUSED_RESERVATION_TURF_1 + SSmapping.unused_turfs["[T.z]"] -= T + SSmapping.used_turfs[T] = src + T.ChangeTurf(turf_type, turf_type) + src.width = width + src.height = height + return TRUE + +/datum/turf_reservation/New() + LAZYADD(SSmapping.turf_reservations, src) + +/datum/turf_reservation/Destroy() + Release() + LAZYREMOVE(SSmapping.turf_reservations, src) + return ..() diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm index 541c3bb1df6b..f6990a5c678d 100644 --- a/code/modules/mining/equipment/mining_tools.dm +++ b/code/modules/mining/equipment/mining_tools.dm @@ -1,133 +1,133 @@ -/*****************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") - -/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_NORMAL - 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 - -/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 - -/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 - -/obj/item/pickaxe/drill/cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/pickaxe/drill/diamonddrill - name = "diamond-tipped mining drill" - icon_state = "diamonddrill" - toolspeed = 0.2 - desc = "Yours is the drill that will pierce the heavens!" - -/obj/item/pickaxe/drill/cyborg/diamond //This is the BORG version! - name = "diamond-tipped cyborg mining drill" //To inherit the NODROP_1 flag, and easier to change borg specific drill mechanics. - icon_state = "diamonddrill" - toolspeed = 0.2 - -/obj/item/pickaxe/drill/jackhammer - name = "sonic jackhammer" - icon_state = "jackhammer" - item_state = "jackhammer" - toolspeed = 0.1 //the epitome of powertools. extremely fast mining, laughs at puny walls - w_class = WEIGHT_CLASS_HUGE //the epitome of power(gamer)tools is CHUNCKY - 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." - -/obj/item/shovel - name = "shovel" - desc = "A large tool for digging and moving dirt." - icon = 'icons/obj/mining.dmi' - icon_state = "shovel" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 8 - tool_behaviour = TOOL_SHOVEL - toolspeed = 1 - usesound = 'sound/effects/shovel_dig.ogg' - throwforce = 4 - item_state = "shovel" - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=50) - attack_verb = list("bashed", "bludgeoned", "thrashed", "whacked") - sharpness = IS_SHARP - -/obj/item/shovel/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 150, 40) //it's sharp, so it works, but barely. - -/obj/item/shovel/suicide_act(mob/living/user) - user.visible_message("[user] begins digging their own grave! It looks like [user.p_theyre()] trying to commit suicide!") - if(use_tool(user, user, 30, volume=50)) - return BRUTELOSS - user.visible_message("[user] couldn't do it!") - return SHAME - -/obj/item/shovel/spade - name = "spade" - desc = "A small tool for digging and moving dirt." - icon_state = "spade" - item_state = "spade" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL +/*****************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") + +/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_NORMAL + 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 + +/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 + +/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 + +/obj/item/pickaxe/drill/cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/pickaxe/drill/diamonddrill + name = "diamond-tipped mining drill" + icon_state = "diamonddrill" + toolspeed = 0.2 + desc = "Yours is the drill that will pierce the heavens!" + +/obj/item/pickaxe/drill/cyborg/diamond //This is the BORG version! + name = "diamond-tipped cyborg mining drill" //To inherit the NODROP_1 flag, and easier to change borg specific drill mechanics. + icon_state = "diamonddrill" + toolspeed = 0.2 + +/obj/item/pickaxe/drill/jackhammer + name = "sonic jackhammer" + icon_state = "jackhammer" + item_state = "jackhammer" + toolspeed = 0.1 //the epitome of powertools. extremely fast mining, laughs at puny walls + w_class = WEIGHT_CLASS_HUGE //the epitome of power(gamer)tools is CHUNCKY + 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." + +/obj/item/shovel + name = "shovel" + desc = "A large tool for digging and moving dirt." + icon = 'icons/obj/mining.dmi' + icon_state = "shovel" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 8 + tool_behaviour = TOOL_SHOVEL + toolspeed = 1 + usesound = 'sound/effects/shovel_dig.ogg' + throwforce = 4 + item_state = "shovel" + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=50) + attack_verb = list("bashed", "bludgeoned", "thrashed", "whacked") + sharpness = IS_SHARP + +/obj/item/shovel/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 150, 40) //it's sharp, so it works, but barely. + +/obj/item/shovel/suicide_act(mob/living/user) + user.visible_message("[user] begins digging their own grave! It looks like [user.p_theyre()] trying to commit suicide!") + if(use_tool(user, user, 30, volume=50)) + return BRUTELOSS + user.visible_message("[user] couldn't do it!") + return SHAME + +/obj/item/shovel/spade + name = "spade" + desc = "A small tool for digging and moving dirt." + icon_state = "spade" + item_state = "spade" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL diff --git a/code/modules/mining/laborcamp/laborshuttle.dm b/code/modules/mining/laborcamp/laborshuttle.dm index 60983aa25a65..dbe607f42fa3 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 b3637bb3c1ca..9132700125cc 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -1,176 +1,176 @@ -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/item/card/id/prisoner/inserted_id - 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/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/card/id/prisoner)) - if(!inserted_id) - if(!user.transferItemToLoc(I, src)) - return - inserted_id = I - to_chat(user, "You insert [I].") - return - else - to_chat(user, "There's an ID inserted already.") - return ..() - -/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(inserted_id) - data["id"] = inserted_id - data["id_name"] = inserted_id.registered_name - data["points"] = inserted_id.points - data["goal"] = inserted_id.goal - if(check_auth()) - can_go_home = TRUE - - 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("handle_id") - if(inserted_id) - if(!usr.get_active_held_item()) - usr.put_in_hands(inserted_id) - inserted_id = null - else - inserted_id.forceMove(get_turf(src)) - inserted_id = null - else - var/obj/item/I = usr.get_active_held_item() - if(istype(I, /obj/item/card/id/prisoner)) - if(!usr.transferItemToLoc(I, src)) - return - inserted_id = I - if("claim_points") - inserted_id.points += stacking_machine.points - stacking_machine.points = 0 - to_chat(usr, "Points transferred.") - 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, "[inserted_id.registered_name] 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/check_auth() - if(obj_flags & EMAGGED) - return 1 //Shuttle is emagged, let any ol' person through - return (istype(inserted_id) && inserted_id.points >= inserted_id.goal) //Otherwise, only let them out if the prisoner's reached his quota. - -/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine() - stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) - if(stacking_machine) - stacking_machine.CONSOLE = src - else - qdel(src) - -/obj/machinery/mineral/labor_claim_console/emag_act(mob/user) - if(!(obj_flags & EMAGGED)) - obj_flags |= EMAGGED - to_chat(user, "PZZTTPFFFT") - - -/**********************Prisoner Collection Unit**************************/ - - -/obj/machinery/mineral/stacking_machine/laborstacker - force_connect = TRUE - var/points = 0 //The unclaimed value of ore stacked. - damage_deflection = 21 -/obj/machinery/mineral/stacking_machine/laborstacker/process_sheet(obj/item/stack/sheet/inp) - points += inp.point_value * inp.amount - ..() - -/obj/machinery/mineral/stacking_machine/laborstacker/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/stack/sheet) && user.canUnEquip(I)) - var/obj/item/stack/sheet/inp = I - points += inp.point_value * inp.amount - return ..() - -/**********************Point Lookup Console**************************/ -/obj/machinery/mineral/labor_points_checker - name = "points checking console" - desc = "A console used by prisoners to check the progress on their quotas. Simply swipe a prisoner ID." - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - density = FALSE - -/obj/machinery/mineral/labor_points_checker/attack_hand(mob/user) - . = ..() - if(.) - return - user.examinate(src) - -/obj/machinery/mineral/labor_points_checker/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/card/id)) - if(istype(I, /obj/item/card/id/prisoner)) - var/obj/item/card/id/prisoner/prisoner_id = I - to_chat(user, "ID: [prisoner_id.registered_name]") - to_chat(user, "Points Collected:[prisoner_id.points]") - to_chat(user, "Point Quota: [prisoner_id.goal]") - to_chat(user, "Collect points by bringing smelted minerals to the Labor Shuttle stacking machine. Reach your quota to earn your release.") - else - to_chat(user, "Error: Invalid ID") - else - return ..() +GLOBAL_LIST(labor_sheet_values) + +/**********************Prisoners' Console**************************/ + +/obj/machinery/mineral/labor_claim_console + name = "point claim console" + desc = "A stacking console with an electromagnetic writer, used to track ore mined by prisoners." + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + density = FALSE + var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null + var/machinedir = SOUTH + var/obj/item/card/id/prisoner/inserted_id + 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/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/card/id/prisoner)) + if(!inserted_id) + if(!user.transferItemToLoc(I, src)) + return + inserted_id = I + to_chat(user, "You insert [I].") + return + else + to_chat(user, "There's an ID inserted already.") + return ..() + +/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(inserted_id) + data["id"] = inserted_id + data["id_name"] = inserted_id.registered_name + data["points"] = inserted_id.points + data["goal"] = inserted_id.goal + if(check_auth()) + can_go_home = TRUE + + 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("handle_id") + if(inserted_id) + if(!usr.get_active_held_item()) + usr.put_in_hands(inserted_id) + inserted_id = null + else + inserted_id.forceMove(get_turf(src)) + inserted_id = null + else + var/obj/item/I = usr.get_active_held_item() + if(istype(I, /obj/item/card/id/prisoner)) + if(!usr.transferItemToLoc(I, src)) + return + inserted_id = I + if("claim_points") + inserted_id.points += stacking_machine.points + stacking_machine.points = 0 + to_chat(usr, "Points transferred.") + 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, "[inserted_id.registered_name] 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/check_auth() + if(obj_flags & EMAGGED) + return 1 //Shuttle is emagged, let any ol' person through + return (istype(inserted_id) && inserted_id.points >= inserted_id.goal) //Otherwise, only let them out if the prisoner's reached his quota. + +/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine() + stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) + if(stacking_machine) + stacking_machine.CONSOLE = src + else + qdel(src) + +/obj/machinery/mineral/labor_claim_console/emag_act(mob/user) + if(!(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + to_chat(user, "PZZTTPFFFT") + + +/**********************Prisoner Collection Unit**************************/ + + +/obj/machinery/mineral/stacking_machine/laborstacker + force_connect = TRUE + var/points = 0 //The unclaimed value of ore stacked. + damage_deflection = 21 +/obj/machinery/mineral/stacking_machine/laborstacker/process_sheet(obj/item/stack/sheet/inp) + points += inp.point_value * inp.amount + ..() + +/obj/machinery/mineral/stacking_machine/laborstacker/attackby(obj/item/I, mob/living/user) + if(istype(I, /obj/item/stack/sheet) && user.canUnEquip(I)) + var/obj/item/stack/sheet/inp = I + points += inp.point_value * inp.amount + return ..() + +/**********************Point Lookup Console**************************/ +/obj/machinery/mineral/labor_points_checker + name = "points checking console" + desc = "A console used by prisoners to check the progress on their quotas. Simply swipe a prisoner ID." + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + density = FALSE + +/obj/machinery/mineral/labor_points_checker/attack_hand(mob/user) + . = ..() + if(.) + return + user.examinate(src) + +/obj/machinery/mineral/labor_points_checker/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/card/id)) + if(istype(I, /obj/item/card/id/prisoner)) + var/obj/item/card/id/prisoner/prisoner_id = I + to_chat(user, "ID: [prisoner_id.registered_name]") + to_chat(user, "Points Collected:[prisoner_id.points]") + to_chat(user, "Point Quota: [prisoner_id.goal]") + to_chat(user, "Collect points by bringing smelted minerals to the Labor Shuttle stacking machine. Reach your quota to earn your release.") + else + to_chat(user, "Error: Invalid ID") + else + return ..() diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm index 16e8b0b26c8c..f5150b47530f 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 51864ae64db5..2392d22bdb2d 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -1,122 +1,122 @@ -/**********************Mineral stacking unit console**************************/ - -/obj/machinery/mineral/stacking_unit_console - name = "stacking machine console" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - desc = "Controls a stacking machine... in theory." - density = FALSE - circuit = /obj/item/circuitboard/machine/stacking_unit_console - var/obj/machinery/mineral/stacking_machine/machine - var/machinedir = SOUTHEAST - -/obj/machinery/mineral/stacking_unit_console/Initialize() - . = ..() - machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) - if (machine) - machine.CONSOLE = src - -/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user) - . = ..() - - if(!machine) - to_chat(user, "[src] is not linked to a machine!") - return - - var/obj/item/stack/sheet/s - var/dat - - dat += text("Stacking unit console

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

                ") - - user << browse(dat, "window=console_stacking_machine") - -/obj/machinery/mineral/stacking_unit_console/multitool_act(mob/living/user, obj/item/I) - if(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You store linkage information in [I]'s buffer.") - return TRUE - -/obj/machinery/mineral/stacking_unit_console/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - src.add_fingerprint(usr) - if(href_list["release"]) - if(!(text2path(href_list["release"]) in machine.stack_list)) - return //someone tried to spawn materials by spoofing hrefs - var/obj/item/stack/sheet/inp = machine.stack_list[text2path(href_list["release"])] - var/obj/item/stack/sheet/out = new inp.type(null, inp.amount) - inp.amount = 0 - machine.unload_mineral(out) - - src.updateUsrDialog() - return - - -/**********************Mineral stacking unit**************************/ - - -/obj/machinery/mineral/stacking_machine - name = "stacking machine" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "stacker" - desc = "A machine that automatically stacks acquired materials. Controlled by a nearby console." - density = TRUE - circuit = /obj/item/circuitboard/machine/stacking_machine - input_dir = EAST - output_dir = WEST - var/obj/machinery/mineral/stacking_unit_console/CONSOLE - var/stk_types = list() - var/stk_amt = list() - var/stack_list[0] //Key: Type. Value: Instance of type. - var/stack_amt = 50 //amount to stack before releassing - var/datum/component/remote_materials/materials - var/force_connect = FALSE - -/obj/machinery/mineral/stacking_machine/Initialize(mapload) - . = ..() - proximity_monitor = new(src, 1) - materials = AddComponent(/datum/component/remote_materials, "stacking", mapload, FALSE, mapload && force_connect) - -/obj/machinery/mineral/stacking_machine/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(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You store linkage information in [I]'s buffer.") + return TRUE + +/obj/machinery/mineral/stacking_unit_console/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + src.add_fingerprint(usr) + if(href_list["release"]) + if(!(text2path(href_list["release"]) in machine.stack_list)) + return //someone tried to spawn materials by spoofing hrefs + var/obj/item/stack/sheet/inp = machine.stack_list[text2path(href_list["release"])] + var/obj/item/stack/sheet/out = new inp.type(null, inp.amount) + inp.amount = 0 + machine.unload_mineral(out) + + src.updateUsrDialog() + return + + +/**********************Mineral stacking unit**************************/ + + +/obj/machinery/mineral/stacking_machine + name = "stacking machine" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "stacker" + desc = "A machine that automatically stacks acquired materials. Controlled by a nearby console." + density = TRUE + circuit = /obj/item/circuitboard/machine/stacking_machine + input_dir = EAST + output_dir = WEST + var/obj/machinery/mineral/stacking_unit_console/CONSOLE + var/stk_types = list() + var/stk_amt = list() + var/stack_list[0] //Key: Type. Value: Instance of type. + var/stack_amt = 50 //amount to stack before releassing + var/datum/component/remote_materials/materials + var/force_connect = FALSE + +/obj/machinery/mineral/stacking_machine/Initialize(mapload) + . = ..() + proximity_monitor = new(src, 1) + materials = AddComponent(/datum/component/remote_materials, "stacking", mapload, FALSE, mapload && force_connect) + +/obj/machinery/mineral/stacking_machine/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 e84c2d6d0d56..cea90bfce029 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 8204977058dd..f32afb2898da 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/money_bag.dm b/code/modules/mining/money_bag.dm index 8a686baea353..56fddac6bd0b 100644 --- a/code/modules/mining/money_bag.dm +++ b/code/modules/mining/money_bag.dm @@ -1,27 +1,27 @@ -/*****************************Money bag********************************/ - -/obj/item/storage/bag/money - name = "money bag" - icon_state = "moneybag" - force = 10 - throwforce = 0 - resistance_flags = FLAMMABLE - max_integrity = 100 - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/bag/money/Initialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_items = 40 - STR.max_combined_w_class = 40 - STR.set_holdable(list(/obj/item/coin, /obj/item/stack/spacecash, /obj/item/holochip)) - -/obj/item/storage/bag/money/vault/PopulateContents() - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/gold(src) - new /obj/item/coin/gold(src) - new /obj/item/coin/adamantine(src) +/*****************************Money bag********************************/ + +/obj/item/storage/bag/money + name = "money bag" + icon_state = "moneybag" + force = 10 + throwforce = 0 + resistance_flags = FLAMMABLE + max_integrity = 100 + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/bag/money/Initialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_items = 40 + STR.max_combined_w_class = 40 + STR.set_holdable(list(/obj/item/coin, /obj/item/stack/spacecash, /obj/item/holochip)) + +/obj/item/storage/bag/money/vault/PopulateContents() + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/gold(src) + new /obj/item/coin/gold(src) + new /obj/item/coin/adamantine(src) diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 4d77f6b7fedb..f52c5b0c2ed5 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -1,481 +1,481 @@ - -#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, datum/thrownthing/throwingdatum) - if(..() || !ishuman(hit_atom)) - return - var/mob/living/carbon/human/C = hit_atom - if(C.is_eyes_covered()) - C.visible_message("[C]'s eye protection blocks the sand!", "Your eye protection blocks the sand!") - return - C.adjust_blurriness(6) - C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage - C.confused += 5 - to_chat(C, "\The [src] gets into your eyes! The pain, it burns!") - qdel(src) - -/obj/item/stack/ore/glass/ex_act(severity, target) - if (severity == EXPLODE_NONE) - return - qdel(src) - -/obj/item/stack/ore/glass/basalt - name = "volcanic ash" - icon_state = "volcanic_sand" - 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) || I.tool_behaviour == TOOL_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/notify_admins = FALSE - if(z != 5)//Only annoy the admins ingame if we're triggered off the mining zlevel - notify_admins = TRUE - - if(triggered_by == 1) - log_bomber(null, "An explosion has primed a", src, "for detonation", notify_admins) - else if(triggered_by == 2) - var/turf/bombturf = get_turf(src) - if(notify_admins) - message_admins("A signal has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)]. Igniter attacher: [ADMIN_LOOKUPFLW(attacher)]") - var/bomb_message = "A signal has primed a [name] for detonation at [AREACOORD(bombturf)]. Igniter attacher: [key_name(attacher)]." - log_game(bomb_message) - GLOB.bombers += bomb_message - else - user.visible_message("[user] strikes \the [src], causing a chain reaction!", "You strike \the [src], causing a chain reaction.") - log_bomber(user, "has primed a", src, "for detonation", notify_admins) - det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE) - -/obj/item/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/get_item_credit_value() - return value - -/obj/item/coin/suicide_act(mob/living/user) - user.visible_message("[user] contemplates suicide with \the [src]!") - if (!attack_self(user)) - user.visible_message("[user] couldn't flip \the [src]!") - return SHAME - addtimer(CALLBACK(src, .proc/manual_suicide, user), 10)//10 = time takes for flip animation - return MANUAL_SUICIDE_NONLETHAL - -/obj/item/coin/proc/manual_suicide(mob/living/user) - var/index = sideslist.Find(coinflip) - if (index==2)//tails - user.visible_message("\the [src] lands on [coinflip]! [user] promptly falls over, dead!") - user.adjustOxyLoss(200) - user.death(0) - user.set_suicide(TRUE) - user.suicide_log() - else - user.visible_message("\the [src] lands on [coinflip]! [user] keeps on living!") - -/obj/item/coin/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 = 25 - 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 = 10 - 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 = 100 - 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 = 40 - 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 = 25 - 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 = 200 //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 = 100 - -/obj/item/coin/mythril - name = "mythril coin" - cmineral = "mythril" - icon_state = "coin_mythril_heads" - value = 300 - -/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, datum/thrownthing/throwingdatum) + if(..() || !ishuman(hit_atom)) + return + var/mob/living/carbon/human/C = hit_atom + if(C.is_eyes_covered()) + C.visible_message("[C]'s eye protection blocks the sand!", "Your eye protection blocks the sand!") + return + C.adjust_blurriness(6) + C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage + C.confused += 5 + to_chat(C, "\The [src] gets into your eyes! The pain, it burns!") + qdel(src) + +/obj/item/stack/ore/glass/ex_act(severity, target) + if (severity == EXPLODE_NONE) + return + qdel(src) + +/obj/item/stack/ore/glass/basalt + name = "volcanic ash" + icon_state = "volcanic_sand" + 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) || I.tool_behaviour == TOOL_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/notify_admins = FALSE + if(z != 5)//Only annoy the admins ingame if we're triggered off the mining zlevel + notify_admins = TRUE + + if(triggered_by == 1) + log_bomber(null, "An explosion has primed a", src, "for detonation", notify_admins) + else if(triggered_by == 2) + var/turf/bombturf = get_turf(src) + if(notify_admins) + message_admins("A signal has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)]. Igniter attacher: [ADMIN_LOOKUPFLW(attacher)]") + var/bomb_message = "A signal has primed a [name] for detonation at [AREACOORD(bombturf)]. Igniter attacher: [key_name(attacher)]." + log_game(bomb_message) + GLOB.bombers += bomb_message + else + user.visible_message("[user] strikes \the [src], causing a chain reaction!", "You strike \the [src], causing a chain reaction.") + log_bomber(user, "has primed a", src, "for detonation", notify_admins) + det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE) + +/obj/item/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/get_item_credit_value() + return value + +/obj/item/coin/suicide_act(mob/living/user) + user.visible_message("[user] contemplates suicide with \the [src]!") + if (!attack_self(user)) + user.visible_message("[user] couldn't flip \the [src]!") + return SHAME + addtimer(CALLBACK(src, .proc/manual_suicide, user), 10)//10 = time takes for flip animation + return MANUAL_SUICIDE_NONLETHAL + +/obj/item/coin/proc/manual_suicide(mob/living/user) + var/index = sideslist.Find(coinflip) + if (index==2)//tails + user.visible_message("\the [src] lands on [coinflip]! [user] promptly falls over, dead!") + user.adjustOxyLoss(200) + user.death(0) + user.set_suicide(TRUE) + user.suicide_log() + else + user.visible_message("\the [src] lands on [coinflip]! [user] keeps on living!") + +/obj/item/coin/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 = 25 + 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 = 10 + 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 = 100 + 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 = 40 + 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 = 25 + 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 = 200 //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 = 100 + +/obj/item/coin/mythril + name = "mythril coin" + cmineral = "mythril" + icon_state = "coin_mythril_heads" + value = 300 + +/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 b158815dacdc..e87754827b2d 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) - . = ..() - -/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) + . = ..() + +/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 b5c5c9a0cb18..a381cc512aac 100644 --- a/code/modules/mob/camera/camera.dm +++ b/code/modules/mob/camera/camera.dm @@ -1,27 +1,27 @@ -// Camera mob, used by AI camera and blob. - -/mob/camera - name = "camera mob" - density = FALSE - move_force = INFINITY - move_resist = INFINITY - status_flags = GODMODE // You can't damage it. - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - see_in_dark = 7 - invisibility = INVISIBILITY_ABSTRACT // No one can see us - sight = SEE_SELF - move_on_shuttle = FALSE - -/mob/camera/experience_pressure_difference() - return - -/mob/camera/forceMove(atom/destination) - var/oldloc = loc - loc = destination - Moved(oldloc, NONE, TRUE) - -/mob/camera/canUseStorage() - return FALSE - -/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE) - return +// Camera mob, used by AI camera and blob. + +/mob/camera + name = "camera mob" + density = FALSE + move_force = INFINITY + move_resist = INFINITY + status_flags = GODMODE // You can't damage it. + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + see_in_dark = 7 + invisibility = INVISIBILITY_ABSTRACT // No one can see us + sight = SEE_SELF + move_on_shuttle = FALSE + +/mob/camera/experience_pressure_difference() + return + +/mob/camera/forceMove(atom/destination) + var/oldloc = loc + loc = destination + Moved(oldloc, NONE, TRUE) + +/mob/camera/canUseStorage() + return FALSE + +/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE) + return diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index 7ab4ff1fe3ef..95b6598e3236 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -1,133 +1,133 @@ -//Dead mobs can exist whenever. This is needful - -INITIALIZE_IMMEDIATE(/mob/dead) - -/mob/dead - sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF - move_resist = INFINITY - throwforce = 0 - -/mob/dead/Initialize() - 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[1] - else - pick = input(src, "Pick a server to jump to", "Server Hop") as null|anything in csa - - if(!pick) - return - - var/addr = csa[pick] - - if(alert(src, "Jump to server [pick] ([addr])?", "Server Hop", "Yes", "No") != "Yes") - return - - var/client/C = client - to_chat(C, "Sending you to [pick].") - new /obj/screen/splash(C) - - notransform = TRUE - sleep(29) //let the animation play - notransform = FALSE - - if(!C) - return - - winset(src, null, "command=.options") //other wise the user never knows if byond is downloading resources - - C << link("[addr]?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/auto_deadmin_on_login() - return - -/mob/dead/Logout() - update_z(null) - return ..() - -/mob/dead/onTransitZ(old_z,new_z) - ..() - update_z(new_z) +//Dead mobs can exist whenever. This is needful + +INITIALIZE_IMMEDIATE(/mob/dead) + +/mob/dead + sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF + move_resist = INFINITY + throwforce = 0 + +/mob/dead/Initialize() + 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[1] + else + pick = input(src, "Pick a server to jump to", "Server Hop") as null|anything in csa + + if(!pick) + return + + var/addr = csa[pick] + + if(alert(src, "Jump to server [pick] ([addr])?", "Server Hop", "Yes", "No") != "Yes") + return + + var/client/C = client + to_chat(C, "Sending you to [pick].") + new /obj/screen/splash(C) + + notransform = TRUE + sleep(29) //let the animation play + notransform = FALSE + + if(!C) + return + + winset(src, null, "command=.options") //other wise the user never knows if byond is downloading resources + + C << link("[addr]?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/auto_deadmin_on_login() + return + +/mob/dead/Logout() + update_z(null) + return ..() + +/mob/dead/onTransitZ(old_z,new_z) + ..() + update_z(new_z) diff --git a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm index c93d9e05aa43..887556500cba 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/logout.dm b/code/modules/mob/dead/new_player/logout.dm index fd15f5e30971..a70800d35b53 100644 --- a/code/modules/mob/dead/new_player/logout.dm +++ b/code/modules/mob/dead/new_player/logout.dm @@ -1,7 +1,7 @@ -/mob/dead/new_player/Logout() - ready = 0 - ..() - if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to. - key = null//We null their key before deleting the mob, so they are properly kicked out. - qdel(src) +/mob/dead/new_player/Logout() + ready = 0 + ..() + if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to. + key = null//We null their key before deleting the mob, so they are properly kicked out. + qdel(src) return \ No newline at end of file diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index d0c6b502b8e3..af31639fe26d 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -1,547 +1,547 @@ -#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 - - 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 = "

                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))) - //yogs start -- donors bypassing the queue - if(ckey(key) in get_donators()) - to_chat(usr, "Because you are a donator, you have bypassed the queue! Thank you for donating!") - LateChoices() - return - //yogs end - 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(!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(!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") - observer.key = key - observer.client = client - observer.set_ghost_appearance() - if(observer.client && observer.client.prefs) - observer.real_name = observer.client.prefs.real_name - observer.name = observer.real_name - observer.update_icon() - observer.stop_sound_channel(CHANNEL_LOBBYMUSIC) - QDEL_NULL(mind) - qdel(src) - return TRUE - -/proc/get_job_unavailable_error_message(retval, jobtitle) - switch(retval) - if(JOB_AVAILABLE) - return "[jobtitle] is available." - if(JOB_UNAVAILABLE_GENERIC) - return "[jobtitle] is unavailable." - if(JOB_UNAVAILABLE_BANNED) - return "You are currently banned from [jobtitle]." - if(JOB_UNAVAILABLE_PLAYTIME) - return "You do not have enough relevant playtime for [jobtitle]." - if(JOB_UNAVAILABLE_ACCOUNTAGE) - return "Your account is not old enough for [jobtitle]." - if(JOB_UNAVAILABLE_SLOTFULL) - return "[jobtitle] is already filled to capacity." - return "Error: Unknown job availability." - -/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) - var/datum/job/job = SSjob.GetJob(rank) - if(!job) - return JOB_UNAVAILABLE_GENERIC - if((job.current_positions >= job.total_positions) && job.total_positions != -1) - if(job.title == "Assistant") - if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants - return JOB_AVAILABLE - for(var/datum/job/J in SSjob.occupations) - if(J && J.current_positions < J.total_positions && J.title != job.title) - return JOB_UNAVAILABLE_SLOTFULL - else - return JOB_UNAVAILABLE_SLOTFULL - if(is_banned_from(ckey, rank)) - return JOB_UNAVAILABLE_BANNED - if(QDELETED(src)) - return JOB_UNAVAILABLE_GENERIC - if(!job.player_old_enough(client)) - return JOB_UNAVAILABLE_ACCOUNTAGE - if(job.required_playtime_remaining(client)) - return JOB_UNAVAILABLE_PLAYTIME - if(latejoin && !job.special_check_latejoin(client)) - return JOB_UNAVAILABLE_GENERIC - return JOB_AVAILABLE - -/mob/dead/new_player/proc/AttemptLateSpawn(rank) - var/error = IsJobUnavailable(rank) - if(error != JOB_AVAILABLE) - alert(src, get_job_unavailable_error_message(error, rank)) - return FALSE - - if(SSticker.late_join_disabled) - alert(src, "An administrator has disabled late join spawning.") - return FALSE - - var/arrivals_docked = TRUE - if(SSshuttle.arrivals) - close_spawn_windows() //In case we get held up - if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin)) - src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.") - return FALSE - - if(CONFIG_GET(flag/arrivals_shuttle_require_undocked)) - SSshuttle.arrivals.RequireUndocked(src) - arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL - - //Remove the player from the join queue if he was in one and reset the timer - SSticker.queued_players -= src - SSticker.queue_delay = 4 - - SSjob.AssignRole(src, rank, 1) - - var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind - character.mind.quiet_round = character.client.prefs.yogtoggles & QUIET_ROUND // yogs - Donor Features - var/equip = SSjob.EquipRank(character, rank, TRUE) - if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob. - character = equip - - var/datum/job/job = SSjob.GetJob(rank) - - if(job && !job.override_latejoin_spawn(character)) - SSjob.SendToLateJoin(character) - if(!arrivals_docked) - var/obj/screen/splash/Spl = new(character.client, TRUE) - Spl.Fade(TRUE) - character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) - - character.update_parallax_teleport() - - SSticker.minds += character.mind - - var/mob/living/carbon/human/humanc - if(ishuman(character)) - humanc = character //Let's retypecast the var to be human, - - if(humanc) //These procs all expect humans - GLOB.data_core.manifest_inject(humanc) - if(SSshuttle.arrivals) - SSshuttle.arrivals.QueueAnnounce(humanc, rank) - else - AnnounceArrival(humanc, rank) - AddEmploymentContract(humanc) - if(GLOB.highlander) - to_chat(humanc, "THERE CAN BE ONLY ONE!!!") - humanc.make_scottish() - - if(GLOB.summon_guns_triggered) - give_guns(humanc) - if(GLOB.summon_magic_triggered) - give_magic(humanc) - if(GLOB.curse_of_madness_triggered) - give_madness(humanc, GLOB.curse_of_madness_triggered) - - GLOB.joined_player_list += character.ckey - - if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc && !character.mind.quiet_round) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. // yogs - Donor Features - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_RECALL, SHUTTLE_IDLE) - SSticker.mode.make_antag_chance(humanc) - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5) - SSticker.mode.make_antag_chance(humanc) - - if(humanc && CONFIG_GET(flag/roundstart_traits)) - SSquirks.AssignQuirks(humanc, humanc.client, TRUE) - - log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) - -/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) - //TODO: figure out a way to exclude wizards/nukeops/demons from this. - for(var/C in GLOB.employmentCabinets) - var/obj/structure/filingcabinet/employment/employmentCabinet = C - if(!employmentCabinet.virgin) - employmentCabinet.addFile(employee) - - -/mob/dead/new_player/proc/LateChoices() - var/list/dat = list("
                Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
                ") - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_ESCAPE) - dat += "
                The station has been evacuated.

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

                " - for(var/datum/job/prioritized_job in SSjob.prioritized_jobs) - if(prioritized_job.current_positions >= prioritized_job.total_positions) - SSjob.prioritized_jobs -= prioritized_job - dat += "
                " - var/column_counter = 0 - for(var/list/category in list(GLOB.command_positions) + list(GLOB.engineering_positions) + list(GLOB.supply_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.science_positions) + list(GLOB.security_positions) + list(GLOB.medical_positions) ) - var/cat_color = SSjob.name_occupations_all[category[1]].selection_color - dat += "
                " - dat += "[SSjob.name_occupations_all[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(column_counter > 0 && (column_counter % 3 == 0)) - dat += "
                " - dat += "
                " - dat += "" - var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580) - popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') - popup.set_content(jointext(dat, "")) - popup.open(FALSE) // 0 is passed to open so that it doesn't use the onclose() proc - -/mob/dead/new_player/proc/create_character(transfer_after) - spawning = 1 - close_spawn_windows() - - var/mob/living/carbon/human/H = new(loc) - - var/frn = CONFIG_GET(flag/force_random_names) - if(!frn) - frn = is_banned_from(ckey, "Appearance") - if(QDELETED(src)) - return - if(frn) - client.prefs.random_character() - client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) - 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 + + 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 = "

                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))) + //yogs start -- donors bypassing the queue + if(ckey(key) in get_donators()) + to_chat(usr, "Because you are a donator, you have bypassed the queue! Thank you for donating!") + LateChoices() + return + //yogs end + 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(!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(!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") + observer.key = key + observer.client = client + observer.set_ghost_appearance() + if(observer.client && observer.client.prefs) + observer.real_name = observer.client.prefs.real_name + observer.name = observer.real_name + observer.update_icon() + observer.stop_sound_channel(CHANNEL_LOBBYMUSIC) + QDEL_NULL(mind) + qdel(src) + return TRUE + +/proc/get_job_unavailable_error_message(retval, jobtitle) + switch(retval) + if(JOB_AVAILABLE) + return "[jobtitle] is available." + if(JOB_UNAVAILABLE_GENERIC) + return "[jobtitle] is unavailable." + if(JOB_UNAVAILABLE_BANNED) + return "You are currently banned from [jobtitle]." + if(JOB_UNAVAILABLE_PLAYTIME) + return "You do not have enough relevant playtime for [jobtitle]." + if(JOB_UNAVAILABLE_ACCOUNTAGE) + return "Your account is not old enough for [jobtitle]." + if(JOB_UNAVAILABLE_SLOTFULL) + return "[jobtitle] is already filled to capacity." + return "Error: Unknown job availability." + +/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) + var/datum/job/job = SSjob.GetJob(rank) + if(!job) + return JOB_UNAVAILABLE_GENERIC + if((job.current_positions >= job.total_positions) && job.total_positions != -1) + if(job.title == "Assistant") + if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants + return JOB_AVAILABLE + for(var/datum/job/J in SSjob.occupations) + if(J && J.current_positions < J.total_positions && J.title != job.title) + return JOB_UNAVAILABLE_SLOTFULL + else + return JOB_UNAVAILABLE_SLOTFULL + if(is_banned_from(ckey, rank)) + return JOB_UNAVAILABLE_BANNED + if(QDELETED(src)) + return JOB_UNAVAILABLE_GENERIC + if(!job.player_old_enough(client)) + return JOB_UNAVAILABLE_ACCOUNTAGE + if(job.required_playtime_remaining(client)) + return JOB_UNAVAILABLE_PLAYTIME + if(latejoin && !job.special_check_latejoin(client)) + return JOB_UNAVAILABLE_GENERIC + return JOB_AVAILABLE + +/mob/dead/new_player/proc/AttemptLateSpawn(rank) + var/error = IsJobUnavailable(rank) + if(error != JOB_AVAILABLE) + alert(src, get_job_unavailable_error_message(error, rank)) + return FALSE + + if(SSticker.late_join_disabled) + alert(src, "An administrator has disabled late join spawning.") + return FALSE + + var/arrivals_docked = TRUE + if(SSshuttle.arrivals) + close_spawn_windows() //In case we get held up + if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin)) + src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.") + return FALSE + + if(CONFIG_GET(flag/arrivals_shuttle_require_undocked)) + SSshuttle.arrivals.RequireUndocked(src) + arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL + + //Remove the player from the join queue if he was in one and reset the timer + SSticker.queued_players -= src + SSticker.queue_delay = 4 + + SSjob.AssignRole(src, rank, 1) + + var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind + character.mind.quiet_round = character.client.prefs.yogtoggles & QUIET_ROUND // yogs - Donor Features + var/equip = SSjob.EquipRank(character, rank, TRUE) + if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob. + character = equip + + var/datum/job/job = SSjob.GetJob(rank) + + if(job && !job.override_latejoin_spawn(character)) + SSjob.SendToLateJoin(character) + if(!arrivals_docked) + var/obj/screen/splash/Spl = new(character.client, TRUE) + Spl.Fade(TRUE) + character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) + + character.update_parallax_teleport() + + SSticker.minds += character.mind + + var/mob/living/carbon/human/humanc + if(ishuman(character)) + humanc = character //Let's retypecast the var to be human, + + if(humanc) //These procs all expect humans + GLOB.data_core.manifest_inject(humanc) + if(SSshuttle.arrivals) + SSshuttle.arrivals.QueueAnnounce(humanc, rank) + else + AnnounceArrival(humanc, rank) + AddEmploymentContract(humanc) + if(GLOB.highlander) + to_chat(humanc, "THERE CAN BE ONLY ONE!!!") + humanc.make_scottish() + + if(GLOB.summon_guns_triggered) + give_guns(humanc) + if(GLOB.summon_magic_triggered) + give_magic(humanc) + if(GLOB.curse_of_madness_triggered) + give_madness(humanc, GLOB.curse_of_madness_triggered) + + GLOB.joined_player_list += character.ckey + + if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc && !character.mind.quiet_round) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. // yogs - Donor Features + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_RECALL, SHUTTLE_IDLE) + SSticker.mode.make_antag_chance(humanc) + if(SHUTTLE_CALL) + if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5) + SSticker.mode.make_antag_chance(humanc) + + if(humanc && CONFIG_GET(flag/roundstart_traits)) + SSquirks.AssignQuirks(humanc, humanc.client, TRUE) + + log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) + +/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) + //TODO: figure out a way to exclude wizards/nukeops/demons from this. + for(var/C in GLOB.employmentCabinets) + var/obj/structure/filingcabinet/employment/employmentCabinet = C + if(!employmentCabinet.virgin) + employmentCabinet.addFile(employee) + + +/mob/dead/new_player/proc/LateChoices() + var/list/dat = list("
                Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
                ") + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_ESCAPE) + dat += "
                The station has been evacuated.

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

                " + for(var/datum/job/prioritized_job in SSjob.prioritized_jobs) + if(prioritized_job.current_positions >= prioritized_job.total_positions) + SSjob.prioritized_jobs -= prioritized_job + dat += "
                " + var/column_counter = 0 + for(var/list/category in list(GLOB.command_positions) + list(GLOB.engineering_positions) + list(GLOB.supply_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.science_positions) + list(GLOB.security_positions) + list(GLOB.medical_positions) ) + var/cat_color = SSjob.name_occupations_all[category[1]].selection_color + dat += "
                " + dat += "[SSjob.name_occupations_all[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(column_counter > 0 && (column_counter % 3 == 0)) + dat += "
                " + dat += "
                " + dat += "" + var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580) + popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') + popup.set_content(jointext(dat, "")) + popup.open(FALSE) // 0 is passed to open so that it doesn't use the onclose() proc + +/mob/dead/new_player/proc/create_character(transfer_after) + spawning = 1 + close_spawn_windows() + + var/mob/living/carbon/human/H = new(loc) + + var/frn = CONFIG_GET(flag/force_random_names) + if(!frn) + frn = is_banned_from(ckey, "Appearance") + if(QDELETED(src)) + return + if(frn) + client.prefs.random_character() + client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) + 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 04a28f6b5a2a..b1e02e37a3ad 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 bc458ef300f8..e77ce2ab4f59 100644 --- a/code/modules/mob/dead/new_player/preferences_setup.dm +++ b/code/modules/mob/dead/new_player/preferences_setup.dm @@ -1,51 +1,51 @@ - - //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) - undershirt = random_undershirt(gender) - socks = random_socks() - 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() - 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() - // Determine what job is marked as 'High' priority, and dress them up as such. - var/datum/job/previewJob - var/highest_pref = 0 - for(var/job in job_preferences) - if(job_preferences[job] > highest_pref) - previewJob = SSjob.GetJob(job) - highest_pref = job_preferences[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) - copy_to(mannequin) - - if(previewJob) - 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) + + //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) + undershirt = random_undershirt(gender) + socks = random_socks() + 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() + 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() + // Determine what job is marked as 'High' priority, and dress them up as such. + var/datum/job/previewJob + var/highest_pref = 0 + for(var/job in job_preferences) + if(job_preferences[job] > highest_pref) + previewJob = SSjob.GetJob(job) + highest_pref = job_preferences[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) + copy_to(mannequin) + + if(previewJob) + 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) diff --git a/code/modules/mob/dead/new_player/sprite_accessories.dm b/code/modules/mob/dead/new_player/sprite_accessories.dm index 26df498b004b..db125e581762 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories.dm @@ -1,1893 +1,1893 @@ -/* - - Hello and welcome to sprite_accessories: For sprite accessories, such as hair, - facial hair, and possibly tattoos and stuff somewhere along the line. This file is - intended to be friendly for people with little to no actual coding experience. - The process of adding in new hairstyles has been made pain-free and easy to do. - Enjoy! - Doohl - - - Notice: This all gets automatically compiled in a list in dna.dm, so you do not - have to define any UI values for sprite accessories manually for hair and facial - hair. Just add in new hair types and the game will naturally adapt. - - !!WARNING!!: changing existing hair information can be VERY hazardous to savefiles, - to the point where you may completely corrupt a server's savefiles. Please refrain - from doing this unless you absolutely know what you are doing, and have defined a - conversion in savefile.dm -*/ -/proc/init_sprite_accessory_subtypes(prototype, list/L, list/male, list/female,var/roundstart = FALSE)//Roundstart argument builds a specific list for roundstart parts where some parts may be locked - if(!istype(L)) - L = list() - if(!istype(male)) - male = list() - if(!istype(female)) - female = list() - - for(var/path in typesof(prototype)) - if(path == prototype) - continue - if(roundstart) - var/datum/sprite_accessory/P = path - if(initial(P.locked)) - continue - var/datum/sprite_accessory/D = new path() - - if(D.icon_state) - L[D.name] = D - else - L += D.name - - switch(D.gender) - if(MALE) - male += D.name - if(FEMALE) - female += D.name - else - male += D.name - female += D.name - return L - -/datum/sprite_accessory - var/icon //the icon file the accessory is located in - var/icon_state //the icon_state of the accessory - var/name //the preview name of the accessory - var/gender = NEUTER //Determines if the accessory will be skipped or included in random hair generations - var/gender_specific //Something that can be worn by either gender, but looks different on each - var/color_src = MUTCOLORS //Currently only used by mutantparts so don't worry about hair and stuff. This is the source that this accessory will get its color from. Default is MUTCOLOR, but can also be HAIR, FACEHAIR, EYECOLOR and 0 if none. - var/hasinner //Decides if this sprite has an "inner" part, such as the fleshy parts on ears. - var/locked = FALSE //Is this part locked from roundstart selection? Used for parts that apply effects - var/dimension_x = 32 - var/dimension_y = 32 - var/center = FALSE //Should we center the sprite? - -////////////////////// -// Hair Definitions // -////////////////////// -/datum/sprite_accessory/hair - icon = 'icons/mob/human_face.dmi' // default icon for all hairs - - // please make sure they're sorted alphabetically and, where needed, categorized - // try to capitalize the names please~ - // try to spell - // you do not need to define _s or _l sub-states, game automatically does this for you - -/datum/sprite_accessory/hair/afro - name = "Afro" - icon_state = "hair_afro" - -/datum/sprite_accessory/hair/afro2 - name = "Afro 2" - icon_state = "hair_afro2" - -/datum/sprite_accessory/hair/afro_large - name = "Afro (Large)" - icon_state = "hair_bigafro" - -/datum/sprite_accessory/hair/antenna - name = "Ahoge" - icon_state = "hair_antenna" - -/datum/sprite_accessory/hair/bald - name = "Bald" - icon_state = null - -/datum/sprite_accessory/hair/balding - name = "Balding Hair" - icon_state = "hair_e" - -/datum/sprite_accessory/hair/bedhead - name = "Bedhead" - icon_state = "hair_bedhead" - -/datum/sprite_accessory/hair/bedhead2 - name = "Bedhead 2" - icon_state = "hair_bedheadv2" - -/datum/sprite_accessory/hair/bedhead3 - name = "Bedhead 3" - icon_state = "hair_bedheadv3" - -/datum/sprite_accessory/hair/bedheadlong - name = "Long Bedhead" - icon_state = "hair_long_bedhead" - -/datum/sprite_accessory/hair/bedheadfloorlength - name = "Floorlength Bedhead" - icon_state = "hair_floorlength_bedhead" - -/datum/sprite_accessory/hair/beehive - name = "Beehive" - icon_state = "hair_beehive" - -/datum/sprite_accessory/hair/beehive2 - name = "Beehive 2" - icon_state = "hair_beehivev2" - -/datum/sprite_accessory/hair/bob - name = "Bob Hair" - icon_state = "hair_bob" - -/datum/sprite_accessory/hair/bob2 - name = "Bob Hair 2" - icon_state = "hair_bob2" - -/datum/sprite_accessory/hair/bob3 - name = "Bob Hair 3" - icon_state = "hair_bobcut" - -/datum/sprite_accessory/hair/bob4 - name = "Bob Hair 4" - icon_state = "hair_bob4" - -/datum/sprite_accessory/hair/bobcurl - name = "Bobcurl" - icon_state = "hair_bobcurl" - -/datum/sprite_accessory/hair/boddicker - name = "Boddicker" - icon_state = "hair_boddicker" - -/datum/sprite_accessory/hair/bowlcut - name = "Bowlcut" - icon_state = "hair_bowlcut" - -/datum/sprite_accessory/hair/bowlcut2 - name = "Bowlcut 2" - icon_state = "hair_bowlcut2" - -/datum/sprite_accessory/hair/braid - name = "Braid (Floorlength)" - icon_state = "hair_braid" - -/datum/sprite_accessory/hair/braided - name = "Braided" - icon_state = "hair_braided" - -/datum/sprite_accessory/hair/front_braid - name = "Braided Front" - icon_state = "hair_braidfront" - -/datum/sprite_accessory/hair/not_floorlength_braid - name = "Braid (High)" - icon_state = "hair_braid2" - -/datum/sprite_accessory/hair/lowbraid - name = "Braid (Low)" - icon_state = "hair_hbraid" - -/datum/sprite_accessory/hair/shortbraid - name = "Braid (Short)" - icon_state = "hair_shortbraid" - -/datum/sprite_accessory/hair/braidtail - name = "Braided Tail" - icon_state = "hair_braidtail" - -/datum/sprite_accessory/hair/bun - name = "Bun Head" - icon_state = "hair_bun" - -/datum/sprite_accessory/hair/bun2 - name = "Bun Head 2" - icon_state = "hair_bunhead2" - -/datum/sprite_accessory/hair/bun3 - name = "Bun Head 3" - icon_state = "hair_bun3" - -/datum/sprite_accessory/hair/largebun - name = "Bun (Large)" - icon_state = "hair_largebun" - -/datum/sprite_accessory/hair/manbun - name = "Bun (Manbun)" - icon_state = "hair_manbun" - -/datum/sprite_accessory/hair/tightbun - name = "Bun (Tight)" - icon_state = "hair_tightbun" - -/datum/sprite_accessory/hair/bun2 - name = "Bun Head 2" - icon_state = "hair_bunhead2" - -/datum/sprite_accessory/hair/bun3 - name = "Bun Head 3" - icon_state = "hair_bun3" - -/datum/sprite_accessory/hair/business - name = "Business Hair" - icon_state = "hair_business" - -/datum/sprite_accessory/hair/business2 - name = "Business Hair 2" - icon_state = "hair_business2" - -/datum/sprite_accessory/hair/business3 - name = "Business Hair 3" - icon_state = "hair_business3" - -/datum/sprite_accessory/hair/business4 - name = "Business Hair 4" - icon_state = "hair_business4" - -/datum/sprite_accessory/hair/buzz - name = "Buzzcut" - icon_state = "hair_buzzcut" - -/datum/sprite_accessory/hair/cia - name = "CIA" - icon_state = "hair_cia" - -/datum/sprite_accessory/hair/coffeehouse - name = "Coffee House" - icon_state = "hair_coffeehouse" - -/datum/sprite_accessory/hair/combover - name = "Combover" - icon_state = "hair_combover" - -/datum/sprite_accessory/hair/cornrows1 - name = "Cornrows" - icon_state = "hair_cornrows" - -/datum/sprite_accessory/hair/cornrows2 - name = "Cornrows 2" - icon_state = "hair_cornrows2" - -/datum/sprite_accessory/hair/cornrowbun - name = "Cornrow Bun" - icon_state = "hair_cornrowbun" - -/datum/sprite_accessory/hair/cornrowbraid - name = "Cornrow Braid" - icon_state = "hair_cornrowbraid" - -/datum/sprite_accessory/hair/cornrowdualtail - name = "Cornrow Tail" - icon_state = "hair_cornrowtail" - -/datum/sprite_accessory/hair/crew - name = "Crewcut" - icon_state = "hair_crewcut" - -/datum/sprite_accessory/hair/curls - name = "Curls" - icon_state = "hair_curls" - -/datum/sprite_accessory/hair/cut - name = "Cut Hair" - icon_state = "hair_c" - -/datum/sprite_accessory/hair/dandpompadour - name = "Dandy Pompadour" - icon_state = "hair_dandypompadour" - -/datum/sprite_accessory/hair/devillock - name = "Devil Lock" - icon_state = "hair_devilock" - -/datum/sprite_accessory/hair/doublebun - name = "Double Bun" - icon_state = "hair_doublebun" - -/datum/sprite_accessory/hair/dreadlocks - name = "Dreadlocks" - icon_state = "hair_dreads" - -/datum/sprite_accessory/hair/drillhair - name = "Drill Hair" - icon_state = "hair_drillhair" - -/datum/sprite_accessory/hair/drillhair - name = "Drillruru" - icon_state = "hair_drillruru" - -/datum/sprite_accessory/hair/drillhairextended - name = "Drill Hair (Extended)" - icon_state = "hair_drillhairextended" - -/datum/sprite_accessory/hair/emo - name = "Emo" - icon_state = "hair_emo" - -/datum/sprite_accessory/hair/emofrine - name = "Emo Fringe" - icon_state = "hair_emofringe" - -/datum/sprite_accessory/hair/nofade - name = "Fade (None)" - icon_state = "hair_nofade" - -/datum/sprite_accessory/hair/highfade - name = "Fade (High)" - icon_state = "hair_highfade" - -/datum/sprite_accessory/hair/medfade - name = "Fade (Medium)" - icon_state = "hair_medfade" - -/datum/sprite_accessory/hair/lowfade - name = "Fade (Low)" - icon_state = "hair_lowfade" - -/datum/sprite_accessory/hair/baldfade - name = "Fade (Bald)" - icon_state = "hair_baldfade" - -/datum/sprite_accessory/hair/feather - name = "Feather" - icon_state = "hair_feather" - -/datum/sprite_accessory/hair/father - name = "Father" - icon_state = "hair_father" - -/datum/sprite_accessory/hair/sargeant - name = "Flat Top" - icon_state = "hair_sargeant" - -/datum/sprite_accessory/hair/flair - name = "Flair" - icon_state = "hair_flair" - -/datum/sprite_accessory/hair/bigflattop - name = "Flat Top (Big)" - icon_state = "hair_bigflattop" - -/datum/sprite_accessory/hair/fag - name = "Flow Hair" - icon_state = "hair_f" - -/datum/sprite_accessory/hair/gelled - name = "Gelled Back" - icon_state = "hair_gelled" - -/datum/sprite_accessory/hair/gentle - name = "Gentle" - icon_state = "hair_gentle" - -/datum/sprite_accessory/hair/halfbang - name = "Half-banged Hair" - icon_state = "hair_halfbang" - -/datum/sprite_accessory/hair/halfbang2 - name = "Half-banged Hair 2" - icon_state = "hair_halfbang2" - -/datum/sprite_accessory/hair/halfshaved - name = "Half-shaved" - icon_state = "hair_halfshaved" - -/datum/sprite_accessory/hair/hedgehog - name = "Hedgehog Hair" - icon_state = "hair_hedgehog" - -/datum/sprite_accessory/hair/himecut - name = "Hime Cut" - icon_state = "hair_himecut" - -/datum/sprite_accessory/hair/himecut2 - name = "Hime Cut 2" - icon_state = "hair_himecut2" - -/datum/sprite_accessory/hair/shorthime - name = "Hime Cut (Short)" - icon_state = "hair_shorthime" - -/datum/sprite_accessory/hair/himeup - name = "Hime Updo" - icon_state = "hair_himeup" - -/datum/sprite_accessory/hair/hitop - name = "Hitop" - icon_state = "hair_hitop" - -/datum/sprite_accessory/hair/jade - name = "Jade" - icon_state = "hair_jade" - -/datum/sprite_accessory/hair/jensen - name = "Jensen Hair" - icon_state = "hair_jensen" - -/datum/sprite_accessory/hair/Joestar - name = "Joestar" - icon_state = "hair_joestar" - -/datum/sprite_accessory/hair/keanu - name = "Keanu Hair" - icon_state = "hair_keanu" - -/datum/sprite_accessory/hair/kusangi - name = "Kusanagi Hair" - icon_state = "hair_kusanagi" - -/datum/sprite_accessory/hair/long - name = "Long Hair 1" - icon_state = "hair_long" - -/datum/sprite_accessory/hair/long2 - name = "Long Hair 2" - icon_state = "hair_long2" - -/datum/sprite_accessory/hair/long3 - name = "Long Hair 3" - icon_state = "hair_long3" - -/datum/sprite_accessory/hair/long_over_eye - name = "Long Over Eye" - icon_state = "hair_longovereye" - -/datum/sprite_accessory/hair/longbangs - name = "Long Bangs" - icon_state = "hair_lbangs" - -/datum/sprite_accessory/hair/longemo - name = "Long Emo" - icon_state = "hair_longemo" - -/datum/sprite_accessory/hair/longfringe - name = "Long Fringe" - icon_state = "hair_longfringe" - -/datum/sprite_accessory/hair/sidepartlongalt - name = "Long Side Part" - icon_state = "hair_longsidepart" - -/datum/sprite_accessory/hair/megaeyebrows - name = "Mega Eyebrows" - icon_state = "hair_megaeyebrows" - -/datum/sprite_accessory/hair/messy - name = "Messy" - icon_state = "hair_messy" - -/datum/sprite_accessory/hair/modern - name = "Modern" - icon_state = "hair_modern" - -/datum/sprite_accessory/hair/mohawk - name = "Mohawk" - icon_state = "hair_d" - -/datum/sprite_accessory/hair/nitori - name = "Nitori" - icon_state = "hair_nitori" - -/datum/sprite_accessory/hair/reversemohawk - name = "Mohawk (Reverse)" - icon_state = "hair_reversemohawk" - -/datum/sprite_accessory/hair/shavedmohawk - name = "Mohawk (Shaved)" - icon_state = "hair_shavedmohawk" - -/datum/sprite_accessory/hair/shavedmohawk - name = "Mohawk (Unshaven)" - icon_state = "hair_unshaven_mohawk" - -/datum/sprite_accessory/hair/mulder - name = "Mulder" - icon_state = "hair_mulder" - -/datum/sprite_accessory/hair/odango - name = "Odango" - icon_state = "hair_odango" - -/datum/sprite_accessory/hair/ombre - name = "Ombre" - icon_state = "hair_ombre" - -/datum/sprite_accessory/hair/oneshoulder - name = "One Shoulder" - icon_state = "hair_oneshoulder" - -/datum/sprite_accessory/hair/over_eye - name = "Over Eye" - icon_state = "hair_shortovereye" - -/datum/sprite_accessory/hair/oxton - name = "Oxton" - icon_state = "hair_oxton" - -/datum/sprite_accessory/hair/parted - name = "Parted" - icon_state = "hair_parted" - -/datum/sprite_accessory/hair/partedside - name = "Parted (Side)" - icon_state = "hair_part" - -/datum/sprite_accessory/hair/kagami - name = "Pigtails" - icon_state = "hair_kagami" - -/datum/sprite_accessory/hair/pigtail - name = "Pigtails 2" - icon_state = "hair_pigtails" - -/datum/sprite_accessory/hair/pigtail2 - name = "Pigtails 3" - icon_state = "hair_pigtails2" - -/datum/sprite_accessory/hair/pixie - name = "Pixie Cut" - icon_state = "hair_pixie" - -/datum/sprite_accessory/hair/pompadour - name = "Pompadour" - icon_state = "hair_pompadour" - -/datum/sprite_accessory/hair/bigpompadour - name = "Pompadour (Big)" - icon_state = "hair_bigpompadour" - -/datum/sprite_accessory/hair/ponytail1 - name = "Ponytail" - icon_state = "hair_ponytail" - -/datum/sprite_accessory/hair/ponytail2 - name = "Ponytail 2" - icon_state = "hair_ponytail2" - -/datum/sprite_accessory/hair/ponytail3 - name = "Ponytail 3" - icon_state = "hair_ponytail3" - -/datum/sprite_accessory/hair/ponytail4 - name = "Ponytail 4" - icon_state = "hair_ponytail4" - -/datum/sprite_accessory/hair/ponytail5 - name = "Ponytail 5" - icon_state = "hair_ponytail5" - -/datum/sprite_accessory/hair/ponytail6 - name = "Ponytail 6" - icon_state = "hair_ponytail6" - -/datum/sprite_accessory/hair/ponytail7 - name = "Ponytail 7" - icon_state = "hair_ponytail7" - -/datum/sprite_accessory/hair/highponytail - name = "Ponytail (High)" - icon_state = "hair_highponytail" - -/datum/sprite_accessory/hair/stail - name = "Ponytail (Short)" - icon_state = "hair_stail" - -/datum/sprite_accessory/hair/longponytail - name = "Ponytail (Long)" - icon_state = "hair_longstraightponytail" - -/datum/sprite_accessory/hair/countryponytail - name = "Ponytail (Country)" - icon_state = "hair_country" - -/datum/sprite_accessory/hair/fringetail - name = "Ponytail (Fringe)" - icon_state = "hair_fringetail" - -/datum/sprite_accessory/hair/sidetail - name = "Ponytail (Side)" - icon_state = "hair_sidetail" - -/datum/sprite_accessory/hair/sidetail2 - name = "Ponytail (Side) 2" - icon_state = "hair_sidetail2" - -/datum/sprite_accessory/hair/sidetail3 - name = "Ponytail (Side) 3" - icon_state = "hair_sidetail3" - -/datum/sprite_accessory/hair/sidetail4 - name = "Ponytail (Side) 4" - icon_state = "hair_sidetail4" - -/datum/sprite_accessory/hair/spikyponytail - name = "Ponytail (Spiky)" - icon_state = "hair_spikyponytail" - -/datum/sprite_accessory/hair/poofy - name = "Poofy" - icon_state = "hair_poofy" - -/datum/sprite_accessory/hair/quiff - name = "Quiff" - icon_state = "hair_quiff" - -/datum/sprite_accessory/hair/ronin - name = "Ronin" - icon_state = "hair_ronin" - -/datum/sprite_accessory/hair/shaved - name = "Shaved" - icon_state = "hair_shaved" - -/datum/sprite_accessory/hair/shavedpart - name = "Shaved Part" - icon_state = "hair_shavedpart" - -/datum/sprite_accessory/hair/shortbangs - name = "Short Bangs" - icon_state = "hair_shortbangs" - -/datum/sprite_accessory/hair/short - name = "Short Hair" - icon_state = "hair_a" - -/datum/sprite_accessory/hair/shorthair2 - name = "Short Hair 2" - icon_state = "hair_shorthair2" - -/datum/sprite_accessory/hair/shorthair3 - name = "Short Hair 3" - icon_state = "hair_shorthair3" - -/datum/sprite_accessory/hair/shorthair4 - name = "Short Hair 4" - icon_state = "hair_d" - -/datum/sprite_accessory/hair/shorthair5 - name = "Short Hair 5" - icon_state = "hair_e" - -/datum/sprite_accessory/hair/shorthair6 - name = "Short Hair 6" - icon_state = "hair_f" - -/datum/sprite_accessory/hair/shorthair7 - name = "Short Hair 7" - icon_state = "hair_shorthairg" - -/datum/sprite_accessory/hair/shorthaireighties - name = "Short Hair 80s" - icon_state = "hair_80s" - -/datum/sprite_accessory/hair/rosa - name = "Short Hair Rosa" - icon_state = "hair_rosa" - -/datum/sprite_accessory/hair/shoulderlength - name = "Shoulder-length Hair" - icon_state = "hair_b" - -/datum/sprite_accessory/hair/sidecut - name = "Sidecut" - icon_state = "hair_sidecut" - -/datum/sprite_accessory/hair/skinhead - name = "Skinhead" - icon_state = "hair_skinhead" - -/datum/sprite_accessory/hair/protagonist - name = "Slightly Long Hair" - icon_state = "hair_protagonist" - -/datum/sprite_accessory/hair/spiky - name = "Spiky" - icon_state = "hair_spikey" - -/datum/sprite_accessory/hair/spiky2 - name = "Spiky 2" - icon_state = "hair_spiky" - -/datum/sprite_accessory/hair/spiky3 - name = "Spiky 3" - icon_state = "hair_spiky2" - -/datum/sprite_accessory/hair/swept - name = "Swept Back Hair" - icon_state = "hair_swept" - -/datum/sprite_accessory/hair/swept2 - name = "Swept Back Hair 2" - icon_state = "hair_swept2" - -/datum/sprite_accessory/hair/thinning - name = "Thinning" - icon_state = "hair_thinning" - -/datum/sprite_accessory/hair/thinningfront - name = "Thinning (Front)" - icon_state = "hair_thinningfront" - -/datum/sprite_accessory/hair/thinningrear - name = "Thinning (Rear)" - icon_state = "hair_thinningrear" - -/datum/sprite_accessory/hair/topknot - name = "Topknot" - icon_state = "hair_topknot" - -/datum/sprite_accessory/hair/tressshoulder - name = "Tress Shoulder" - icon_state = "hair_tressshoulder" - -/datum/sprite_accessory/hair/trimmed - name = "Trimmed" - icon_state = "hair_trimmed" - -/datum/sprite_accessory/hair/trimflat - name = "Trim Flat" - icon_state = "hair_trimflat" - -/datum/sprite_accessory/hair/twintails - name = "Twintails" - icon_state = "hair_twintail" - -/datum/sprite_accessory/hair/undercut - name = "Undercut" - icon_state = "hair_undercut" - -/datum/sprite_accessory/hair/undercutleft - name = "Undercut Left" - icon_state = "hair_undercutleft" - -/datum/sprite_accessory/hair/undercutright - name = "Undercut Right" - icon_state = "hair_undercutright" - -/datum/sprite_accessory/hair/unkept - name = "Unkept" - icon_state = "hair_unkept" - -/datum/sprite_accessory/hair/updo - name = "Updo" - icon_state = "hair_updo" - -/datum/sprite_accessory/hair/longer - name = "Very Long Hair" - icon_state = "hair_vlong" - -/datum/sprite_accessory/hair/longest - name = "Very Long Hair 2" - icon_state = "hair_longest" - -/datum/sprite_accessory/hair/longest2 - name = "Very Long Over Eye" - icon_state = "hair_longest2" - -/datum/sprite_accessory/hair/veryshortovereye - name = "Very Short Over Eye" - icon_state = "hair_veryshortovereyealternate" - -/datum/sprite_accessory/hair/longestalt - name = "Very Long with Fringe" - icon_state = "hair_vlongfringe" - -/datum/sprite_accessory/hair/volaju - name = "Volaju" - icon_state = "hair_volaju" - -/datum/sprite_accessory/hair/wisp - name = "Wisp" - icon_state = "hair_wisp" - -///////////////////////////// -// Facial Hair Definitions // -///////////////////////////// - -/datum/sprite_accessory/facial_hair - icon = 'icons/mob/human_face.dmi' - gender = MALE // barf (unless you're a dorf, dorfs dig chix w/ beards :P) - -// please make sure they're sorted alphabetically and categorized - -/datum/sprite_accessory/facial_hair/abe - name = "Beard (Abraham Lincoln)" - icon_state = "facial_abe" - -/datum/sprite_accessory/facial_hair/brokenman - name = "Beard (Broken Man)" - icon_state = "facial_brokenman" - -/datum/sprite_accessory/facial_hair/chinstrap - name = "Beard (Chinstrap)" - icon_state = "facial_chin" - -/datum/sprite_accessory/facial_hair/dwarf - name = "Beard (Dwarf)" - icon_state = "facial_dwarf" - -/datum/sprite_accessory/facial_hair/fullbeard - name = "Beard (Full)" - icon_state = "facial_fullbeard" - -/datum/sprite_accessory/facial_hair/croppedfullbeard - name = "Beard (Cropped Fullbeard)" - icon_state = "facial_croppedfullbeard" - -/datum/sprite_accessory/facial_hair/gt - name = "Beard (Goatee)" - icon_state = "facial_gt" - -/datum/sprite_accessory/facial_hair/hip - name = "Beard (Hipster)" - icon_state = "facial_hip" - -/datum/sprite_accessory/facial_hair/jensen - name = "Beard (Jensen)" - icon_state = "facial_jensen" - -/datum/sprite_accessory/facial_hair/neckbeard - name = "Beard (Neckbeard)" - icon_state = "facial_neckbeard" - -/datum/sprite_accessory/facial_hair/vlongbeard - name = "Beard (Very Long)" - icon_state = "facial_wise" - -/datum/sprite_accessory/facial_hair/muttonmus - name = "Beard (Muttonmus)" - icon_state = "facial_muttonmus" - -/datum/sprite_accessory/facial_hair/martialartist - name = "Beard (Martial Artist)" - icon_state = "facial_martialartist" - -/datum/sprite_accessory/facial_hair/chinlessbeard - name = "Beard (Chinless Beard)" - icon_state = "facial_chinlessbeard" - -/datum/sprite_accessory/facial_hair/moonshiner - name = "Beard (Moonshiner)" - icon_state = "facial_moonshiner" - -/datum/sprite_accessory/facial_hair/longbeard - name = "Beard (Long)" - icon_state = "facial_longbeard" - -/datum/sprite_accessory/facial_hair/volaju - name = "Beard (Volaju)" - icon_state = "facial_volaju" - -/datum/sprite_accessory/facial_hair/threeoclock - name = "Beard (Three o Clock Shadow)" - icon_state = "facial_3oclock" - -/datum/sprite_accessory/facial_hair/fiveoclock - name = "Beard (Five o Clock Shadow)" - icon_state = "facial_fiveoclock" - -/datum/sprite_accessory/facial_hair/fiveoclockm - name = "Beard (Five o Clock Moustache)" - icon_state = "facial_5oclockmoustache" - -/datum/sprite_accessory/facial_hair/sevenoclock - name = "Beard (Seven o Clock Shadow)" - icon_state = "facial_7oclock" - -/datum/sprite_accessory/facial_hair/sevenoclockm - name = "Beard (Seven o Clock Moustache)" - icon_state = "facial_7oclockmoustache" - -/datum/sprite_accessory/facial_hair/moustache - name = "Moustache" - icon_state = "facial_moustache" - -/datum/sprite_accessory/facial_hair/pencilstache - name = "Moustache (Pencilstache)" - icon_state = "facial_pencilstache" - -/datum/sprite_accessory/facial_hair/smallstache - name = "Moustache (Smallstache)" - icon_state = "facial_smallstache" - -/datum/sprite_accessory/facial_hair/walrus - name = "Moustache (Walrus)" - icon_state = "facial_walrus" - -/datum/sprite_accessory/facial_hair/fu - name = "Moustache (Fu Manchu)" - icon_state = "facial_fumanchu" - -/datum/sprite_accessory/facial_hair/hogan - name = "Moustache (Hulk Hogan)" - icon_state = "facial_hogan" //-Neek - -/datum/sprite_accessory/facial_hair/selleck - name = "Moustache (Selleck)" - icon_state = "facial_selleck" - -/datum/sprite_accessory/facial_hair/chaplin - name = "Moustache (Square)" - icon_state = "facial_chaplin" - -/datum/sprite_accessory/facial_hair/vandyke - name = "Moustache (Van Dyke)" - icon_state = "facial_vandyke" - -/datum/sprite_accessory/facial_hair/watson - name = "Moustache (Watson)" - icon_state = "facial_watson" - -/datum/sprite_accessory/facial_hair/elvis - name = "Sideburns (Elvis)" - icon_state = "facial_elvis" - -/datum/sprite_accessory/facial_hair/mutton - name = "Sideburns (Mutton Chops)" - icon_state = "facial_mutton" - -/datum/sprite_accessory/facial_hair/sideburn - name = "Sideburns" - icon_state = "facial_sideburn" - -/datum/sprite_accessory/facial_hair/shaved - name = "Shaved" - icon_state = null - gender = NEUTER - -/////////////////////////// -// Underwear Definitions // -/////////////////////////// -/datum/sprite_accessory/underwear - icon = 'icons/mob/underwear.dmi' - -/datum/sprite_accessory/underwear/nude - name = "Nude" - icon_state = null - gender = NEUTER - -/datum/sprite_accessory/underwear/male_mankini - name = "Mankini" - icon_state = "male_mankini" - gender = MALE - -/datum/sprite_accessory/underwear/male_black - name = "Men's Black" - icon_state = "male_black" - gender = MALE - -/datum/sprite_accessory/underwear/male_blackalt - name = "Men's Black Boxer" - icon_state = "male_blackalt" - gender = MALE - -/datum/sprite_accessory/underwear/male_blue - name = "Men's Blue" - icon_state = "male_blue" - gender = MALE - -/datum/sprite_accessory/underwear/male_green - name = "Men's Green" - icon_state = "male_green" - gender = MALE - -/datum/sprite_accessory/underwear/male_grey - name = "Men's Grey" - icon_state = "male_grey" - gender = MALE - -/datum/sprite_accessory/underwear/male_greyalt - name = "Men's Grey Boxer" - icon_state = "male_greyalt" - gender = MALE - -/datum/sprite_accessory/underwear/male_hearts - name = "Men's Hearts Boxer" - icon_state = "male_hearts" - gender = MALE - -/datum/sprite_accessory/underwear/male_kinky - name = "Men's Kinky" - icon_state = "male_kinky" - gender = MALE - -/datum/sprite_accessory/underwear/male_red - name = "Men's Red" - icon_state = "male_red" - gender = MALE - -/datum/sprite_accessory/underwear/male_stripe - name = "Men's Striped Boxer" - icon_state = "male_stripe" - gender = MALE - -/datum/sprite_accessory/underwear/male_commie - name = "Men's Striped Commie Boxer" - icon_state = "male_commie" - gender = MALE - -/datum/sprite_accessory/underwear/male_usastripe - name = "Men's Striped Freedom Boxer" - icon_state = "male_assblastusa" - gender = MALE - -/datum/sprite_accessory/underwear/male_uk - name = "Men's Striped UK Boxer" - icon_state = "male_uk" - gender = MALE - -/datum/sprite_accessory/underwear/male_white - name = "Men's White" - icon_state = "male_white" - gender = MALE - -/datum/sprite_accessory/underwear/female_babydoll - name = "Babydoll" - icon_state = "female_babydoll" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_babyblue - name = "Ladies' Baby-Blue" - icon_state = "female_babyblue" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_black - name = "Ladies' Black" - icon_state = "female_black" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_black_neko - name = "Ladies' Black Neko" - icon_state = "female_neko_black" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_blackalt - name = "Ladies' Black Sport" - icon_state = "female_blackalt" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_blue - name = "Ladies' Blue" - icon_state = "female_blue" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_commie - name = "Ladies' Commie" - icon_state = "female_commie" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_usastripe - name = "Ladies' Freedom" - icon_state = "female_assblastusa" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_green - name = "Ladies' Green" - icon_state = "female_green" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_kinky - name = "Ladies' Kinky" - icon_state = "female_kinky" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_pink - name = "Ladies' Pink" - icon_state = "female_pink" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_red - name = "Ladies' Red" - icon_state = "female_red" - gender = FEMALE - -/datum/sprite_accessory/underwear/swimsuit - name = "Ladies' Swimsuit (Black)" - icon_state = "swim_black" - gender = FEMALE - -/datum/sprite_accessory/underwear/swimsuit_blue - name = "Ladies' Swimsuit (Blue)" - icon_state = "swim_blue" - gender = FEMALE - -/datum/sprite_accessory/underwear/swimsuit_green - name = "Ladies' Swimsuit (Green)" - icon_state = "swim_green" - gender = FEMALE - -/datum/sprite_accessory/underwear/swimsuit_purple - name = "Ladies' Swimsuit (Purple)" - icon_state = "swim_purple" - gender = FEMALE - -/datum/sprite_accessory/underwear/swimsuit_red - name = "Ladies' Swimsuit (Red)" - icon_state = "swim_red" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_thong - name = "Ladies' Thong" - icon_state = "female_thong" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_uk - name = "Ladies' UK" - icon_state = "female_uk" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_white - name = "Ladies' White" - icon_state = "female_white" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_white_neko - name = "Ladies' White Neko" - icon_state = "female_neko_white" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_whitealt - name = "Ladies' White Sport" - icon_state = "female_whitealt" - gender = FEMALE - -/datum/sprite_accessory/underwear/female_yellow - name = "Ladies' Yellow" - icon_state = "female_yellow" - gender = FEMALE - -//////////////////////////// -// Undershirt Definitions // -//////////////////////////// - -/datum/sprite_accessory/undershirt - icon = 'icons/mob/underwear.dmi' - -/datum/sprite_accessory/undershirt/nude - name = "Nude" - icon_state = null - gender = NEUTER - -// please make sure they're sorted alphabetically and categorized - -/datum/sprite_accessory/undershirt/bluejersey - name = "Jersey (Blue)" - icon_state = "shirt_bluejersey" - gender = NEUTER - -/datum/sprite_accessory/undershirt/redjersey - name = "Jersey (Red)" - icon_state = "shirt_redjersey" - gender = NEUTER - -/datum/sprite_accessory/undershirt/bluepolo - name = "Polo Shirt (Blue)" - icon_state = "bluepolo" - gender = NEUTER - -/datum/sprite_accessory/undershirt/grayyellowpolo - name = "Polo Shirt (Gray-Yellow)" - icon_state = "grayyellowpolo" - gender = NEUTER - -/datum/sprite_accessory/undershirt/redpolo - name = "Polo Shirt (Red)" - icon_state = "redpolo" - gender = NEUTER - -/datum/sprite_accessory/undershirt/whitepolo - name = "Polo Shirt (White)" - icon_state = "whitepolo" - gender = NEUTER - -/datum/sprite_accessory/undershirt/alienshirt - name = "Shirt (Alien)" - icon_state = "shirt_alien" - gender = NEUTER - -/datum/sprite_accessory/undershirt/mondmondjaja - name = "Shirt (Band)" - icon_state = "band" - gender = NEUTER - -/datum/sprite_accessory/undershirt/shirt_black - name = "Shirt (Black)" - icon_state = "shirt_black" - gender = NEUTER - -/datum/sprite_accessory/undershirt/blueshirt - name = "Shirt (Blue)" - icon_state = "shirt_blue" - gender = NEUTER - -/datum/sprite_accessory/undershirt/clownshirt - name = "Shirt (Clown)" - icon_state = "shirt_clown" - gender = NEUTER - -/datum/sprite_accessory/undershirt/commie - name = "Shirt (Commie)" - icon_state = "shirt_commie" - gender = NEUTER - -/datum/sprite_accessory/undershirt/greenshirt - name = "Shirt (Green)" - icon_state = "shirt_green" - gender = NEUTER - -/datum/sprite_accessory/undershirt/shirt_grey - name = "Shirt (Grey)" - icon_state = "shirt_grey" - gender = NEUTER - -/datum/sprite_accessory/undershirt/ian - name = "Shirt (Ian)" - icon_state = "ian" - gender = NEUTER - -/datum/sprite_accessory/undershirt/ilovent - name = "Shirt (I Love NT)" - icon_state = "ilovent" - gender = NEUTER - -/datum/sprite_accessory/undershirt/lover - name = "Shirt (Lover)" - icon_state = "lover" - gender = NEUTER - -/datum/sprite_accessory/undershirt/matroska - name = "Shirt (Matroska)" - icon_state = "matroska" - gender = NEUTER - -/datum/sprite_accessory/undershirt/meat - name = "Shirt (Meat)" - icon_state = "shirt_meat" - gender = NEUTER - -/datum/sprite_accessory/undershirt/nano - name = "Shirt (Nanotrasen)" - icon_state = "shirt_nano" - gender = NEUTER - -/datum/sprite_accessory/undershirt/peace - name = "Shirt (Peace)" - icon_state = "peace" - gender = NEUTER - -/datum/sprite_accessory/undershirt/pacman - name = "Shirt (Pogoman)" - icon_state = "pogoman" - gender = NEUTER - -/datum/sprite_accessory/undershirt/question - name = "Shirt (Question)" - icon_state = "shirt_question" - gender = NEUTER - -/datum/sprite_accessory/undershirt/redshirt - name = "Shirt (Red)" - icon_state = "shirt_red" - gender = NEUTER - -/datum/sprite_accessory/undershirt/skull - name = "Shirt (Skull)" - icon_state = "shirt_skull" - gender = NEUTER - -/datum/sprite_accessory/undershirt/ss13 - name = "Shirt (SS13)" - icon_state = "shirt_ss13" - gender = NEUTER - -/datum/sprite_accessory/undershirt/stripe - name = "Shirt (Striped)" - icon_state = "shirt_stripes" - gender = NEUTER - -/datum/sprite_accessory/undershirt/tiedye - name = "Shirt (Tie-dye)" - icon_state = "shirt_tiedye" - gender = NEUTER - -/datum/sprite_accessory/undershirt/uk - name = "Shirt (UK)" - icon_state = "uk" - gender = NEUTER - -/datum/sprite_accessory/undershirt/usa - name = "Shirt (USA)" - icon_state = "shirt_assblastusa" - gender = NEUTER - -/datum/sprite_accessory/undershirt/shirt_white - name = "Shirt (White)" - icon_state = "shirt_white" - gender = NEUTER - -/datum/sprite_accessory/undershirt/blackshortsleeve - name = "Short-sleeved Shirt (Black)" - icon_state = "blackshortsleeve" - gender = NEUTER - -/datum/sprite_accessory/undershirt/blueshortsleeve - name = "Short-sleeved Shirt (Blue)" - icon_state = "blueshortsleeve" - gender = NEUTER - -/datum/sprite_accessory/undershirt/greenshortsleeve - name = "Short-sleeved Shirt (Green)" - icon_state = "greenshortsleeve" - gender = NEUTER - -/datum/sprite_accessory/undershirt/purpleshortsleeve - name = "Short-sleeved Shirt (Purple)" - icon_state = "purpleshortsleeve" - gender = NEUTER - -/datum/sprite_accessory/undershirt/whiteshortsleeve - name = "Short-sleeved Shirt (White)" - icon_state = "whiteshortsleeve" - gender = NEUTER - -/datum/sprite_accessory/undershirt/sports_bra - name = "Sports Bra" - icon_state = "sports_bra" - gender = NEUTER - -/datum/sprite_accessory/undershirt/sports_bra2 - name = "Sports Bra (Alt)" - icon_state = "sports_bra_alt" - gender = NEUTER - -/datum/sprite_accessory/undershirt/blueshirtsport - name = "Sports Shirt (Blue)" - icon_state = "blueshirtsport" - gender = NEUTER - -/datum/sprite_accessory/undershirt/greenshirtsport - name = "Sports Shirt (Green)" - icon_state = "greenshirtsport" - gender = NEUTER - -/datum/sprite_accessory/undershirt/redshirtsport - name = "Sports Shirt (Red)" - icon_state = "redshirtsport" - gender = NEUTER - -/datum/sprite_accessory/undershirt/tank_black - name = "Tank Top (Black)" - icon_state = "tank_black" - gender = NEUTER - -/datum/sprite_accessory/undershirt/tankfire - name = "Tank Top (Fire)" - icon_state = "tank_fire" - gender = NEUTER - -/datum/sprite_accessory/undershirt/tank_grey - name = "Tank Top (Grey)" - icon_state = "tank_grey" - gender = NEUTER - -/datum/sprite_accessory/undershirt/female_midriff - name = "Tank Top (Midriff)" - icon_state = "tank_midriff" - gender = FEMALE - -/datum/sprite_accessory/undershirt/tank_red - name = "Tank Top (Red)" - icon_state = "tank_red" - gender = NEUTER - -/datum/sprite_accessory/undershirt/tankstripe - name = "Tank Top (Striped)" - icon_state = "tank_stripes" - gender = NEUTER - -/datum/sprite_accessory/undershirt/tank_white - name = "Tank Top (White)" - icon_state = "tank_white" - gender = NEUTER - -/datum/sprite_accessory/undershirt/redtop - name = "Top (Red)" - icon_state = "redtop" - gender = FEMALE - -/datum/sprite_accessory/undershirt/whitetop - name = "Top (White)" - icon_state = "whitetop" - gender = FEMALE - -/datum/sprite_accessory/undershirt/tshirt_blue - name = "T-Shirt (Blue)" - icon_state = "blueshirt" - gender = NEUTER - -/datum/sprite_accessory/undershirt/tshirt_green - name = "T-Shirt (Green)" - icon_state = "greenshirt" - gender = NEUTER - -/datum/sprite_accessory/undershirt/tshirt_red - name = "T-Shirt (Red)" - icon_state = "redshirt" - gender = NEUTER - -/datum/sprite_accessory/undershirt/yellowshirt - name = "T-Shirt (Yellow)" - icon_state = "yellowshirt" - gender = NEUTER - -/////////////////////// -// Socks Definitions // -/////////////////////// - -/datum/sprite_accessory/socks - icon = 'icons/mob/underwear.dmi' - -/datum/sprite_accessory/socks/nude - name = "Nude" - icon_state = null - -// please make sure they're sorted alphabetically and categorized - -/datum/sprite_accessory/socks/black_knee - name = "Knee-high (Black)" - icon_state = "black_knee" - -/datum/sprite_accessory/socks/commie_knee - name = "Knee-High (Commie)" - icon_state = "commie_knee" - -/datum/sprite_accessory/socks/usa_knee - name = "Knee-High (Freedom)" - icon_state = "assblastusa_knee" - -/datum/sprite_accessory/socks/rainbow_knee - name = "Knee-high (Rainbow)" - icon_state = "rainbow_knee" - -/datum/sprite_accessory/socks/striped_knee - name = "Knee-high (Striped)" - icon_state = "striped_knee" - -/datum/sprite_accessory/socks/thin_knee - name = "Knee-high (Thin)" - icon_state = "thin_knee" - -/datum/sprite_accessory/socks/uk_knee - name = "Knee-High (UK)" - icon_state = "uk_knee" - -/datum/sprite_accessory/socks/white_knee - name = "Knee-high (White)" - icon_state = "white_knee" - -/datum/sprite_accessory/socks/bee_knee - name = "Knee-high (Bee)" - icon_state = "bee_knee" - -/datum/sprite_accessory/socks/black_norm - name = "Normal (Black)" - icon_state = "black_norm" - -/datum/sprite_accessory/socks/white_norm - name = "Normal (White)" - icon_state = "white_norm" - -/datum/sprite_accessory/socks/pantyhose - name = "Pantyhose" - icon_state = "pantyhose" - -/datum/sprite_accessory/socks/black_short - name = "Short (Black)" - icon_state = "black_short" - -/datum/sprite_accessory/socks/white_short - name = "Short (White)" - icon_state = "white_short" - -/datum/sprite_accessory/socks/stockings_blue - name = "Stockings (Blue)" - icon_state = "stockings_blue" - -/datum/sprite_accessory/socks/stockings_cyan - name = "Stockings (Cyan)" - icon_state = "stockings_cyan" - -/datum/sprite_accessory/socks/stockings_dpink - name = "Stockings (Dark Pink)" - icon_state = "stockings_dpink" - -/datum/sprite_accessory/socks/stockings_green - name = "Stockings (Green)" - icon_state = "stockings_black" - -/datum/sprite_accessory/socks/stockings_orange - name = "Stockings (Orange)" - icon_state = "stockings_orange" - -/datum/sprite_accessory/socks/stockings_programmer - name = "Stockings (Programmer)" - icon_state = "stockings_lpink" - -/datum/sprite_accessory/socks/stockings_purple - name = "Stockings (Purple)" - icon_state = "stockings_purple" - -/datum/sprite_accessory/socks/stockings_yellow - name = "Stockings (Yellow)" - icon_state = "stockings_yellow" - -/datum/sprite_accessory/socks/black_thigh - name = "Thigh-high (Black)" - icon_state = "black_thigh" - -/datum/sprite_accessory/socks/commie_thigh - name = "Thigh-high (Commie)" - icon_state = "commie_thigh" - -/datum/sprite_accessory/socks/usa_thigh - name = "Thigh-high (Freedom)" - icon_state = "assblastusa_thigh" - -/datum/sprite_accessory/socks/rainbow_thigh - name = "Thigh-high (Rainbow)" - icon_state = "rainbow_thigh" - -/datum/sprite_accessory/socks/striped_thigh - name = "Thigh-high (Striped)" - icon_state = "striped_thigh" - -/datum/sprite_accessory/socks/thin_thigh - name = "Thigh-high (Thin)" - icon_state = "thin_thigh" - -/datum/sprite_accessory/socks/uk_thigh - name = "Thigh-high (UK)" - icon_state = "uk_thigh" - -/datum/sprite_accessory/socks/white_thigh - name = "Thigh-high (White)" - icon_state = "white_thigh" - -/datum/sprite_accessory/socks/bee_thigh - name = "Thigh-high (Bee)" - icon_state = "bee_thigh" - -//////////.////////////////// -// MutantParts Definitions // -///////////////////////////// - -/datum/sprite_accessory/body_markings - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/body_markings/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/body_markings/dtiger - name = "Dark Tiger Body" - icon_state = "dtiger" - gender_specific = 1 - -/datum/sprite_accessory/body_markings/ltiger - name = "Light Tiger Body" - icon_state = "ltiger" - gender_specific = 1 - -/datum/sprite_accessory/body_markings/lbelly - name = "Light Belly" - icon_state = "lbelly" - gender_specific = 1 - -/datum/sprite_accessory/tails - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/tails_animated - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/tails/lizard/smooth - name = "Smooth" - icon_state = "smooth" - -/datum/sprite_accessory/tails_animated/lizard/smooth - name = "Smooth" - icon_state = "smooth" - -/datum/sprite_accessory/tails/lizard/dtiger - name = "Dark Tiger" - icon_state = "dtiger" - -/datum/sprite_accessory/tails_animated/lizard/dtiger - name = "Dark Tiger" - icon_state = "dtiger" - -/datum/sprite_accessory/tails/lizard/ltiger - name = "Light Tiger" - icon_state = "ltiger" - -/datum/sprite_accessory/tails_animated/lizard/ltiger - name = "Light Tiger" - icon_state = "ltiger" - -/datum/sprite_accessory/tails/lizard/spikes - name = "Spikes" - icon_state = "spikes" - -/datum/sprite_accessory/tails_animated/lizard/spikes - name = "Spikes" - icon_state = "spikes" - -/datum/sprite_accessory/tails/human/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/tails_animated/human/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/tails/human/cat - name = "Cat" - icon_state = "cat" - color_src = HAIR - -/datum/sprite_accessory/tails_animated/human/cat - name = "Cat" - icon_state = "cat" - color_src = HAIR - -/datum/sprite_accessory/snouts - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/snouts/sharp - name = "Sharp" - icon_state = "sharp" - -/datum/sprite_accessory/snouts/round - name = "Round" - icon_state = "round" - -/datum/sprite_accessory/snouts/sharplight - name = "Sharp + Light" - icon_state = "sharplight" - -/datum/sprite_accessory/snouts/roundlight - name = "Round + Light" - icon_state = "roundlight" - -/datum/sprite_accessory/horns - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/horns/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/horns/simple - name = "Simple" - icon_state = "simple" - -/datum/sprite_accessory/horns/short - name = "Short" - icon_state = "short" - -/datum/sprite_accessory/horns/curled - name = "Curled" - icon_state = "curled" - -/datum/sprite_accessory/horns/ram - name = "Ram" - icon_state = "ram" - -/datum/sprite_accessory/horns/angler - name = "Angeler" - icon_state = "angler" - -/datum/sprite_accessory/ears - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/ears/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/ears/cat - name = "Cat" - icon_state = "cat" - hasinner = 1 - color_src = HAIR - -/datum/sprite_accessory/wings/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/wings_open - icon = 'icons/mob/wings.dmi' - -/datum/sprite_accessory/wings_open/angel - name = "Angel" - icon_state = "angel" - color_src = 0 - dimension_x = 46 - center = TRUE - dimension_y = 34 - -/datum/sprite_accessory/wings - icon = 'icons/mob/wings.dmi' - -/datum/sprite_accessory/wings/angel - name = "Angel" - icon_state = "angel" - color_src = 0 - dimension_x = 46 - center = TRUE - dimension_y = 34 - locked = TRUE - -/datum/sprite_accessory/frills - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/frills/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/frills/simple - name = "Simple" - icon_state = "simple" - -/datum/sprite_accessory/frills/short - name = "Short" - icon_state = "short" - -/datum/sprite_accessory/frills/aquatic - name = "Aquatic" - icon_state = "aqua" - -/datum/sprite_accessory/spines - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/spines_animated - icon = 'icons/mob/mutant_bodyparts.dmi' - -/datum/sprite_accessory/spines/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/spines_animated/none - name = "None" - icon_state = "none" - -/datum/sprite_accessory/spines/short - name = "Short" - icon_state = "short" - -/datum/sprite_accessory/spines_animated/short - name = "Short" - icon_state = "short" - -/datum/sprite_accessory/spines/shortmeme - name = "Short + Membrane" - icon_state = "shortmeme" - -/datum/sprite_accessory/spines_animated/shortmeme - name = "Short + Membrane" - icon_state = "shortmeme" - -/datum/sprite_accessory/spines/long - name = "Long" - icon_state = "long" - -/datum/sprite_accessory/spines_animated/long - name = "Long" - icon_state = "long" - -/datum/sprite_accessory/spines/longmeme - name = "Long + Membrane" - icon_state = "longmeme" - -/datum/sprite_accessory/spines_animated/longmeme - name = "Long + Membrane" - icon_state = "longmeme" - -/datum/sprite_accessory/spines/aqautic - name = "Aquatic" - icon_state = "aqua" - -/datum/sprite_accessory/spines_animated/aqautic - name = "Aquatic" - icon_state = "aqua" - -/datum/sprite_accessory/legs //legs are a special case, they aren't actually sprite_accessories but are updated with them. - icon = null //These datums exist for selecting legs on preference, and little else - -/datum/sprite_accessory/legs/none - name = "Normal Legs" - -/datum/sprite_accessory/legs/digitigrade_lizard - name = "Digitigrade Legs" - -/datum/sprite_accessory/caps - icon = 'icons/mob/mutant_bodyparts.dmi' - color_src = HAIR - -/datum/sprite_accessory/caps/round - name = "Round" - icon_state = "round" - -/datum/sprite_accessory/moth_wings - icon = 'yogstation/icons/mob/wings.dmi' //yogs moth sprite fix - color_src = null - -/datum/sprite_accessory/moth_wings/plain - name = "Plain" - icon_state = "plain" - -/datum/sprite_accessory/moth_wings/monarch - name = "Monarch" - icon_state = "monarch" - -/datum/sprite_accessory/moth_wings/luna - name = "Luna" - icon_state = "luna" - -/datum/sprite_accessory/moth_wings/atlas - name = "Atlas" - icon_state = "atlas" - -/datum/sprite_accessory/moth_wings/reddish - name = "Reddish" - icon_state = "redish" - -/datum/sprite_accessory/moth_wings/royal - name = "Royal" - icon_state = "royal" - -/datum/sprite_accessory/moth_wings/gothic - name = "Gothic" - icon_state = "gothic" - -/datum/sprite_accessory/moth_wings/lovers - name = "Lovers" - icon_state = "lovers" - -/datum/sprite_accessory/moth_wings/whitefly - name = "White Fly" - icon_state = "whitefly" - -/datum/sprite_accessory/moth_wings/punished - name = "Burnt Off" - icon_state = "punished" - locked = TRUE - -/datum/sprite_accessory/moth_wings/firewatch - name = "Firewatch" - icon_state = "firewatch" - -/datum/sprite_accessory/moth_wings/deathhead - name = "Deathshead" - icon_state = "deathhead" - -/datum/sprite_accessory/moth_wings/poison - name = "Poison" - icon_state = "poison" - -/datum/sprite_accessory/moth_wings/ragged - name = "Ragged" - icon_state = "ragged" - -/datum/sprite_accessory/moth_wings/moonfly - name = "Moon Fly" - icon_state = "moonfly" - -/datum/sprite_accessory/moth_wings/snow - name = "Snow" - icon_state = "snow" +/* + + Hello and welcome to sprite_accessories: For sprite accessories, such as hair, + facial hair, and possibly tattoos and stuff somewhere along the line. This file is + intended to be friendly for people with little to no actual coding experience. + The process of adding in new hairstyles has been made pain-free and easy to do. + Enjoy! - Doohl + + + Notice: This all gets automatically compiled in a list in dna.dm, so you do not + have to define any UI values for sprite accessories manually for hair and facial + hair. Just add in new hair types and the game will naturally adapt. + + !!WARNING!!: changing existing hair information can be VERY hazardous to savefiles, + to the point where you may completely corrupt a server's savefiles. Please refrain + from doing this unless you absolutely know what you are doing, and have defined a + conversion in savefile.dm +*/ +/proc/init_sprite_accessory_subtypes(prototype, list/L, list/male, list/female,var/roundstart = FALSE)//Roundstart argument builds a specific list for roundstart parts where some parts may be locked + if(!istype(L)) + L = list() + if(!istype(male)) + male = list() + if(!istype(female)) + female = list() + + for(var/path in typesof(prototype)) + if(path == prototype) + continue + if(roundstart) + var/datum/sprite_accessory/P = path + if(initial(P.locked)) + continue + var/datum/sprite_accessory/D = new path() + + if(D.icon_state) + L[D.name] = D + else + L += D.name + + switch(D.gender) + if(MALE) + male += D.name + if(FEMALE) + female += D.name + else + male += D.name + female += D.name + return L + +/datum/sprite_accessory + var/icon //the icon file the accessory is located in + var/icon_state //the icon_state of the accessory + var/name //the preview name of the accessory + var/gender = NEUTER //Determines if the accessory will be skipped or included in random hair generations + var/gender_specific //Something that can be worn by either gender, but looks different on each + var/color_src = MUTCOLORS //Currently only used by mutantparts so don't worry about hair and stuff. This is the source that this accessory will get its color from. Default is MUTCOLOR, but can also be HAIR, FACEHAIR, EYECOLOR and 0 if none. + var/hasinner //Decides if this sprite has an "inner" part, such as the fleshy parts on ears. + var/locked = FALSE //Is this part locked from roundstart selection? Used for parts that apply effects + var/dimension_x = 32 + var/dimension_y = 32 + var/center = FALSE //Should we center the sprite? + +////////////////////// +// Hair Definitions // +////////////////////// +/datum/sprite_accessory/hair + icon = 'icons/mob/human_face.dmi' // default icon for all hairs + + // please make sure they're sorted alphabetically and, where needed, categorized + // try to capitalize the names please~ + // try to spell + // you do not need to define _s or _l sub-states, game automatically does this for you + +/datum/sprite_accessory/hair/afro + name = "Afro" + icon_state = "hair_afro" + +/datum/sprite_accessory/hair/afro2 + name = "Afro 2" + icon_state = "hair_afro2" + +/datum/sprite_accessory/hair/afro_large + name = "Afro (Large)" + icon_state = "hair_bigafro" + +/datum/sprite_accessory/hair/antenna + name = "Ahoge" + icon_state = "hair_antenna" + +/datum/sprite_accessory/hair/bald + name = "Bald" + icon_state = null + +/datum/sprite_accessory/hair/balding + name = "Balding Hair" + icon_state = "hair_e" + +/datum/sprite_accessory/hair/bedhead + name = "Bedhead" + icon_state = "hair_bedhead" + +/datum/sprite_accessory/hair/bedhead2 + name = "Bedhead 2" + icon_state = "hair_bedheadv2" + +/datum/sprite_accessory/hair/bedhead3 + name = "Bedhead 3" + icon_state = "hair_bedheadv3" + +/datum/sprite_accessory/hair/bedheadlong + name = "Long Bedhead" + icon_state = "hair_long_bedhead" + +/datum/sprite_accessory/hair/bedheadfloorlength + name = "Floorlength Bedhead" + icon_state = "hair_floorlength_bedhead" + +/datum/sprite_accessory/hair/beehive + name = "Beehive" + icon_state = "hair_beehive" + +/datum/sprite_accessory/hair/beehive2 + name = "Beehive 2" + icon_state = "hair_beehivev2" + +/datum/sprite_accessory/hair/bob + name = "Bob Hair" + icon_state = "hair_bob" + +/datum/sprite_accessory/hair/bob2 + name = "Bob Hair 2" + icon_state = "hair_bob2" + +/datum/sprite_accessory/hair/bob3 + name = "Bob Hair 3" + icon_state = "hair_bobcut" + +/datum/sprite_accessory/hair/bob4 + name = "Bob Hair 4" + icon_state = "hair_bob4" + +/datum/sprite_accessory/hair/bobcurl + name = "Bobcurl" + icon_state = "hair_bobcurl" + +/datum/sprite_accessory/hair/boddicker + name = "Boddicker" + icon_state = "hair_boddicker" + +/datum/sprite_accessory/hair/bowlcut + name = "Bowlcut" + icon_state = "hair_bowlcut" + +/datum/sprite_accessory/hair/bowlcut2 + name = "Bowlcut 2" + icon_state = "hair_bowlcut2" + +/datum/sprite_accessory/hair/braid + name = "Braid (Floorlength)" + icon_state = "hair_braid" + +/datum/sprite_accessory/hair/braided + name = "Braided" + icon_state = "hair_braided" + +/datum/sprite_accessory/hair/front_braid + name = "Braided Front" + icon_state = "hair_braidfront" + +/datum/sprite_accessory/hair/not_floorlength_braid + name = "Braid (High)" + icon_state = "hair_braid2" + +/datum/sprite_accessory/hair/lowbraid + name = "Braid (Low)" + icon_state = "hair_hbraid" + +/datum/sprite_accessory/hair/shortbraid + name = "Braid (Short)" + icon_state = "hair_shortbraid" + +/datum/sprite_accessory/hair/braidtail + name = "Braided Tail" + icon_state = "hair_braidtail" + +/datum/sprite_accessory/hair/bun + name = "Bun Head" + icon_state = "hair_bun" + +/datum/sprite_accessory/hair/bun2 + name = "Bun Head 2" + icon_state = "hair_bunhead2" + +/datum/sprite_accessory/hair/bun3 + name = "Bun Head 3" + icon_state = "hair_bun3" + +/datum/sprite_accessory/hair/largebun + name = "Bun (Large)" + icon_state = "hair_largebun" + +/datum/sprite_accessory/hair/manbun + name = "Bun (Manbun)" + icon_state = "hair_manbun" + +/datum/sprite_accessory/hair/tightbun + name = "Bun (Tight)" + icon_state = "hair_tightbun" + +/datum/sprite_accessory/hair/bun2 + name = "Bun Head 2" + icon_state = "hair_bunhead2" + +/datum/sprite_accessory/hair/bun3 + name = "Bun Head 3" + icon_state = "hair_bun3" + +/datum/sprite_accessory/hair/business + name = "Business Hair" + icon_state = "hair_business" + +/datum/sprite_accessory/hair/business2 + name = "Business Hair 2" + icon_state = "hair_business2" + +/datum/sprite_accessory/hair/business3 + name = "Business Hair 3" + icon_state = "hair_business3" + +/datum/sprite_accessory/hair/business4 + name = "Business Hair 4" + icon_state = "hair_business4" + +/datum/sprite_accessory/hair/buzz + name = "Buzzcut" + icon_state = "hair_buzzcut" + +/datum/sprite_accessory/hair/cia + name = "CIA" + icon_state = "hair_cia" + +/datum/sprite_accessory/hair/coffeehouse + name = "Coffee House" + icon_state = "hair_coffeehouse" + +/datum/sprite_accessory/hair/combover + name = "Combover" + icon_state = "hair_combover" + +/datum/sprite_accessory/hair/cornrows1 + name = "Cornrows" + icon_state = "hair_cornrows" + +/datum/sprite_accessory/hair/cornrows2 + name = "Cornrows 2" + icon_state = "hair_cornrows2" + +/datum/sprite_accessory/hair/cornrowbun + name = "Cornrow Bun" + icon_state = "hair_cornrowbun" + +/datum/sprite_accessory/hair/cornrowbraid + name = "Cornrow Braid" + icon_state = "hair_cornrowbraid" + +/datum/sprite_accessory/hair/cornrowdualtail + name = "Cornrow Tail" + icon_state = "hair_cornrowtail" + +/datum/sprite_accessory/hair/crew + name = "Crewcut" + icon_state = "hair_crewcut" + +/datum/sprite_accessory/hair/curls + name = "Curls" + icon_state = "hair_curls" + +/datum/sprite_accessory/hair/cut + name = "Cut Hair" + icon_state = "hair_c" + +/datum/sprite_accessory/hair/dandpompadour + name = "Dandy Pompadour" + icon_state = "hair_dandypompadour" + +/datum/sprite_accessory/hair/devillock + name = "Devil Lock" + icon_state = "hair_devilock" + +/datum/sprite_accessory/hair/doublebun + name = "Double Bun" + icon_state = "hair_doublebun" + +/datum/sprite_accessory/hair/dreadlocks + name = "Dreadlocks" + icon_state = "hair_dreads" + +/datum/sprite_accessory/hair/drillhair + name = "Drill Hair" + icon_state = "hair_drillhair" + +/datum/sprite_accessory/hair/drillhair + name = "Drillruru" + icon_state = "hair_drillruru" + +/datum/sprite_accessory/hair/drillhairextended + name = "Drill Hair (Extended)" + icon_state = "hair_drillhairextended" + +/datum/sprite_accessory/hair/emo + name = "Emo" + icon_state = "hair_emo" + +/datum/sprite_accessory/hair/emofrine + name = "Emo Fringe" + icon_state = "hair_emofringe" + +/datum/sprite_accessory/hair/nofade + name = "Fade (None)" + icon_state = "hair_nofade" + +/datum/sprite_accessory/hair/highfade + name = "Fade (High)" + icon_state = "hair_highfade" + +/datum/sprite_accessory/hair/medfade + name = "Fade (Medium)" + icon_state = "hair_medfade" + +/datum/sprite_accessory/hair/lowfade + name = "Fade (Low)" + icon_state = "hair_lowfade" + +/datum/sprite_accessory/hair/baldfade + name = "Fade (Bald)" + icon_state = "hair_baldfade" + +/datum/sprite_accessory/hair/feather + name = "Feather" + icon_state = "hair_feather" + +/datum/sprite_accessory/hair/father + name = "Father" + icon_state = "hair_father" + +/datum/sprite_accessory/hair/sargeant + name = "Flat Top" + icon_state = "hair_sargeant" + +/datum/sprite_accessory/hair/flair + name = "Flair" + icon_state = "hair_flair" + +/datum/sprite_accessory/hair/bigflattop + name = "Flat Top (Big)" + icon_state = "hair_bigflattop" + +/datum/sprite_accessory/hair/fag + name = "Flow Hair" + icon_state = "hair_f" + +/datum/sprite_accessory/hair/gelled + name = "Gelled Back" + icon_state = "hair_gelled" + +/datum/sprite_accessory/hair/gentle + name = "Gentle" + icon_state = "hair_gentle" + +/datum/sprite_accessory/hair/halfbang + name = "Half-banged Hair" + icon_state = "hair_halfbang" + +/datum/sprite_accessory/hair/halfbang2 + name = "Half-banged Hair 2" + icon_state = "hair_halfbang2" + +/datum/sprite_accessory/hair/halfshaved + name = "Half-shaved" + icon_state = "hair_halfshaved" + +/datum/sprite_accessory/hair/hedgehog + name = "Hedgehog Hair" + icon_state = "hair_hedgehog" + +/datum/sprite_accessory/hair/himecut + name = "Hime Cut" + icon_state = "hair_himecut" + +/datum/sprite_accessory/hair/himecut2 + name = "Hime Cut 2" + icon_state = "hair_himecut2" + +/datum/sprite_accessory/hair/shorthime + name = "Hime Cut (Short)" + icon_state = "hair_shorthime" + +/datum/sprite_accessory/hair/himeup + name = "Hime Updo" + icon_state = "hair_himeup" + +/datum/sprite_accessory/hair/hitop + name = "Hitop" + icon_state = "hair_hitop" + +/datum/sprite_accessory/hair/jade + name = "Jade" + icon_state = "hair_jade" + +/datum/sprite_accessory/hair/jensen + name = "Jensen Hair" + icon_state = "hair_jensen" + +/datum/sprite_accessory/hair/Joestar + name = "Joestar" + icon_state = "hair_joestar" + +/datum/sprite_accessory/hair/keanu + name = "Keanu Hair" + icon_state = "hair_keanu" + +/datum/sprite_accessory/hair/kusangi + name = "Kusanagi Hair" + icon_state = "hair_kusanagi" + +/datum/sprite_accessory/hair/long + name = "Long Hair 1" + icon_state = "hair_long" + +/datum/sprite_accessory/hair/long2 + name = "Long Hair 2" + icon_state = "hair_long2" + +/datum/sprite_accessory/hair/long3 + name = "Long Hair 3" + icon_state = "hair_long3" + +/datum/sprite_accessory/hair/long_over_eye + name = "Long Over Eye" + icon_state = "hair_longovereye" + +/datum/sprite_accessory/hair/longbangs + name = "Long Bangs" + icon_state = "hair_lbangs" + +/datum/sprite_accessory/hair/longemo + name = "Long Emo" + icon_state = "hair_longemo" + +/datum/sprite_accessory/hair/longfringe + name = "Long Fringe" + icon_state = "hair_longfringe" + +/datum/sprite_accessory/hair/sidepartlongalt + name = "Long Side Part" + icon_state = "hair_longsidepart" + +/datum/sprite_accessory/hair/megaeyebrows + name = "Mega Eyebrows" + icon_state = "hair_megaeyebrows" + +/datum/sprite_accessory/hair/messy + name = "Messy" + icon_state = "hair_messy" + +/datum/sprite_accessory/hair/modern + name = "Modern" + icon_state = "hair_modern" + +/datum/sprite_accessory/hair/mohawk + name = "Mohawk" + icon_state = "hair_d" + +/datum/sprite_accessory/hair/nitori + name = "Nitori" + icon_state = "hair_nitori" + +/datum/sprite_accessory/hair/reversemohawk + name = "Mohawk (Reverse)" + icon_state = "hair_reversemohawk" + +/datum/sprite_accessory/hair/shavedmohawk + name = "Mohawk (Shaved)" + icon_state = "hair_shavedmohawk" + +/datum/sprite_accessory/hair/shavedmohawk + name = "Mohawk (Unshaven)" + icon_state = "hair_unshaven_mohawk" + +/datum/sprite_accessory/hair/mulder + name = "Mulder" + icon_state = "hair_mulder" + +/datum/sprite_accessory/hair/odango + name = "Odango" + icon_state = "hair_odango" + +/datum/sprite_accessory/hair/ombre + name = "Ombre" + icon_state = "hair_ombre" + +/datum/sprite_accessory/hair/oneshoulder + name = "One Shoulder" + icon_state = "hair_oneshoulder" + +/datum/sprite_accessory/hair/over_eye + name = "Over Eye" + icon_state = "hair_shortovereye" + +/datum/sprite_accessory/hair/oxton + name = "Oxton" + icon_state = "hair_oxton" + +/datum/sprite_accessory/hair/parted + name = "Parted" + icon_state = "hair_parted" + +/datum/sprite_accessory/hair/partedside + name = "Parted (Side)" + icon_state = "hair_part" + +/datum/sprite_accessory/hair/kagami + name = "Pigtails" + icon_state = "hair_kagami" + +/datum/sprite_accessory/hair/pigtail + name = "Pigtails 2" + icon_state = "hair_pigtails" + +/datum/sprite_accessory/hair/pigtail2 + name = "Pigtails 3" + icon_state = "hair_pigtails2" + +/datum/sprite_accessory/hair/pixie + name = "Pixie Cut" + icon_state = "hair_pixie" + +/datum/sprite_accessory/hair/pompadour + name = "Pompadour" + icon_state = "hair_pompadour" + +/datum/sprite_accessory/hair/bigpompadour + name = "Pompadour (Big)" + icon_state = "hair_bigpompadour" + +/datum/sprite_accessory/hair/ponytail1 + name = "Ponytail" + icon_state = "hair_ponytail" + +/datum/sprite_accessory/hair/ponytail2 + name = "Ponytail 2" + icon_state = "hair_ponytail2" + +/datum/sprite_accessory/hair/ponytail3 + name = "Ponytail 3" + icon_state = "hair_ponytail3" + +/datum/sprite_accessory/hair/ponytail4 + name = "Ponytail 4" + icon_state = "hair_ponytail4" + +/datum/sprite_accessory/hair/ponytail5 + name = "Ponytail 5" + icon_state = "hair_ponytail5" + +/datum/sprite_accessory/hair/ponytail6 + name = "Ponytail 6" + icon_state = "hair_ponytail6" + +/datum/sprite_accessory/hair/ponytail7 + name = "Ponytail 7" + icon_state = "hair_ponytail7" + +/datum/sprite_accessory/hair/highponytail + name = "Ponytail (High)" + icon_state = "hair_highponytail" + +/datum/sprite_accessory/hair/stail + name = "Ponytail (Short)" + icon_state = "hair_stail" + +/datum/sprite_accessory/hair/longponytail + name = "Ponytail (Long)" + icon_state = "hair_longstraightponytail" + +/datum/sprite_accessory/hair/countryponytail + name = "Ponytail (Country)" + icon_state = "hair_country" + +/datum/sprite_accessory/hair/fringetail + name = "Ponytail (Fringe)" + icon_state = "hair_fringetail" + +/datum/sprite_accessory/hair/sidetail + name = "Ponytail (Side)" + icon_state = "hair_sidetail" + +/datum/sprite_accessory/hair/sidetail2 + name = "Ponytail (Side) 2" + icon_state = "hair_sidetail2" + +/datum/sprite_accessory/hair/sidetail3 + name = "Ponytail (Side) 3" + icon_state = "hair_sidetail3" + +/datum/sprite_accessory/hair/sidetail4 + name = "Ponytail (Side) 4" + icon_state = "hair_sidetail4" + +/datum/sprite_accessory/hair/spikyponytail + name = "Ponytail (Spiky)" + icon_state = "hair_spikyponytail" + +/datum/sprite_accessory/hair/poofy + name = "Poofy" + icon_state = "hair_poofy" + +/datum/sprite_accessory/hair/quiff + name = "Quiff" + icon_state = "hair_quiff" + +/datum/sprite_accessory/hair/ronin + name = "Ronin" + icon_state = "hair_ronin" + +/datum/sprite_accessory/hair/shaved + name = "Shaved" + icon_state = "hair_shaved" + +/datum/sprite_accessory/hair/shavedpart + name = "Shaved Part" + icon_state = "hair_shavedpart" + +/datum/sprite_accessory/hair/shortbangs + name = "Short Bangs" + icon_state = "hair_shortbangs" + +/datum/sprite_accessory/hair/short + name = "Short Hair" + icon_state = "hair_a" + +/datum/sprite_accessory/hair/shorthair2 + name = "Short Hair 2" + icon_state = "hair_shorthair2" + +/datum/sprite_accessory/hair/shorthair3 + name = "Short Hair 3" + icon_state = "hair_shorthair3" + +/datum/sprite_accessory/hair/shorthair4 + name = "Short Hair 4" + icon_state = "hair_d" + +/datum/sprite_accessory/hair/shorthair5 + name = "Short Hair 5" + icon_state = "hair_e" + +/datum/sprite_accessory/hair/shorthair6 + name = "Short Hair 6" + icon_state = "hair_f" + +/datum/sprite_accessory/hair/shorthair7 + name = "Short Hair 7" + icon_state = "hair_shorthairg" + +/datum/sprite_accessory/hair/shorthaireighties + name = "Short Hair 80s" + icon_state = "hair_80s" + +/datum/sprite_accessory/hair/rosa + name = "Short Hair Rosa" + icon_state = "hair_rosa" + +/datum/sprite_accessory/hair/shoulderlength + name = "Shoulder-length Hair" + icon_state = "hair_b" + +/datum/sprite_accessory/hair/sidecut + name = "Sidecut" + icon_state = "hair_sidecut" + +/datum/sprite_accessory/hair/skinhead + name = "Skinhead" + icon_state = "hair_skinhead" + +/datum/sprite_accessory/hair/protagonist + name = "Slightly Long Hair" + icon_state = "hair_protagonist" + +/datum/sprite_accessory/hair/spiky + name = "Spiky" + icon_state = "hair_spikey" + +/datum/sprite_accessory/hair/spiky2 + name = "Spiky 2" + icon_state = "hair_spiky" + +/datum/sprite_accessory/hair/spiky3 + name = "Spiky 3" + icon_state = "hair_spiky2" + +/datum/sprite_accessory/hair/swept + name = "Swept Back Hair" + icon_state = "hair_swept" + +/datum/sprite_accessory/hair/swept2 + name = "Swept Back Hair 2" + icon_state = "hair_swept2" + +/datum/sprite_accessory/hair/thinning + name = "Thinning" + icon_state = "hair_thinning" + +/datum/sprite_accessory/hair/thinningfront + name = "Thinning (Front)" + icon_state = "hair_thinningfront" + +/datum/sprite_accessory/hair/thinningrear + name = "Thinning (Rear)" + icon_state = "hair_thinningrear" + +/datum/sprite_accessory/hair/topknot + name = "Topknot" + icon_state = "hair_topknot" + +/datum/sprite_accessory/hair/tressshoulder + name = "Tress Shoulder" + icon_state = "hair_tressshoulder" + +/datum/sprite_accessory/hair/trimmed + name = "Trimmed" + icon_state = "hair_trimmed" + +/datum/sprite_accessory/hair/trimflat + name = "Trim Flat" + icon_state = "hair_trimflat" + +/datum/sprite_accessory/hair/twintails + name = "Twintails" + icon_state = "hair_twintail" + +/datum/sprite_accessory/hair/undercut + name = "Undercut" + icon_state = "hair_undercut" + +/datum/sprite_accessory/hair/undercutleft + name = "Undercut Left" + icon_state = "hair_undercutleft" + +/datum/sprite_accessory/hair/undercutright + name = "Undercut Right" + icon_state = "hair_undercutright" + +/datum/sprite_accessory/hair/unkept + name = "Unkept" + icon_state = "hair_unkept" + +/datum/sprite_accessory/hair/updo + name = "Updo" + icon_state = "hair_updo" + +/datum/sprite_accessory/hair/longer + name = "Very Long Hair" + icon_state = "hair_vlong" + +/datum/sprite_accessory/hair/longest + name = "Very Long Hair 2" + icon_state = "hair_longest" + +/datum/sprite_accessory/hair/longest2 + name = "Very Long Over Eye" + icon_state = "hair_longest2" + +/datum/sprite_accessory/hair/veryshortovereye + name = "Very Short Over Eye" + icon_state = "hair_veryshortovereyealternate" + +/datum/sprite_accessory/hair/longestalt + name = "Very Long with Fringe" + icon_state = "hair_vlongfringe" + +/datum/sprite_accessory/hair/volaju + name = "Volaju" + icon_state = "hair_volaju" + +/datum/sprite_accessory/hair/wisp + name = "Wisp" + icon_state = "hair_wisp" + +///////////////////////////// +// Facial Hair Definitions // +///////////////////////////// + +/datum/sprite_accessory/facial_hair + icon = 'icons/mob/human_face.dmi' + gender = MALE // barf (unless you're a dorf, dorfs dig chix w/ beards :P) + +// please make sure they're sorted alphabetically and categorized + +/datum/sprite_accessory/facial_hair/abe + name = "Beard (Abraham Lincoln)" + icon_state = "facial_abe" + +/datum/sprite_accessory/facial_hair/brokenman + name = "Beard (Broken Man)" + icon_state = "facial_brokenman" + +/datum/sprite_accessory/facial_hair/chinstrap + name = "Beard (Chinstrap)" + icon_state = "facial_chin" + +/datum/sprite_accessory/facial_hair/dwarf + name = "Beard (Dwarf)" + icon_state = "facial_dwarf" + +/datum/sprite_accessory/facial_hair/fullbeard + name = "Beard (Full)" + icon_state = "facial_fullbeard" + +/datum/sprite_accessory/facial_hair/croppedfullbeard + name = "Beard (Cropped Fullbeard)" + icon_state = "facial_croppedfullbeard" + +/datum/sprite_accessory/facial_hair/gt + name = "Beard (Goatee)" + icon_state = "facial_gt" + +/datum/sprite_accessory/facial_hair/hip + name = "Beard (Hipster)" + icon_state = "facial_hip" + +/datum/sprite_accessory/facial_hair/jensen + name = "Beard (Jensen)" + icon_state = "facial_jensen" + +/datum/sprite_accessory/facial_hair/neckbeard + name = "Beard (Neckbeard)" + icon_state = "facial_neckbeard" + +/datum/sprite_accessory/facial_hair/vlongbeard + name = "Beard (Very Long)" + icon_state = "facial_wise" + +/datum/sprite_accessory/facial_hair/muttonmus + name = "Beard (Muttonmus)" + icon_state = "facial_muttonmus" + +/datum/sprite_accessory/facial_hair/martialartist + name = "Beard (Martial Artist)" + icon_state = "facial_martialartist" + +/datum/sprite_accessory/facial_hair/chinlessbeard + name = "Beard (Chinless Beard)" + icon_state = "facial_chinlessbeard" + +/datum/sprite_accessory/facial_hair/moonshiner + name = "Beard (Moonshiner)" + icon_state = "facial_moonshiner" + +/datum/sprite_accessory/facial_hair/longbeard + name = "Beard (Long)" + icon_state = "facial_longbeard" + +/datum/sprite_accessory/facial_hair/volaju + name = "Beard (Volaju)" + icon_state = "facial_volaju" + +/datum/sprite_accessory/facial_hair/threeoclock + name = "Beard (Three o Clock Shadow)" + icon_state = "facial_3oclock" + +/datum/sprite_accessory/facial_hair/fiveoclock + name = "Beard (Five o Clock Shadow)" + icon_state = "facial_fiveoclock" + +/datum/sprite_accessory/facial_hair/fiveoclockm + name = "Beard (Five o Clock Moustache)" + icon_state = "facial_5oclockmoustache" + +/datum/sprite_accessory/facial_hair/sevenoclock + name = "Beard (Seven o Clock Shadow)" + icon_state = "facial_7oclock" + +/datum/sprite_accessory/facial_hair/sevenoclockm + name = "Beard (Seven o Clock Moustache)" + icon_state = "facial_7oclockmoustache" + +/datum/sprite_accessory/facial_hair/moustache + name = "Moustache" + icon_state = "facial_moustache" + +/datum/sprite_accessory/facial_hair/pencilstache + name = "Moustache (Pencilstache)" + icon_state = "facial_pencilstache" + +/datum/sprite_accessory/facial_hair/smallstache + name = "Moustache (Smallstache)" + icon_state = "facial_smallstache" + +/datum/sprite_accessory/facial_hair/walrus + name = "Moustache (Walrus)" + icon_state = "facial_walrus" + +/datum/sprite_accessory/facial_hair/fu + name = "Moustache (Fu Manchu)" + icon_state = "facial_fumanchu" + +/datum/sprite_accessory/facial_hair/hogan + name = "Moustache (Hulk Hogan)" + icon_state = "facial_hogan" //-Neek + +/datum/sprite_accessory/facial_hair/selleck + name = "Moustache (Selleck)" + icon_state = "facial_selleck" + +/datum/sprite_accessory/facial_hair/chaplin + name = "Moustache (Square)" + icon_state = "facial_chaplin" + +/datum/sprite_accessory/facial_hair/vandyke + name = "Moustache (Van Dyke)" + icon_state = "facial_vandyke" + +/datum/sprite_accessory/facial_hair/watson + name = "Moustache (Watson)" + icon_state = "facial_watson" + +/datum/sprite_accessory/facial_hair/elvis + name = "Sideburns (Elvis)" + icon_state = "facial_elvis" + +/datum/sprite_accessory/facial_hair/mutton + name = "Sideburns (Mutton Chops)" + icon_state = "facial_mutton" + +/datum/sprite_accessory/facial_hair/sideburn + name = "Sideburns" + icon_state = "facial_sideburn" + +/datum/sprite_accessory/facial_hair/shaved + name = "Shaved" + icon_state = null + gender = NEUTER + +/////////////////////////// +// Underwear Definitions // +/////////////////////////// +/datum/sprite_accessory/underwear + icon = 'icons/mob/underwear.dmi' + +/datum/sprite_accessory/underwear/nude + name = "Nude" + icon_state = null + gender = NEUTER + +/datum/sprite_accessory/underwear/male_mankini + name = "Mankini" + icon_state = "male_mankini" + gender = MALE + +/datum/sprite_accessory/underwear/male_black + name = "Men's Black" + icon_state = "male_black" + gender = MALE + +/datum/sprite_accessory/underwear/male_blackalt + name = "Men's Black Boxer" + icon_state = "male_blackalt" + gender = MALE + +/datum/sprite_accessory/underwear/male_blue + name = "Men's Blue" + icon_state = "male_blue" + gender = MALE + +/datum/sprite_accessory/underwear/male_green + name = "Men's Green" + icon_state = "male_green" + gender = MALE + +/datum/sprite_accessory/underwear/male_grey + name = "Men's Grey" + icon_state = "male_grey" + gender = MALE + +/datum/sprite_accessory/underwear/male_greyalt + name = "Men's Grey Boxer" + icon_state = "male_greyalt" + gender = MALE + +/datum/sprite_accessory/underwear/male_hearts + name = "Men's Hearts Boxer" + icon_state = "male_hearts" + gender = MALE + +/datum/sprite_accessory/underwear/male_kinky + name = "Men's Kinky" + icon_state = "male_kinky" + gender = MALE + +/datum/sprite_accessory/underwear/male_red + name = "Men's Red" + icon_state = "male_red" + gender = MALE + +/datum/sprite_accessory/underwear/male_stripe + name = "Men's Striped Boxer" + icon_state = "male_stripe" + gender = MALE + +/datum/sprite_accessory/underwear/male_commie + name = "Men's Striped Commie Boxer" + icon_state = "male_commie" + gender = MALE + +/datum/sprite_accessory/underwear/male_usastripe + name = "Men's Striped Freedom Boxer" + icon_state = "male_assblastusa" + gender = MALE + +/datum/sprite_accessory/underwear/male_uk + name = "Men's Striped UK Boxer" + icon_state = "male_uk" + gender = MALE + +/datum/sprite_accessory/underwear/male_white + name = "Men's White" + icon_state = "male_white" + gender = MALE + +/datum/sprite_accessory/underwear/female_babydoll + name = "Babydoll" + icon_state = "female_babydoll" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_babyblue + name = "Ladies' Baby-Blue" + icon_state = "female_babyblue" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_black + name = "Ladies' Black" + icon_state = "female_black" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_black_neko + name = "Ladies' Black Neko" + icon_state = "female_neko_black" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_blackalt + name = "Ladies' Black Sport" + icon_state = "female_blackalt" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_blue + name = "Ladies' Blue" + icon_state = "female_blue" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_commie + name = "Ladies' Commie" + icon_state = "female_commie" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_usastripe + name = "Ladies' Freedom" + icon_state = "female_assblastusa" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_green + name = "Ladies' Green" + icon_state = "female_green" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_kinky + name = "Ladies' Kinky" + icon_state = "female_kinky" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_pink + name = "Ladies' Pink" + icon_state = "female_pink" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_red + name = "Ladies' Red" + icon_state = "female_red" + gender = FEMALE + +/datum/sprite_accessory/underwear/swimsuit + name = "Ladies' Swimsuit (Black)" + icon_state = "swim_black" + gender = FEMALE + +/datum/sprite_accessory/underwear/swimsuit_blue + name = "Ladies' Swimsuit (Blue)" + icon_state = "swim_blue" + gender = FEMALE + +/datum/sprite_accessory/underwear/swimsuit_green + name = "Ladies' Swimsuit (Green)" + icon_state = "swim_green" + gender = FEMALE + +/datum/sprite_accessory/underwear/swimsuit_purple + name = "Ladies' Swimsuit (Purple)" + icon_state = "swim_purple" + gender = FEMALE + +/datum/sprite_accessory/underwear/swimsuit_red + name = "Ladies' Swimsuit (Red)" + icon_state = "swim_red" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_thong + name = "Ladies' Thong" + icon_state = "female_thong" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_uk + name = "Ladies' UK" + icon_state = "female_uk" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_white + name = "Ladies' White" + icon_state = "female_white" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_white_neko + name = "Ladies' White Neko" + icon_state = "female_neko_white" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_whitealt + name = "Ladies' White Sport" + icon_state = "female_whitealt" + gender = FEMALE + +/datum/sprite_accessory/underwear/female_yellow + name = "Ladies' Yellow" + icon_state = "female_yellow" + gender = FEMALE + +//////////////////////////// +// Undershirt Definitions // +//////////////////////////// + +/datum/sprite_accessory/undershirt + icon = 'icons/mob/underwear.dmi' + +/datum/sprite_accessory/undershirt/nude + name = "Nude" + icon_state = null + gender = NEUTER + +// please make sure they're sorted alphabetically and categorized + +/datum/sprite_accessory/undershirt/bluejersey + name = "Jersey (Blue)" + icon_state = "shirt_bluejersey" + gender = NEUTER + +/datum/sprite_accessory/undershirt/redjersey + name = "Jersey (Red)" + icon_state = "shirt_redjersey" + gender = NEUTER + +/datum/sprite_accessory/undershirt/bluepolo + name = "Polo Shirt (Blue)" + icon_state = "bluepolo" + gender = NEUTER + +/datum/sprite_accessory/undershirt/grayyellowpolo + name = "Polo Shirt (Gray-Yellow)" + icon_state = "grayyellowpolo" + gender = NEUTER + +/datum/sprite_accessory/undershirt/redpolo + name = "Polo Shirt (Red)" + icon_state = "redpolo" + gender = NEUTER + +/datum/sprite_accessory/undershirt/whitepolo + name = "Polo Shirt (White)" + icon_state = "whitepolo" + gender = NEUTER + +/datum/sprite_accessory/undershirt/alienshirt + name = "Shirt (Alien)" + icon_state = "shirt_alien" + gender = NEUTER + +/datum/sprite_accessory/undershirt/mondmondjaja + name = "Shirt (Band)" + icon_state = "band" + gender = NEUTER + +/datum/sprite_accessory/undershirt/shirt_black + name = "Shirt (Black)" + icon_state = "shirt_black" + gender = NEUTER + +/datum/sprite_accessory/undershirt/blueshirt + name = "Shirt (Blue)" + icon_state = "shirt_blue" + gender = NEUTER + +/datum/sprite_accessory/undershirt/clownshirt + name = "Shirt (Clown)" + icon_state = "shirt_clown" + gender = NEUTER + +/datum/sprite_accessory/undershirt/commie + name = "Shirt (Commie)" + icon_state = "shirt_commie" + gender = NEUTER + +/datum/sprite_accessory/undershirt/greenshirt + name = "Shirt (Green)" + icon_state = "shirt_green" + gender = NEUTER + +/datum/sprite_accessory/undershirt/shirt_grey + name = "Shirt (Grey)" + icon_state = "shirt_grey" + gender = NEUTER + +/datum/sprite_accessory/undershirt/ian + name = "Shirt (Ian)" + icon_state = "ian" + gender = NEUTER + +/datum/sprite_accessory/undershirt/ilovent + name = "Shirt (I Love NT)" + icon_state = "ilovent" + gender = NEUTER + +/datum/sprite_accessory/undershirt/lover + name = "Shirt (Lover)" + icon_state = "lover" + gender = NEUTER + +/datum/sprite_accessory/undershirt/matroska + name = "Shirt (Matroska)" + icon_state = "matroska" + gender = NEUTER + +/datum/sprite_accessory/undershirt/meat + name = "Shirt (Meat)" + icon_state = "shirt_meat" + gender = NEUTER + +/datum/sprite_accessory/undershirt/nano + name = "Shirt (Nanotrasen)" + icon_state = "shirt_nano" + gender = NEUTER + +/datum/sprite_accessory/undershirt/peace + name = "Shirt (Peace)" + icon_state = "peace" + gender = NEUTER + +/datum/sprite_accessory/undershirt/pacman + name = "Shirt (Pogoman)" + icon_state = "pogoman" + gender = NEUTER + +/datum/sprite_accessory/undershirt/question + name = "Shirt (Question)" + icon_state = "shirt_question" + gender = NEUTER + +/datum/sprite_accessory/undershirt/redshirt + name = "Shirt (Red)" + icon_state = "shirt_red" + gender = NEUTER + +/datum/sprite_accessory/undershirt/skull + name = "Shirt (Skull)" + icon_state = "shirt_skull" + gender = NEUTER + +/datum/sprite_accessory/undershirt/ss13 + name = "Shirt (SS13)" + icon_state = "shirt_ss13" + gender = NEUTER + +/datum/sprite_accessory/undershirt/stripe + name = "Shirt (Striped)" + icon_state = "shirt_stripes" + gender = NEUTER + +/datum/sprite_accessory/undershirt/tiedye + name = "Shirt (Tie-dye)" + icon_state = "shirt_tiedye" + gender = NEUTER + +/datum/sprite_accessory/undershirt/uk + name = "Shirt (UK)" + icon_state = "uk" + gender = NEUTER + +/datum/sprite_accessory/undershirt/usa + name = "Shirt (USA)" + icon_state = "shirt_assblastusa" + gender = NEUTER + +/datum/sprite_accessory/undershirt/shirt_white + name = "Shirt (White)" + icon_state = "shirt_white" + gender = NEUTER + +/datum/sprite_accessory/undershirt/blackshortsleeve + name = "Short-sleeved Shirt (Black)" + icon_state = "blackshortsleeve" + gender = NEUTER + +/datum/sprite_accessory/undershirt/blueshortsleeve + name = "Short-sleeved Shirt (Blue)" + icon_state = "blueshortsleeve" + gender = NEUTER + +/datum/sprite_accessory/undershirt/greenshortsleeve + name = "Short-sleeved Shirt (Green)" + icon_state = "greenshortsleeve" + gender = NEUTER + +/datum/sprite_accessory/undershirt/purpleshortsleeve + name = "Short-sleeved Shirt (Purple)" + icon_state = "purpleshortsleeve" + gender = NEUTER + +/datum/sprite_accessory/undershirt/whiteshortsleeve + name = "Short-sleeved Shirt (White)" + icon_state = "whiteshortsleeve" + gender = NEUTER + +/datum/sprite_accessory/undershirt/sports_bra + name = "Sports Bra" + icon_state = "sports_bra" + gender = NEUTER + +/datum/sprite_accessory/undershirt/sports_bra2 + name = "Sports Bra (Alt)" + icon_state = "sports_bra_alt" + gender = NEUTER + +/datum/sprite_accessory/undershirt/blueshirtsport + name = "Sports Shirt (Blue)" + icon_state = "blueshirtsport" + gender = NEUTER + +/datum/sprite_accessory/undershirt/greenshirtsport + name = "Sports Shirt (Green)" + icon_state = "greenshirtsport" + gender = NEUTER + +/datum/sprite_accessory/undershirt/redshirtsport + name = "Sports Shirt (Red)" + icon_state = "redshirtsport" + gender = NEUTER + +/datum/sprite_accessory/undershirt/tank_black + name = "Tank Top (Black)" + icon_state = "tank_black" + gender = NEUTER + +/datum/sprite_accessory/undershirt/tankfire + name = "Tank Top (Fire)" + icon_state = "tank_fire" + gender = NEUTER + +/datum/sprite_accessory/undershirt/tank_grey + name = "Tank Top (Grey)" + icon_state = "tank_grey" + gender = NEUTER + +/datum/sprite_accessory/undershirt/female_midriff + name = "Tank Top (Midriff)" + icon_state = "tank_midriff" + gender = FEMALE + +/datum/sprite_accessory/undershirt/tank_red + name = "Tank Top (Red)" + icon_state = "tank_red" + gender = NEUTER + +/datum/sprite_accessory/undershirt/tankstripe + name = "Tank Top (Striped)" + icon_state = "tank_stripes" + gender = NEUTER + +/datum/sprite_accessory/undershirt/tank_white + name = "Tank Top (White)" + icon_state = "tank_white" + gender = NEUTER + +/datum/sprite_accessory/undershirt/redtop + name = "Top (Red)" + icon_state = "redtop" + gender = FEMALE + +/datum/sprite_accessory/undershirt/whitetop + name = "Top (White)" + icon_state = "whitetop" + gender = FEMALE + +/datum/sprite_accessory/undershirt/tshirt_blue + name = "T-Shirt (Blue)" + icon_state = "blueshirt" + gender = NEUTER + +/datum/sprite_accessory/undershirt/tshirt_green + name = "T-Shirt (Green)" + icon_state = "greenshirt" + gender = NEUTER + +/datum/sprite_accessory/undershirt/tshirt_red + name = "T-Shirt (Red)" + icon_state = "redshirt" + gender = NEUTER + +/datum/sprite_accessory/undershirt/yellowshirt + name = "T-Shirt (Yellow)" + icon_state = "yellowshirt" + gender = NEUTER + +/////////////////////// +// Socks Definitions // +/////////////////////// + +/datum/sprite_accessory/socks + icon = 'icons/mob/underwear.dmi' + +/datum/sprite_accessory/socks/nude + name = "Nude" + icon_state = null + +// please make sure they're sorted alphabetically and categorized + +/datum/sprite_accessory/socks/black_knee + name = "Knee-high (Black)" + icon_state = "black_knee" + +/datum/sprite_accessory/socks/commie_knee + name = "Knee-High (Commie)" + icon_state = "commie_knee" + +/datum/sprite_accessory/socks/usa_knee + name = "Knee-High (Freedom)" + icon_state = "assblastusa_knee" + +/datum/sprite_accessory/socks/rainbow_knee + name = "Knee-high (Rainbow)" + icon_state = "rainbow_knee" + +/datum/sprite_accessory/socks/striped_knee + name = "Knee-high (Striped)" + icon_state = "striped_knee" + +/datum/sprite_accessory/socks/thin_knee + name = "Knee-high (Thin)" + icon_state = "thin_knee" + +/datum/sprite_accessory/socks/uk_knee + name = "Knee-High (UK)" + icon_state = "uk_knee" + +/datum/sprite_accessory/socks/white_knee + name = "Knee-high (White)" + icon_state = "white_knee" + +/datum/sprite_accessory/socks/bee_knee + name = "Knee-high (Bee)" + icon_state = "bee_knee" + +/datum/sprite_accessory/socks/black_norm + name = "Normal (Black)" + icon_state = "black_norm" + +/datum/sprite_accessory/socks/white_norm + name = "Normal (White)" + icon_state = "white_norm" + +/datum/sprite_accessory/socks/pantyhose + name = "Pantyhose" + icon_state = "pantyhose" + +/datum/sprite_accessory/socks/black_short + name = "Short (Black)" + icon_state = "black_short" + +/datum/sprite_accessory/socks/white_short + name = "Short (White)" + icon_state = "white_short" + +/datum/sprite_accessory/socks/stockings_blue + name = "Stockings (Blue)" + icon_state = "stockings_blue" + +/datum/sprite_accessory/socks/stockings_cyan + name = "Stockings (Cyan)" + icon_state = "stockings_cyan" + +/datum/sprite_accessory/socks/stockings_dpink + name = "Stockings (Dark Pink)" + icon_state = "stockings_dpink" + +/datum/sprite_accessory/socks/stockings_green + name = "Stockings (Green)" + icon_state = "stockings_black" + +/datum/sprite_accessory/socks/stockings_orange + name = "Stockings (Orange)" + icon_state = "stockings_orange" + +/datum/sprite_accessory/socks/stockings_programmer + name = "Stockings (Programmer)" + icon_state = "stockings_lpink" + +/datum/sprite_accessory/socks/stockings_purple + name = "Stockings (Purple)" + icon_state = "stockings_purple" + +/datum/sprite_accessory/socks/stockings_yellow + name = "Stockings (Yellow)" + icon_state = "stockings_yellow" + +/datum/sprite_accessory/socks/black_thigh + name = "Thigh-high (Black)" + icon_state = "black_thigh" + +/datum/sprite_accessory/socks/commie_thigh + name = "Thigh-high (Commie)" + icon_state = "commie_thigh" + +/datum/sprite_accessory/socks/usa_thigh + name = "Thigh-high (Freedom)" + icon_state = "assblastusa_thigh" + +/datum/sprite_accessory/socks/rainbow_thigh + name = "Thigh-high (Rainbow)" + icon_state = "rainbow_thigh" + +/datum/sprite_accessory/socks/striped_thigh + name = "Thigh-high (Striped)" + icon_state = "striped_thigh" + +/datum/sprite_accessory/socks/thin_thigh + name = "Thigh-high (Thin)" + icon_state = "thin_thigh" + +/datum/sprite_accessory/socks/uk_thigh + name = "Thigh-high (UK)" + icon_state = "uk_thigh" + +/datum/sprite_accessory/socks/white_thigh + name = "Thigh-high (White)" + icon_state = "white_thigh" + +/datum/sprite_accessory/socks/bee_thigh + name = "Thigh-high (Bee)" + icon_state = "bee_thigh" + +//////////.////////////////// +// MutantParts Definitions // +///////////////////////////// + +/datum/sprite_accessory/body_markings + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/body_markings/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/body_markings/dtiger + name = "Dark Tiger Body" + icon_state = "dtiger" + gender_specific = 1 + +/datum/sprite_accessory/body_markings/ltiger + name = "Light Tiger Body" + icon_state = "ltiger" + gender_specific = 1 + +/datum/sprite_accessory/body_markings/lbelly + name = "Light Belly" + icon_state = "lbelly" + gender_specific = 1 + +/datum/sprite_accessory/tails + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/tails_animated + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/tails/lizard/smooth + name = "Smooth" + icon_state = "smooth" + +/datum/sprite_accessory/tails_animated/lizard/smooth + name = "Smooth" + icon_state = "smooth" + +/datum/sprite_accessory/tails/lizard/dtiger + name = "Dark Tiger" + icon_state = "dtiger" + +/datum/sprite_accessory/tails_animated/lizard/dtiger + name = "Dark Tiger" + icon_state = "dtiger" + +/datum/sprite_accessory/tails/lizard/ltiger + name = "Light Tiger" + icon_state = "ltiger" + +/datum/sprite_accessory/tails_animated/lizard/ltiger + name = "Light Tiger" + icon_state = "ltiger" + +/datum/sprite_accessory/tails/lizard/spikes + name = "Spikes" + icon_state = "spikes" + +/datum/sprite_accessory/tails_animated/lizard/spikes + name = "Spikes" + icon_state = "spikes" + +/datum/sprite_accessory/tails/human/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/tails_animated/human/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/tails/human/cat + name = "Cat" + icon_state = "cat" + color_src = HAIR + +/datum/sprite_accessory/tails_animated/human/cat + name = "Cat" + icon_state = "cat" + color_src = HAIR + +/datum/sprite_accessory/snouts + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/snouts/sharp + name = "Sharp" + icon_state = "sharp" + +/datum/sprite_accessory/snouts/round + name = "Round" + icon_state = "round" + +/datum/sprite_accessory/snouts/sharplight + name = "Sharp + Light" + icon_state = "sharplight" + +/datum/sprite_accessory/snouts/roundlight + name = "Round + Light" + icon_state = "roundlight" + +/datum/sprite_accessory/horns + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/horns/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/horns/simple + name = "Simple" + icon_state = "simple" + +/datum/sprite_accessory/horns/short + name = "Short" + icon_state = "short" + +/datum/sprite_accessory/horns/curled + name = "Curled" + icon_state = "curled" + +/datum/sprite_accessory/horns/ram + name = "Ram" + icon_state = "ram" + +/datum/sprite_accessory/horns/angler + name = "Angeler" + icon_state = "angler" + +/datum/sprite_accessory/ears + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/ears/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/ears/cat + name = "Cat" + icon_state = "cat" + hasinner = 1 + color_src = HAIR + +/datum/sprite_accessory/wings/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/wings_open + icon = 'icons/mob/wings.dmi' + +/datum/sprite_accessory/wings_open/angel + name = "Angel" + icon_state = "angel" + color_src = 0 + dimension_x = 46 + center = TRUE + dimension_y = 34 + +/datum/sprite_accessory/wings + icon = 'icons/mob/wings.dmi' + +/datum/sprite_accessory/wings/angel + name = "Angel" + icon_state = "angel" + color_src = 0 + dimension_x = 46 + center = TRUE + dimension_y = 34 + locked = TRUE + +/datum/sprite_accessory/frills + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/frills/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/frills/simple + name = "Simple" + icon_state = "simple" + +/datum/sprite_accessory/frills/short + name = "Short" + icon_state = "short" + +/datum/sprite_accessory/frills/aquatic + name = "Aquatic" + icon_state = "aqua" + +/datum/sprite_accessory/spines + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/spines_animated + icon = 'icons/mob/mutant_bodyparts.dmi' + +/datum/sprite_accessory/spines/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/spines_animated/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/spines/short + name = "Short" + icon_state = "short" + +/datum/sprite_accessory/spines_animated/short + name = "Short" + icon_state = "short" + +/datum/sprite_accessory/spines/shortmeme + name = "Short + Membrane" + icon_state = "shortmeme" + +/datum/sprite_accessory/spines_animated/shortmeme + name = "Short + Membrane" + icon_state = "shortmeme" + +/datum/sprite_accessory/spines/long + name = "Long" + icon_state = "long" + +/datum/sprite_accessory/spines_animated/long + name = "Long" + icon_state = "long" + +/datum/sprite_accessory/spines/longmeme + name = "Long + Membrane" + icon_state = "longmeme" + +/datum/sprite_accessory/spines_animated/longmeme + name = "Long + Membrane" + icon_state = "longmeme" + +/datum/sprite_accessory/spines/aqautic + name = "Aquatic" + icon_state = "aqua" + +/datum/sprite_accessory/spines_animated/aqautic + name = "Aquatic" + icon_state = "aqua" + +/datum/sprite_accessory/legs //legs are a special case, they aren't actually sprite_accessories but are updated with them. + icon = null //These datums exist for selecting legs on preference, and little else + +/datum/sprite_accessory/legs/none + name = "Normal Legs" + +/datum/sprite_accessory/legs/digitigrade_lizard + name = "Digitigrade Legs" + +/datum/sprite_accessory/caps + icon = 'icons/mob/mutant_bodyparts.dmi' + color_src = HAIR + +/datum/sprite_accessory/caps/round + name = "Round" + icon_state = "round" + +/datum/sprite_accessory/moth_wings + icon = 'yogstation/icons/mob/wings.dmi' //yogs moth sprite fix + color_src = null + +/datum/sprite_accessory/moth_wings/plain + name = "Plain" + icon_state = "plain" + +/datum/sprite_accessory/moth_wings/monarch + name = "Monarch" + icon_state = "monarch" + +/datum/sprite_accessory/moth_wings/luna + name = "Luna" + icon_state = "luna" + +/datum/sprite_accessory/moth_wings/atlas + name = "Atlas" + icon_state = "atlas" + +/datum/sprite_accessory/moth_wings/reddish + name = "Reddish" + icon_state = "redish" + +/datum/sprite_accessory/moth_wings/royal + name = "Royal" + icon_state = "royal" + +/datum/sprite_accessory/moth_wings/gothic + name = "Gothic" + icon_state = "gothic" + +/datum/sprite_accessory/moth_wings/lovers + name = "Lovers" + icon_state = "lovers" + +/datum/sprite_accessory/moth_wings/whitefly + name = "White Fly" + icon_state = "whitefly" + +/datum/sprite_accessory/moth_wings/punished + name = "Burnt Off" + icon_state = "punished" + locked = TRUE + +/datum/sprite_accessory/moth_wings/firewatch + name = "Firewatch" + icon_state = "firewatch" + +/datum/sprite_accessory/moth_wings/deathhead + name = "Deathshead" + icon_state = "deathhead" + +/datum/sprite_accessory/moth_wings/poison + name = "Poison" + icon_state = "poison" + +/datum/sprite_accessory/moth_wings/ragged + name = "Ragged" + icon_state = "ragged" + +/datum/sprite_accessory/moth_wings/moonfly + name = "Moon Fly" + icon_state = "moonfly" + +/datum/sprite_accessory/moth_wings/snow + name = "Snow" + icon_state = "snow" diff --git a/code/modules/mob/dead/observer/login.dm b/code/modules/mob/dead/observer/login.dm index e23021e9146e..1b328dbc697c 100644 --- a/code/modules/mob/dead/observer/login.dm +++ b/code/modules/mob/dead/observer/login.dm @@ -1,20 +1,20 @@ -/mob/dead/observer/Login() - ..() - - ghost_accs = client.prefs.ghost_accs - ghost_others = client.prefs.ghost_others - var/preferred_form = null - - if(IsAdminGhost(src)) - has_unlimited_silicon_privilege = 1 - - if(client.prefs.unlock_content) - preferred_form = client.prefs.ghost_form - ghost_orbit = client.prefs.ghost_orbit - - var/turf/T = get_turf(src) - if (isturf(T)) - update_z(T.z) - - update_icon(preferred_form) - updateghostimages() +/mob/dead/observer/Login() + ..() + + ghost_accs = client.prefs.ghost_accs + ghost_others = client.prefs.ghost_others + var/preferred_form = null + + if(IsAdminGhost(src)) + has_unlimited_silicon_privilege = 1 + + if(client.prefs.unlock_content) + preferred_form = client.prefs.ghost_form + ghost_orbit = client.prefs.ghost_orbit + + var/turf/T = get_turf(src) + if (isturf(T)) + update_z(T.z) + + update_icon(preferred_form) + updateghostimages() diff --git a/code/modules/mob/dead/observer/logout.dm b/code/modules/mob/dead/observer/logout.dm index 41e2035d6174..3ca6405a0cff 100644 --- a/code/modules/mob/dead/observer/logout.dm +++ b/code/modules/mob/dead/observer/logout.dm @@ -1,16 +1,16 @@ -/mob/dead/observer/Logout() - update_z(null) - if (client) - client.images -= (GLOB.ghost_images_default+GLOB.ghost_images_simple) - - if(observetarget) - if(ismob(observetarget)) - var/mob/target = observetarget - if(target.observers) - target.observers -= src - UNSETEMPTY(target.observers) - observetarget = null - ..() - spawn(0) - if(src && !key) //we've transferred to another mob. This ghost should be deleted. - qdel(src) +/mob/dead/observer/Logout() + update_z(null) + if (client) + client.images -= (GLOB.ghost_images_default+GLOB.ghost_images_simple) + + if(observetarget) + if(ismob(observetarget)) + var/mob/target = observetarget + if(target.observers) + target.observers -= src + UNSETEMPTY(target.observers) + observetarget = null + ..() + spawn(0) + if(src && !key) //we've transferred to another mob. This ghost should be deleted. + qdel(src) diff --git a/code/modules/mob/dead/observer/observer_movement.dm b/code/modules/mob/dead/observer/observer_movement.dm index 7ed0c9d9eecd..f3e411fb06a2 100644 --- a/code/modules/mob/dead/observer/observer_movement.dm +++ b/code/modules/mob/dead/observer/observer_movement.dm @@ -1,2 +1,2 @@ -/mob/dead/observer/canZMove(direction, turf/target) - return TRUE +/mob/dead/observer/canZMove(direction, turf/target) + return TRUE diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm index 8364d42796b6..19aaab32de0c 100644 --- a/code/modules/mob/dead/observer/say.dm +++ b/code/modules/mob/dead/observer/say.dm @@ -1,38 +1,38 @@ -/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 - - if(check_emote(message, forced)) - return - - . = say_dead(message) - -/mob/dead/observer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - 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) - 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 + + if(check_emote(message, forced)) + return + + . = say_dead(message) + +/mob/dead/observer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + 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) + to_chat(src, "[link] [message]") + diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm index 2a74b7554bd4..5eb20b8dfa8f 100644 --- a/code/modules/mob/death.dm +++ b/code/modules/mob/death.dm @@ -1,14 +1,14 @@ -//This is the proc for gibbing a mob. Cannot gib ghosts. -//added different sort of gibs and animations. N -/mob/proc/gib() - return - -//This is the proc for turning a mob into ash. Mostly a copy of gib code (above). -//Originally created for wizard disintegrate. I've removed the virus code since it's irrelevant here. -//Dusting robots does not eject the MMI, so it's a bit more powerful than gib() /N -/mob/proc/dust(just_ash, drop_items, force) - return - -/mob/proc/death(gibbed) - SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed) - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_DEATH, src , gibbed) +//This is the proc for gibbing a mob. Cannot gib ghosts. +//added different sort of gibs and animations. N +/mob/proc/gib() + return + +//This is the proc for turning a mob into ash. Mostly a copy of gib code (above). +//Originally created for wizard disintegrate. I've removed the virus code since it's irrelevant here. +//Dusting robots does not eject the MMI, so it's a bit more powerful than gib() /N +/mob/proc/dust(just_ash, drop_items, force) + return + +/mob/proc/death(gibbed) + SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_DEATH, src , gibbed) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index b6737aed2dbb..ce6addd66102 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -1,497 +1,497 @@ -//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) - 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) - return FALSE //nonliving mobs don't have hands - -/mob/living/put_in_hand_check(obj/item/I) - if(istype(I) && ((mobility_flags & MOBILITY_PICKUP) || (I.item_flags & ABSTRACT))) - return TRUE - return FALSE - -//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() - - -/mob/living/carbon/proc/check_obscured_slots(transparent_protection) - var/list/obscured = list() - var/hidden_slots = NONE - - for(var/obj/item/I in get_equipped_items()) - hidden_slots |= I.flags_inv - if(transparent_protection) - hidden_slots |= I.transparent_protection - - if(hidden_slots & HIDENECK) - obscured |= SLOT_NECK - if(hidden_slots & HIDEMASK) - obscured |= SLOT_WEAR_MASK - if(hidden_slots & HIDEEYES) - obscured |= SLOT_GLASSES - if(hidden_slots & HIDEEARS) - obscured |= SLOT_EARS - if(hidden_slots & HIDEGLOVES) - obscured |= SLOT_GLOVES - if(hidden_slots & HIDEJUMPSUIT) - obscured |= SLOT_W_UNIFORM - if(hidden_slots & HIDESHOES) - obscured |= SLOT_SHOES - if(hidden_slots & HIDESUITSTORAGE) - obscured |= SLOT_S_STORE - - return obscured - - -/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 +//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) + 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) + return FALSE //nonliving mobs don't have hands + +/mob/living/put_in_hand_check(obj/item/I) + if(istype(I) && ((mobility_flags & MOBILITY_PICKUP) || (I.item_flags & ABSTRACT))) + return TRUE + return FALSE + +//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() + + +/mob/living/carbon/proc/check_obscured_slots(transparent_protection) + var/list/obscured = list() + var/hidden_slots = NONE + + for(var/obj/item/I in get_equipped_items()) + hidden_slots |= I.flags_inv + if(transparent_protection) + hidden_slots |= I.transparent_protection + + if(hidden_slots & HIDENECK) + obscured |= SLOT_NECK + if(hidden_slots & HIDEMASK) + obscured |= SLOT_WEAR_MASK + if(hidden_slots & HIDEEYES) + obscured |= SLOT_GLASSES + if(hidden_slots & HIDEEARS) + obscured |= SLOT_EARS + if(hidden_slots & HIDEGLOVES) + obscured |= SLOT_GLOVES + if(hidden_slots & HIDEJUMPSUIT) + obscured |= SLOT_W_UNIFORM + if(hidden_slots & HIDESHOES) + obscured |= SLOT_SHOES + if(hidden_slots & HIDESUITSTORAGE) + obscured |= SLOT_S_STORE + + return obscured + + +/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 diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index a741656f37a3..ddc098c26b0f 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -1,147 +1,147 @@ -/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/xenoblood/xgibs - unique_name = 1 - - var/static/regex/alien_name_regex = new("alien (larva|sentinel|drone|hunter|praetorian|queen)( \\(\\d+\\))?") - blood_volume = BLOOD_VOLUME_XENO //Yogs -- Makes monkeys/xenos have different amounts of blood from normal carbonbois - -/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/liver/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/xenoblood/xgibs + unique_name = 1 + + var/static/regex/alien_name_regex = new("alien (larva|sentinel|drone|hunter|praetorian|queen)( \\(\\d+\\))?") + blood_volume = BLOOD_VOLUME_XENO //Yogs -- Makes monkeys/xenos have different amounts of blood from normal carbonbois + +/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/liver/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 c9ad587e3ed2..27a8cbf819cf 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, blocked, datum/thrownthing/throwingdatum) - ..(AM, skipcatch = TRUE, hitpush = 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(isturf(loc) && istype(loc.loc, /area/start)) - to_chat(M, "No attacking people at spawn, you jackass.") - return - - switch(M.a_intent) - - if ("help") - set_resting(FALSE) - AdjustStun(-60) - AdjustKnockdown(-60) - AdjustImmobilized(-60) - AdjustParalyzed(-60) - AdjustUnconscious(-60) - AdjustSleeping(-100) - visible_message("[M.name] nuzzles [src] trying to wake [p_them()] up!") - - if ("grab") - grabbedby(M) - - else - 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 0 - - switch(M.a_intent) - if("help") - help_shake_act(M) - if("grab") - grabbedby(M) - if ("harm") - M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) - return 1 - if("disarm") - M.do_attack_animation(src, ATTACK_EFFECT_DISARM) - return 1 - return 0 - - -/mob/living/carbon/alien/attack_paw(mob/living/carbon/monkey/M) - if(..()) - if (stat != DEAD) - 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(..()) //successful slime attack - 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 (1) - gib() - return - - if (2) - take_overall_damage(60, 60) - adjustEarDamage(30,120) - - if(3) - 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, blocked, datum/thrownthing/throwingdatum) + ..(AM, skipcatch = TRUE, hitpush = 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(isturf(loc) && istype(loc.loc, /area/start)) + to_chat(M, "No attacking people at spawn, you jackass.") + return + + switch(M.a_intent) + + if ("help") + set_resting(FALSE) + AdjustStun(-60) + AdjustKnockdown(-60) + AdjustImmobilized(-60) + AdjustParalyzed(-60) + AdjustUnconscious(-60) + AdjustSleeping(-100) + visible_message("[M.name] nuzzles [src] trying to wake [p_them()] up!") + + if ("grab") + grabbedby(M) + + else + 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 0 + + switch(M.a_intent) + if("help") + help_shake_act(M) + if("grab") + grabbedby(M) + if ("harm") + M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) + return 1 + if("disarm") + M.do_attack_animation(src, ATTACK_EFFECT_DISARM) + return 1 + return 0 + + +/mob/living/carbon/alien/attack_paw(mob/living/carbon/monkey/M) + if(..()) + if (stat != DEAD) + 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(..()) //successful slime attack + 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 (1) + gib() + return + + if (2) + take_overall_damage(60, 60) + adjustEarDamage(30,120) + + if(3) + 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 58b1163b129a..718186c9078e 100644 --- a/code/modules/mob/living/carbon/alien/death.dm +++ b/code/modules/mob/living/carbon/alien/death.dm @@ -1,14 +1,14 @@ -/mob/living/carbon/alien/spawn_gibs(with_bodyparts) - if(with_bodyparts) - new /obj/effect/gibspawner/xeno(drop_location(), src) - else - new /obj/effect/gibspawner/xeno/bodypartless(drop_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) + if(with_bodyparts) + new /obj/effect/gibspawner/xeno(drop_location(), src) + else + new /obj/effect/gibspawner/xeno/bodypartless(drop_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/drone.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm index d3ed8a6541ad..183bf07e8cca 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm @@ -1,43 +1,43 @@ -/mob/living/carbon/alien/humanoid/drone - name = "alien drone" - caste = "d" - maxHealth = 125 - health = 125 - icon_state = "aliend" - -/mob/living/carbon/alien/humanoid/drone/Initialize() - AddAbility(new/obj/effect/proc_holder/alien/evolve(null)) - . = ..() - -/mob/living/carbon/alien/humanoid/drone/create_internal_organs() - internal_organs += new /obj/item/organ/alien/plasmavessel/large - internal_organs += new /obj/item/organ/alien/resinspinner - internal_organs += new /obj/item/organ/alien/acid - ..() - -/obj/effect/proc_holder/alien/evolve - name = "Evolve to Praetorian" - desc = "Praetorian" - plasma_cost = 500 - - action_icon_state = "alien_evolve_drone" - -/obj/effect/proc_holder/alien/evolve/fire(mob/living/carbon/alien/humanoid/user) - var/obj/item/organ/alien/hivenode/node = user.getorgan(/obj/item/organ/alien/hivenode) - if(!node) //Players are Murphy's Law. We may not expect there to ever be a living xeno with no hivenode, but they _WILL_ make it happen. - to_chat(user, "Without the hivemind, you can't possibly hold the responsibility of leadership!") - return 0 - if(node.recent_queen_death) - to_chat(user, "Your thoughts are still too scattered to take up the position of leadership.") - return 0 - - if(!isturf(user.loc)) - to_chat(user, "You can't evolve here!") - return 0 - if(!get_alien_type(/mob/living/carbon/alien/humanoid/royal)) - var/mob/living/carbon/alien/humanoid/royal/praetorian/new_xeno = new (user.loc) - user.alien_evolve(new_xeno) - return 1 - else - to_chat(user, "We already have a living royal!") +/mob/living/carbon/alien/humanoid/drone + name = "alien drone" + caste = "d" + maxHealth = 125 + health = 125 + icon_state = "aliend" + +/mob/living/carbon/alien/humanoid/drone/Initialize() + AddAbility(new/obj/effect/proc_holder/alien/evolve(null)) + . = ..() + +/mob/living/carbon/alien/humanoid/drone/create_internal_organs() + internal_organs += new /obj/item/organ/alien/plasmavessel/large + internal_organs += new /obj/item/organ/alien/resinspinner + internal_organs += new /obj/item/organ/alien/acid + ..() + +/obj/effect/proc_holder/alien/evolve + name = "Evolve to Praetorian" + desc = "Praetorian" + plasma_cost = 500 + + action_icon_state = "alien_evolve_drone" + +/obj/effect/proc_holder/alien/evolve/fire(mob/living/carbon/alien/humanoid/user) + var/obj/item/organ/alien/hivenode/node = user.getorgan(/obj/item/organ/alien/hivenode) + if(!node) //Players are Murphy's Law. We may not expect there to ever be a living xeno with no hivenode, but they _WILL_ make it happen. + to_chat(user, "Without the hivemind, you can't possibly hold the responsibility of leadership!") + return 0 + if(node.recent_queen_death) + to_chat(user, "Your thoughts are still too scattered to take up the position of leadership.") + return 0 + + if(!isturf(user.loc)) + to_chat(user, "You can't evolve here!") + return 0 + if(!get_alien_type(/mob/living/carbon/alien/humanoid/royal)) + var/mob/living/carbon/alien/humanoid/royal/praetorian/new_xeno = new (user.loc) + user.alien_evolve(new_xeno) + return 1 + else + to_chat(user, "We already have a living royal!") return 0 \ No newline at end of file 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 a998d63ec878..824b0ab36eef 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm @@ -1,95 +1,95 @@ -/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((mobility_flags & (MOBILITY_MOVE | MOBILITY_STAND)) != (MOBILITY_MOVE | MOBILITY_STAND) || 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/hit_atom, datum/thrownthing/throwingdatum) - - if(!leaping) - return ..() - - pounce_cooldown = world.time + pounce_cooldown_time - if(hit_atom) - if(isliving(hit_atom)) - var/mob/living/L = hit_atom - var/blocked = FALSE - if(ishuman(hit_atom)) - var/mob/living/carbon/human/H = hit_atom - if(H.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) - blocked = TRUE - if(!blocked) - L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") - L.Paralyze(100) - sleep(2)//Runtime prevention (infinite bump() calls on hulks) - step_towards(src,L) - else - Paralyze(40, 1, 1) - - toggle_leap(0) - else if(hit_atom.density && !hit_atom.CanPass(src)) - visible_message("[src] smashes into [hit_atom]!", "[src] smashes into [hit_atom]!") - Paralyze(40, 1, 1) - - if(leaping) - leaping = FALSE - update_icons() - update_mobility() - - -/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((mobility_flags & (MOBILITY_MOVE | MOBILITY_STAND)) != (MOBILITY_MOVE | MOBILITY_STAND) || 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/hit_atom, datum/thrownthing/throwingdatum) + + if(!leaping) + return ..() + + pounce_cooldown = world.time + pounce_cooldown_time + if(hit_atom) + if(isliving(hit_atom)) + var/mob/living/L = hit_atom + var/blocked = FALSE + if(ishuman(hit_atom)) + var/mob/living/carbon/human/H = hit_atom + if(H.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) + blocked = TRUE + if(!blocked) + L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") + L.Paralyze(100) + sleep(2)//Runtime prevention (infinite bump() calls on hulks) + step_towards(src,L) + else + Paralyze(40, 1, 1) + + toggle_leap(0) + else if(hit_atom.density && !hit_atom.CanPass(src)) + visible_message("[src] smashes into [hit_atom]!", "[src] smashes into [hit_atom]!") + Paralyze(40, 1, 1) + + if(leaping) + leaping = FALSE + update_icons() + update_mobility() + + +/mob/living/carbon/alien/humanoid/float(on) + if(leaping) + return + ..() + + diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm index 97486e6a9d53..7c6443cfae7b 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm @@ -1,16 +1,16 @@ -/mob/living/carbon/alien/humanoid/sentinel - name = "alien sentinel" - caste = "s" - maxHealth = 150 - health = 150 - icon_state = "aliens" - -/mob/living/carbon/alien/humanoid/sentinel/Initialize() - AddAbility(new /obj/effect/proc_holder/alien/sneak) - . = ..() - -/mob/living/carbon/alien/humanoid/sentinel/create_internal_organs() - internal_organs += new /obj/item/organ/alien/plasmavessel - internal_organs += new /obj/item/organ/alien/acid - internal_organs += new /obj/item/organ/alien/neurotoxin - ..() +/mob/living/carbon/alien/humanoid/sentinel + name = "alien sentinel" + caste = "s" + maxHealth = 150 + health = 150 + icon_state = "aliens" + +/mob/living/carbon/alien/humanoid/sentinel/Initialize() + AddAbility(new /obj/effect/proc_holder/alien/sneak) + . = ..() + +/mob/living/carbon/alien/humanoid/sentinel/create_internal_organs() + internal_organs += new /obj/item/organ/alien/plasmavessel + internal_organs += new /obj/item/organ/alien/acid + internal_organs += new /obj/item/organ/alien/neurotoxin + ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/death.dm b/code/modules/mob/living/carbon/alien/humanoid/death.dm index ad0d8bbb9d58..fb995d8fb509 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/death.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/death.dm @@ -1,22 +1,22 @@ -/mob/living/carbon/alien/humanoid/death(gibbed) - if(stat == DEAD) - return - - . = ..() - - 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_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/inventory.dm b/code/modules/mob/living/carbon/alien/humanoid/inventory.dm index d094c9b1b1ae..686588dcb3c5 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/inventory.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/inventory.dm @@ -1,5 +1,5 @@ -/mob/living/carbon/alien/humanoid/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) - . = ..() - if(!. || !I) - return - +/mob/living/carbon/alien/humanoid/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) + . = ..() + if(!. || !I) + return + diff --git a/code/modules/mob/living/carbon/alien/humanoid/life.dm b/code/modules/mob/living/carbon/alien/humanoid/life.dm index 38267336841e..fd6a73ae7bf9 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/life.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/life.dm @@ -1,18 +1,18 @@ - - -/mob/living/carbon/alien/humanoid/proc/adjust_body_temperature(current, loc_temp, boost) - var/temperature = current - var/difference = abs(current-loc_temp) //get difference - var/increments// = difference/10 //find how many increments apart they are - if(difference > 50) - increments = difference/5 - else - increments = difference/10 - var/change = increments*boost // Get the amount to change by (x per increment) - var/temp_change - if(current < loc_temp) - temperature = min(loc_temp, temperature+change) - else if(current > loc_temp) - temperature = max(loc_temp, temperature-change) - temp_change = (temperature - current) - return temp_change + + +/mob/living/carbon/alien/humanoid/proc/adjust_body_temperature(current, loc_temp, boost) + var/temperature = current + var/difference = abs(current-loc_temp) //get difference + var/increments// = difference/10 //find how many increments apart they are + if(difference > 50) + increments = difference/5 + else + increments = difference/10 + var/change = increments*boost // Get the amount to change by (x per increment) + var/temp_change + if(current < loc_temp) + temperature = min(loc_temp, temperature+change) + else if(current > loc_temp) + temperature = max(loc_temp, temperature-change) + temp_change = (temperature - current) + return temp_change diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index 34897d1aef14..a0e4b04f60a6 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -1,157 +1,157 @@ -/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() - SSshuttle.registerHostileEnvironment(src) //yogs: aliens delay shuttle - addtimer(CALLBACK(src, .proc/game_end), 30 MINUTES) //yogs: time until shuttle is freed/called - //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 - ..() - -/mob/living/carbon/alien/humanoid/royal/queen/proc/game_end() - if(stat != DEAD) - SSshuttle.clearHostileEnvironment(src) - if(EMERGENCY_IDLE_OR_RECALLED) - priority_announce("Xenomorph infestation detected: crisis shuttle protocols activated - jamming recall signals across all frequencies. P.S.: Bring back a live one please.") - SSshuttle.emergency.request(null, set_coefficient=0.5) - SSshuttle.emergencyNoRecall = TRUE - -/mob/living/carbon/alien/humanoid/royal/queen/death()//yogs start: dead queen doesnt stop shuttle - SSshuttle.clearHostileEnvironment(src) - ..() - -/mob/living/carbon/alien/humanoid/royal/queen/Destroy() - SSshuttle.clearHostileEnvironment(src) - ..() //yogs end - -//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() + SSshuttle.registerHostileEnvironment(src) //yogs: aliens delay shuttle + addtimer(CALLBACK(src, .proc/game_end), 30 MINUTES) //yogs: time until shuttle is freed/called + //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 + ..() + +/mob/living/carbon/alien/humanoid/royal/queen/proc/game_end() + if(stat != DEAD) + SSshuttle.clearHostileEnvironment(src) + if(EMERGENCY_IDLE_OR_RECALLED) + priority_announce("Xenomorph infestation detected: crisis shuttle protocols activated - jamming recall signals across all frequencies. P.S.: Bring back a live one please.") + SSshuttle.emergency.request(null, set_coefficient=0.5) + SSshuttle.emergencyNoRecall = TRUE + +/mob/living/carbon/alien/humanoid/royal/queen/death()//yogs start: dead queen doesnt stop shuttle + SSshuttle.clearHostileEnvironment(src) + ..() + +/mob/living/carbon/alien/humanoid/royal/queen/Destroy() + SSshuttle.clearHostileEnvironment(src) + ..() //yogs end + +//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 3aabbd2e81d7..705381e6b8bb 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm @@ -1,94 +1,94 @@ - -/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 || IsParalyzed()) - icon_state = "alien[caste]_unconscious" - else if(leap_on_click) - icon_state = "alien[caste]_pounce" - - else if(!(mobility_flags & MOBILITY_STAND)) - 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(mobility_flags & MOBILITY_STAND) - pixel_y = get_standard_pixel_y_offset(mobility_flags & MOBILITY_STAND) - 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) - lying = 90 //Anything else looks retarded - ..() - update_icons() - -/mob/living/carbon/alien/humanoid/update_inv_handcuffed() - remove_overlay(HANDCUFF_LAYER) - var/cuff_icon = "aliencuff" - var/dmi_file = 'icons/mob/alien.dmi' - - if(mob_size == MOB_SIZE_LARGE) - cuff_icon = "aliencuff_[caste]" - dmi_file = 'icons/mob/alienqueen.dmi' - - if(handcuffed) - overlays_standing[HANDCUFF_LAYER] = mutable_appearance(dmi_file, cuff_icon, -HANDCUFF_LAYER) - 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 || IsParalyzed()) + icon_state = "alien[caste]_unconscious" + else if(leap_on_click) + icon_state = "alien[caste]_pounce" + + else if(!(mobility_flags & MOBILITY_STAND)) + 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(mobility_flags & MOBILITY_STAND) + pixel_y = get_standard_pixel_y_offset(mobility_flags & MOBILITY_STAND) + 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) + lying = 90 //Anything else looks retarded + ..() + update_icons() + +/mob/living/carbon/alien/humanoid/update_inv_handcuffed() + remove_overlay(HANDCUFF_LAYER) + var/cuff_icon = "aliencuff" + var/dmi_file = 'icons/mob/alien.dmi' + + if(mob_size == MOB_SIZE_LARGE) + cuff_icon = "aliencuff_[caste]" + dmi_file = 'icons/mob/alienqueen.dmi' + + if(handcuffed) + overlays_standing[HANDCUFF_LAYER] = mutable_appearance(dmi_file, cuff_icon, -HANDCUFF_LAYER) + 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 c11039c8ad50..8fd6329a0c1d 100644 --- a/code/modules/mob/living/carbon/alien/larva/death.dm +++ b/code/modules/mob/living/carbon/alien/larva/death.dm @@ -1,22 +1,22 @@ -/mob/living/carbon/alien/larva/death(gibbed) - if(stat == DEAD) - return - - . = ..() - - update_icons() - -/mob/living/carbon/alien/larva/spawn_gibs(with_bodyparts) - if(with_bodyparts) - new /obj/effect/gibspawner/larva(drop_location(), src) - else - new /obj/effect/gibspawner/larva/bodypartless(drop_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) + if(with_bodyparts) + new /obj/effect/gibspawner/larva(drop_location(), src) + else + new /obj/effect/gibspawner/larva/bodypartless(drop_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/inventory.dm b/code/modules/mob/living/carbon/alien/larva/inventory.dm index 1c58b28e2818..17689785acf6 100644 --- a/code/modules/mob/living/carbon/alien/larva/inventory.dm +++ b/code/modules/mob/living/carbon/alien/larva/inventory.dm @@ -1,3 +1,3 @@ -//can't unequip since it can't equip anything -/mob/living/carbon/alien/larva/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) - return +//can't unequip since it can't equip anything +/mob/living/carbon/alien/larva/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) + return diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index c5c8a8038a64..3b150a2264c3 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -1,69 +1,69 @@ -/mob/living/carbon/alien/larva - name = "alien larva" - real_name = "alien larva" - icon_state = "larva0" - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - density = FALSE - hud_type = /datum/hud/larva - - maxHealth = 25 - health = 25 - - var/amount_grown = 0 - var/max_grown = 100 - var/time_of_birth - - rotate_on_lying = 0 - bodyparts = list(/obj/item/bodypart/chest/larva, /obj/item/bodypart/head/larva) - - -//This is fine right now, if we're adding organ specific damage this needs to be updated -/mob/living/carbon/alien/larva/Initialize() - - AddAbility(new/obj/effect/proc_holder/alien/hide(null)) - AddAbility(new/obj/effect/proc_holder/alien/larva_evolve(null)) - . = ..() - -/mob/living/carbon/alien/larva/create_internal_organs() - internal_organs += new /obj/item/organ/alien/plasmavessel/small/tiny - ..() - -//This needs to be fixed -/mob/living/carbon/alien/larva/Stat() - ..() - if(statpanel("Status")) - stat(null, "Progress: [amount_grown]/[max_grown]") - -/mob/living/carbon/alien/larva/adjustPlasma(amount) - if(stat != DEAD && amount > 0) - amount_grown = min(amount_grown + 1, max_grown) - ..(amount) - -//can't equip anything -/mob/living/carbon/alien/larva/attack_ui(slot_id) - return - -/mob/living/carbon/alien/larva/restrained(ignore_grab) - . = 0 - -// new damage icon system -// now constructs damage icon for each organ from mask * damage field - - -/mob/living/carbon/alien/larva/show_inv(mob/user) - return - -/mob/living/carbon/alien/larva/toggle_throw_mode() - return - -/mob/living/carbon/alien/larva/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) - return - -/mob/living/carbon/alien/larva/stripPanelUnequip(obj/item/what, mob/who) - to_chat(src, "You don't have the dexterity to do this!") - return - -/mob/living/carbon/alien/larva/stripPanelEquip(obj/item/what, mob/who) - to_chat(src, "You don't have the dexterity to do this!") - return +/mob/living/carbon/alien/larva + name = "alien larva" + real_name = "alien larva" + icon_state = "larva0" + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + density = FALSE + hud_type = /datum/hud/larva + + maxHealth = 25 + health = 25 + + var/amount_grown = 0 + var/max_grown = 100 + var/time_of_birth + + rotate_on_lying = 0 + bodyparts = list(/obj/item/bodypart/chest/larva, /obj/item/bodypart/head/larva) + + +//This is fine right now, if we're adding organ specific damage this needs to be updated +/mob/living/carbon/alien/larva/Initialize() + + AddAbility(new/obj/effect/proc_holder/alien/hide(null)) + AddAbility(new/obj/effect/proc_holder/alien/larva_evolve(null)) + . = ..() + +/mob/living/carbon/alien/larva/create_internal_organs() + internal_organs += new /obj/item/organ/alien/plasmavessel/small/tiny + ..() + +//This needs to be fixed +/mob/living/carbon/alien/larva/Stat() + ..() + if(statpanel("Status")) + stat(null, "Progress: [amount_grown]/[max_grown]") + +/mob/living/carbon/alien/larva/adjustPlasma(amount) + if(stat != DEAD && amount > 0) + amount_grown = min(amount_grown + 1, max_grown) + ..(amount) + +//can't equip anything +/mob/living/carbon/alien/larva/attack_ui(slot_id) + return + +/mob/living/carbon/alien/larva/restrained(ignore_grab) + . = 0 + +// new damage icon system +// now constructs damage icon for each organ from mask * damage field + + +/mob/living/carbon/alien/larva/show_inv(mob/user) + return + +/mob/living/carbon/alien/larva/toggle_throw_mode() + return + +/mob/living/carbon/alien/larva/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) + return + +/mob/living/carbon/alien/larva/stripPanelUnequip(obj/item/what, mob/who) + to_chat(src, "You don't have the dexterity to do this!") + return + +/mob/living/carbon/alien/larva/stripPanelEquip(obj/item/what, mob/who) + to_chat(src, "You don't have the dexterity to do this!") + return diff --git a/code/modules/mob/living/carbon/alien/larva/life.dm b/code/modules/mob/living/carbon/alien/larva/life.dm index acfe3f6fafbc..095c28991211 100644 --- a/code/modules/mob/living/carbon/alien/larva/life.dm +++ b/code/modules/mob/living/carbon/alien/larva/life.dm @@ -1,32 +1,32 @@ - - -/mob/living/carbon/alien/larva/Life() - set invisibility = 0 - if (notransform) - return - if(..() && !IS_IN_STASIS(src)) //not dead and not in stasis - // GROW! - if(amount_grown < max_grown) - amount_grown++ - update_icons() - - -/mob/living/carbon/alien/larva/update_stat() - if(status_flags & GODMODE) - return - if(stat != DEAD) - if(health<= -maxHealth || !getorgan(/obj/item/organ/brain)) - death() - return - if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || health <= crit_threshold) - if(stat == CONSCIOUS) - stat = UNCONSCIOUS - blind_eyes(1) - update_mobility() - else - if(stat == UNCONSCIOUS) - stat = CONSCIOUS - set_resting(FALSE) - adjust_blindness(-1) - update_damage_hud() - update_health_hud() + + +/mob/living/carbon/alien/larva/Life() + set invisibility = 0 + if (notransform) + return + if(..() && !IS_IN_STASIS(src)) //not dead and not in stasis + // GROW! + if(amount_grown < max_grown) + amount_grown++ + update_icons() + + +/mob/living/carbon/alien/larva/update_stat() + if(status_flags & GODMODE) + return + if(stat != DEAD) + if(health<= -maxHealth || !getorgan(/obj/item/organ/brain)) + death() + return + if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || health <= crit_threshold) + if(stat == CONSCIOUS) + stat = UNCONSCIOUS + blind_eyes(1) + update_mobility() + else + if(stat == UNCONSCIOUS) + stat = CONSCIOUS + set_resting(FALSE) + adjust_blindness(-1) + update_damage_hud() + update_health_hud() diff --git a/code/modules/mob/living/carbon/alien/larva/powers.dm b/code/modules/mob/living/carbon/alien/larva/powers.dm index 9d5617b3e731..7204759db567 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/larva/update_icons.dm b/code/modules/mob/living/carbon/alien/larva/update_icons.dm index e1af00c174c7..983544e8c72e 100644 --- a/code/modules/mob/living/carbon/alien/larva/update_icons.dm +++ b/code/modules/mob/living/carbon/alien/larva/update_icons.dm @@ -1,31 +1,31 @@ - -/mob/living/carbon/alien/larva/regenerate_icons() - cut_overlays() - update_icons() - -/mob/living/carbon/alien/larva/update_icons() - var/state = 0 - if(amount_grown > 80) - state = 2 - else if(amount_grown > 50) - state = 1 - - if(stat == DEAD) - icon_state = "larva[state]_dead" - else if(handcuffed || legcuffed) //This should be an overlay. Who made this an icon_state? - icon_state = "larva[state]_cuff" - else if(!(mobility_flags & MOBILITY_STAND)) - icon_state = "larva[state]_sleep" - else if(IsStun()) - icon_state = "larva[state]_stun" - else - icon_state = "larva[state]" - -/mob/living/carbon/alien/larva/update_transform() //All this is handled in update_icons() - ..() - return update_icons() - -/mob/living/carbon/alien/larva/update_inv_handcuffed() - update_icons() //larva icon_state changes if cuffed/uncuffed. - - + +/mob/living/carbon/alien/larva/regenerate_icons() + cut_overlays() + update_icons() + +/mob/living/carbon/alien/larva/update_icons() + var/state = 0 + if(amount_grown > 80) + state = 2 + else if(amount_grown > 50) + state = 1 + + if(stat == DEAD) + icon_state = "larva[state]_dead" + else if(handcuffed || legcuffed) //This should be an overlay. Who made this an icon_state? + icon_state = "larva[state]_cuff" + else if(!(mobility_flags & MOBILITY_STAND)) + icon_state = "larva[state]_sleep" + else if(IsStun()) + icon_state = "larva[state]_stun" + else + icon_state = "larva[state]" + +/mob/living/carbon/alien/larva/update_transform() //All this is handled in update_icons() + ..() + return update_icons() + +/mob/living/carbon/alien/larva/update_inv_handcuffed() + update_icons() //larva icon_state changes if cuffed/uncuffed. + + diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index be4a0ed74603..fec41173585c 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -1,54 +1,54 @@ -/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 - - breath.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) - - //Partial pressure of the toxins in our breath - var/Toxins_pp = (breath_gases[/datum/gas/plasma][MOLES]/breath.total_moles())*breath_pressure - - if(Toxins_pp > tox_detect_threshold) // Detect toxins in air - adjustPlasma(breath_gases[/datum/gas/plasma][MOLES]*250) - throw_alert("alien_tox", /obj/screen/alert/alien_tox) - - toxins_used = breath_gases[/datum/gas/plasma][MOLES] - - else - clear_alert("alien_tox") - - //Breathe in toxins and out oxygen - breath_gases[/datum/gas/plasma][MOLES] -= toxins_used - breath_gases[/datum/gas/oxygen][MOLES] += toxins_used - - breath.garbage_collect() - - //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(.) //if the mob isn't on fire anymore - return - adjust_bodytemperature(BODYTEMP_HEATING_MAX) //If you're on fire, you heat up! +/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 + + breath.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) + + //Partial pressure of the toxins in our breath + var/Toxins_pp = (breath_gases[/datum/gas/plasma][MOLES]/breath.total_moles())*breath_pressure + + if(Toxins_pp > tox_detect_threshold) // Detect toxins in air + adjustPlasma(breath_gases[/datum/gas/plasma][MOLES]*250) + throw_alert("alien_tox", /obj/screen/alert/alien_tox) + + toxins_used = breath_gases[/datum/gas/plasma][MOLES] + + else + clear_alert("alien_tox") + + //Breathe in toxins and out oxygen + breath_gases[/datum/gas/plasma][MOLES] -= toxins_used + breath_gases[/datum/gas/oxygen][MOLES] += toxins_used + + breath.garbage_collect() + + //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(.) //if the mob isn't on fire anymore + return + adjust_bodytemperature(BODYTEMP_HEATING_MAX) //If you're on fire, you heat up! diff --git a/code/modules/mob/living/carbon/alien/login.dm b/code/modules/mob/living/carbon/alien/login.dm index 8d5968848b6d..5c177fbb6228 100644 --- a/code/modules/mob/living/carbon/alien/login.dm +++ b/code/modules/mob/living/carbon/alien/login.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/alien/Login() - ..() - AddInfectionImages() - return +/mob/living/carbon/alien/Login() + ..() + AddInfectionImages() + return diff --git a/code/modules/mob/living/carbon/alien/logout.dm b/code/modules/mob/living/carbon/alien/logout.dm index a4145581cb3c..f25b21f0cca9 100644 --- a/code/modules/mob/living/carbon/alien/logout.dm +++ b/code/modules/mob/living/carbon/alien/logout.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/alien/Logout() - ..() - RemoveInfectionImages() - return +/mob/living/carbon/alien/Logout() + ..() + RemoveInfectionImages() + return diff --git a/code/modules/mob/living/carbon/alien/say.dm b/code/modules/mob/living/carbon/alien/say.dm index 5f1e1a2830e1..1ad0a1e7ffd6 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 b5b646b08b69..1e7ff076318f 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 6fb3968731d9..47c1f18f9567 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -1,137 +1,137 @@ -// 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(gib_on_success=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) - new_xeno.key = ghost.key - SEND_SOUND(new_xeno, sound('sound/voice/hiss5.ogg',0,0,0,100)) //To get the player's attention - new_xeno.mobility_flags = NONE //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.mobility_flags = MOBILITY_FLAGS_DEFAULT - new_xeno.notransform = 0 - new_xeno.invisibility = 0 - - if(gib_on_success) - new_xeno.visible_message("[new_xeno] bursts out of [owner] in a shower of gore!", "You exit [owner], your previous host.", "You hear organic matter ripping and tearing!") - var/obj/item/organ/brain/BR = owner.getorgan(/obj/item/organ/brain) //yogs start - if(BR) - BR.setOrganDamage(maxHealth) //trauma from having a FUCKING XENO COME OUT OF YOUR BODY - owner.gib() //yogs end - else - new_xeno.visible_message("[new_xeno] wriggles out of [owner]!", "You exit [owner], your previous host.") - owner.adjustBruteLoss(40) - 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() - for(var/mob/living/carbon/alien/alien in GLOB.player_list) - if(alien.client) - var/I = image('icons/mob/alien.dmi', loc = owner, 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() - 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 == owner) - 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(gib_on_success=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) + new_xeno.key = ghost.key + SEND_SOUND(new_xeno, sound('sound/voice/hiss5.ogg',0,0,0,100)) //To get the player's attention + new_xeno.mobility_flags = NONE //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.mobility_flags = MOBILITY_FLAGS_DEFAULT + new_xeno.notransform = 0 + new_xeno.invisibility = 0 + + if(gib_on_success) + new_xeno.visible_message("[new_xeno] bursts out of [owner] in a shower of gore!", "You exit [owner], your previous host.", "You hear organic matter ripping and tearing!") + var/obj/item/organ/brain/BR = owner.getorgan(/obj/item/organ/brain) //yogs start + if(BR) + BR.setOrganDamage(maxHealth) //trauma from having a FUCKING XENO COME OUT OF YOUR BODY + owner.gib() //yogs end + else + new_xeno.visible_message("[new_xeno] wriggles out of [owner]!", "You exit [owner], your previous host.") + owner.adjustBruteLoss(40) + 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() + for(var/mob/living/carbon/alien/alien in GLOB.player_list) + if(alien.client) + var/I = image('icons/mob/alien.dmi', loc = owner, 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() + 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 == owner) + 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 aa9c0aeccb5c..cd82fb898db7 100644 --- a/code/modules/mob/living/carbon/alien/special/facehugger.dm +++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm @@ -1,270 +1,270 @@ - - -//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 = MASKINTERNALS - throw_range = 5 - tint = 3 - flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH - layer = MOB_LAYER - max_integrity = 100 - - 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 = 1 - -/obj/item/clothing/mask/facehugger/dead - icon_state = "facehugger_dead" - item_state = "facehugger_inactive" - stat = DEAD - -/obj/item/clothing/mask/facehugger/impregnated - icon_state = "facehugger_impregnated" - item_state = "facehugger_impregnated" - 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, datum/thrownthing/throwingdatum) - ..() - 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) - var/turf/T = get_turf(target) - log_game("[key_name(target)] was impregnated by a facehugger at [loc_name(T)]") - - 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 = MASKINTERNALS + throw_range = 5 + tint = 3 + flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH + layer = MOB_LAYER + max_integrity = 100 + + 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 = 1 + +/obj/item/clothing/mask/facehugger/dead + icon_state = "facehugger_dead" + item_state = "facehugger_inactive" + stat = DEAD + +/obj/item/clothing/mask/facehugger/impregnated + icon_state = "facehugger_impregnated" + item_state = "facehugger_impregnated" + 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, datum/thrownthing/throwingdatum) + ..() + 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) + var/turf/T = get_turf(target) + log_game("[key_name(target)] was impregnated by a facehugger at [loc_name(T)]") + + 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_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index c5a8e994a517..e00afe4c16d4 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -1,393 +1,393 @@ -/mob/living/carbon/get_eye_protection() - . = ..() - var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) - if(!E) - return INFINITY //Can't get flashed without eyes - else - . += E.flash_protect - if(isclothing(head)) //Adds head protection - . += head.flash_protect - if(isclothing(glasses)) //Glasses - . += glasses.flash_protect - if(isclothing(wear_mask)) //Mask - . += wear_mask.flash_protect - -/mob/living/carbon/get_ear_protection() - . = ..() - var/obj/item/organ/ears/E = getorganslot(ORGAN_SLOT_EARS) - if(!E) - return INFINITY - else - . += E.bang_protect - -/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 = TRUE, check_head = TRUE, check_mask = TRUE) - if(check_head && head && (head.flags_cover & HEADCOVERSEYES)) - return head - if(check_mask && wear_mask && (wear_mask.flags_cover & MASKCOVERSEYES)) - return wear_mask - if(check_glasses && glasses && (glasses.flags_cover & GLASSESCOVERSEYES)) - return glasses - -/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/proc/can_catch_item(skip_throw_mode_check) - . = FALSE - if(!skip_throw_mode_check && !in_throw_mode) - return - if(get_active_held_item()) - return - if(!(mobility_flags & MOBILITY_MOVE)) - return - if(restrained()) - return - return TRUE - -/mob/living/carbon/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) - if(!skipcatch) //ugly, but easy - if(can_catch_item()) - if(istype(AM, /obj/item)) - var/obj/item/I = AM - if(isturf(I.loc)) - 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 1 - ..() - - -/mob/living/carbon/attacked_by(obj/item/I, mob/living/user) - 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(I.force, I.damtype, affecting) - if(I.damtype == BRUTE && affecting.status == BODYPART_ORGANIC) - if(prob(33)) - I.add_mob_blood(src) - var/turf/location = get_turf(src) - add_splatter_floor(location) - if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood - user.add_mob_blood(src) - if(ishuman(user)) - var/mob/living/carbon/human/dirtyboy = user - dirtyboy.adjust_hygiene(-10) - if(affecting.body_zone == BODY_ZONE_HEAD) - if(wear_mask) - wear_mask.add_mob_blood(src) - update_inv_wear_mask() - if(wear_neck) - wear_neck.add_mob_blood(src) - update_inv_neck() - if(head) - 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) - - 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) - - for(var/datum/surgery/S in surgeries) - if(!(mobility_flags & MOBILITY_STAND) || !S.lying_required) - if(user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM) - if(S.next_step(user, user.a_intent)) - return 1 - return 0 - - -/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(..()) //successful slime attack - 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) - Paralyze(power*20) - if(stuttering < power) - stuttering = power - if (prob(stunprob) && M.powerlevel >= 8) - adjustFireLoss(M.powerlevel * rand(6,10)) - updatehealth() - return 1 - -/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) - Paralyze(40) - spawn(20) - jitteriness = max(jitteriness - 990, 10) //Still jittery, but vastly less - if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) - Paralyze(60) - if(stat == DEAD && can_defib()) //yogs: ZZAPP - if(!illusion && (shock_damage * siemens_coeff >= 1) && prob(25)) - set_heartattack(FALSE) - revive() - emote("gasp") - Jitter(100) - SEND_SIGNAL(src, COMSIG_LIVING_MINOR_SHOCK) - adjustOrganLoss(ORGAN_SLOT_BRAIN, 100, 199) //yogs end - 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(!(mobility_flags & MOBILITY_STAND)) - 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 - 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) - for(var/datum/brain_trauma/trauma in M.get_traumas()) - trauma.on_hug(M, src) - AdjustStun(-60) - AdjustKnockdown(-60) - AdjustUnconscious(-60) - AdjustSleeping(-100) - AdjustParalyzed(-60) - AdjustImmobilized(-60) - set_resting(FALSE) - - 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) - if(NOFLASH in dna?.species?.species_traits) - return - var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES) - if(!eyes) //can't flash what can't see! - return - - . = ..() - - var/damage = intensity - get_eye_protection() - if(.) // we've been flashed - 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) - Paralyze(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/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) + if(!E) + return INFINITY //Can't get flashed without eyes + else + . += E.flash_protect + if(isclothing(head)) //Adds head protection + . += head.flash_protect + if(isclothing(glasses)) //Glasses + . += glasses.flash_protect + if(isclothing(wear_mask)) //Mask + . += wear_mask.flash_protect + +/mob/living/carbon/get_ear_protection() + . = ..() + var/obj/item/organ/ears/E = getorganslot(ORGAN_SLOT_EARS) + if(!E) + return INFINITY + else + . += E.bang_protect + +/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 = TRUE, check_head = TRUE, check_mask = TRUE) + if(check_head && head && (head.flags_cover & HEADCOVERSEYES)) + return head + if(check_mask && wear_mask && (wear_mask.flags_cover & MASKCOVERSEYES)) + return wear_mask + if(check_glasses && glasses && (glasses.flags_cover & GLASSESCOVERSEYES)) + return glasses + +/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/proc/can_catch_item(skip_throw_mode_check) + . = FALSE + if(!skip_throw_mode_check && !in_throw_mode) + return + if(get_active_held_item()) + return + if(!(mobility_flags & MOBILITY_MOVE)) + return + if(restrained()) + return + return TRUE + +/mob/living/carbon/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + if(!skipcatch) //ugly, but easy + if(can_catch_item()) + if(istype(AM, /obj/item)) + var/obj/item/I = AM + if(isturf(I.loc)) + 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 1 + ..() + + +/mob/living/carbon/attacked_by(obj/item/I, mob/living/user) + 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(I.force, I.damtype, affecting) + if(I.damtype == BRUTE && affecting.status == BODYPART_ORGANIC) + if(prob(33)) + I.add_mob_blood(src) + var/turf/location = get_turf(src) + add_splatter_floor(location) + if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood + user.add_mob_blood(src) + if(ishuman(user)) + var/mob/living/carbon/human/dirtyboy = user + dirtyboy.adjust_hygiene(-10) + if(affecting.body_zone == BODY_ZONE_HEAD) + if(wear_mask) + wear_mask.add_mob_blood(src) + update_inv_wear_mask() + if(wear_neck) + wear_neck.add_mob_blood(src) + update_inv_neck() + if(head) + 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) + + 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) + + for(var/datum/surgery/S in surgeries) + if(!(mobility_flags & MOBILITY_STAND) || !S.lying_required) + if(user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM) + if(S.next_step(user, user.a_intent)) + return 1 + return 0 + + +/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(..()) //successful slime attack + 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) + Paralyze(power*20) + if(stuttering < power) + stuttering = power + if (prob(stunprob) && M.powerlevel >= 8) + adjustFireLoss(M.powerlevel * rand(6,10)) + updatehealth() + return 1 + +/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) + Paralyze(40) + spawn(20) + jitteriness = max(jitteriness - 990, 10) //Still jittery, but vastly less + if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) + Paralyze(60) + if(stat == DEAD && can_defib()) //yogs: ZZAPP + if(!illusion && (shock_damage * siemens_coeff >= 1) && prob(25)) + set_heartattack(FALSE) + revive() + emote("gasp") + Jitter(100) + SEND_SIGNAL(src, COMSIG_LIVING_MINOR_SHOCK) + adjustOrganLoss(ORGAN_SLOT_BRAIN, 100, 199) //yogs end + 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(!(mobility_flags & MOBILITY_STAND)) + 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 + 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) + for(var/datum/brain_trauma/trauma in M.get_traumas()) + trauma.on_hug(M, src) + AdjustStun(-60) + AdjustKnockdown(-60) + AdjustUnconscious(-60) + AdjustSleeping(-100) + AdjustParalyzed(-60) + AdjustImmobilized(-60) + set_resting(FALSE) + + 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) + if(NOFLASH in dna?.species?.species_traits) + return + var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES) + if(!eyes) //can't flash what can't see! + return + + . = ..() + + var/damage = intensity - get_eye_protection() + if(.) // we've been flashed + 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) + Paralyze(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 6b2fdc770b0c..505e958a3f07 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -1,65 +1,65 @@ -/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) - has_limbs = 1 - held_items = list(null, null) - 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/handcuffed = null //Whether or not the mob is handcuffed - var/obj/item/legcuffed = null //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/clothing/head = null - - var/obj/item/clothing/gloves = null //only used by humans - var/obj/item/clothing/shoes = null //only used by humans. - var/obj/item/clothing/glasses/glasses = null //only used by humans. - var/obj/item/clothing/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/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 - - var/rotate_on_lying = 1 - - 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/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 - var/stam_regen_start_time = 0 //used to halt stamina regen temporarily - var/stam_paralyzed = FALSE //knocks you down +/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) + has_limbs = 1 + held_items = list(null, null) + 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/handcuffed = null //Whether or not the mob is handcuffed + var/obj/item/legcuffed = null //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/clothing/head = null + + var/obj/item/clothing/gloves = null //only used by humans + var/obj/item/clothing/shoes = null //only used by humans. + var/obj/item/clothing/glasses/glasses = null //only used by humans. + var/obj/item/clothing/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/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 + + var/rotate_on_lying = 1 + + 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/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 + var/stam_regen_start_time = 0 //used to halt stamina regen temporarily + var/stam_paralyzed = FALSE //knocks you down diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm index 9c5086acd363..e40f97f3d30a 100644 --- a/code/modules/mob/living/carbon/emote.dm +++ b/code/modules/mob/living/carbon/emote.dm @@ -1,97 +1,97 @@ -/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 - vary = TRUE - -/datum/emote/living/carbon/clap/get_sound(mob/living/user) - if(ishuman(user)) - if(!user.get_bodypart(BODY_ZONE_L_ARM) || !user.get_bodypart(BODY_ZONE_R_ARM)) - return - else - return pick('sound/misc/clap1.ogg', - 'sound/misc/clap2.ogg', - 'sound/misc/clap3.ogg', - 'sound/misc/clap4.ogg') - -/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) - -/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 + vary = TRUE + +/datum/emote/living/carbon/clap/get_sound(mob/living/user) + if(ishuman(user)) + if(!user.get_bodypart(BODY_ZONE_L_ARM) || !user.get_bodypart(BODY_ZONE_R_ARM)) + return + else + return pick('sound/misc/clap1.ogg', + 'sound/misc/clap2.ogg', + 'sound/misc/clap3.ogg', + 'sound/misc/clap4.ogg') + +/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) + +/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 c32f67f30f33..3f2a47b7b821 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -1,60 +1,60 @@ -/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) - if(with_bodyparts) - new /obj/effect/gibspawner/human(drop_location(), src, get_static_viruses()) - else - new /obj/effect/gibspawner/human/bodypartless(drop_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) - log_game("[key_name(src)] has died (BRUTE: [src.getBruteLoss()], BURN: [src.getFireLoss()], TOX: [src.getToxLoss()], OXY: [src.getOxyLoss()], CLONE: [src.getCloneLoss()]) ([AREACOORD(src)])") - if(is_devil(src)) - INVOKE_ASYNC(is_devil(src), /datum/antagonist/devil.proc/beginResurrectionCheck, src) - if(is_hivemember(src)) - remove_hivemember(src) - if(is_hivehost(src)) - var/datum/antagonist/hivemind/hive = mind.has_antag_datum(/datum/antagonist/hivemind) - hive.destroy_hive() - -/mob/living/carbon/human/proc/makeSkeleton() - ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) - set_species(/datum/species/skeleton) - return 1 - - -/mob/living/carbon/proc/Drain() - become_husk(CHANGELING_DRAIN) - ADD_TRAIT(src, TRAIT_BADDNA, CHANGELING_DRAIN) - blood_volume = 0 - return 1 +/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) + if(with_bodyparts) + new /obj/effect/gibspawner/human(drop_location(), src, get_static_viruses()) + else + new /obj/effect/gibspawner/human/bodypartless(drop_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) + log_game("[key_name(src)] has died (BRUTE: [src.getBruteLoss()], BURN: [src.getFireLoss()], TOX: [src.getToxLoss()], OXY: [src.getOxyLoss()], CLONE: [src.getCloneLoss()]) ([AREACOORD(src)])") + if(is_devil(src)) + INVOKE_ASYNC(is_devil(src), /datum/antagonist/devil.proc/beginResurrectionCheck, src) + if(is_hivemember(src)) + remove_hivemember(src) + if(is_hivehost(src)) + var/datum/antagonist/hivemind/hive = mind.has_antag_datum(/datum/antagonist/hivemind) + hive.destroy_hive() + +/mob/living/carbon/human/proc/makeSkeleton() + ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) + set_species(/datum/species/skeleton) + return 1 + + +/mob/living/carbon/proc/Drain() + become_husk(CHANGELING_DRAIN) + ADD_TRAIT(src, TRAIT_BADDNA, CHANGELING_DRAIN) + blood_volume = 0 + return 1 diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index bca350c4d471..904b27adb9af 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -1,49 +1,49 @@ - -/mob/living/carbon/human/dummy - real_name = "Test Dummy" - status_flags = GODMODE|CANPUSH - mouse_drag_pointer = MOUSE_INACTIVE_POINTER - var/in_use = FALSE - -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() - cut_overlays(TRUE) - -/mob/living/carbon/human/dummy/setup_human_dna() - create_dna(src) - randomize_human(src) - dna.initialize_dna(skip_index = TRUE) //Skip stuff that requires full round init. - -//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) - 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 + +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() + cut_overlays(TRUE) + +/mob/living/carbon/human/dummy/setup_human_dna() + create_dna(src) + randomize_human(src) + dna.initialize_dna(skip_index = TRUE) //Skip stuff that requires full round init. + +//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) + 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 b9b84b589fc1..58b1a5184b3b 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -1,176 +1,176 @@ -/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/mumble - key = "mumble" - key_third_person = "mumbles" - message = "mumbles!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/carbon/human/scream - key = "scream" - key_third_person = "screams" - message = "screams!" - emote_type = EMOTE_AUDIBLE - only_forced_audio = TRUE - vary = TRUE - -/datum/emote/living/carbon/human/scream/get_sound(mob/living/user) - if(!ishuman(user)) - return - var/mob/living/carbon/human/H = user - if(H.mind?.miming) - return - if(ishumanbasic(H) || iscatperson(H)) - if(user.gender == FEMALE) - return pick('sound/voice/human/femalescream_1.ogg', 'sound/voice/human/femalescream_2.ogg', 'sound/voice/human/femalescream_3.ogg', 'sound/voice/human/femalescream_4.ogg', 'sound/voice/human/femalescream_5.ogg') - else - if(prob(1)) - return 'sound/voice/human/wilhelm_scream.ogg' - return pick('sound/voice/human/malescream_1.ogg', 'sound/voice/human/malescream_2.ogg', 'sound/voice/human/malescream_3.ogg', 'sound/voice/human/malescream_4.ogg', 'sound/voice/human/malescream_5.ogg') - else if(ismoth(H)) - return 'sound/voice/moth/scream_moth.ogg' - else if(H.dna?.species?.screamsound) //yogs start: grabs scream from screamsound located in the appropriate species file. - return H.dna.species.screamsound //yogs end - current added screams: lizard, preternis. - - -/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, type_override, intentional) - . = ..() - 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 , intentional) - 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, intentional) - . = ..() - 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, type_override, intentional) - . = ..() - if(.) - var/mob/living/carbon/human/H = user - if(findtext(select_message_type(user,intentional), "open")) - H.OpenWings() - else - H.CloseWings() - -/datum/emote/living/carbon/human/wing/select_message_type(mob/user, intentional) - . = ..() - 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, intentional) - 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) - -//Ayy lmao +/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/mumble + key = "mumble" + key_third_person = "mumbles" + message = "mumbles!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/scream + key = "scream" + key_third_person = "screams" + message = "screams!" + emote_type = EMOTE_AUDIBLE + only_forced_audio = TRUE + vary = TRUE + +/datum/emote/living/carbon/human/scream/get_sound(mob/living/user) + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(H.mind?.miming) + return + if(ishumanbasic(H) || iscatperson(H)) + if(user.gender == FEMALE) + return pick('sound/voice/human/femalescream_1.ogg', 'sound/voice/human/femalescream_2.ogg', 'sound/voice/human/femalescream_3.ogg', 'sound/voice/human/femalescream_4.ogg', 'sound/voice/human/femalescream_5.ogg') + else + if(prob(1)) + return 'sound/voice/human/wilhelm_scream.ogg' + return pick('sound/voice/human/malescream_1.ogg', 'sound/voice/human/malescream_2.ogg', 'sound/voice/human/malescream_3.ogg', 'sound/voice/human/malescream_4.ogg', 'sound/voice/human/malescream_5.ogg') + else if(ismoth(H)) + return 'sound/voice/moth/scream_moth.ogg' + else if(H.dna?.species?.screamsound) //yogs start: grabs scream from screamsound located in the appropriate species file. + return H.dna.species.screamsound //yogs end - current added screams: lizard, preternis. + + +/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, type_override, intentional) + . = ..() + 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 , intentional) + 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, intentional) + . = ..() + 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, type_override, intentional) + . = ..() + if(.) + var/mob/living/carbon/human/H = user + if(findtext(select_message_type(user,intentional), "open")) + H.OpenWings() + else + H.CloseWings() + +/datum/emote/living/carbon/human/wing/select_message_type(mob/user, intentional) + . = ..() + 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, intentional) + 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) + +//Ayy lmao diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index fb4413157f1a..dc940cf5e98b 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1,1075 +1,1075 @@ -/mob/living/carbon/human - name = "Unknown" - real_name = "Unknown" - icon = 'icons/mob/human.dmi' - icon_state = "human_basic" - 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 - - icon_state = "" //Remove the inherent human icon that is visible on the map editor. We're rendering ourselves limb by limb, having it still be there results in a bug where the basic human icon appears below as south in all directions and generally looks nasty. - - //initialize limbs first - create_bodyparts() - - setup_human_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() - - . = ..() - - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - AddComponent(/datum/component/personal_crafting) - -/mob/living/carbon/human/proc/setup_human_dna() - //initialize dna. for spawned humans; overwritten by other code - create_dna(src) - randomize_human(src) - dna.initialize_dna() - -/mob/living/carbon/human/ComponentInitialize() - . = ..() - if(!(CONFIG_GET(flag/disable_human_mood))) - AddComponent(/datum/component/mood) - -/mob/living/carbon/human/Destroy() - QDEL_NULL(physiology) - return ..() - - -/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) - var/datum/antagonist/hivemind/hivemind = mind.has_antag_datum(/datum/antagonist/hivemind) - if(hivemind) - stat("Hivemind Vessels", "[hivemind.hive_size] (+[hivemind.size_mod])") - stat("Psychic Link Duration", "[(hivemind.track_bonus + TRACKER_DEFAULT_TIME)/10] seconds") - - //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()]") - 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) - if(SLOT_S_STORE in obscured) - dat += "" - else - 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 += "" - - // yogs start - show bandaged parts - for (var/obj/item/bodypart/org in bodyparts) - if (org.bandaged) - dat += "" - // yogs end - - 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:
                 ↳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"]
                [org.name] wrapped with:[org.bandaged]
                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(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - 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 - 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)) - 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"]) //canUseTopic check for this is handled by mob/Topic() - var/slot = text2num(href_list["item"]) - if(slot in check_obscured_slots(TRUE)) - to_chat(usr, "You can't reach that! Something is covering it.") - return - - if(href_list["pockets"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) //TODO: Make it match (or intergrate it into) strippanel so you get 'item cannot fit here' warnings if mob_can_equip fails - 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)) //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) - 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 - //updating inv screen after handled by living/Topic() - else - // Display a warning if the user mocks up - to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") - -///////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/hud/security/G = H.glasses - if(istype(G) && (G.obj_flags & EMAGGED)) - allowed_access = "@%&ERROR_%$*" - else //Implant and standard glasses check access - if(H.wear_id) - var/list/access = H.wear_id.GetAccess() - if(ACCESS_SEC_DOORS in access) - allowed_access = H.get_authentification_name() - - 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()) - 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()) - 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(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) - to_chat(usr, "Successfully added comment.") - return - to_chat(usr, "Unable to locate a data core entry for this person.") - - ..() //end of this massive fucking chain. TODO: make the hud chain not spooky. - - -/mob/living/carbon/human/proc/canUseHUD() - return (mobility_flags & MOBILITY_USE) - -/mob/living/carbon/human/can_inject(mob/user, error_msg, target_zone, var/penetrate_thick = 0) - . = 1 // Default to returning true. - if(user && !target_zone) - target_zone = user.zone_selected - if(HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) - . = 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/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)) - threatcount += 2 - - //Check for nonhuman scum - if(dna && dna.species.id && dna.species.id != "human") - threatcount += 1 - - //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 -= 5 - - 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" - update_body() - update_hair() - -/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) - -/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) - var/they_ashlung = C.getorgan(/obj/item/organ/lungs/ashwalker) // yogs - Do they have ashwalker lungs? - var/we_ashlung = getorgan(/obj/item/organ/lungs/ashwalker) // yogs - Does the guy doing CPR have ashwalker lungs? - var/anticpr = min(C.getOxyLoss(), 10) // yogs - for incompatible 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") - // yogs start - can't CPR people with ash walker lungs whithout having them yourself - if(they_breathe && they_ashlung && !we_ashlung) - C.adjustOxyLoss(anticpr) - C.updatehealth() - to_chat(C, "You feel a breath of fresh air enter your lungs... you feel worse...") - else if(they_breathe && they_lung && we_ashlung) - C.adjustOxyLoss(anticpr) - C.updatehealth() - to_chat(C, "You feel a breath of fresh air enter your lungs... you feel worse...") - //yogs end - else 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/proc/clean_blood(datum/source, strength) - if(strength < CLEAN_STRENGTH_BLOOD) - return - if(gloves) - if(SEND_SIGNAL(gloves, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)) - update_inv_gloves() - else - if(bloody_hands) - bloody_hands = 0 - update_inv_gloves() - -/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(!(mobility_flags & MOBILITY_UI)) - to_chat(src, "You can't do that right now!") - return FALSE - if(!Adjacent(M) && (M.loc != src)) - if((be_close == FALSE) || (!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 = min(health, maxHealth - getStaminaLoss()) - 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" - -/mob/living/carbon/human/fully_heal(admin_revive = 0) - dna?.species.spec_fully_heal(src) - if(admin_revive) - regenerate_limbs() - regenerate_organs() - remove_all_embedded_objects() - set_heartattack(FALSE) - drunkenness = 0 - set_hygiene(HYGIENE_LEVEL_NORMAL) - for(var/datum/mutation/human/HM in dna.mutations) - if(HM.quality != POSITIVE) - dna.remove_mutation(HM.name) - ..() - -/mob/living/carbon/human/check_weakness(obj/item/weapon, mob/living/attacker) - . = ..() - if (dna && dna.species) - . += dna.species.check_species_weakness(weapon, attacker) - -/mob/living/carbon/human/is_literate() - return TRUE - -/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) - Paralyze(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)]" - .["Make Cluwne"] = "?_src_=vars;[HrefToken()];cluwneing=[REF(src)]" // yogs -- make cluwne - -/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user) - //If they dragged themselves and we're currently aggressively grabbing them try to piggyback - if(user == target && can_piggyback(target) && pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS) - buckle_mob(target,TRUE,TRUE) - . = ..() - -//Can C try to piggyback at all. -/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/C) - if(istype(C) && C.stat == CONSCIOUS) - return TRUE - return FALSE - -/mob/living/carbon/human/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE) - if(!force)//humans are only meant to be ridden through piggybacking and special cases - return - if(!is_type_in_typecache(M, can_ride_typecache)) - M.visible_message("[M] really can't seem to mount [src]...") - return - var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human) - riding_datum.ride_check_rider_incapacitated = TRUE - riding_datum.ride_check_rider_restrained = TRUE - riding_datum.set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4))) - if(buckled_mobs && ((M in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled || (M.stat != CONSCIOUS)) - return - if(can_piggyback(M)) - riding_datum.ride_check_ridden_incapacitated = TRUE - visible_message("[M] starts to climb onto [src]...") - if(do_after(M, 15, target = src)) - if(can_piggyback(M)) - if(M.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE)) - M.visible_message("[M] can't hang onto [src]!") - return - if(!riding_datum.equip_buckle_inhands(M, 2)) //MAKE SURE THIS IS LAST!! - M.visible_message("[M] can't climb onto [src]!") - return - stop_pulling() - . = ..(M, force, check_loc) - else - visible_message("[M] fails to climb onto [src]!") - else - stop_pulling() - . = ..(M,force,check_loc) - -/mob/living/carbon/human/do_after_coefficent() - . = ..() - . *= physiology.do_after_speed - -/mob/living/carbon/human/updatehealth() - . = ..() - dna?.species.spec_updatehealth(src) - -/mob/living/carbon/human/adjust_nutrition(var/change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks - if(HAS_TRAIT(src, TRAIT_NOHUNGER)) - return FALSE - return ..() - -/mob/living/carbon/human/set_nutrition(var/change) //Seriously fuck you oldcoders. - if(HAS_TRAIT(src, TRAIT_NOHUNGER)) - return FALSE - return ..() - -/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/golem/capitalist - race = /datum/species/golem/capitalist - -/mob/living/carbon/human/species/golem/soviet - race = /datum/species/golem/soviet - -/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/ethereal - race = /datum/species/ethereal - -/mob/living/carbon/human/species/lizard/ashwalker - race = /datum/species/lizard/ashwalker - -/mob/living/carbon/human/species/moth - race = /datum/species/moth - -/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 + name = "Unknown" + real_name = "Unknown" + icon = 'icons/mob/human.dmi' + icon_state = "human_basic" + 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 + + icon_state = "" //Remove the inherent human icon that is visible on the map editor. We're rendering ourselves limb by limb, having it still be there results in a bug where the basic human icon appears below as south in all directions and generally looks nasty. + + //initialize limbs first + create_bodyparts() + + setup_human_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() + + . = ..() + + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + AddComponent(/datum/component/personal_crafting) + +/mob/living/carbon/human/proc/setup_human_dna() + //initialize dna. for spawned humans; overwritten by other code + create_dna(src) + randomize_human(src) + dna.initialize_dna() + +/mob/living/carbon/human/ComponentInitialize() + . = ..() + if(!(CONFIG_GET(flag/disable_human_mood))) + AddComponent(/datum/component/mood) + +/mob/living/carbon/human/Destroy() + QDEL_NULL(physiology) + return ..() + + +/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) + var/datum/antagonist/hivemind/hivemind = mind.has_antag_datum(/datum/antagonist/hivemind) + if(hivemind) + stat("Hivemind Vessels", "[hivemind.hive_size] (+[hivemind.size_mod])") + stat("Psychic Link Duration", "[(hivemind.track_bonus + TRACKER_DEFAULT_TIME)/10] seconds") + + //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()]") + 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) + if(SLOT_S_STORE in obscured) + dat += "" + else + 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 += "" + + // yogs start - show bandaged parts + for (var/obj/item/bodypart/org in bodyparts) + if (org.bandaged) + dat += "" + // yogs end + + 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:
                 ↳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"]
                [org.name] wrapped with:[org.bandaged]
                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(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + 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 + 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)) + 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"]) //canUseTopic check for this is handled by mob/Topic() + var/slot = text2num(href_list["item"]) + if(slot in check_obscured_slots(TRUE)) + to_chat(usr, "You can't reach that! Something is covering it.") + return + + if(href_list["pockets"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) //TODO: Make it match (or intergrate it into) strippanel so you get 'item cannot fit here' warnings if mob_can_equip fails + 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)) //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) + 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 + //updating inv screen after handled by living/Topic() + else + // Display a warning if the user mocks up + to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") + +///////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/hud/security/G = H.glasses + if(istype(G) && (G.obj_flags & EMAGGED)) + allowed_access = "@%&ERROR_%$*" + else //Implant and standard glasses check access + if(H.wear_id) + var/list/access = H.wear_id.GetAccess() + if(ACCESS_SEC_DOORS in access) + allowed_access = H.get_authentification_name() + + 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()) + 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()) + 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(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) + to_chat(usr, "Successfully added comment.") + return + to_chat(usr, "Unable to locate a data core entry for this person.") + + ..() //end of this massive fucking chain. TODO: make the hud chain not spooky. + + +/mob/living/carbon/human/proc/canUseHUD() + return (mobility_flags & MOBILITY_USE) + +/mob/living/carbon/human/can_inject(mob/user, error_msg, target_zone, var/penetrate_thick = 0) + . = 1 // Default to returning true. + if(user && !target_zone) + target_zone = user.zone_selected + if(HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) + . = 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/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)) + threatcount += 2 + + //Check for nonhuman scum + if(dna && dna.species.id && dna.species.id != "human") + threatcount += 1 + + //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 -= 5 + + 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" + update_body() + update_hair() + +/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) + +/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) + var/they_ashlung = C.getorgan(/obj/item/organ/lungs/ashwalker) // yogs - Do they have ashwalker lungs? + var/we_ashlung = getorgan(/obj/item/organ/lungs/ashwalker) // yogs - Does the guy doing CPR have ashwalker lungs? + var/anticpr = min(C.getOxyLoss(), 10) // yogs - for incompatible 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") + // yogs start - can't CPR people with ash walker lungs whithout having them yourself + if(they_breathe && they_ashlung && !we_ashlung) + C.adjustOxyLoss(anticpr) + C.updatehealth() + to_chat(C, "You feel a breath of fresh air enter your lungs... you feel worse...") + else if(they_breathe && they_lung && we_ashlung) + C.adjustOxyLoss(anticpr) + C.updatehealth() + to_chat(C, "You feel a breath of fresh air enter your lungs... you feel worse...") + //yogs end + else 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/proc/clean_blood(datum/source, strength) + if(strength < CLEAN_STRENGTH_BLOOD) + return + if(gloves) + if(SEND_SIGNAL(gloves, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)) + update_inv_gloves() + else + if(bloody_hands) + bloody_hands = 0 + update_inv_gloves() + +/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(!(mobility_flags & MOBILITY_UI)) + to_chat(src, "You can't do that right now!") + return FALSE + if(!Adjacent(M) && (M.loc != src)) + if((be_close == FALSE) || (!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 = min(health, maxHealth - getStaminaLoss()) + 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" + +/mob/living/carbon/human/fully_heal(admin_revive = 0) + dna?.species.spec_fully_heal(src) + if(admin_revive) + regenerate_limbs() + regenerate_organs() + remove_all_embedded_objects() + set_heartattack(FALSE) + drunkenness = 0 + set_hygiene(HYGIENE_LEVEL_NORMAL) + for(var/datum/mutation/human/HM in dna.mutations) + if(HM.quality != POSITIVE) + dna.remove_mutation(HM.name) + ..() + +/mob/living/carbon/human/check_weakness(obj/item/weapon, mob/living/attacker) + . = ..() + if (dna && dna.species) + . += dna.species.check_species_weakness(weapon, attacker) + +/mob/living/carbon/human/is_literate() + return TRUE + +/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) + Paralyze(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)]" + .["Make Cluwne"] = "?_src_=vars;[HrefToken()];cluwneing=[REF(src)]" // yogs -- make cluwne + +/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user) + //If they dragged themselves and we're currently aggressively grabbing them try to piggyback + if(user == target && can_piggyback(target) && pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS) + buckle_mob(target,TRUE,TRUE) + . = ..() + +//Can C try to piggyback at all. +/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/C) + if(istype(C) && C.stat == CONSCIOUS) + return TRUE + return FALSE + +/mob/living/carbon/human/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE) + if(!force)//humans are only meant to be ridden through piggybacking and special cases + return + if(!is_type_in_typecache(M, can_ride_typecache)) + M.visible_message("[M] really can't seem to mount [src]...") + return + var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human) + riding_datum.ride_check_rider_incapacitated = TRUE + riding_datum.ride_check_rider_restrained = TRUE + riding_datum.set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4))) + if(buckled_mobs && ((M in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled || (M.stat != CONSCIOUS)) + return + if(can_piggyback(M)) + riding_datum.ride_check_ridden_incapacitated = TRUE + visible_message("[M] starts to climb onto [src]...") + if(do_after(M, 15, target = src)) + if(can_piggyback(M)) + if(M.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE)) + M.visible_message("[M] can't hang onto [src]!") + return + if(!riding_datum.equip_buckle_inhands(M, 2)) //MAKE SURE THIS IS LAST!! + M.visible_message("[M] can't climb onto [src]!") + return + stop_pulling() + . = ..(M, force, check_loc) + else + visible_message("[M] fails to climb onto [src]!") + else + stop_pulling() + . = ..(M,force,check_loc) + +/mob/living/carbon/human/do_after_coefficent() + . = ..() + . *= physiology.do_after_speed + +/mob/living/carbon/human/updatehealth() + . = ..() + dna?.species.spec_updatehealth(src) + +/mob/living/carbon/human/adjust_nutrition(var/change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks + if(HAS_TRAIT(src, TRAIT_NOHUNGER)) + return FALSE + return ..() + +/mob/living/carbon/human/set_nutrition(var/change) //Seriously fuck you oldcoders. + if(HAS_TRAIT(src, TRAIT_NOHUNGER)) + return FALSE + return ..() + +/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/golem/capitalist + race = /datum/species/golem/capitalist + +/mob/living/carbon/human/species/golem/soviet + race = /datum/species/golem/soviet + +/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/ethereal + race = /datum/species/ethereal + +/mob/living/carbon/human/species/lizard/ashwalker + race = /datum/species/lizard/ashwalker + +/mob/living/carbon/human/species/moth + race = /datum/species/moth + +/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 diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index d590c8dfb840..2933748565a3 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -1,899 +1,899 @@ -/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) - return checkarmor(def_zone, type) - var/obj/item/bodypart/affecting = get_bodypart(check_zone(def_zone)) - if(affecting) - 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) //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(bp && 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 && !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((mobility_flags & MOBILITY_USE) && dna && !dna.check_mutation(HULK)) //But only if they're otherwise able to use items, and hulks can't do it - if(!isturf(loc)) //if we're inside something and still got hit - P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage. - loc.bullet_act(P) - return BULLET_ACT_HIT - 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 BULLET_ACT_BLOCK - else - P.firer = src - P.setAngle(rand(0, 360))//SHING - return BULLET_ACT_FORCE_PIERCE - - if(!(P.original == src && P.firer == src)) //can't block or reflect when shooting yourself - if(P.reflectable & REFLECT_NORMAL) - if(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(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit - P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage. - loc.bullet_act(P) - return BULLET_ACT_HIT - 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 BULLET_ACT_FORCE_PIERCE // 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 BULLET_ACT_HIT - - return ..(P, def_zone) - -/mob/living/carbon/human/proc/check_reflect(def_zone) //Reflection checks for anything in your l_hand, r_hand, or wear_suit based on the reflection chance of the object - if(wear_suit) - if(wear_suit.IsReflect(def_zone) == 1) - return 1 - for(var/obj/item/I in held_items) - if(I.IsReflect(def_zone) == 1) - return 1 - return 0 - -/mob/living/carbon/human/proc/check_shields(atom/AM, var/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)) - if (istype(I, /obj/item/shield)) - var/obj/item/shield/S = I - return S.on_shield_block(src, AM, attack_text, damage, attack_type) - return 1 - 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 1 - 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 1 - return 0 - -/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, datum/thrownthing/throwingdatum) - if(dna && dna.species) - var/spec_return = dna.species.spec_hitby(AM, src) - if(spec_return) - return spec_return - var/obj/item/I - var/throwpower = 30 - if(istype(AM, /obj/item)) - I = AM - throwpower = I.throwforce - if(I.thrownby == src) //No throwing stuff at yourself to trigger hit reactions - return ..() - if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) - hitpush = FALSE - skipcatch = TRUE - blocked = TRUE - else if(I) - if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedding.embedded_ignore_throwspeed_threshold) - if(can_embed(I)) - if(prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) - 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) - hitpush = FALSE - skipcatch = TRUE //can't catch the now embedded item - - return ..() - -/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 = 0) - if(user.a_intent == INTENT_HARM) - var/hulk_verb = pick("smash","pummel") - if(check_shields(user, 15, "the [hulk_verb]ing")) - return - ..(user, 1) - 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 - if(H.a_intent == INTENT_HARM && handle_vamp_biting(H)) // yogs start -- vampire biting - return // yogs end - 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) - ..() //shaking - return 0 - - 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) - if (src.IsKnockdown() && !src.IsParalyzed()) - Paralyze(40) - log_combat(M, src, "pinned") - visible_message("[M] has pinned down [src]!", \ - "[M] has pinned down [src]!") - else - Knockdown(30) - 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) - if(check_shields(M, damage, "the [M.name]")) - return 0 - if(stat != DEAD) - 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(check_shields(M, 0, "the M.name")) - visible_message("[M] attempted to touch [src]!") - return 0 - - if(..()) - 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","","",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 - //yogs start - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armour = run_armor_check(affecting, "melee") - if(prob(armour)) - to_chat(M, "[src]'s armour shields the blow!") - return - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - if(armour > 0) - Paralyze(50 + armour) - else - Paralyze(50) - //yogs end - 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(..()) //successful larva bite. - var/damage = rand(1, 3) - if(check_shields(L, damage, "the [L.name]")) - return 0 - 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) - if(check_shields(M, damage, "the [M.name]", MELEE_ATTACK, M.armour_penetration)) - return FALSE - 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(..()) //successful slime attack - var/damage = rand(5, 25) - if(M.is_adult) - damage = rand(10, 35) - - if(check_shields(M, damage, "the [M.name]")) - return 0 - - 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) - 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) - switch(M.damtype) - if("brute") - if(M.force > 20) - Unconscious(20) - 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/b_loss = 0 - var/f_loss = 0 - var/bomb_armor = getarmor(null, "bomb") - - switch (severity) - if (1) - if(prob(bomb_armor)) - b_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") - else - for(var/I in contents) - var/atom/A = I - A.ex_act(severity) - gib() - return - - if (2) - b_loss = 60 - f_loss = 60 - if(bomb_armor) - b_loss = 30*(2 - round(bomb_armor*0.01, 0.05)) - f_loss = b_loss - damage_clothes(200 - bomb_armor, BRUTE, "bomb") - if (!istype(ears, /obj/item/clothing/ears/earmuffs)) - adjustEarDamage(30, 120) - if (prob(max(70 - (bomb_armor * 0.5), 0))) - Unconscious(200) - - if(3) - b_loss = 30 - if(bomb_armor) - b_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) - if (prob(max(50 - (bomb_armor * 0.5), 0))) - Unconscious(160) - - take_overall_damage(b_loss,f_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 - - dna.species.spec_electrocute_act(src, shock_damage,source,siemens_coeff,safety,override,tesla_shock, illusion, stun) - . = ..(shock_damage,source,siemens_coeff,safety,override,tesla_shock, illusion, stun) - if(.) - electrocution_animation(40) - -/mob/living/carbon/human/emag_act(mob/user) - .=..() - dna?.species.spec_emag_act(src) - -/mob/living/carbon/human/emp_act(severity) - dna?.species.spec_emp_act(src, 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) - Paralyze(200) - if(2) - L.receive_damage(0,5) - Paralyze(100) - -/mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) //todo: update this to utilize check_obscured_slots() //and make sure it's check_obscured_slots(TRUE) to stop aciding through visors etc - 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// - var/damagemod = (dna && dna.species) ? dna.species.acidmod : 1 // yogs - Old Plant People - 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*damagemod) // yogs - Old Plant People - 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(mind.assigned_role == "Clown") - gain = rand(-1000, 1000) - 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(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 - check_self_for_injuries() - - - else - if(wear_suit) - wear_suit.add_fingerprint(M) - else if(w_uniform) - w_uniform.add_fingerprint(M) - - ..() - -/mob/living/carbon/human/proc/check_self_for_injuries() - 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_chat(src, "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].") - - for(var/obj/item/I in LB.embedded_objects) - to_chat(src, "\t There is \a [I] embedded in your [LB.name]!") - - for(var/t in missing) - to_chat(src, "Your [parse_zone(t)] is missing!") - - if(bleed_rate) - to_chat(src, "You are bleeding!") - if(getStaminaLoss()) - if(getStaminaLoss() > 30) - to_chat(src, "You're completely exhausted.") - else - to_chat(src, "You feel fatigued.") - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - if(toxloss) - if(toxloss > 10) - to_chat(src, "You feel sick.") - else if(toxloss > 20) - to_chat(src, "You feel nauseated.") - else if(toxloss > 40) - to_chat(src, "You feel very unwell!") - if(oxyloss) - if(oxyloss > 10) - to_chat(src, "You feel lightheaded.") - else if(oxyloss > 20) - to_chat(src, "Your thinking is clouded and distant.") - else if(oxyloss > 30) - to_chat(src, "You're choking!") - - if(!HAS_TRAIT(src, TRAIT_NOHUNGER)) - switch(nutrition) - if(NUTRITION_LEVEL_FULL to INFINITY) - to_chat(src, "You're completely stuffed!") - if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) - to_chat(src, "You're well fed!") - if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) - to_chat(src, "You're not hungry.") - if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) - to_chat(src, "You could use a bite to eat.") - if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) - to_chat(src, "You feel quite hungry.") - if(0 to NUTRITION_LEVEL_STARVING) - to_chat(src, "You're starving!") - - //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[lentext(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[lentext(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_chat(src, "You have these quirks: [get_trait_string()].") - -/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) + return checkarmor(def_zone, type) + var/obj/item/bodypart/affecting = get_bodypart(check_zone(def_zone)) + if(affecting) + 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) //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(bp && 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 && !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((mobility_flags & MOBILITY_USE) && dna && !dna.check_mutation(HULK)) //But only if they're otherwise able to use items, and hulks can't do it + if(!isturf(loc)) //if we're inside something and still got hit + P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage. + loc.bullet_act(P) + return BULLET_ACT_HIT + 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 BULLET_ACT_BLOCK + else + P.firer = src + P.setAngle(rand(0, 360))//SHING + return BULLET_ACT_FORCE_PIERCE + + if(!(P.original == src && P.firer == src)) //can't block or reflect when shooting yourself + if(P.reflectable & REFLECT_NORMAL) + if(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(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit + P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage. + loc.bullet_act(P) + return BULLET_ACT_HIT + 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 BULLET_ACT_FORCE_PIERCE // 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 BULLET_ACT_HIT + + return ..(P, def_zone) + +/mob/living/carbon/human/proc/check_reflect(def_zone) //Reflection checks for anything in your l_hand, r_hand, or wear_suit based on the reflection chance of the object + if(wear_suit) + if(wear_suit.IsReflect(def_zone) == 1) + return 1 + for(var/obj/item/I in held_items) + if(I.IsReflect(def_zone) == 1) + return 1 + return 0 + +/mob/living/carbon/human/proc/check_shields(atom/AM, var/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)) + if (istype(I, /obj/item/shield)) + var/obj/item/shield/S = I + return S.on_shield_block(src, AM, attack_text, damage, attack_type) + return 1 + 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 1 + 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 1 + return 0 + +/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, datum/thrownthing/throwingdatum) + if(dna && dna.species) + var/spec_return = dna.species.spec_hitby(AM, src) + if(spec_return) + return spec_return + var/obj/item/I + var/throwpower = 30 + if(istype(AM, /obj/item)) + I = AM + throwpower = I.throwforce + if(I.thrownby == src) //No throwing stuff at yourself to trigger hit reactions + return ..() + if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) + hitpush = FALSE + skipcatch = TRUE + blocked = TRUE + else if(I) + if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedding.embedded_ignore_throwspeed_threshold) + if(can_embed(I)) + if(prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) + 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) + hitpush = FALSE + skipcatch = TRUE //can't catch the now embedded item + + return ..() + +/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 = 0) + if(user.a_intent == INTENT_HARM) + var/hulk_verb = pick("smash","pummel") + if(check_shields(user, 15, "the [hulk_verb]ing")) + return + ..(user, 1) + 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 + if(H.a_intent == INTENT_HARM && handle_vamp_biting(H)) // yogs start -- vampire biting + return // yogs end + 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) + ..() //shaking + return 0 + + 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) + if (src.IsKnockdown() && !src.IsParalyzed()) + Paralyze(40) + log_combat(M, src, "pinned") + visible_message("[M] has pinned down [src]!", \ + "[M] has pinned down [src]!") + else + Knockdown(30) + 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) + if(check_shields(M, damage, "the [M.name]")) + return 0 + if(stat != DEAD) + 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(check_shields(M, 0, "the M.name")) + visible_message("[M] attempted to touch [src]!") + return 0 + + if(..()) + 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","","",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 + //yogs start + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armour = run_armor_check(affecting, "melee") + if(prob(armour)) + to_chat(M, "[src]'s armour shields the blow!") + return + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + if(armour > 0) + Paralyze(50 + armour) + else + Paralyze(50) + //yogs end + 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(..()) //successful larva bite. + var/damage = rand(1, 3) + if(check_shields(L, damage, "the [L.name]")) + return 0 + 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) + if(check_shields(M, damage, "the [M.name]", MELEE_ATTACK, M.armour_penetration)) + return FALSE + 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(..()) //successful slime attack + var/damage = rand(5, 25) + if(M.is_adult) + damage = rand(10, 35) + + if(check_shields(M, damage, "the [M.name]")) + return 0 + + 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) + 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) + switch(M.damtype) + if("brute") + if(M.force > 20) + Unconscious(20) + 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/b_loss = 0 + var/f_loss = 0 + var/bomb_armor = getarmor(null, "bomb") + + switch (severity) + if (1) + if(prob(bomb_armor)) + b_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") + else + for(var/I in contents) + var/atom/A = I + A.ex_act(severity) + gib() + return + + if (2) + b_loss = 60 + f_loss = 60 + if(bomb_armor) + b_loss = 30*(2 - round(bomb_armor*0.01, 0.05)) + f_loss = b_loss + damage_clothes(200 - bomb_armor, BRUTE, "bomb") + if (!istype(ears, /obj/item/clothing/ears/earmuffs)) + adjustEarDamage(30, 120) + if (prob(max(70 - (bomb_armor * 0.5), 0))) + Unconscious(200) + + if(3) + b_loss = 30 + if(bomb_armor) + b_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) + if (prob(max(50 - (bomb_armor * 0.5), 0))) + Unconscious(160) + + take_overall_damage(b_loss,f_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 + + dna.species.spec_electrocute_act(src, shock_damage,source,siemens_coeff,safety,override,tesla_shock, illusion, stun) + . = ..(shock_damage,source,siemens_coeff,safety,override,tesla_shock, illusion, stun) + if(.) + electrocution_animation(40) + +/mob/living/carbon/human/emag_act(mob/user) + .=..() + dna?.species.spec_emag_act(src) + +/mob/living/carbon/human/emp_act(severity) + dna?.species.spec_emp_act(src, 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) + Paralyze(200) + if(2) + L.receive_damage(0,5) + Paralyze(100) + +/mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) //todo: update this to utilize check_obscured_slots() //and make sure it's check_obscured_slots(TRUE) to stop aciding through visors etc + 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// + var/damagemod = (dna && dna.species) ? dna.species.acidmod : 1 // yogs - Old Plant People + 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*damagemod) // yogs - Old Plant People + 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(mind.assigned_role == "Clown") + gain = rand(-1000, 1000) + 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(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 + check_self_for_injuries() + + + else + if(wear_suit) + wear_suit.add_fingerprint(M) + else if(w_uniform) + w_uniform.add_fingerprint(M) + + ..() + +/mob/living/carbon/human/proc/check_self_for_injuries() + 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_chat(src, "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].") + + for(var/obj/item/I in LB.embedded_objects) + to_chat(src, "\t There is \a [I] embedded in your [LB.name]!") + + for(var/t in missing) + to_chat(src, "Your [parse_zone(t)] is missing!") + + if(bleed_rate) + to_chat(src, "You are bleeding!") + if(getStaminaLoss()) + if(getStaminaLoss() > 30) + to_chat(src, "You're completely exhausted.") + else + to_chat(src, "You feel fatigued.") + if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) + if(toxloss) + if(toxloss > 10) + to_chat(src, "You feel sick.") + else if(toxloss > 20) + to_chat(src, "You feel nauseated.") + else if(toxloss > 40) + to_chat(src, "You feel very unwell!") + if(oxyloss) + if(oxyloss > 10) + to_chat(src, "You feel lightheaded.") + else if(oxyloss > 20) + to_chat(src, "Your thinking is clouded and distant.") + else if(oxyloss > 30) + to_chat(src, "You're choking!") + + if(!HAS_TRAIT(src, TRAIT_NOHUNGER)) + switch(nutrition) + if(NUTRITION_LEVEL_FULL to INFINITY) + to_chat(src, "You're completely stuffed!") + if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) + to_chat(src, "You're well fed!") + if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) + to_chat(src, "You're not hungry.") + if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) + to_chat(src, "You could use a bite to eat.") + if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + to_chat(src, "You feel quite hungry.") + if(0 to NUTRITION_LEVEL_STARVING) + to_chat(src, "You're starving!") + + //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[lentext(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[lentext(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_chat(src, "You have these quirks: [get_trait_string()].") + +/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 df4cd9e48ad2..2b943d5fa98c 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -1,58 +1,58 @@ -/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) - 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/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 - - var/underwear = "Nude" //Which underwear the player wants - var/undershirt = "Nude" //Which undershirt the player wants - var/socks = "Nude" //Which socks the player wants - var/backbag = DBACKPACK //Which backpack type the player has chosen. - - //Equipment slots - var/obj/item/clothing/wear_suit = null - var/obj/item/clothing/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/hygiene = HYGIENE_LEVEL_NORMAL - - 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/name_override //For temporary visible name changes - - 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 - var/account_id +/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) + 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/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 + + var/underwear = "Nude" //Which underwear the player wants + var/undershirt = "Nude" //Which undershirt the player wants + var/socks = "Nude" //Which socks the player wants + var/backbag = DBACKPACK //Which backpack type the player has chosen. + + //Equipment slots + var/obj/item/clothing/wear_suit = null + var/obj/item/clothing/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/hygiene = HYGIENE_LEVEL_NORMAL + + 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/name_override //For temporary visible name changes + + 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 + var/account_id diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 72950716bd87..cab8d70ae828 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -1,178 +1,178 @@ - -/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 ) //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 from a human. If hand_first is false the one in the id slot is prioritized, otherwise inventory slots go first. -/mob/living/carbon/human/get_idcard(hand_first = TRUE) - //Check hands - var/obj/item/card/id/id_card - var/obj/item/held_item - held_item = get_active_held_item() - if(held_item) //Check active hand - id_card = held_item.GetID() - if(!id_card) //If there is no id, check the other hand - held_item = get_inactive_held_item() - if(held_item) - id_card = held_item.GetID() - - if(id_card) - if(hand_first) - return id_card - else - . = id_card - - //Check inventory slots - if(wear_id) - id_card = wear_id.GetID() - if(id_card) - return id_card - else if(belt) - id_card = belt.GetID() - if(id_card) - 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(G.trigger_guard == TRIGGER_GUARD_NORMAL) - if(src.dna.check_mutation(HULK)) - 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 - if(mind) - if(mind.martial_art && mind.martial_art.no_guns) //great dishonor to famiry - to_chat(src, "Use of ranged weaponry would bring dishonor to the clan.") - return FALSE - - return . - -/mob/living/carbon/human/proc/get_bank_account() - RETURN_TYPE(/datum/bank_account) - var/datum/bank_account/account - var/obj/item/card/id/I = get_idcard() - - if(I && I.registered_account) - account = I.registered_account - return account - - return FALSE - -/mob/living/carbon/human/get_policy_keywords() - . = ..() - . += "[dna.species.type]" - -/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 - if(isclothing(head) && (head.clothing_flags & SCAN_REAGENTS)) - return TRUE - if(isclothing(wear_mask) && (wear_mask.clothing_flags & SCAN_REAGENTS)) - return TRUE + +/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 ) //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 from a human. If hand_first is false the one in the id slot is prioritized, otherwise inventory slots go first. +/mob/living/carbon/human/get_idcard(hand_first = TRUE) + //Check hands + var/obj/item/card/id/id_card + var/obj/item/held_item + held_item = get_active_held_item() + if(held_item) //Check active hand + id_card = held_item.GetID() + if(!id_card) //If there is no id, check the other hand + held_item = get_inactive_held_item() + if(held_item) + id_card = held_item.GetID() + + if(id_card) + if(hand_first) + return id_card + else + . = id_card + + //Check inventory slots + if(wear_id) + id_card = wear_id.GetID() + if(id_card) + return id_card + else if(belt) + id_card = belt.GetID() + if(id_card) + 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(G.trigger_guard == TRIGGER_GUARD_NORMAL) + if(src.dna.check_mutation(HULK)) + 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 + if(mind) + if(mind.martial_art && mind.martial_art.no_guns) //great dishonor to famiry + to_chat(src, "Use of ranged weaponry would bring dishonor to the clan.") + return FALSE + + return . + +/mob/living/carbon/human/proc/get_bank_account() + RETURN_TYPE(/datum/bank_account) + var/datum/bank_account/account + var/obj/item/card/id/I = get_idcard() + + if(I && I.registered_account) + account = I.registered_account + return account + + return FALSE + +/mob/living/carbon/human/get_policy_keywords() + . = ..() + . += "[dna.species.type]" + +/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 + if(isclothing(head) && (head.clothing_flags & SCAN_REAGENTS)) + return TRUE + if(isclothing(wear_mask) && (wear_mask.clothing_flags & SCAN_REAGENTS)) + return TRUE diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 698d76a88007..5fffa2a6bb17 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -1,76 +1,76 @@ -/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, paralyze, forcedrop) - 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) - . = ..() - - if(shoes) - if(mobility_flags & MOBILITY_STAND) - 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]) - for(var/obj/effect/decal/cleanable/blood/footprints/oldFP in T) - if (oldFP.blood_state == S.blood_state) - return - //No oldFP or they're all a different kind of blood - S.bloody_shoes[S.blood_state] = max(0, S.bloody_shoes[S.blood_state] - BLOOD_LOSS_PER_STEP) - if (S.bloody_shoes[S.blood_state] > BLOOD_LOSS_IN_SPREAD) - 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] - BLOOD_LOSS_IN_SPREAD - FP.add_blood_DNA(S.return_blood_DNA()) - 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, paralyze, forcedrop) + 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) + . = ..() + + if(shoes) + if(mobility_flags & MOBILITY_STAND) + 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]) + for(var/obj/effect/decal/cleanable/blood/footprints/oldFP in T) + if (oldFP.blood_state == S.blood_state) + return + //No oldFP or they're all a different kind of blood + S.bloody_shoes[S.blood_state] = max(0, S.bloody_shoes[S.blood_state] - BLOOD_LOSS_PER_STEP) + if (S.bloody_shoes[S.blood_state] > BLOOD_LOSS_IN_SPREAD) + 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] - BLOOD_LOSS_IN_SPREAD + FP.add_blood_DNA(S.return_blood_DNA()) + 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 5a4e26008585..17f1b9def67e 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -1,271 +1,271 @@ -/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!") - - //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) - dropItemToGround(wear_id) - if(belt) - 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/I, toggle_off = 1) - if((I.flags_inv & (HIDEHAIR|HIDEFACIALHAIR)) || (initial(I.flags_inv) & (HIDEHAIR|HIDEFACIALHAIR))) - update_hair() - if(toggle_off && internal && !getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - update_internals_hud_icon(0) - internal = null - if(I.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) - 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) - - -//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!") + + //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) + dropItemToGround(wear_id) + if(belt) + 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/I, toggle_off = 1) + if((I.flags_inv & (HIDEHAIR|HIDEFACIALHAIR)) || (initial(I.flags_inv) & (HIDEHAIR|HIDEFACIALHAIR))) + update_hair() + if(toggle_off && internal && !getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + update_internals_hud_icon(0) + internal = null + if(I.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) + 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) + + +//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 107c157ddd00..5f9b29d3f990 100644 --- a/code/modules/mob/living/carbon/human/say.dm +++ b/code/modules/mob/living/carbon/human/say.dm @@ -1,111 +1,111 @@ -/mob/living/carbon/human/say_mod(input, message_mode) - verb_say = dna.species.say_mod - if(slurring) - return "slurs" - else - . = ..() - -/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(. != 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 - -/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(slurring) + return "slurs" + else + . = ..() + +/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(. != 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 + +/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 9ae22bde5dca..bf6bf08851ab 100644 --- a/code/modules/mob/living/carbon/human/species_types/abductors.dm +++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm @@ -1,19 +1,19 @@ -/datum/species/abductor - name = "Abductor" - id = "abductor" - say_mod = "gibbers" - sexes = FALSE - species_traits = list(NOBLOOD,NOEYESPRITES) - inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NOGUNS,TRAIT_NOHUNGER,TRAIT_NOBREATH) - mutanttongue = /obj/item/organ/tongue/abductor - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - -/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,NOEYESPRITES) + inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NOGUNS,TRAIT_NOHUNGER,TRAIT_NOBREATH) + mutanttongue = /obj/item/organ/tongue/abductor + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT + +/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 c021e3bb9070..227f83064187 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) - 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 - damage_overlay_type = "synth" - mutanttongue = /obj/item/organ/tongue/robot - limbs_id = "synth" - changesource_flags = MIRROR_BADMIN | WABBAJACK | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - -/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) + 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 + damage_overlay_type = "synth" + mutanttongue = /obj/item/organ/tongue/robot + limbs_id = "synth" + changesource_flags = MIRROR_BADMIN | WABBAJACK | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT + +/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 c9d51d4bd6cc..8f4ff118e022 100644 --- a/code/modules/mob/living/carbon/human/species_types/angel.dm +++ b/code/modules/mob/living/carbon/human/species_types/angel.dm @@ -1,144 +1,144 @@ -/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) - limbs_id = "human" - skinned_type = /obj/item/stack/sheet/animalhide/human - changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN - - 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.mobility_flags & MOBILITY_STAND)) - 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,FALSE) - H.update_mobility() - else - to_chat(H, "You beat your wings and begin to hover gently above the ground...") - A.ToggleFlight(H,TRUE) - H.set_resting(FALSE, FALSE) - -/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) + limbs_id = "human" + skinned_type = /obj/item/stack/sheet/animalhide/human + changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN + + 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.mobility_flags & MOBILITY_STAND)) + 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,FALSE) + H.update_mobility() + else + to_chat(H, "You beat your wings and begin to hover gently above the ground...") + A.ToggleFlight(H,TRUE) + H.set_resting(FALSE, FALSE) + +/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/corporate.dm b/code/modules/mob/living/carbon/human/species_types/corporate.dm index 077e6b1498be..7ccf5354df28 100644 --- a/code/modules/mob/living/carbon/human/species_types/corporate.dm +++ b/code/modules/mob/living/carbon/human/species_types/corporate.dm @@ -1,20 +1,20 @@ -/datum/species/corporate - name = "Corporate Agent" - id = "agent" - hair_alpha = 0 - say_mod = "declares" - speedmod = -2//Fast - brutemod = 0.7//Tough against firearms - burnmod = 0.65//Tough against lasers - coldmod = 0 - heatmod = 0.5//it's a little tough to burn them to death not as hard though. - punchdamagelow = 20 - punchdamagehigh = 30//they are inhumanly strong - punchstunthreshold = 25 - attack_verb = "smash" - attack_sound = 'sound/weapons/resonator_blast.ogg' - use_skintones = 0 - species_traits = list(NOBLOOD,EYECOLOR) - inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER) - sexes = 0 - changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN +/datum/species/corporate + name = "Corporate Agent" + id = "agent" + hair_alpha = 0 + say_mod = "declares" + speedmod = -2//Fast + brutemod = 0.7//Tough against firearms + burnmod = 0.65//Tough against lasers + coldmod = 0 + heatmod = 0.5//it's a little tough to burn them to death not as hard though. + punchdamagelow = 20 + punchdamagehigh = 30//they are inhumanly strong + punchstunthreshold = 25 + attack_verb = "smash" + attack_sound = 'sound/weapons/resonator_blast.ogg' + use_skintones = 0 + species_traits = list(NOBLOOD,EYECOLOR) + inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER) + sexes = 0 + changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index 3b1a6101109d..10e1d65e7d78 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -1,131 +1,131 @@ -//Subtype of human -/datum/species/human/felinid - name = "Felinid" - id = "felinid" - limbs_id = "human" - - mutant_bodyparts = list("ears", "tail_human") - default_features = list("mcolor" = "FFF", "tail_human" = "Cat", "ears" = "Cat", "wings" = "None") - - mutantears = /obj/item/organ/ears/cat - mutanttail = /obj/item/organ/tail/cat - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - -/datum/species/human/felinid/qualifies_for_rank(rank, list/features) - return TRUE - -//Curiosity killed the cat's wagging tail. -/datum/species/human/felinid/spec_death(gibbed, mob/living/carbon/human/H) - if(H) - stop_wagging_tail(H) - -/datum/species/human/felinid/spec_stun(mob/living/carbon/human/H,amount) - if(H) - stop_wagging_tail(H) - . = ..() - -/datum/species/human/felinid/can_wag_tail(mob/living/carbon/human/H) - return ("tail_human" in mutant_bodyparts) || ("waggingtail_human" in mutant_bodyparts) - -/datum/species/human/felinid/is_wagging_tail(mob/living/carbon/human/H) - return ("waggingtail_human" in mutant_bodyparts) - -/datum/species/human/felinid/start_wagging_tail(mob/living/carbon/human/H) - if("tail_human" in mutant_bodyparts) - mutant_bodyparts -= "tail_human" - mutant_bodyparts |= "waggingtail_human" - H.update_body() - -/datum/species/human/felinid/stop_wagging_tail(mob/living/carbon/human/H) - if("waggingtail_human" in mutant_bodyparts) - mutant_bodyparts -= "waggingtail_human" - mutant_bodyparts |= "tail_human" - H.update_body() - -/datum/species/human/felinid/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(!pref_load) //Hah! They got forcefully purrbation'd. Force default felinid parts on them if they have no mutant parts in those areas! - if(H.dna.features["tail_human"] == "None") - H.dna.features["tail_human"] = "Cat" - if(H.dna.features["ears"] == "None") - H.dna.features["ears"] = "Cat" - if(H.dna.features["ears"] == "Cat") - var/obj/item/organ/ears/cat/ears = new - ears.Insert(H, drop_if_replaced = FALSE) - else - mutantears = /obj/item/organ/ears - if(H.dna.features["tail_human"] == "Cat") - var/obj/item/organ/tail/cat/tail = new - tail.Insert(H, drop_if_replaced = FALSE) - else - mutanttail = null - return ..() - -/datum/species/human/felinid/on_species_loss(mob/living/carbon/H, datum/species/new_species, pref_load) - var/obj/item/organ/ears/cat/ears = H.getorgan(/obj/item/organ/ears/cat) - var/obj/item/organ/tail/cat/tail = H.getorgan(/obj/item/organ/tail/cat) - - if(ears) - var/obj/item/organ/ears/NE - if(new_species && new_species.mutantears) - // Roundstart cat ears override new_species.mutantears, reset it here. - new_species.mutantears = initial(new_species.mutantears) - if(new_species.mutantears) - NE = new new_species.mutantears - if(!NE) - // Go with default ears - NE = new /obj/item/organ/ears - NE.Insert(H, drop_if_replaced = FALSE) - - if(tail) - var/obj/item/organ/tail/NT - if(new_species && new_species.mutanttail) - // Roundstart cat tail overrides new_species.mutanttail, reset it here. - new_species.mutanttail = initial(new_species.mutanttail) - if(new_species.mutanttail) - NT = new new_species.mutanttail - if(NT) - NT.Insert(H, drop_if_replaced = FALSE) - else - tail.Remove(H) - -/proc/mass_purrbation() - for(var/M in GLOB.mob_list) - if(ishumanbasic(M)) - purrbation_apply(M) - CHECK_TICK - -/proc/mass_remove_purrbation() - for(var/M in GLOB.mob_list) - if(ishumanbasic(M)) - purrbation_remove(M) - CHECK_TICK - -/proc/purrbation_toggle(mob/living/carbon/human/H, silent = FALSE) - if(!ishumanbasic(H)) - return - if(!iscatperson(H)) - purrbation_apply(H, silent) - . = TRUE - else - purrbation_remove(H, silent) - . = FALSE - -/proc/purrbation_apply(mob/living/carbon/human/H, silent = FALSE) - if(!ishuman(H) || iscatperson(H)) - return - H.set_species(/datum/species/human/felinid) - - if(!silent) - to_chat(H, "Something is nya~t right.") - playsound(get_turf(H), 'sound/effects/meow1.ogg', 50, 1, -1) - -/proc/purrbation_remove(mob/living/carbon/human/H, silent = FALSE) - if(!ishuman(H) || !iscatperson(H)) - return - - H.set_species(/datum/species/human) - - if(!silent) - to_chat(H, "You are no longer a cat.") +//Subtype of human +/datum/species/human/felinid + name = "Felinid" + id = "felinid" + limbs_id = "human" + + mutant_bodyparts = list("ears", "tail_human") + default_features = list("mcolor" = "FFF", "tail_human" = "Cat", "ears" = "Cat", "wings" = "None") + + mutantears = /obj/item/organ/ears/cat + mutanttail = /obj/item/organ/tail/cat + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT + +/datum/species/human/felinid/qualifies_for_rank(rank, list/features) + return TRUE + +//Curiosity killed the cat's wagging tail. +/datum/species/human/felinid/spec_death(gibbed, mob/living/carbon/human/H) + if(H) + stop_wagging_tail(H) + +/datum/species/human/felinid/spec_stun(mob/living/carbon/human/H,amount) + if(H) + stop_wagging_tail(H) + . = ..() + +/datum/species/human/felinid/can_wag_tail(mob/living/carbon/human/H) + return ("tail_human" in mutant_bodyparts) || ("waggingtail_human" in mutant_bodyparts) + +/datum/species/human/felinid/is_wagging_tail(mob/living/carbon/human/H) + return ("waggingtail_human" in mutant_bodyparts) + +/datum/species/human/felinid/start_wagging_tail(mob/living/carbon/human/H) + if("tail_human" in mutant_bodyparts) + mutant_bodyparts -= "tail_human" + mutant_bodyparts |= "waggingtail_human" + H.update_body() + +/datum/species/human/felinid/stop_wagging_tail(mob/living/carbon/human/H) + if("waggingtail_human" in mutant_bodyparts) + mutant_bodyparts -= "waggingtail_human" + mutant_bodyparts |= "tail_human" + H.update_body() + +/datum/species/human/felinid/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) + if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(!pref_load) //Hah! They got forcefully purrbation'd. Force default felinid parts on them if they have no mutant parts in those areas! + if(H.dna.features["tail_human"] == "None") + H.dna.features["tail_human"] = "Cat" + if(H.dna.features["ears"] == "None") + H.dna.features["ears"] = "Cat" + if(H.dna.features["ears"] == "Cat") + var/obj/item/organ/ears/cat/ears = new + ears.Insert(H, drop_if_replaced = FALSE) + else + mutantears = /obj/item/organ/ears + if(H.dna.features["tail_human"] == "Cat") + var/obj/item/organ/tail/cat/tail = new + tail.Insert(H, drop_if_replaced = FALSE) + else + mutanttail = null + return ..() + +/datum/species/human/felinid/on_species_loss(mob/living/carbon/H, datum/species/new_species, pref_load) + var/obj/item/organ/ears/cat/ears = H.getorgan(/obj/item/organ/ears/cat) + var/obj/item/organ/tail/cat/tail = H.getorgan(/obj/item/organ/tail/cat) + + if(ears) + var/obj/item/organ/ears/NE + if(new_species && new_species.mutantears) + // Roundstart cat ears override new_species.mutantears, reset it here. + new_species.mutantears = initial(new_species.mutantears) + if(new_species.mutantears) + NE = new new_species.mutantears + if(!NE) + // Go with default ears + NE = new /obj/item/organ/ears + NE.Insert(H, drop_if_replaced = FALSE) + + if(tail) + var/obj/item/organ/tail/NT + if(new_species && new_species.mutanttail) + // Roundstart cat tail overrides new_species.mutanttail, reset it here. + new_species.mutanttail = initial(new_species.mutanttail) + if(new_species.mutanttail) + NT = new new_species.mutanttail + if(NT) + NT.Insert(H, drop_if_replaced = FALSE) + else + tail.Remove(H) + +/proc/mass_purrbation() + for(var/M in GLOB.mob_list) + if(ishumanbasic(M)) + purrbation_apply(M) + CHECK_TICK + +/proc/mass_remove_purrbation() + for(var/M in GLOB.mob_list) + if(ishumanbasic(M)) + purrbation_remove(M) + CHECK_TICK + +/proc/purrbation_toggle(mob/living/carbon/human/H, silent = FALSE) + if(!ishumanbasic(H)) + return + if(!iscatperson(H)) + purrbation_apply(H, silent) + . = TRUE + else + purrbation_remove(H, silent) + . = FALSE + +/proc/purrbation_apply(mob/living/carbon/human/H, silent = FALSE) + if(!ishuman(H) || iscatperson(H)) + return + H.set_species(/datum/species/human/felinid) + + if(!silent) + to_chat(H, "Something is nya~t right.") + playsound(get_turf(H), 'sound/effects/meow1.ogg', 50, 1, -1) + +/proc/purrbation_remove(mob/living/carbon/human/H, silent = FALSE) + if(!ishuman(H) || !iscatperson(H)) + return + + H.set_species(/datum/species/human) + + if(!silent) + to_chat(H, "You are no longer a cat.") 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 a12fb1d68d6e..6fc43bb6b156 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 = "Flyperson" - id = "fly" - say_mod = "buzzes" - species_traits = list(NOEYESPRITES) - 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 - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - -/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_species_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 = "Flyperson" + id = "fly" + say_mod = "buzzes" + species_traits = list(NOEYESPRITES) + 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 + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT + +/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_species_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 660932202852..6ec44fd09329 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -1,1129 +1,1129 @@ -/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) - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) // yogs - Gives golems NOHUNGER - 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 - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC - 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 - 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 = "Random Golem" - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN - 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_NOGUNS,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","Grill") - -//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, massively slowpokes, but gravproof and obstructive. Basically, The Wall. -/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. You also magnetically attach to surfaces and so don't float without gravity and cannot have positions swapped with other beings." - attack_verb = "smash" - attack_sound = 'sound/effects/meteorimpact.ogg' //hits pretty hard - prefix = "Plasteel" - special_names = null - -/datum/species/golem/plasteel/negates_gravity(mob/living/carbon/human/H) - return TRUE - -/datum/species/golem/plasteel/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - ADD_TRAIT(C, TRAIT_NOMOBSWAP, SPECIES_TRAIT) //THE WALL THE WALL THE WALL - -/datum/species/golem/plasteel/on_species_loss(mob/living/carbon/C) - REMOVE_TRAIT(C, TRAIT_NOMOBSWAP, SPECIES_TRAIT) //NOTHING ON ERF CAN MAKE IT FALL - ..() - -//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, 0, BODYPART_ORGANIC) - 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_NOGUNS,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.adjust_nutrition(light_amount * 10) - if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) - H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) - if(light_amount > 0.2) //if there's enough light, heal - H.heal_overall_damage(1,1,0, BODYPART_ORGANIC) - 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", "235") - -/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 BULLET_ACT_BLOCK - return BULLET_ACT_HIT - -//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) - // redirect the projectile - P.firer = H - P.preparePixelProjectile(locate(CLAMP(new_x, 1, world.maxx), CLAMP(new_y, 1, world.maxy), H.z), H) - return BULLET_ACT_FORCE_PIERCE - return ..() - -//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,NOEYESPRITES,NOFLASH) //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(istype(chem, /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. On death, you will break down into scrap." - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) - inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) - 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 - changesource_flags = MIRROR_BADMIN | WABBAJACK - 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 - random_eligible = FALSE - info_text = "As a Clockwork Golem Servant, you are faster than other types of golems." //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) //no mutcolors, and can burn - inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOGUNS) - 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 || cloth_golem.hellbound) - 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.is_hot()) - visible_message("[src] bursts into flames!") - fire_act() - -/datum/species/golem/plastic - name = "Plastic Golem" - id = "plastic golem" - prefix = "Plastic" - special_names = list("Sheet", "Bag", "Bottle") - 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' ) - 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 BULLET_ACT_HIT - if(P.flag == "bullet" || P.flag == "bomb") - gong(H) - return BULLET_ACT_HIT - -/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!", 2) - 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!", 2) - 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!", 2) - 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!", 2) - 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,NOEYESPRITES,NOFLASH) - inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - limbs_id = "c_golem" //special sprites - attack_verb = "whips" - attack_sound = 'sound/weapons/whip.ogg' - miss_sound = 'sound/weapons/etherealmiss.ogg' - fixed_mut_color = null - 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_NOGUNS,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." - 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,NOEYESPRITES,NOFLASH) - fixed_mut_color = null - inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,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 - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) - inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) - mutanttongue = /obj/item/organ/tongue/bone - sexes = FALSE - fixed_mut_color = null - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,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/capitalist - name = "Capitalist Golem" - id = "capitalist golem" - prefix = "Capitalist" - attack_verb = "monopoliz" - limbs_id = "ca_golem" - special_names = list("John D. Rockefeller","Rich Uncle Pennybags","Commodore Vanderbilt","Entrepreneur","Mr. Moneybags", "Adam Smith") - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) - fixed_mut_color = null - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - info_text = "As a Capitalist Golem, your fist spreads the powerful industrializing light of capitalism." - changesource_flags = MIRROR_BADMIN - random_eligible = FALSE - - var/last_cash = 0 - var/cash_cooldown = 100 - -/datum/species/golem/capitalist/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.equip_to_slot_or_del(new /obj/item/clothing/head/that (), SLOT_HEAD) - C.equip_to_slot_or_del(new /obj/item/clothing/glasses/monocle (), SLOT_GLASSES) - C.revive(full_heal = TRUE) - to_chat(C, "You are now a capitalist golem! Do not harm fellow capitalist golems. Kill communist golems and hit people with your fists to spread the industrializing light of capitalism to others! Hello I like money!") //yogs memes - - SEND_SOUND(C, sound('sound/misc/capitialism.ogg')) - C.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock ()) - RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) - C.mind.add_antag_datum(/datum/antagonist/golem/capitalist) - -/datum/species/golem/capitalist/on_species_loss(mob/living/carbon/C) - . = ..() - UnregisterSignal(C, COMSIG_MOB_SAY) - for(var/obj/effect/proc_holder/spell/aoe_turf/knock/spell in C.mob_spell_list) - C.RemoveSpell(spell) - var/datum/antagonist/golem/capitalist/CA = C.mind.has_antag_datum(/datum/antagonist/golem/capitalist) - if(CA && !CA.removing) - C.mind.remove_antag_datum(/datum/antagonist/golem/capitalist) - -/datum/species/golem/capitalist/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) - ..() - if(isgolem(target)) - return - if(target.nutrition >= NUTRITION_LEVEL_FAT) - target.set_species(/datum/species/golem/capitalist) - return - target.adjust_nutrition(40) - -/datum/species/golem/capitalist/proc/handle_speech(datum/source, list/speech_args) - playsound(source, 'sound/misc/mymoney.ogg', 25, 0) - speech_args[SPEECH_MESSAGE] = "Hello, I like money!" - -/datum/species/golem/soviet - name = "Soviet Golem" - id = "soviet golem" - prefix = "Comrade" - attack_verb = "nationaliz" - limbs_id = "s_golem" - special_names = list("Stalin","Lenin","Trotsky","Marx","Comrade") //comrade comrade - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) - fixed_mut_color = null - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - info_text = "As a Soviet Golem, your fist spreads the bright soviet light of communism." - changesource_flags = MIRROR_BADMIN - random_eligible = FALSE - -/datum/species/golem/soviet/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.equip_to_slot_or_del(new /obj/item/clothing/head/ushanka (), SLOT_HEAD) - C.revive(full_heal = TRUE) - to_chat(C, "You are now a soviet golem! Do not harm fellow soviet golems. Kill captalist golems and hit people with your fists to spread the glorious light of communism to others! Cyka Blyat!") //yogs memes - - SEND_SOUND(C, sound('sound/misc/Russian_Anthem_chorus.ogg')) - C.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock ()) - RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) - C.mind.add_antag_datum(/datum/antagonist/golem/communist) - -/datum/species/golem/soviet/on_species_loss(mob/living/carbon/C) - . = ..() - for(var/obj/effect/proc_holder/spell/aoe_turf/knock/spell in C.mob_spell_list) - C.RemoveSpell(spell) - UnregisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) - var/datum/antagonist/golem/communist/CU = C.mind.has_antag_datum(/datum/antagonist/golem/communist) - if(CU && !CU.removing) - C.mind.remove_antag_datum(/datum/antagonist/golem/communist) - -/datum/species/golem/soviet/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) - ..() - if(isgolem(target)) - return - if(target.nutrition <= NUTRITION_LEVEL_STARVING) - target.set_species(/datum/species/golem/soviet) - return - target.adjust_nutrition(-40) - -/datum/species/golem/soviet/proc/handle_speech(datum/source, list/speech_args) - playsound(source, 'sound/misc/Cyka Blyat.ogg', 25, 0) - speech_args[SPEECH_MESSAGE] = "Cyka Blyat" +/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) + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) // yogs - Gives golems NOHUNGER + 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 + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC + 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 + 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 = "Random Golem" + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN + 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_NOGUNS,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","Grill") + +//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, massively slowpokes, but gravproof and obstructive. Basically, The Wall. +/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. You also magnetically attach to surfaces and so don't float without gravity and cannot have positions swapped with other beings." + attack_verb = "smash" + attack_sound = 'sound/effects/meteorimpact.ogg' //hits pretty hard + prefix = "Plasteel" + special_names = null + +/datum/species/golem/plasteel/negates_gravity(mob/living/carbon/human/H) + return TRUE + +/datum/species/golem/plasteel/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + ADD_TRAIT(C, TRAIT_NOMOBSWAP, SPECIES_TRAIT) //THE WALL THE WALL THE WALL + +/datum/species/golem/plasteel/on_species_loss(mob/living/carbon/C) + REMOVE_TRAIT(C, TRAIT_NOMOBSWAP, SPECIES_TRAIT) //NOTHING ON ERF CAN MAKE IT FALL + ..() + +//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, 0, BODYPART_ORGANIC) + 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_NOGUNS,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.adjust_nutrition(light_amount * 10) + if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) + H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) + if(light_amount > 0.2) //if there's enough light, heal + H.heal_overall_damage(1,1,0, BODYPART_ORGANIC) + 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", "235") + +/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 BULLET_ACT_BLOCK + return BULLET_ACT_HIT + +//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) + // redirect the projectile + P.firer = H + P.preparePixelProjectile(locate(CLAMP(new_x, 1, world.maxx), CLAMP(new_y, 1, world.maxy), H.z), H) + return BULLET_ACT_FORCE_PIERCE + return ..() + +//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,NOEYESPRITES,NOFLASH) //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(istype(chem, /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. On death, you will break down into scrap." + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) + inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) + 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 + changesource_flags = MIRROR_BADMIN | WABBAJACK + 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 + random_eligible = FALSE + info_text = "As a Clockwork Golem Servant, you are faster than other types of golems." //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) //no mutcolors, and can burn + inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOGUNS) + 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 || cloth_golem.hellbound) + 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.is_hot()) + visible_message("[src] bursts into flames!") + fire_act() + +/datum/species/golem/plastic + name = "Plastic Golem" + id = "plastic golem" + prefix = "Plastic" + special_names = list("Sheet", "Bag", "Bottle") + 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' ) + 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 BULLET_ACT_HIT + if(P.flag == "bullet" || P.flag == "bomb") + gong(H) + return BULLET_ACT_HIT + +/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!", 2) + 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!", 2) + 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!", 2) + 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!", 2) + 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,NOEYESPRITES,NOFLASH) + inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + limbs_id = "c_golem" //special sprites + attack_verb = "whips" + attack_sound = 'sound/weapons/whip.ogg' + miss_sound = 'sound/weapons/etherealmiss.ogg' + fixed_mut_color = null + 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_NOGUNS,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." + 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,NOEYESPRITES,NOFLASH) + fixed_mut_color = null + inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,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 + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) + inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) + mutanttongue = /obj/item/organ/tongue/bone + sexes = FALSE + fixed_mut_color = null + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,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/capitalist + name = "Capitalist Golem" + id = "capitalist golem" + prefix = "Capitalist" + attack_verb = "monopoliz" + limbs_id = "ca_golem" + special_names = list("John D. Rockefeller","Rich Uncle Pennybags","Commodore Vanderbilt","Entrepreneur","Mr. Moneybags", "Adam Smith") + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) + fixed_mut_color = null + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + info_text = "As a Capitalist Golem, your fist spreads the powerful industrializing light of capitalism." + changesource_flags = MIRROR_BADMIN + random_eligible = FALSE + + var/last_cash = 0 + var/cash_cooldown = 100 + +/datum/species/golem/capitalist/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.equip_to_slot_or_del(new /obj/item/clothing/head/that (), SLOT_HEAD) + C.equip_to_slot_or_del(new /obj/item/clothing/glasses/monocle (), SLOT_GLASSES) + C.revive(full_heal = TRUE) + to_chat(C, "You are now a capitalist golem! Do not harm fellow capitalist golems. Kill communist golems and hit people with your fists to spread the industrializing light of capitalism to others! Hello I like money!") //yogs memes + + SEND_SOUND(C, sound('sound/misc/capitialism.ogg')) + C.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock ()) + RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) + C.mind.add_antag_datum(/datum/antagonist/golem/capitalist) + +/datum/species/golem/capitalist/on_species_loss(mob/living/carbon/C) + . = ..() + UnregisterSignal(C, COMSIG_MOB_SAY) + for(var/obj/effect/proc_holder/spell/aoe_turf/knock/spell in C.mob_spell_list) + C.RemoveSpell(spell) + var/datum/antagonist/golem/capitalist/CA = C.mind.has_antag_datum(/datum/antagonist/golem/capitalist) + if(CA && !CA.removing) + C.mind.remove_antag_datum(/datum/antagonist/golem/capitalist) + +/datum/species/golem/capitalist/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) + ..() + if(isgolem(target)) + return + if(target.nutrition >= NUTRITION_LEVEL_FAT) + target.set_species(/datum/species/golem/capitalist) + return + target.adjust_nutrition(40) + +/datum/species/golem/capitalist/proc/handle_speech(datum/source, list/speech_args) + playsound(source, 'sound/misc/mymoney.ogg', 25, 0) + speech_args[SPEECH_MESSAGE] = "Hello, I like money!" + +/datum/species/golem/soviet + name = "Soviet Golem" + id = "soviet golem" + prefix = "Comrade" + attack_verb = "nationaliz" + limbs_id = "s_golem" + special_names = list("Stalin","Lenin","Trotsky","Marx","Comrade") //comrade comrade + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) + fixed_mut_color = null + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + info_text = "As a Soviet Golem, your fist spreads the bright soviet light of communism." + changesource_flags = MIRROR_BADMIN + random_eligible = FALSE + +/datum/species/golem/soviet/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.equip_to_slot_or_del(new /obj/item/clothing/head/ushanka (), SLOT_HEAD) + C.revive(full_heal = TRUE) + to_chat(C, "You are now a soviet golem! Do not harm fellow soviet golems. Kill captalist golems and hit people with your fists to spread the glorious light of communism to others! Cyka Blyat!") //yogs memes + + SEND_SOUND(C, sound('sound/misc/Russian_Anthem_chorus.ogg')) + C.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock ()) + RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) + C.mind.add_antag_datum(/datum/antagonist/golem/communist) + +/datum/species/golem/soviet/on_species_loss(mob/living/carbon/C) + . = ..() + for(var/obj/effect/proc_holder/spell/aoe_turf/knock/spell in C.mob_spell_list) + C.RemoveSpell(spell) + UnregisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) + var/datum/antagonist/golem/communist/CU = C.mind.has_antag_datum(/datum/antagonist/golem/communist) + if(CU && !CU.removing) + C.mind.remove_antag_datum(/datum/antagonist/golem/communist) + +/datum/species/golem/soviet/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) + ..() + if(isgolem(target)) + return + if(target.nutrition <= NUTRITION_LEVEL_STARVING) + target.set_species(/datum/species/golem/soviet) + return + target.adjust_nutrition(-40) + +/datum/species/golem/soviet/proc/handle_speech(datum/source, list/speech_args) + playsound(source, 'sound/misc/Cyka Blyat.ogg', 25, 0) + speech_args[SPEECH_MESSAGE] = "Cyka Blyat" diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm index 055ba80c555b..d3a2505853b5 100644 --- a/code/modules/mob/living/carbon/human/species_types/humans.dm +++ b/code/modules/mob/living/carbon/human/species_types/humans.dm @@ -1,14 +1,14 @@ -/datum/species/human - name = "Human" - id = "human" - default_color = "FFFFFF" - species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS) - default_features = list("mcolor" = "FFF", "wings" = "None") - use_skintones = 1 - skinned_type = /obj/item/stack/sheet/animalhide/human - disliked_food = GROSS | RAW - liked_food = JUNKFOOD | FRIED - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT - -/datum/species/human/qualifies_for_rank(rank, list/features) - return TRUE //Pure humans are always allowed in all roles. +/datum/species/human + name = "Human" + id = "human" + default_color = "FFFFFF" + species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS) + default_features = list("mcolor" = "FFF", "wings" = "None") + use_skintones = 1 + skinned_type = /obj/item/stack/sheet/animalhide/human + disliked_food = GROSS | RAW + liked_food = JUNKFOOD | FRIED + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT + +/datum/species/human/qualifies_for_rank(rank, list/features) + return TRUE //Pure humans are always allowed in all roles. 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 561310c0efa9..f91233a47c68 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -1,737 +1,737 @@ -/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 = "Jellyperson" - id = "jelly" - default_color = "00FF90" - say_mod = "chirps" - species_traits = list(MUTCOLORS,EYECOLOR,NOBLOOD) - inherent_traits = list(TRAIT_TOXINLOVER) - mutantlungs = /obj/item/organ/lungs/slime - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime - exotic_blood = /datum/reagent/toxin/slimejelly - damage_overlay_type = "" - var/datum/action/innate/regenerate_limbs/regenerate_limbs - liked_food = MEAT - coldmod = 6 // = 3x cold damage - heatmod = 0.5 // = 1/4x heat damage - burnmod = 0.5 // = 1/2x generic burn damage - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - -/datum/species/jelly/on_species_loss(mob/living/carbon/C) - if(regenerate_limbs) - regenerate_limbs.Remove(C) - 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) - C.faction |= "slime" - -/datum/species/jelly/spec_life(mob/living/carbon/human/H) - if(H.stat == DEAD) //can't farm slime jelly from a dead slime/jelly person indefinitely - 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)) - if(H.nutrition >= NUTRITION_LEVEL_STARVING) - H.blood_volume += 3 - H.adjust_nutrition(-2.5) - if(H.blood_volume < BLOOD_VOLUME_OKAY(H)) - if(prob(5)) - to_chat(H, "You feel drained!") - if(H.blood_volume < BLOOD_VOLUME_BAD(H)) - 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) + REGEN_BLOOD_REQUIREMENT) - 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 >= REGEN_BLOOD_REQUIREMENT*limbs_to_heal.len + BLOOD_VOLUME_OKAY(H)) - H.regenerate_limbs() - H.blood_volume -= REGEN_BLOOD_REQUIREMENT*limbs_to_heal.len - to_chat(H, "...and after a moment you finish reforming!") - return - else if(H.blood_volume >= REGEN_BLOOD_REQUIREMENT)//We can partially heal some limbs - while(H.blood_volume >= BLOOD_VOLUME_OKAY(H) + REGEN_BLOOD_REQUIREMENT) - var/healed_limb = pick(limbs_to_heal) - H.regenerate_limb(healed_limb) - limbs_to_heal -= healed_limb - H.blood_volume -= REGEN_BLOOD_REQUIREMENT - 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!") - -////////////////////////////////////////////////////////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 = "Slimeperson" - id = "slime" - default_color = "00FFFF" - species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,NOBLOOD) - 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)) - ..() - -/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(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.adjust_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" - //yogs start - if(SOFT_CRIT) - stat = "Barely Conscious" - //yogs end - 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/datum/species/jelly/slime/SS = H.dna.species - var/mob/living/carbon/human/selected = locate(params["ref"]) in SS.bodies - if(!can_swap(selected)) - return - // SStgui.close_uis(src) // yogs - don't. - 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.") - - -///////////////////////////////////LUMINESCENTS////////////////////////////////////////// - -//Luminescents are able to consume and use slime extracts, without them decaying. - -/datum/species/jelly/luminescent - name = "Luminescent" - 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" - 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) - -/datum/species/jelly/stargazer/proc/link_mob(mob/living/M) - if(QDELETED(M) || M.stat == DEAD) - return FALSE - if(HAS_TRAIT(M, TRAIT_MINDSHIELD)) //mindshield implant, no dice - return FALSE - if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) - return FALSE - if(M in linked_mobs) - return FALSE - linked_mobs.Add(M) - 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...") +/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 = "Jellyperson" + id = "jelly" + default_color = "00FF90" + say_mod = "chirps" + species_traits = list(MUTCOLORS,EYECOLOR,NOBLOOD) + inherent_traits = list(TRAIT_TOXINLOVER) + mutantlungs = /obj/item/organ/lungs/slime + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime + exotic_blood = /datum/reagent/toxin/slimejelly + damage_overlay_type = "" + var/datum/action/innate/regenerate_limbs/regenerate_limbs + liked_food = MEAT + coldmod = 6 // = 3x cold damage + heatmod = 0.5 // = 1/4x heat damage + burnmod = 0.5 // = 1/2x generic burn damage + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT + +/datum/species/jelly/on_species_loss(mob/living/carbon/C) + if(regenerate_limbs) + regenerate_limbs.Remove(C) + 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) + C.faction |= "slime" + +/datum/species/jelly/spec_life(mob/living/carbon/human/H) + if(H.stat == DEAD) //can't farm slime jelly from a dead slime/jelly person indefinitely + 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)) + if(H.nutrition >= NUTRITION_LEVEL_STARVING) + H.blood_volume += 3 + H.adjust_nutrition(-2.5) + if(H.blood_volume < BLOOD_VOLUME_OKAY(H)) + if(prob(5)) + to_chat(H, "You feel drained!") + if(H.blood_volume < BLOOD_VOLUME_BAD(H)) + 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) + REGEN_BLOOD_REQUIREMENT) + 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 >= REGEN_BLOOD_REQUIREMENT*limbs_to_heal.len + BLOOD_VOLUME_OKAY(H)) + H.regenerate_limbs() + H.blood_volume -= REGEN_BLOOD_REQUIREMENT*limbs_to_heal.len + to_chat(H, "...and after a moment you finish reforming!") + return + else if(H.blood_volume >= REGEN_BLOOD_REQUIREMENT)//We can partially heal some limbs + while(H.blood_volume >= BLOOD_VOLUME_OKAY(H) + REGEN_BLOOD_REQUIREMENT) + var/healed_limb = pick(limbs_to_heal) + H.regenerate_limb(healed_limb) + limbs_to_heal -= healed_limb + H.blood_volume -= REGEN_BLOOD_REQUIREMENT + 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!") + +////////////////////////////////////////////////////////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 = "Slimeperson" + id = "slime" + default_color = "00FFFF" + species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,NOBLOOD) + 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)) + ..() + +/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(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.adjust_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" + //yogs start + if(SOFT_CRIT) + stat = "Barely Conscious" + //yogs end + 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/datum/species/jelly/slime/SS = H.dna.species + var/mob/living/carbon/human/selected = locate(params["ref"]) in SS.bodies + if(!can_swap(selected)) + return + // SStgui.close_uis(src) // yogs - don't. + 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.") + + +///////////////////////////////////LUMINESCENTS////////////////////////////////////////// + +//Luminescents are able to consume and use slime extracts, without them decaying. + +/datum/species/jelly/luminescent + name = "Luminescent" + 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" + 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) + +/datum/species/jelly/stargazer/proc/link_mob(mob/living/M) + if(QDELETED(M) || M.stat == DEAD) + return FALSE + if(HAS_TRAIT(M, TRAIT_MINDSHIELD)) //mindshield implant, no dice + return FALSE + if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) + return FALSE + if(M in linked_mobs) + return FALSE + linked_mobs.Add(M) + 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.") \ No newline at end of file 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 b695241e7281..4cb25439d02d 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -1,94 +1,94 @@ -/datum/species/lizard - // Reptilian humanoids with scaled skin and tails. - name = "Lizardperson" - id = "lizard" - say_mod = "hisses" - default_color = "00FF00" - species_traits = list(MUTCOLORS,EYECOLOR,LIPS) - inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_REPTILE) - mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs") - mutanttongue = /obj/item/organ/tongue/lizard - mutanttail = /obj/item/organ/tail/lizard - coldmod = 1.5 - heatmod = 0.67 - default_features = list("mcolor" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round", "horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs") - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - 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 - skinned_type = /obj/item/stack/sheet/animalhide/lizard - exotic_bloodtype = "L" - disliked_food = GRAIN | DAIRY - liked_food = GROSS | MEAT - inert_mutation = FIREBREATH - deathsound = 'sound/voice/lizard/deathsound.ogg' - screamsound = 'yogstation/sound/voice/lizardperson/lizard_scream.ogg' //yogs - lizard scream - -/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 - -//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_NOGUNS) //yogs start - ashwalkers have special lungs and actually breathe - mutantlungs = /obj/item/organ/lungs/ashwalker - breathid = "n2" // yogs end - -// yogs start - Ashwalkers now have ash immunity -/datum/species/lizard/ashwalker/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.weather_immunities |= "ash" - -/datum/species/lizard/ashwalker/on_species_loss(mob/living/carbon/C) - . = ..() - C.weather_immunities -= "ash" -// yogs end +/datum/species/lizard + // Reptilian humanoids with scaled skin and tails. + name = "Lizardperson" + id = "lizard" + say_mod = "hisses" + default_color = "00FF00" + species_traits = list(MUTCOLORS,EYECOLOR,LIPS) + inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_REPTILE) + mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs") + mutanttongue = /obj/item/organ/tongue/lizard + mutanttail = /obj/item/organ/tail/lizard + coldmod = 1.5 + heatmod = 0.67 + default_features = list("mcolor" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round", "horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", "legs" = "Normal Legs") + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT + 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 + skinned_type = /obj/item/stack/sheet/animalhide/lizard + exotic_bloodtype = "L" + disliked_food = GRAIN | DAIRY + liked_food = GROSS | MEAT + inert_mutation = FIREBREATH + deathsound = 'sound/voice/lizard/deathsound.ogg' + screamsound = 'yogstation/sound/voice/lizardperson/lizard_scream.ogg' //yogs - lizard scream + +/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 + +//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_NOGUNS) //yogs start - ashwalkers have special lungs and actually breathe + mutantlungs = /obj/item/organ/lungs/ashwalker + breathid = "n2" // yogs end + +// yogs start - Ashwalkers now have ash immunity +/datum/species/lizard/ashwalker/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.weather_immunities |= "ash" + +/datum/species/lizard/ashwalker/on_species_loss(mob/living/carbon/C) + . = ..() + C.weather_immunities -= "ash" +// yogs end 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 67a90413bee2..f4efc11a4355 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -1,140 +1,140 @@ -/datum/species/plasmaman - name = "Plasmaman" - id = "plasmaman" - say_mod = "rattles" - sexes = 0 - meat = /obj/item/stack/sheet/mineral/plasma - species_traits = list(NOBLOOD,NOTRANSSTING) - inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_NOHUNGER,TRAIT_CALCIUM_HEALER,TRAIT_ALWAYS_CLEAN) - 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 - burnmod = 1.5 - heatmod = 1.5 - brutemod = 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 - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC - -/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][MOLES]) >= 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") - O = new /datum/outfit/plasmaman/cargo - - if("Quartermaster") - O = new /datum/outfit/plasmaman/qm - - 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("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) + inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_NOHUNGER,TRAIT_CALCIUM_HEALER,TRAIT_ALWAYS_CLEAN) + 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 + burnmod = 1.5 + heatmod = 1.5 + brutemod = 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 + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC + +/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][MOLES]) >= 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") + O = new /datum/outfit/plasmaman/cargo + + if("Quartermaster") + O = new /datum/outfit/plasmaman/qm + + 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("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/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index 6163106aac0d..4f6203791970 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -1,70 +1,70 @@ -// yogs - This file is mirrored to plantpeople.dm -/datum/species/pod - // A mutation caused by a human being ressurected in a revival pod. These regain health in light, and begin to wither in darkness. - name = "Podperson" - id = "pod" - default_color = "59CE00" - species_traits = list(MUTCOLORS,EYECOLOR) - inherent_traits = list(TRAIT_ALWAYS_CLEAN) - attack_verb = "slash" - attack_sound = 'sound/weapons/slice.ogg' - miss_sound = 'sound/weapons/slashmiss.ogg' - burnmod = 1.25 - heatmod = 1.5 - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/plant - disliked_food = MEAT | DAIRY - liked_food = VEGETABLES | FRUIT | GRAIN - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - -/datum/species/pod/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.faction |= "plants" - C.faction |= "vines" - -/datum/species/pod/on_species_loss(mob/living/carbon/C) - . = ..() - C.faction -= "plants" - C.faction -= "vines" - -/datum/species/pod/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.adjust_nutrition(light_amount * 10) - if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) - H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) - if(light_amount > 0.2) //if there's enough light, heal - H.heal_overall_damage(1,1, 0, BODYPART_ORGANIC) - H.adjustToxLoss(-1) - H.adjustOxyLoss(-1) - - if(H.nutrition < NUTRITION_LEVEL_STARVING + 50) - H.take_overall_damage(2,0) - -/datum/species/pod/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 - -/datum/species/pod/on_hit(obj/item/projectile/P, mob/living/carbon/human/H) - switch(P.type) - if(/obj/item/projectile/energy/floramut) - if(prob(15)) - H.rad_act(rand(30,80)) - H.Paralyze(100) - H.visible_message("[H] writhes in pain as [H.p_their()] vacuoles boil.", "You writhe in pain as your vacuoles boil!", "You hear the crunching of leaves.") - if(prob(80)) - H.easy_randmut(NEGATIVE+MINOR_NEGATIVE) - else - H.easy_randmut(POSITIVE) - H.randmuti() - H.domutcheck() - else - H.adjustFireLoss(rand(5,15)) - H.show_message("The radiation beam singes you!") - if(/obj/item/projectile/energy/florayield) - H.set_nutrition(min(H.nutrition+30, NUTRITION_LEVEL_FULL)) +// yogs - This file is mirrored to plantpeople.dm +/datum/species/pod + // A mutation caused by a human being ressurected in a revival pod. These regain health in light, and begin to wither in darkness. + name = "Podperson" + id = "pod" + default_color = "59CE00" + species_traits = list(MUTCOLORS,EYECOLOR) + inherent_traits = list(TRAIT_ALWAYS_CLEAN) + attack_verb = "slash" + attack_sound = 'sound/weapons/slice.ogg' + miss_sound = 'sound/weapons/slashmiss.ogg' + burnmod = 1.25 + heatmod = 1.5 + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/plant + disliked_food = MEAT | DAIRY + liked_food = VEGETABLES | FRUIT | GRAIN + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT + +/datum/species/pod/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.faction |= "plants" + C.faction |= "vines" + +/datum/species/pod/on_species_loss(mob/living/carbon/C) + . = ..() + C.faction -= "plants" + C.faction -= "vines" + +/datum/species/pod/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.adjust_nutrition(light_amount * 10) + if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) + H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) + if(light_amount > 0.2) //if there's enough light, heal + H.heal_overall_damage(1,1, 0, BODYPART_ORGANIC) + H.adjustToxLoss(-1) + H.adjustOxyLoss(-1) + + if(H.nutrition < NUTRITION_LEVEL_STARVING + 50) + H.take_overall_damage(2,0) + +/datum/species/pod/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 + +/datum/species/pod/on_hit(obj/item/projectile/P, mob/living/carbon/human/H) + switch(P.type) + if(/obj/item/projectile/energy/floramut) + if(prob(15)) + H.rad_act(rand(30,80)) + H.Paralyze(100) + H.visible_message("[H] writhes in pain as [H.p_their()] vacuoles boil.", "You writhe in pain as your vacuoles boil!", "You hear the crunching of leaves.") + if(prob(80)) + H.easy_randmut(NEGATIVE+MINOR_NEGATIVE) + else + H.easy_randmut(POSITIVE) + H.randmuti() + H.domutcheck() + else + H.adjustFireLoss(rand(5,15)) + H.show_message("The radiation beam singes you!") + if(/obj/item/projectile/energy/florayield) + H.set_nutrition(min(H.nutrition+30, NUTRITION_LEVEL_FULL)) 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 4d83964e8f48..1b5b77c46d21 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -1,220 +1,220 @@ -#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 - 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,NOEYESPRITES,NOFLASH) - inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH) - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC - - 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, 0, BODYPART_ORGANIC) - else if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark - H.heal_overall_damage(1,1, 0, BODYPART_ORGANIC) - -/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 - 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,NOEYESPRITES,NOFLASH) - inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,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("[C.real_name]","[pick(GLOB.nightmare_names)]") // Yogs -- fixes nightmares not having special spooky names. this proc takes the old name first, and *THEN* the new name! - -/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 BULLET_ACT_FORCE_PIERCE - return ..() - -/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) - ..() - 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) - ..() - if(special != HEART_SPECIAL_SHADOWIFY) - blade = new/obj/item/light_eater - M.put_in_hands(blade) - START_PROCESSING(SSobj, src) - -/obj/item/organ/heart/nightmare/Remove(mob/living/carbon/M, special = 0) - STOP_PROCESSING(SSobj, src) - 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/process() - if(QDELETED(owner) || owner.stat != DEAD) - respawn_progress = 0 - 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 - -/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)) //So you can actually melee with it - return - if(isliving(AM)) - var/mob/living/L = AM - if(isethereal(AM)) - AM.emp_act(EMP_LIGHT) - - if(iscyborg(AM)) - var/mob/living/silicon/robot/borg = AM - if(!borg.lamp_cooldown) - borg.update_headlamp(TRUE, INFINITY) - to_chat(borg, "Your headlamp is fried! You'll need a human to help replace it.") - 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 + 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,NOEYESPRITES,NOFLASH) + inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH) + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC + + 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, 0, BODYPART_ORGANIC) + else if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark + H.heal_overall_damage(1,1, 0, BODYPART_ORGANIC) + +/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 + 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,NOEYESPRITES,NOFLASH) + inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,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("[C.real_name]","[pick(GLOB.nightmare_names)]") // Yogs -- fixes nightmares not having special spooky names. this proc takes the old name first, and *THEN* the new name! + +/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 BULLET_ACT_FORCE_PIERCE + return ..() + +/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) + ..() + 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) + ..() + if(special != HEART_SPECIAL_SHADOWIFY) + blade = new/obj/item/light_eater + M.put_in_hands(blade) + START_PROCESSING(SSobj, src) + +/obj/item/organ/heart/nightmare/Remove(mob/living/carbon/M, special = 0) + STOP_PROCESSING(SSobj, src) + 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/process() + if(QDELETED(owner) || owner.stat != DEAD) + respawn_progress = 0 + 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 + +/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)) //So you can actually melee with it + return + if(isliving(AM)) + var/mob/living/L = AM + if(isethereal(AM)) + AM.emp_act(EMP_LIGHT) + + if(iscyborg(AM)) + var/mob/living/silicon/robot/borg = AM + if(!borg.lamp_cooldown) + borg.update_headlamp(TRUE, INFINITY) + to_chat(borg, "Your headlamp is fried! You'll need a human to help replace it.") + 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 bbeaf077ad00..069ec1137cb4 100644 --- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm +++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm @@ -1,21 +1,21 @@ -/datum/species/skeleton - // 2spooky - name = "Spooky Scary Skeleton" - id = "skeleton" - say_mod = "rattles" - sexes = 0 - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton - species_traits = list(NOBLOOD) - 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) - 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 - //They can technically be in an ERT - changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN - -/datum/species/skeleton/check_roundstart_eligible() - if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) - return TRUE - return ..() +/datum/species/skeleton + // 2spooky + name = "Spooky Scary Skeleton" + id = "skeleton" + say_mod = "rattles" + sexes = 0 + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton + species_traits = list(NOBLOOD) + 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) + 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 + //They can technically be in an ERT + changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN + +/datum/species/skeleton/check_roundstart_eligible() + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + return TRUE + return ..() 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 71b6dfe5a7cd..9521ca1f9226 100644 --- a/code/modules/mob/living/carbon/human/species_types/synths.dm +++ b/code/modules/mob/living/carbon/human/species_types/synths.dm @@ -1,134 +1,134 @@ -/datum/species/synth - name = "Synth" //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) //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) - meat = null - damage_overlay_type = "synth" - limbs_id = "synth" - var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off - var/datum/species/fake_species //a species to do most of our work for us, unless we're damaged - var/list/initial_species_traits //for getting these values back for assume_disguise() - var/list/initial_inherent_traits - changesource_flags = MIRROR_BADMIN | WABBAJACK | SLIME_EXTRACT//Yogs -- Slime_extract - -/datum/species/synth/New() - initial_species_traits = species_traits.Copy() - initial_inherent_traits = inherent_traits.Copy() - ..() - -/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) - H.reagents.remove_reagent(chem.type, chem.volume) //yogs start - synths don't process chems - return TRUE - /* - 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 ..() - */ //yogs end - -/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 +/datum/species/synth + name = "Synth" //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) //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) + meat = null + damage_overlay_type = "synth" + limbs_id = "synth" + var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off + var/datum/species/fake_species //a species to do most of our work for us, unless we're damaged + var/list/initial_species_traits //for getting these values back for assume_disguise() + var/list/initial_inherent_traits + changesource_flags = MIRROR_BADMIN | WABBAJACK | SLIME_EXTRACT//Yogs -- Slime_extract + +/datum/species/synth/New() + initial_species_traits = species_traits.Copy() + initial_inherent_traits = inherent_traits.Copy() + ..() + +/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) + H.reagents.remove_reagent(chem.type, chem.volume) //yogs start - synths don't process chems + return TRUE + /* + 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 ..() + */ //yogs end + +/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 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 835cc1973ab4..b6559544c973 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -1,94 +1,94 @@ -#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 - 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 - changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | ERT_SPAWN - -/datum/species/zombie/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 - changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN - -/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) - . = ..() - 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(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 - changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN - -#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 + 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 + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | ERT_SPAWN + +/datum/species/zombie/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 + changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN + +/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) + . = ..() + 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(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 + changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN + +#undef REGENERATION_DELAY diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index a8429951a765..054b965d2846 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -1,676 +1,676 @@ - /////////////////////// - //UPDATE_ICONS SYSTEM// - /////////////////////// -/* Keep these comments up-to-date if you -insist- on hurting my code-baby ;_; -This system allows you to update individual mob-overlays, without regenerating them all each time. -When we generate overlays we generate the standing version and then rotate the mob as necessary.. - -As of the time of writing there are 20 layers within this list. Please try to keep this from increasing. //22 and counting, good job guys - var/overlays_standing[20] //For the standing stance - -Most of the time we only wish to update one overlay: - e.g. - we dropped the fireaxe out of our left hand and need to remove its icon from our mob - e.g.2 - our hair colour has changed, so we need to update our hair icons on our mob -In these cases, instead of updating every overlay using the old behaviour (regenerate_icons), we instead call -the appropriate update_X proc. - e.g. - update_l_hand() - e.g.2 - update_hair() - -Note: Recent changes by aranclanos+carn: - update_icons() no longer needs to be called. - the system is easier to use. update_icons() should not be called unless you absolutely -know- you need it. - IN ALL OTHER CASES it's better to just call the specific update_X procs. - -Note: The defines for layer numbers is now kept exclusvely in __DEFINES/misc.dm instead of being defined there, - then redefined and undefiend everywhere else. If you need to change the layering of sprites (or add a new layer) - that's where you should start. - -All of this means that this code is more maintainable, faster and still fairly easy to use. - -There are several things that need to be remembered: -> Whenever we do something that should cause an overlay to update (which doesn't use standard procs - ( i.e. you do something like l_hand = /obj/item/something new(src), rather than using the helper procs) - You will need to call the relevant update_inv_* proc - - All of these are named after the variable they update from. They are defined at the mob/ level like - update_clothing was, so you won't cause undefined proc runtimes with usr.update_inv_wear_id() if the usr is a - slime etc. Instead, it'll just return without doing any work. So no harm in calling it for slimes and such. - - -> There are also these special cases: - update_damage_overlays() //handles damage overlays for brute/burn damage - update_body() //Handles updating your mob's body layer and mutant bodyparts - as well as sprite-accessories that didn't really fit elsewhere (underwear, undershirts, socks, lips, eyes) - //NOTE: update_mutantrace() is now merged into this! - update_hair() //Handles updating your hair overlay (used to be update_face, but mouth and - eyes were merged into update_body()) - - -*/ - -//HAIR OVERLAY -/mob/living/carbon/human/update_hair() - dna.species.handle_hair(src) - -//used when putting/removing clothes that hide certain mutant body parts to just update those and not update the whole body. -/mob/living/carbon/human/proc/update_mutant_bodyparts() - dna.species.handle_mutant_bodyparts(src) - - -/mob/living/carbon/human/update_body() - remove_overlay(BODY_LAYER) - dna.species.handle_body(src) - ..() - -/mob/living/carbon/human/update_fire() - ..((fire_stacks > HUMAN_FIRE_STACK_ICON_NUM) ? "Standing" : "Generic_mob_burning") - - -/* --------------------------------------- */ -//For legacy support. -/mob/living/carbon/human/regenerate_icons() - - if(!..()) - icon_render_key = null //invalidate bodyparts cache - update_body() - update_hair() - update_inv_w_uniform() - update_inv_wear_id() - update_inv_gloves() - update_inv_glasses() - update_inv_ears() - update_inv_shoes() - update_inv_s_store() - update_inv_wear_mask() - update_inv_head() - update_inv_belt() - update_inv_back() - update_inv_wear_suit() - update_inv_pockets() - update_inv_neck() - update_transform() - //mutations - update_mutations_overlay() - //damage overlays - update_damage_overlays() - -/* --------------------------------------- */ -//vvvvvv UPDATE_INV PROCS vvvvvv - -/mob/living/carbon/human/update_inv_w_uniform() - remove_overlay(UNIFORM_LAYER) - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_W_UNIFORM] - inv.update_icon() - - if(istype(w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = w_uniform - U.screen_loc = ui_iclothing - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) - client.screen += w_uniform - update_observer_view(w_uniform,1) - - if(wear_suit && (wear_suit.flags_inv & HIDEJUMPSUIT)) - return - - - var/t_color = U.item_color - if(!t_color) - t_color = U.icon_state - if(U.adjusted == ALT_STYLE) - t_color = "[t_color]_d" - else if(U.adjusted == DIGITIGRADE_STYLE) - t_color = "[t_color]_l" - - var/mutable_appearance/uniform_overlay - - if(dna && dna.species.sexes) - var/G = (gender == FEMALE) ? "f" : "m" - if(G == "f" && U.fitted != NO_FEMALE_UNIFORM) - uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = 'icons/mob/uniform.dmi', isinhands = FALSE, femaleuniform = U.fitted) - - if(!uniform_overlay) - uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = 'icons/mob/uniform.dmi', isinhands = FALSE) - - - if(OFFSET_UNIFORM in dna.species.offset_features) - uniform_overlay.pixel_x += dna.species.offset_features[OFFSET_UNIFORM][1] - uniform_overlay.pixel_y += dna.species.offset_features[OFFSET_UNIFORM][2] - overlays_standing[UNIFORM_LAYER] = uniform_overlay - - apply_overlay(UNIFORM_LAYER) - update_mutant_bodyparts() - - -/mob/living/carbon/human/update_inv_wear_id() - remove_overlay(ID_LAYER) - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_ID] - inv.update_icon() - - var/mutable_appearance/id_overlay = overlays_standing[ID_LAYER] - - if(wear_id) - wear_id.screen_loc = ui_id - if(client && hud_used && hud_used.hud_shown) - client.screen += wear_id - update_observer_view(wear_id) - - //TODO: add an icon file for ID slot stuff, so it's less snowflakey - id_overlay = wear_id.build_worn_icon(state = wear_id.item_state, default_layer = ID_LAYER, default_icon_file = 'icons/mob/mob.dmi') - if(OFFSET_ID in dna.species.offset_features) - id_overlay.pixel_x += dna.species.offset_features[OFFSET_ID][1] - id_overlay.pixel_y += dna.species.offset_features[OFFSET_ID][2] - overlays_standing[ID_LAYER] = id_overlay - - apply_overlay(ID_LAYER) - - -/mob/living/carbon/human/update_inv_gloves() - remove_overlay(GLOVES_LAYER) - - if(client && hud_used && hud_used.inv_slots[SLOT_GLOVES]) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_GLOVES] - inv.update_icon() - - if(!gloves && bloody_hands) - var/mutable_appearance/bloody_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands", -GLOVES_LAYER) - if(get_num_arms(FALSE) < 2) - if(has_left_hand(FALSE)) - bloody_overlay.icon_state = "bloodyhands_left" - else if(has_right_hand(FALSE)) - bloody_overlay.icon_state = "bloodyhands_right" - - overlays_standing[GLOVES_LAYER] = bloody_overlay - - var/mutable_appearance/gloves_overlay = overlays_standing[GLOVES_LAYER] - if(gloves) - gloves.screen_loc = ui_gloves - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) - client.screen += gloves - update_observer_view(gloves,1) - var/t_state = gloves.item_state - if(!t_state) - t_state = gloves.icon_state - overlays_standing[GLOVES_LAYER] = gloves.build_worn_icon(state = t_state, default_layer = GLOVES_LAYER, default_icon_file = 'icons/mob/hands.dmi') - gloves_overlay = overlays_standing[GLOVES_LAYER] - if(OFFSET_GLOVES in dna.species.offset_features) - gloves_overlay.pixel_x += dna.species.offset_features[OFFSET_GLOVES][1] - gloves_overlay.pixel_y += dna.species.offset_features[OFFSET_GLOVES][2] - overlays_standing[GLOVES_LAYER] = gloves_overlay - apply_overlay(GLOVES_LAYER) - - -/mob/living/carbon/human/update_inv_glasses() - remove_overlay(GLASSES_LAYER) - - if(!get_bodypart(BODY_ZONE_HEAD)) //decapitated - return - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_GLASSES] - inv.update_icon() - - if(glasses) - glasses.screen_loc = ui_glasses //...draw the item in the inventory screen - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - client.screen += glasses //Either way, add the item to the HUD - update_observer_view(glasses,1) - if(!(head && (head.flags_inv & HIDEEYES)) && !(wear_mask && (wear_mask.flags_inv & HIDEEYES))) - overlays_standing[GLASSES_LAYER] = glasses.build_worn_icon(state = glasses.icon_state, default_layer = GLASSES_LAYER, default_icon_file = 'icons/mob/eyes.dmi') - - var/mutable_appearance/glasses_overlay = overlays_standing[GLASSES_LAYER] - if(glasses_overlay) - if(OFFSET_GLASSES in dna.species.offset_features) - glasses_overlay.pixel_x += dna.species.offset_features[OFFSET_GLASSES][1] - glasses_overlay.pixel_y += dna.species.offset_features[OFFSET_GLASSES][2] - overlays_standing[GLASSES_LAYER] = glasses_overlay - apply_overlay(GLASSES_LAYER) - - -/mob/living/carbon/human/update_inv_ears() - remove_overlay(EARS_LAYER) - - if(!get_bodypart(BODY_ZONE_HEAD)) //decapitated - return - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_EARS] - inv.update_icon() - - if(ears) - ears.screen_loc = ui_ears //move the item to the appropriate screen loc - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open - client.screen += ears //add it to the client's screen - update_observer_view(ears,1) - - overlays_standing[EARS_LAYER] = ears.build_worn_icon(state = ears.icon_state, default_layer = EARS_LAYER, default_icon_file = 'icons/mob/ears.dmi') - var/mutable_appearance/ears_overlay = overlays_standing[EARS_LAYER] - if(OFFSET_EARS in dna.species.offset_features) - ears_overlay.pixel_x += dna.species.offset_features[OFFSET_EARS][1] - ears_overlay.pixel_y += dna.species.offset_features[OFFSET_EARS][2] - overlays_standing[EARS_LAYER] = ears_overlay - apply_overlay(EARS_LAYER) - - -/mob/living/carbon/human/update_inv_shoes() - remove_overlay(SHOES_LAYER) - - if(get_num_legs(FALSE) <2) - return - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_SHOES] - inv.update_icon() - - if(shoes) - shoes.screen_loc = ui_shoes //move the item to the appropriate screen loc - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open - client.screen += shoes //add it to client's screen - update_observer_view(shoes,1) - overlays_standing[SHOES_LAYER] = shoes.build_worn_icon(state = shoes.icon_state, default_layer = SHOES_LAYER, default_icon_file = 'icons/mob/feet.dmi') - var/mutable_appearance/shoes_overlay = overlays_standing[SHOES_LAYER] - if(OFFSET_SHOES in dna.species.offset_features) - shoes_overlay.pixel_x += dna.species.offset_features[OFFSET_SHOES][1] - shoes_overlay.pixel_y += dna.species.offset_features[OFFSET_SHOES][2] - overlays_standing[SHOES_LAYER] = shoes_overlay - - apply_overlay(SHOES_LAYER) - - -/mob/living/carbon/human/update_inv_s_store() - remove_overlay(SUIT_STORE_LAYER) - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_S_STORE] - inv.update_icon() - - if(s_store) - s_store.screen_loc = ui_sstore1 - if(client && hud_used && hud_used.hud_shown) - client.screen += s_store - update_observer_view(s_store) - var/t_state = s_store.item_state - if(!t_state) - t_state = s_store.icon_state - overlays_standing[SUIT_STORE_LAYER] = mutable_appearance('icons/mob/belt_mirror.dmi', t_state, -SUIT_STORE_LAYER) - var/mutable_appearance/s_store_overlay = overlays_standing[SUIT_STORE_LAYER] - if(OFFSET_S_STORE in dna.species.offset_features) - s_store_overlay.pixel_x += dna.species.offset_features[OFFSET_S_STORE][1] - s_store_overlay.pixel_y += dna.species.offset_features[OFFSET_S_STORE][2] - overlays_standing[SUIT_STORE_LAYER] = s_store_overlay - apply_overlay(SUIT_STORE_LAYER) - - -/mob/living/carbon/human/update_inv_head() - ..() - update_mutant_bodyparts() - var/mutable_appearance/head_overlay = overlays_standing[HEAD_LAYER] - if(head_overlay) - remove_overlay(HEAD_LAYER) - if(OFFSET_HEAD in dna.species.offset_features) - head_overlay.pixel_x += dna.species.offset_features[OFFSET_HEAD][1] - head_overlay.pixel_y += dna.species.offset_features[OFFSET_HEAD][2] - overlays_standing[HEAD_LAYER] = head_overlay - apply_overlay(HEAD_LAYER) - -/mob/living/carbon/human/update_inv_belt() - remove_overlay(BELT_LAYER) - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_BELT] - inv.update_icon() - - if(belt) - belt.screen_loc = ui_belt - if(client && hud_used && hud_used.hud_shown) - client.screen += belt - update_observer_view(belt) - - var/t_state = belt.item_state - if(!t_state) - t_state = belt.icon_state - - overlays_standing[BELT_LAYER] = belt.build_worn_icon(state = t_state, default_layer = BELT_LAYER, default_icon_file = 'icons/mob/belt.dmi') - var/mutable_appearance/belt_overlay = overlays_standing[BELT_LAYER] - if(OFFSET_BELT in dna.species.offset_features) - belt_overlay.pixel_x += dna.species.offset_features[OFFSET_BELT][1] - belt_overlay.pixel_y += dna.species.offset_features[OFFSET_BELT][2] - overlays_standing[BELT_LAYER] = belt_overlay - - apply_overlay(BELT_LAYER) - - - -/mob/living/carbon/human/update_inv_wear_suit() - remove_overlay(SUIT_LAYER) - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_SUIT] - inv.update_icon() - - if(istype(wear_suit, /obj/item/clothing/suit)) - wear_suit.screen_loc = ui_oclothing - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) - client.screen += wear_suit - update_observer_view(wear_suit,1) - - overlays_standing[SUIT_LAYER] = wear_suit.build_worn_icon(state = wear_suit.icon_state, default_layer = SUIT_LAYER, default_icon_file = 'icons/mob/suit.dmi') - var/mutable_appearance/suit_overlay = overlays_standing[SUIT_LAYER] - if(OFFSET_SUIT in dna.species.offset_features) - suit_overlay.pixel_x += dna.species.offset_features[OFFSET_SUIT][1] - suit_overlay.pixel_y += dna.species.offset_features[OFFSET_SUIT][2] - overlays_standing[SUIT_LAYER] = suit_overlay - update_hair() - update_mutant_bodyparts() - - apply_overlay(SUIT_LAYER) - - -/mob/living/carbon/human/update_inv_pockets() - if(client && hud_used) - var/obj/screen/inventory/inv - - inv = hud_used.inv_slots[SLOT_L_STORE] - inv.update_icon() - - inv = hud_used.inv_slots[SLOT_R_STORE] - inv.update_icon() - - if(l_store) - l_store.screen_loc = ui_storage1 - if(hud_used.hud_shown) - client.screen += l_store - update_observer_view(l_store) - - if(r_store) - r_store.screen_loc = ui_storage2 - if(hud_used.hud_shown) - client.screen += r_store - update_observer_view(r_store) - - -/mob/living/carbon/human/update_inv_wear_mask() - ..() - var/mutable_appearance/mask_overlay = overlays_standing[FACEMASK_LAYER] - if(mask_overlay) - remove_overlay(FACEMASK_LAYER) - if(OFFSET_FACEMASK in dna.species.offset_features) - mask_overlay.pixel_x += dna.species.offset_features[OFFSET_FACEMASK][1] - mask_overlay.pixel_y += dna.species.offset_features[OFFSET_FACEMASK][2] - overlays_standing[FACEMASK_LAYER] = mask_overlay - apply_overlay(FACEMASK_LAYER) - update_mutant_bodyparts() //e.g. upgate needed because mask now hides lizard snout - -/mob/living/carbon/human/update_inv_back() - ..() - var/mutable_appearance/back_overlay = overlays_standing[BACK_LAYER] - if(back_overlay) - remove_overlay(BACK_LAYER) - if(OFFSET_BACK in dna.species.offset_features) - back_overlay.pixel_x += dna.species.offset_features[OFFSET_BACK][1] - back_overlay.pixel_y += dna.species.offset_features[OFFSET_BACK][2] - overlays_standing[BACK_LAYER] = back_overlay - apply_overlay(BACK_LAYER) - -/mob/living/carbon/human/update_inv_legcuffed() - remove_overlay(LEGCUFF_LAYER) - clear_alert("legcuffed") - if(legcuffed) - overlays_standing[LEGCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "legcuff1", -LEGCUFF_LAYER) - apply_overlay(LEGCUFF_LAYER) - throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = src.legcuffed) - -/proc/wear_female_version(t_color, icon, layer, type) - var/index = t_color - var/icon/female_clothing_icon = GLOB.female_clothing_icons[index] - if(!female_clothing_icon) //Create standing/laying icons if they don't exist - generate_female_clothing(index,t_color,icon,type) - return mutable_appearance(GLOB.female_clothing_icons[t_color], layer = -layer) - -/mob/living/carbon/human/proc/get_overlays_copy(list/unwantedLayers) - var/list/out = new - for(var/i in 1 to TOTAL_LAYERS) - if(overlays_standing[i]) - if(i in unwantedLayers) - continue - out += overlays_standing[i] - return out - - -//human HUD updates for items in our inventory - -//update whether our head item appears on our hud. -/mob/living/carbon/human/update_hud_head(obj/item/I) - I.screen_loc = ui_head - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) - client.screen += I - update_observer_view(I,1) - -//update whether our mask item appears on our hud. -/mob/living/carbon/human/update_hud_wear_mask(obj/item/I) - I.screen_loc = ui_mask - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) - client.screen += I - update_observer_view(I,1) - -//update whether our neck item appears on our hud. -/mob/living/carbon/human/update_hud_neck(obj/item/I) - I.screen_loc = ui_neck - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) - client.screen += I - update_observer_view(I,1) - -//update whether our back item appears on our hud. -/mob/living/carbon/human/update_hud_back(obj/item/I) - I.screen_loc = ui_back - if(client && hud_used && hud_used.hud_shown) - client.screen += I - update_observer_view(I) - -//Update whether we smell -/mob/living/carbon/human/proc/update_smell(var/smelly_icon = "generic_mob_smell") - remove_overlay(SMELL_LAYER) - if(hygiene <= HYGIENE_LEVEL_DIRTY) - var/mutable_appearance/new_smell_overlay = mutable_appearance('icons/mob/smelly.dmi', smelly_icon, -SMELL_LAYER) - overlays_standing[SMELL_LAYER] = new_smell_overlay - apply_overlay(SMELL_LAYER) - -/* -Does everything in relation to building the /mutable_appearance used in the mob's overlays list -covers: - inhands and any other form of worn item - centering large appearances - layering appearances on custom layers - building appearances from custom icon files - -By Remie Richards (yes I'm taking credit because this just removed 90% of the copypaste in update_icons()) - -state: A string to use as the state, this is FAR too complex to solve in this proc thanks to shitty old code -so it's specified as an argument instead. - -default_layer: The layer to draw this on if no other layer is specified - -default_icon_file: The icon file to draw states from if no other icon file is specified - -isinhands: If true then alternate_worn_icon is skipped so that default_icon_file is used, -in this situation default_icon_file is expected to match either the lefthand_ or righthand_ file var - -femalueuniform: A value matching a uniform item's fitted var, if this is anything but NO_FEMALE_UNIFORM, we -generate/load female uniform sprites matching all previously decided variables - - -*/ -/obj/item/proc/build_worn_icon(var/state = "", var/default_layer = 0, var/default_icon_file = null, var/isinhands = FALSE, var/femaleuniform = NO_FEMALE_UNIFORM) - - //Find a valid icon file from variables+arguments - var/file2use - if(!isinhands && alternate_worn_icon) - file2use = alternate_worn_icon - if(!file2use) - file2use = default_icon_file - - //Find a valid layer from variables+arguments - var/layer2use - if(alternate_worn_layer) - layer2use = alternate_worn_layer - if(!layer2use) - layer2use = default_layer - - var/mutable_appearance/standing - if(femaleuniform) - standing = wear_female_version(state,file2use,layer2use,femaleuniform) - if(!standing) - standing = mutable_appearance(file2use, state, -layer2use) - - //Get the overlays for this item when it's being worn - //eg: ammo counters, primed grenade flashes, etc. - var/list/worn_overlays = worn_overlays(isinhands, file2use) - if(worn_overlays && worn_overlays.len) - standing.overlays.Add(worn_overlays) - - standing = center_image(standing, isinhands ? inhand_x_dimension : worn_x_dimension, isinhands ? inhand_y_dimension : worn_y_dimension) - - //Handle held offsets - var/mob/M = loc - if(istype(M)) - var/list/L = get_held_offsets() - if(L) - standing.pixel_x += L["x"] //+= because of center()ing - standing.pixel_y += L["y"] - - standing.alpha = alpha - standing.color = color - - return standing - - -/obj/item/proc/get_held_offsets() - var/list/L - if(ismob(loc)) - var/mob/M = loc - L = M.get_item_offsets_for_index(M.get_held_index_of_item(src)) - return L - - -//Can't think of a better way to do this, sadly -/mob/proc/get_item_offsets_for_index(i) - switch(i) - if(3) //odd = left hands - return list("x" = 0, "y" = 16) - if(4) //even = right hands - return list("x" = 0, "y" = 16) - else //No offsets or Unwritten number of hands - return list("x" = 0, "y" = 0)//Handle held offsets - -//produces a key based on the human's limbs -/mob/living/carbon/human/generate_icon_render_key() - . = "[dna.species.limbs_id]" - - if(dna.check_mutation(HULK)) - . += "-coloured-hulk" - else if(dna.species.use_skintones) - . += "-coloured-[skin_tone]" - else if(dna.species.fixed_mut_color) - . += "-coloured-[dna.species.fixed_mut_color]" - else if(dna.features["mcolor"]) - . += "-coloured-[dna.features["mcolor"]]" - else - . += "-not_coloured" - - . += "-[gender]" - - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - . += "-[BP.body_zone]" - if(BP.status == BODYPART_ORGANIC) - . += "-organic" - else - . += "-robotic" - if(BP.use_digitigrade) - . += "-digitigrade[BP.use_digitigrade]" - if(BP.dmg_overlay_type) - . += "-[BP.dmg_overlay_type]" - - if(HAS_TRAIT(src, TRAIT_HUSK)) - . += "-husk" - -/mob/living/carbon/human/load_limb_from_cache() - ..() - update_hair() - - - -/mob/living/carbon/human/proc/update_observer_view(obj/item/I, inventory) - if(observers && observers.len) - for(var/M in observers) - var/mob/dead/observe = M - if(observe.client && observe.client.eye == src) - if(observe.hud_used) - if(inventory && !observe.hud_used.inventory_shown) - continue - observe.client.screen += I - else - observers -= observe - if(!observers.len) - observers = null - break - -// Only renders the head of the human -/mob/living/carbon/human/proc/update_body_parts_head_only() - if (!dna) - return - - if (!dna.species) - return - - var/obj/item/bodypart/HD = get_bodypart("head") - - if (!istype(HD)) - return - - HD.update_limb() - - add_overlay(HD.get_limb_icon()) - update_damage_overlays() - - if(HD && !(HAS_TRAIT(src, TRAIT_HUSK))) - // lipstick - if(lip_style && (LIPS in dna.species.species_traits)) - var/mutable_appearance/lip_overlay = mutable_appearance('icons/mob/human_face.dmi', "lips_[lip_style]", -BODY_LAYER) - lip_overlay.color = lip_color - if(OFFSET_FACE in dna.species.offset_features) - lip_overlay.pixel_x += dna.species.offset_features[OFFSET_FACE][1] - lip_overlay.pixel_y += dna.species.offset_features[OFFSET_FACE][2] - add_overlay(lip_overlay) - - // eyes - if(!(NOEYESPRITES in dna.species.species_traits)) - var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) - var/mutable_appearance/eye_overlay - if(!E) - eye_overlay = mutable_appearance('icons/mob/human_face.dmi', "eyes_missing", -BODY_LAYER) - else - eye_overlay = mutable_appearance('icons/mob/human_face.dmi', E.eye_icon_state, -BODY_LAYER) - if((EYECOLOR in dna.species.species_traits) && E) - eye_overlay.color = "#" + eye_color - if(OFFSET_FACE in dna.species.offset_features) - eye_overlay.pixel_x += dna.species.offset_features[OFFSET_FACE][1] - eye_overlay.pixel_y += dna.species.offset_features[OFFSET_FACE][2] - add_overlay(eye_overlay) - - dna.species.handle_hair(src) - - update_inv_head() + /////////////////////// + //UPDATE_ICONS SYSTEM// + /////////////////////// +/* Keep these comments up-to-date if you -insist- on hurting my code-baby ;_; +This system allows you to update individual mob-overlays, without regenerating them all each time. +When we generate overlays we generate the standing version and then rotate the mob as necessary.. + +As of the time of writing there are 20 layers within this list. Please try to keep this from increasing. //22 and counting, good job guys + var/overlays_standing[20] //For the standing stance + +Most of the time we only wish to update one overlay: + e.g. - we dropped the fireaxe out of our left hand and need to remove its icon from our mob + e.g.2 - our hair colour has changed, so we need to update our hair icons on our mob +In these cases, instead of updating every overlay using the old behaviour (regenerate_icons), we instead call +the appropriate update_X proc. + e.g. - update_l_hand() + e.g.2 - update_hair() + +Note: Recent changes by aranclanos+carn: + update_icons() no longer needs to be called. + the system is easier to use. update_icons() should not be called unless you absolutely -know- you need it. + IN ALL OTHER CASES it's better to just call the specific update_X procs. + +Note: The defines for layer numbers is now kept exclusvely in __DEFINES/misc.dm instead of being defined there, + then redefined and undefiend everywhere else. If you need to change the layering of sprites (or add a new layer) + that's where you should start. + +All of this means that this code is more maintainable, faster and still fairly easy to use. + +There are several things that need to be remembered: +> Whenever we do something that should cause an overlay to update (which doesn't use standard procs + ( i.e. you do something like l_hand = /obj/item/something new(src), rather than using the helper procs) + You will need to call the relevant update_inv_* proc + + All of these are named after the variable they update from. They are defined at the mob/ level like + update_clothing was, so you won't cause undefined proc runtimes with usr.update_inv_wear_id() if the usr is a + slime etc. Instead, it'll just return without doing any work. So no harm in calling it for slimes and such. + + +> There are also these special cases: + update_damage_overlays() //handles damage overlays for brute/burn damage + update_body() //Handles updating your mob's body layer and mutant bodyparts + as well as sprite-accessories that didn't really fit elsewhere (underwear, undershirts, socks, lips, eyes) + //NOTE: update_mutantrace() is now merged into this! + update_hair() //Handles updating your hair overlay (used to be update_face, but mouth and + eyes were merged into update_body()) + + +*/ + +//HAIR OVERLAY +/mob/living/carbon/human/update_hair() + dna.species.handle_hair(src) + +//used when putting/removing clothes that hide certain mutant body parts to just update those and not update the whole body. +/mob/living/carbon/human/proc/update_mutant_bodyparts() + dna.species.handle_mutant_bodyparts(src) + + +/mob/living/carbon/human/update_body() + remove_overlay(BODY_LAYER) + dna.species.handle_body(src) + ..() + +/mob/living/carbon/human/update_fire() + ..((fire_stacks > HUMAN_FIRE_STACK_ICON_NUM) ? "Standing" : "Generic_mob_burning") + + +/* --------------------------------------- */ +//For legacy support. +/mob/living/carbon/human/regenerate_icons() + + if(!..()) + icon_render_key = null //invalidate bodyparts cache + update_body() + update_hair() + update_inv_w_uniform() + update_inv_wear_id() + update_inv_gloves() + update_inv_glasses() + update_inv_ears() + update_inv_shoes() + update_inv_s_store() + update_inv_wear_mask() + update_inv_head() + update_inv_belt() + update_inv_back() + update_inv_wear_suit() + update_inv_pockets() + update_inv_neck() + update_transform() + //mutations + update_mutations_overlay() + //damage overlays + update_damage_overlays() + +/* --------------------------------------- */ +//vvvvvv UPDATE_INV PROCS vvvvvv + +/mob/living/carbon/human/update_inv_w_uniform() + remove_overlay(UNIFORM_LAYER) + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_W_UNIFORM] + inv.update_icon() + + if(istype(w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = w_uniform + U.screen_loc = ui_iclothing + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) + client.screen += w_uniform + update_observer_view(w_uniform,1) + + if(wear_suit && (wear_suit.flags_inv & HIDEJUMPSUIT)) + return + + + var/t_color = U.item_color + if(!t_color) + t_color = U.icon_state + if(U.adjusted == ALT_STYLE) + t_color = "[t_color]_d" + else if(U.adjusted == DIGITIGRADE_STYLE) + t_color = "[t_color]_l" + + var/mutable_appearance/uniform_overlay + + if(dna && dna.species.sexes) + var/G = (gender == FEMALE) ? "f" : "m" + if(G == "f" && U.fitted != NO_FEMALE_UNIFORM) + uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = 'icons/mob/uniform.dmi', isinhands = FALSE, femaleuniform = U.fitted) + + if(!uniform_overlay) + uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = 'icons/mob/uniform.dmi', isinhands = FALSE) + + + if(OFFSET_UNIFORM in dna.species.offset_features) + uniform_overlay.pixel_x += dna.species.offset_features[OFFSET_UNIFORM][1] + uniform_overlay.pixel_y += dna.species.offset_features[OFFSET_UNIFORM][2] + overlays_standing[UNIFORM_LAYER] = uniform_overlay + + apply_overlay(UNIFORM_LAYER) + update_mutant_bodyparts() + + +/mob/living/carbon/human/update_inv_wear_id() + remove_overlay(ID_LAYER) + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_ID] + inv.update_icon() + + var/mutable_appearance/id_overlay = overlays_standing[ID_LAYER] + + if(wear_id) + wear_id.screen_loc = ui_id + if(client && hud_used && hud_used.hud_shown) + client.screen += wear_id + update_observer_view(wear_id) + + //TODO: add an icon file for ID slot stuff, so it's less snowflakey + id_overlay = wear_id.build_worn_icon(state = wear_id.item_state, default_layer = ID_LAYER, default_icon_file = 'icons/mob/mob.dmi') + if(OFFSET_ID in dna.species.offset_features) + id_overlay.pixel_x += dna.species.offset_features[OFFSET_ID][1] + id_overlay.pixel_y += dna.species.offset_features[OFFSET_ID][2] + overlays_standing[ID_LAYER] = id_overlay + + apply_overlay(ID_LAYER) + + +/mob/living/carbon/human/update_inv_gloves() + remove_overlay(GLOVES_LAYER) + + if(client && hud_used && hud_used.inv_slots[SLOT_GLOVES]) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_GLOVES] + inv.update_icon() + + if(!gloves && bloody_hands) + var/mutable_appearance/bloody_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands", -GLOVES_LAYER) + if(get_num_arms(FALSE) < 2) + if(has_left_hand(FALSE)) + bloody_overlay.icon_state = "bloodyhands_left" + else if(has_right_hand(FALSE)) + bloody_overlay.icon_state = "bloodyhands_right" + + overlays_standing[GLOVES_LAYER] = bloody_overlay + + var/mutable_appearance/gloves_overlay = overlays_standing[GLOVES_LAYER] + if(gloves) + gloves.screen_loc = ui_gloves + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) + client.screen += gloves + update_observer_view(gloves,1) + var/t_state = gloves.item_state + if(!t_state) + t_state = gloves.icon_state + overlays_standing[GLOVES_LAYER] = gloves.build_worn_icon(state = t_state, default_layer = GLOVES_LAYER, default_icon_file = 'icons/mob/hands.dmi') + gloves_overlay = overlays_standing[GLOVES_LAYER] + if(OFFSET_GLOVES in dna.species.offset_features) + gloves_overlay.pixel_x += dna.species.offset_features[OFFSET_GLOVES][1] + gloves_overlay.pixel_y += dna.species.offset_features[OFFSET_GLOVES][2] + overlays_standing[GLOVES_LAYER] = gloves_overlay + apply_overlay(GLOVES_LAYER) + + +/mob/living/carbon/human/update_inv_glasses() + remove_overlay(GLASSES_LAYER) + + if(!get_bodypart(BODY_ZONE_HEAD)) //decapitated + return + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_GLASSES] + inv.update_icon() + + if(glasses) + glasses.screen_loc = ui_glasses //...draw the item in the inventory screen + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + client.screen += glasses //Either way, add the item to the HUD + update_observer_view(glasses,1) + if(!(head && (head.flags_inv & HIDEEYES)) && !(wear_mask && (wear_mask.flags_inv & HIDEEYES))) + overlays_standing[GLASSES_LAYER] = glasses.build_worn_icon(state = glasses.icon_state, default_layer = GLASSES_LAYER, default_icon_file = 'icons/mob/eyes.dmi') + + var/mutable_appearance/glasses_overlay = overlays_standing[GLASSES_LAYER] + if(glasses_overlay) + if(OFFSET_GLASSES in dna.species.offset_features) + glasses_overlay.pixel_x += dna.species.offset_features[OFFSET_GLASSES][1] + glasses_overlay.pixel_y += dna.species.offset_features[OFFSET_GLASSES][2] + overlays_standing[GLASSES_LAYER] = glasses_overlay + apply_overlay(GLASSES_LAYER) + + +/mob/living/carbon/human/update_inv_ears() + remove_overlay(EARS_LAYER) + + if(!get_bodypart(BODY_ZONE_HEAD)) //decapitated + return + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_EARS] + inv.update_icon() + + if(ears) + ears.screen_loc = ui_ears //move the item to the appropriate screen loc + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open + client.screen += ears //add it to the client's screen + update_observer_view(ears,1) + + overlays_standing[EARS_LAYER] = ears.build_worn_icon(state = ears.icon_state, default_layer = EARS_LAYER, default_icon_file = 'icons/mob/ears.dmi') + var/mutable_appearance/ears_overlay = overlays_standing[EARS_LAYER] + if(OFFSET_EARS in dna.species.offset_features) + ears_overlay.pixel_x += dna.species.offset_features[OFFSET_EARS][1] + ears_overlay.pixel_y += dna.species.offset_features[OFFSET_EARS][2] + overlays_standing[EARS_LAYER] = ears_overlay + apply_overlay(EARS_LAYER) + + +/mob/living/carbon/human/update_inv_shoes() + remove_overlay(SHOES_LAYER) + + if(get_num_legs(FALSE) <2) + return + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_SHOES] + inv.update_icon() + + if(shoes) + shoes.screen_loc = ui_shoes //move the item to the appropriate screen loc + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open + client.screen += shoes //add it to client's screen + update_observer_view(shoes,1) + overlays_standing[SHOES_LAYER] = shoes.build_worn_icon(state = shoes.icon_state, default_layer = SHOES_LAYER, default_icon_file = 'icons/mob/feet.dmi') + var/mutable_appearance/shoes_overlay = overlays_standing[SHOES_LAYER] + if(OFFSET_SHOES in dna.species.offset_features) + shoes_overlay.pixel_x += dna.species.offset_features[OFFSET_SHOES][1] + shoes_overlay.pixel_y += dna.species.offset_features[OFFSET_SHOES][2] + overlays_standing[SHOES_LAYER] = shoes_overlay + + apply_overlay(SHOES_LAYER) + + +/mob/living/carbon/human/update_inv_s_store() + remove_overlay(SUIT_STORE_LAYER) + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_S_STORE] + inv.update_icon() + + if(s_store) + s_store.screen_loc = ui_sstore1 + if(client && hud_used && hud_used.hud_shown) + client.screen += s_store + update_observer_view(s_store) + var/t_state = s_store.item_state + if(!t_state) + t_state = s_store.icon_state + overlays_standing[SUIT_STORE_LAYER] = mutable_appearance('icons/mob/belt_mirror.dmi', t_state, -SUIT_STORE_LAYER) + var/mutable_appearance/s_store_overlay = overlays_standing[SUIT_STORE_LAYER] + if(OFFSET_S_STORE in dna.species.offset_features) + s_store_overlay.pixel_x += dna.species.offset_features[OFFSET_S_STORE][1] + s_store_overlay.pixel_y += dna.species.offset_features[OFFSET_S_STORE][2] + overlays_standing[SUIT_STORE_LAYER] = s_store_overlay + apply_overlay(SUIT_STORE_LAYER) + + +/mob/living/carbon/human/update_inv_head() + ..() + update_mutant_bodyparts() + var/mutable_appearance/head_overlay = overlays_standing[HEAD_LAYER] + if(head_overlay) + remove_overlay(HEAD_LAYER) + if(OFFSET_HEAD in dna.species.offset_features) + head_overlay.pixel_x += dna.species.offset_features[OFFSET_HEAD][1] + head_overlay.pixel_y += dna.species.offset_features[OFFSET_HEAD][2] + overlays_standing[HEAD_LAYER] = head_overlay + apply_overlay(HEAD_LAYER) + +/mob/living/carbon/human/update_inv_belt() + remove_overlay(BELT_LAYER) + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_BELT] + inv.update_icon() + + if(belt) + belt.screen_loc = ui_belt + if(client && hud_used && hud_used.hud_shown) + client.screen += belt + update_observer_view(belt) + + var/t_state = belt.item_state + if(!t_state) + t_state = belt.icon_state + + overlays_standing[BELT_LAYER] = belt.build_worn_icon(state = t_state, default_layer = BELT_LAYER, default_icon_file = 'icons/mob/belt.dmi') + var/mutable_appearance/belt_overlay = overlays_standing[BELT_LAYER] + if(OFFSET_BELT in dna.species.offset_features) + belt_overlay.pixel_x += dna.species.offset_features[OFFSET_BELT][1] + belt_overlay.pixel_y += dna.species.offset_features[OFFSET_BELT][2] + overlays_standing[BELT_LAYER] = belt_overlay + + apply_overlay(BELT_LAYER) + + + +/mob/living/carbon/human/update_inv_wear_suit() + remove_overlay(SUIT_LAYER) + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_SUIT] + inv.update_icon() + + if(istype(wear_suit, /obj/item/clothing/suit)) + wear_suit.screen_loc = ui_oclothing + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) + client.screen += wear_suit + update_observer_view(wear_suit,1) + + overlays_standing[SUIT_LAYER] = wear_suit.build_worn_icon(state = wear_suit.icon_state, default_layer = SUIT_LAYER, default_icon_file = 'icons/mob/suit.dmi') + var/mutable_appearance/suit_overlay = overlays_standing[SUIT_LAYER] + if(OFFSET_SUIT in dna.species.offset_features) + suit_overlay.pixel_x += dna.species.offset_features[OFFSET_SUIT][1] + suit_overlay.pixel_y += dna.species.offset_features[OFFSET_SUIT][2] + overlays_standing[SUIT_LAYER] = suit_overlay + update_hair() + update_mutant_bodyparts() + + apply_overlay(SUIT_LAYER) + + +/mob/living/carbon/human/update_inv_pockets() + if(client && hud_used) + var/obj/screen/inventory/inv + + inv = hud_used.inv_slots[SLOT_L_STORE] + inv.update_icon() + + inv = hud_used.inv_slots[SLOT_R_STORE] + inv.update_icon() + + if(l_store) + l_store.screen_loc = ui_storage1 + if(hud_used.hud_shown) + client.screen += l_store + update_observer_view(l_store) + + if(r_store) + r_store.screen_loc = ui_storage2 + if(hud_used.hud_shown) + client.screen += r_store + update_observer_view(r_store) + + +/mob/living/carbon/human/update_inv_wear_mask() + ..() + var/mutable_appearance/mask_overlay = overlays_standing[FACEMASK_LAYER] + if(mask_overlay) + remove_overlay(FACEMASK_LAYER) + if(OFFSET_FACEMASK in dna.species.offset_features) + mask_overlay.pixel_x += dna.species.offset_features[OFFSET_FACEMASK][1] + mask_overlay.pixel_y += dna.species.offset_features[OFFSET_FACEMASK][2] + overlays_standing[FACEMASK_LAYER] = mask_overlay + apply_overlay(FACEMASK_LAYER) + update_mutant_bodyparts() //e.g. upgate needed because mask now hides lizard snout + +/mob/living/carbon/human/update_inv_back() + ..() + var/mutable_appearance/back_overlay = overlays_standing[BACK_LAYER] + if(back_overlay) + remove_overlay(BACK_LAYER) + if(OFFSET_BACK in dna.species.offset_features) + back_overlay.pixel_x += dna.species.offset_features[OFFSET_BACK][1] + back_overlay.pixel_y += dna.species.offset_features[OFFSET_BACK][2] + overlays_standing[BACK_LAYER] = back_overlay + apply_overlay(BACK_LAYER) + +/mob/living/carbon/human/update_inv_legcuffed() + remove_overlay(LEGCUFF_LAYER) + clear_alert("legcuffed") + if(legcuffed) + overlays_standing[LEGCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "legcuff1", -LEGCUFF_LAYER) + apply_overlay(LEGCUFF_LAYER) + throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = src.legcuffed) + +/proc/wear_female_version(t_color, icon, layer, type) + var/index = t_color + var/icon/female_clothing_icon = GLOB.female_clothing_icons[index] + if(!female_clothing_icon) //Create standing/laying icons if they don't exist + generate_female_clothing(index,t_color,icon,type) + return mutable_appearance(GLOB.female_clothing_icons[t_color], layer = -layer) + +/mob/living/carbon/human/proc/get_overlays_copy(list/unwantedLayers) + var/list/out = new + for(var/i in 1 to TOTAL_LAYERS) + if(overlays_standing[i]) + if(i in unwantedLayers) + continue + out += overlays_standing[i] + return out + + +//human HUD updates for items in our inventory + +//update whether our head item appears on our hud. +/mob/living/carbon/human/update_hud_head(obj/item/I) + I.screen_loc = ui_head + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) + client.screen += I + update_observer_view(I,1) + +//update whether our mask item appears on our hud. +/mob/living/carbon/human/update_hud_wear_mask(obj/item/I) + I.screen_loc = ui_mask + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) + client.screen += I + update_observer_view(I,1) + +//update whether our neck item appears on our hud. +/mob/living/carbon/human/update_hud_neck(obj/item/I) + I.screen_loc = ui_neck + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) + client.screen += I + update_observer_view(I,1) + +//update whether our back item appears on our hud. +/mob/living/carbon/human/update_hud_back(obj/item/I) + I.screen_loc = ui_back + if(client && hud_used && hud_used.hud_shown) + client.screen += I + update_observer_view(I) + +//Update whether we smell +/mob/living/carbon/human/proc/update_smell(var/smelly_icon = "generic_mob_smell") + remove_overlay(SMELL_LAYER) + if(hygiene <= HYGIENE_LEVEL_DIRTY) + var/mutable_appearance/new_smell_overlay = mutable_appearance('icons/mob/smelly.dmi', smelly_icon, -SMELL_LAYER) + overlays_standing[SMELL_LAYER] = new_smell_overlay + apply_overlay(SMELL_LAYER) + +/* +Does everything in relation to building the /mutable_appearance used in the mob's overlays list +covers: + inhands and any other form of worn item + centering large appearances + layering appearances on custom layers + building appearances from custom icon files + +By Remie Richards (yes I'm taking credit because this just removed 90% of the copypaste in update_icons()) + +state: A string to use as the state, this is FAR too complex to solve in this proc thanks to shitty old code +so it's specified as an argument instead. + +default_layer: The layer to draw this on if no other layer is specified + +default_icon_file: The icon file to draw states from if no other icon file is specified + +isinhands: If true then alternate_worn_icon is skipped so that default_icon_file is used, +in this situation default_icon_file is expected to match either the lefthand_ or righthand_ file var + +femalueuniform: A value matching a uniform item's fitted var, if this is anything but NO_FEMALE_UNIFORM, we +generate/load female uniform sprites matching all previously decided variables + + +*/ +/obj/item/proc/build_worn_icon(var/state = "", var/default_layer = 0, var/default_icon_file = null, var/isinhands = FALSE, var/femaleuniform = NO_FEMALE_UNIFORM) + + //Find a valid icon file from variables+arguments + var/file2use + if(!isinhands && alternate_worn_icon) + file2use = alternate_worn_icon + if(!file2use) + file2use = default_icon_file + + //Find a valid layer from variables+arguments + var/layer2use + if(alternate_worn_layer) + layer2use = alternate_worn_layer + if(!layer2use) + layer2use = default_layer + + var/mutable_appearance/standing + if(femaleuniform) + standing = wear_female_version(state,file2use,layer2use,femaleuniform) + if(!standing) + standing = mutable_appearance(file2use, state, -layer2use) + + //Get the overlays for this item when it's being worn + //eg: ammo counters, primed grenade flashes, etc. + var/list/worn_overlays = worn_overlays(isinhands, file2use) + if(worn_overlays && worn_overlays.len) + standing.overlays.Add(worn_overlays) + + standing = center_image(standing, isinhands ? inhand_x_dimension : worn_x_dimension, isinhands ? inhand_y_dimension : worn_y_dimension) + + //Handle held offsets + var/mob/M = loc + if(istype(M)) + var/list/L = get_held_offsets() + if(L) + standing.pixel_x += L["x"] //+= because of center()ing + standing.pixel_y += L["y"] + + standing.alpha = alpha + standing.color = color + + return standing + + +/obj/item/proc/get_held_offsets() + var/list/L + if(ismob(loc)) + var/mob/M = loc + L = M.get_item_offsets_for_index(M.get_held_index_of_item(src)) + return L + + +//Can't think of a better way to do this, sadly +/mob/proc/get_item_offsets_for_index(i) + switch(i) + if(3) //odd = left hands + return list("x" = 0, "y" = 16) + if(4) //even = right hands + return list("x" = 0, "y" = 16) + else //No offsets or Unwritten number of hands + return list("x" = 0, "y" = 0)//Handle held offsets + +//produces a key based on the human's limbs +/mob/living/carbon/human/generate_icon_render_key() + . = "[dna.species.limbs_id]" + + if(dna.check_mutation(HULK)) + . += "-coloured-hulk" + else if(dna.species.use_skintones) + . += "-coloured-[skin_tone]" + else if(dna.species.fixed_mut_color) + . += "-coloured-[dna.species.fixed_mut_color]" + else if(dna.features["mcolor"]) + . += "-coloured-[dna.features["mcolor"]]" + else + . += "-not_coloured" + + . += "-[gender]" + + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + . += "-[BP.body_zone]" + if(BP.status == BODYPART_ORGANIC) + . += "-organic" + else + . += "-robotic" + if(BP.use_digitigrade) + . += "-digitigrade[BP.use_digitigrade]" + if(BP.dmg_overlay_type) + . += "-[BP.dmg_overlay_type]" + + if(HAS_TRAIT(src, TRAIT_HUSK)) + . += "-husk" + +/mob/living/carbon/human/load_limb_from_cache() + ..() + update_hair() + + + +/mob/living/carbon/human/proc/update_observer_view(obj/item/I, inventory) + if(observers && observers.len) + for(var/M in observers) + var/mob/dead/observe = M + if(observe.client && observe.client.eye == src) + if(observe.hud_used) + if(inventory && !observe.hud_used.inventory_shown) + continue + observe.client.screen += I + else + observers -= observe + if(!observers.len) + observers = null + break + +// Only renders the head of the human +/mob/living/carbon/human/proc/update_body_parts_head_only() + if (!dna) + return + + if (!dna.species) + return + + var/obj/item/bodypart/HD = get_bodypart("head") + + if (!istype(HD)) + return + + HD.update_limb() + + add_overlay(HD.get_limb_icon()) + update_damage_overlays() + + if(HD && !(HAS_TRAIT(src, TRAIT_HUSK))) + // lipstick + if(lip_style && (LIPS in dna.species.species_traits)) + var/mutable_appearance/lip_overlay = mutable_appearance('icons/mob/human_face.dmi', "lips_[lip_style]", -BODY_LAYER) + lip_overlay.color = lip_color + if(OFFSET_FACE in dna.species.offset_features) + lip_overlay.pixel_x += dna.species.offset_features[OFFSET_FACE][1] + lip_overlay.pixel_y += dna.species.offset_features[OFFSET_FACE][2] + add_overlay(lip_overlay) + + // eyes + if(!(NOEYESPRITES in dna.species.species_traits)) + var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) + var/mutable_appearance/eye_overlay + if(!E) + eye_overlay = mutable_appearance('icons/mob/human_face.dmi', "eyes_missing", -BODY_LAYER) + else + eye_overlay = mutable_appearance('icons/mob/human_face.dmi', E.eye_icon_state, -BODY_LAYER) + if((EYECOLOR in dna.species.species_traits) && E) + eye_overlay.color = "#" + eye_color + if(OFFSET_FACE in dna.species.offset_features) + eye_overlay.pixel_x += dna.species.offset_features[OFFSET_FACE][1] + eye_overlay.pixel_y += dna.species.offset_features[OFFSET_FACE][2] + add_overlay(eye_overlay) + + dna.species.handle_hair(src) + + update_inv_head() update_inv_wear_mask() \ No newline at end of file diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 105d039b17f8..b97d85e7b6d9 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -1,143 +1,143 @@ -/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(!back || !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/I, toggle_off = 1) - update_inv_wear_mask() - -/mob/living/carbon/wear_mask_update(obj/item/I, toggle_off = 1) - var/obj/item/clothing/C = I - if(istype(C) && (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(!back || !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/I, toggle_off = 1) + update_inv_wear_mask() + +/mob/living/carbon/wear_mask_update(obj/item/I, toggle_off = 1) + var/obj/item/clothing/C = I + if(istype(C) && (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 ef89d0fd6340..f5394adffdd0 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -1,678 +1,678 @@ -/mob/living/carbon/Life() - set invisibility = 0 - - if(notransform) - return - - if(damageoverlaytemp) - damageoverlaytemp = 0 - update_damage_hud() - - if(!IS_IN_STASIS(src)) - - if(stat != DEAD) //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) - update_stamina() //needs to go before updatehealth to remove stamcrit - updatehealth() - - if(stat != DEAD) - handle_brain_damage() - - if(stat != DEAD) - handle_liver() - - else - . = ..() - - if(stat == DEAD) - stop_sound_channel(CHANNEL_HEARTBEAT) - LoadComponent(/datum/component/rot/corpse) - - //Updates the number of stored chemicals for powers - handle_changeling() - - if(stat != DEAD) - return 1 - -/////////////// -// 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, needs_metabolizing = TRUE)) - return - if(istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) - 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.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 - if(HAS_TRAIT(src, TRAIT_NOBREATH)) - 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, needs_metabolizing = TRUE) && 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_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 - breath.assert_gases(/datum/gas/oxygen, /datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz) - var/O2_partialpressure = (breath_gases[/datum/gas/oxygen][MOLES]/breath.total_moles())*breath_pressure - var/Toxins_partialpressure = (breath_gases[/datum/gas/plasma][MOLES]/breath.total_moles())*breath_pressure - var/CO2_partialpressure = (breath_gases[/datum/gas/carbon_dioxide][MOLES]/breath.total_moles())*breath_pressure - - - //OXYGEN - 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][MOLES]*ratio - else - adjustOxyLoss(3) - failed_last_breath = 1 - throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) - - else //Enough oxygen - failed_last_breath = 0 - if(health >= crit_threshold) - adjustOxyLoss(-5) - oxygen_used = breath_gases[/datum/gas/oxygen][MOLES] - clear_alert("not_enough_oxy") - - breath_gases[/datum/gas/oxygen][MOLES] -= oxygen_used - breath_gases[/datum/gas/carbon_dioxide][MOLES] += 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][MOLES]/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][MOLES]/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") - - //yogs start -- Adds Nitrogen Narcosis https://en.wikipedia.org/wiki/Nitrogen_narcosis - //NITROGEN - if(breath_gases[/datum/gas/nitrogen]) - var/SA_partialpressure = (breath_gases[/datum/gas/nitrogen][MOLES]/breath.total_moles())*breath_pressure - if(SA_partialpressure > NITROGEN_NARCOSIS_PRESSURE_LOW) // Giggles - if(prob(20)) - emote(pick("giggle","laugh")) - if(SA_partialpressure > NITROGEN_NARCOSIS_PRESSURE_HIGH) // Hallucinations - if(prob(15)) - to_chat(src, "You can't think straight!") - confused = min(SA_partialpressure/10, confused + 12) - hallucination += 5 - //yogs end - //BZ (Facepunch port of their Agent B) - if(breath_gases[/datum/gas/bz]) - var/bz_partialpressure = (breath_gases[/datum/gas/bz][MOLES]/breath.total_moles())*breath_pressure - /* Yogs comment-out: Smoothed BZ hallucination levels - if(bz_partialpressure > 1) - hallucination += 10 - else if(bz_partialpressure > 0.01) - hallucination += 5 - */ - hallucination += round(BZ_MAX_HALLUCINATION * (1 - NUM_E ** (-BZ_LAMBDA * bz_partialpressure))) // Yogs -- Better BZ hallucination values. Keep in mind that hallucination has to be an integer value, due to how it's handled in handle_hallucination() - - //TRITIUM - if(breath_gases[/datum/gas/tritium]) - var/tritium_partialpressure = (breath_gases[/datum/gas/tritium][MOLES]/breath.total_moles())*breath_pressure - radiation += tritium_partialpressure/10 - - //NITRYL - if(breath_gases[/datum/gas/nitryl]) - var/nitryl_partialpressure = (breath_gases[/datum/gas/nitryl][MOLES]/breath.total_moles())*breath_pressure - adjustFireLoss(nitryl_partialpressure/4) - - //MIASMA - if(breath_gases[/datum/gas/miasma]) - var/miasma_partialpressure = (breath_gases[/datum/gas/miasma][MOLES]/breath.total_moles())*breath_pressure - - if(prob(1 * 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(0.25 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") - - - - - breath.garbage_collect() - - //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) - if(internal) - if(internal.loc != src) - internal = null - update_internals_hud_icon(0) - else if ((!wear_mask || !(wear_mask.clothing_flags & MASKINTERNALS)) && !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 - -/mob/living/carbon/proc/handle_blood() - return - -/mob/living/carbon/proc/handle_bodyparts() - var/stam_regen = FALSE - if(stam_regen_start_time <= world.time) - stam_regen = TRUE - if(stam_paralyzed) - . |= BODYPART_LIFE_UPDATE_HEALTH //make sure we remove the stamcrit - for(var/I in bodyparts) - var/obj/item/bodypart/BP = I - if(BP.needs_processing) - . |= BP.on_life(stam_regen) - -/mob/living/carbon/proc/handle_organs() - for(var/V in internal_organs) - var/obj/item/organ/O = V - O.on_life() - -/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) - 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 - for(var/datum/mutation/human/HM in dna.mutations) - if(HM && HM.timed) - dna.remove_mutation(HM.type) - - radiation -= min(radiation, RAD_LOSS_PER_TICK) - if(radiation > RAD_MOB_SAFE) - adjustToxLoss(log(radiation-RAD_MOB_SAFE)*RAD_TOX_COEFFICIENT) - - -/* -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.", - "We should store bank records in a webscale datastore, like /dev/null.", - "You ever wonder if /dev/null supports sharding?", - "What if we use a language that was written on a napkin and created over 1 weekend for all of our servers?")) - -//this updates all special effects: stun, sleeping, knockdown, druggy, stuttering, etc.. -/mob/living/carbon/handle_status_effects() - ..() - - 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) - slurring = max(slurring-1,0) - - 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.01, 0) - if(drunkenness >= 6) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "drunk", /datum/mood_event/drunk) - if(prob(25)) - slurring += 2 - jitteriness = max(jitteriness - 3, 0) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) - adjustBruteLoss(-0.12, FALSE) - adjustFireLoss(-0.06, FALSE) - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk") - - if(drunkenness >= 11 && slurring < 5) - slurring += 1.2 - - 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(3)) - confused += 15 - vomit() // vomiting clears toxloss, consider this a blessing - 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(1) - if(prob(5) && !stat) - to_chat(src, "Maybe you should lie down for a bit...") - - if(drunkenness >= 91) - adjustToxLoss(1) - adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4) - 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(2) //Let's be honest you shouldn't be alive by now - -//used in human and monkey handle_environment() -/mob/living/carbon/proc/natural_bodytemperature_stabilization() - 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// -///////// - -///Decides if the liver is failing or not. -/mob/living/carbon/proc/handle_liver() - if(!dna) - return - var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) - if(liver) - if(liver.damage < liver.maxHealth) - return - liver.organ_flags |= ORGAN_FAILING - liver_failure() - -/mob/living/carbon/proc/undergoing_liver_failure() - var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) - if(liver && (liver.organ_flags & ORGAN_FAILING)) - return TRUE - -/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() + + if(!IS_IN_STASIS(src)) + + if(stat != DEAD) //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) + update_stamina() //needs to go before updatehealth to remove stamcrit + updatehealth() + + if(stat != DEAD) + handle_brain_damage() + + if(stat != DEAD) + handle_liver() + + else + . = ..() + + if(stat == DEAD) + stop_sound_channel(CHANNEL_HEARTBEAT) + LoadComponent(/datum/component/rot/corpse) + + //Updates the number of stored chemicals for powers + handle_changeling() + + if(stat != DEAD) + return 1 + +/////////////// +// 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, needs_metabolizing = TRUE)) + return + if(istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) + 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.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 + if(HAS_TRAIT(src, TRAIT_NOBREATH)) + 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, needs_metabolizing = TRUE) && 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_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 + breath.assert_gases(/datum/gas/oxygen, /datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz) + var/O2_partialpressure = (breath_gases[/datum/gas/oxygen][MOLES]/breath.total_moles())*breath_pressure + var/Toxins_partialpressure = (breath_gases[/datum/gas/plasma][MOLES]/breath.total_moles())*breath_pressure + var/CO2_partialpressure = (breath_gases[/datum/gas/carbon_dioxide][MOLES]/breath.total_moles())*breath_pressure + + + //OXYGEN + 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][MOLES]*ratio + else + adjustOxyLoss(3) + failed_last_breath = 1 + throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) + + else //Enough oxygen + failed_last_breath = 0 + if(health >= crit_threshold) + adjustOxyLoss(-5) + oxygen_used = breath_gases[/datum/gas/oxygen][MOLES] + clear_alert("not_enough_oxy") + + breath_gases[/datum/gas/oxygen][MOLES] -= oxygen_used + breath_gases[/datum/gas/carbon_dioxide][MOLES] += 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][MOLES]/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][MOLES]/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") + + //yogs start -- Adds Nitrogen Narcosis https://en.wikipedia.org/wiki/Nitrogen_narcosis + //NITROGEN + if(breath_gases[/datum/gas/nitrogen]) + var/SA_partialpressure = (breath_gases[/datum/gas/nitrogen][MOLES]/breath.total_moles())*breath_pressure + if(SA_partialpressure > NITROGEN_NARCOSIS_PRESSURE_LOW) // Giggles + if(prob(20)) + emote(pick("giggle","laugh")) + if(SA_partialpressure > NITROGEN_NARCOSIS_PRESSURE_HIGH) // Hallucinations + if(prob(15)) + to_chat(src, "You can't think straight!") + confused = min(SA_partialpressure/10, confused + 12) + hallucination += 5 + //yogs end + //BZ (Facepunch port of their Agent B) + if(breath_gases[/datum/gas/bz]) + var/bz_partialpressure = (breath_gases[/datum/gas/bz][MOLES]/breath.total_moles())*breath_pressure + /* Yogs comment-out: Smoothed BZ hallucination levels + if(bz_partialpressure > 1) + hallucination += 10 + else if(bz_partialpressure > 0.01) + hallucination += 5 + */ + hallucination += round(BZ_MAX_HALLUCINATION * (1 - NUM_E ** (-BZ_LAMBDA * bz_partialpressure))) // Yogs -- Better BZ hallucination values. Keep in mind that hallucination has to be an integer value, due to how it's handled in handle_hallucination() + + //TRITIUM + if(breath_gases[/datum/gas/tritium]) + var/tritium_partialpressure = (breath_gases[/datum/gas/tritium][MOLES]/breath.total_moles())*breath_pressure + radiation += tritium_partialpressure/10 + + //NITRYL + if(breath_gases[/datum/gas/nitryl]) + var/nitryl_partialpressure = (breath_gases[/datum/gas/nitryl][MOLES]/breath.total_moles())*breath_pressure + adjustFireLoss(nitryl_partialpressure/4) + + //MIASMA + if(breath_gases[/datum/gas/miasma]) + var/miasma_partialpressure = (breath_gases[/datum/gas/miasma][MOLES]/breath.total_moles())*breath_pressure + + if(prob(1 * 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(0.25 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") + + + + + breath.garbage_collect() + + //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) + if(internal) + if(internal.loc != src) + internal = null + update_internals_hud_icon(0) + else if ((!wear_mask || !(wear_mask.clothing_flags & MASKINTERNALS)) && !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 + +/mob/living/carbon/proc/handle_blood() + return + +/mob/living/carbon/proc/handle_bodyparts() + var/stam_regen = FALSE + if(stam_regen_start_time <= world.time) + stam_regen = TRUE + if(stam_paralyzed) + . |= BODYPART_LIFE_UPDATE_HEALTH //make sure we remove the stamcrit + for(var/I in bodyparts) + var/obj/item/bodypart/BP = I + if(BP.needs_processing) + . |= BP.on_life(stam_regen) + +/mob/living/carbon/proc/handle_organs() + for(var/V in internal_organs) + var/obj/item/organ/O = V + O.on_life() + +/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) + 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 + for(var/datum/mutation/human/HM in dna.mutations) + if(HM && HM.timed) + dna.remove_mutation(HM.type) + + radiation -= min(radiation, RAD_LOSS_PER_TICK) + if(radiation > RAD_MOB_SAFE) + adjustToxLoss(log(radiation-RAD_MOB_SAFE)*RAD_TOX_COEFFICIENT) + + +/* +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.", + "We should store bank records in a webscale datastore, like /dev/null.", + "You ever wonder if /dev/null supports sharding?", + "What if we use a language that was written on a napkin and created over 1 weekend for all of our servers?")) + +//this updates all special effects: stun, sleeping, knockdown, druggy, stuttering, etc.. +/mob/living/carbon/handle_status_effects() + ..() + + 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) + slurring = max(slurring-1,0) + + 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.01, 0) + if(drunkenness >= 6) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "drunk", /datum/mood_event/drunk) + if(prob(25)) + slurring += 2 + jitteriness = max(jitteriness - 3, 0) + if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) + adjustBruteLoss(-0.12, FALSE) + adjustFireLoss(-0.06, FALSE) + else + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk") + + if(drunkenness >= 11 && slurring < 5) + slurring += 1.2 + + 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(3)) + confused += 15 + vomit() // vomiting clears toxloss, consider this a blessing + 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(1) + if(prob(5) && !stat) + to_chat(src, "Maybe you should lie down for a bit...") + + if(drunkenness >= 91) + adjustToxLoss(1) + adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4) + 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(2) //Let's be honest you shouldn't be alive by now + +//used in human and monkey handle_environment() +/mob/living/carbon/proc/natural_bodytemperature_stabilization() + 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// +///////// + +///Decides if the liver is failing or not. +/mob/living/carbon/proc/handle_liver() + if(!dna) + return + var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) + if(liver) + if(liver.damage < liver.maxHealth) + return + liver.organ_flags |= ORGAN_FAILING + liver_failure() + +/mob/living/carbon/proc/undergoing_liver_failure() + var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) + if(liver && (liver.organ_flags & ORGAN_FAILING)) + return TRUE + +/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 5b9125a5d106..22621a3750e4 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/inventory.dm b/code/modules/mob/living/carbon/monkey/inventory.dm index 12fc442086f9..d5fffc70a2ac 100644 --- a/code/modules/mob/living/carbon/monkey/inventory.dm +++ b/code/modules/mob/living/carbon/monkey/inventory.dm @@ -1,34 +1,34 @@ -/mob/living/carbon/monkey/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) - switch(slot) - if(SLOT_HANDS) - if(get_empty_held_indexes()) - return TRUE - return FALSE - if(SLOT_WEAR_MASK) - if(wear_mask) - return FALSE - if( !(I.slot_flags & ITEM_SLOT_MASK) ) - return FALSE - return TRUE - if(SLOT_NECK) - if(wear_neck) - return FALSE - if( !(I.slot_flags & ITEM_SLOT_NECK) ) - return FALSE - return TRUE - if(SLOT_HEAD) - if(head) - return FALSE - if( !(I.slot_flags & ITEM_SLOT_HEAD) ) - return FALSE - return TRUE - if(SLOT_BACK) - if(back) - return FALSE - if( !(I.slot_flags & ITEM_SLOT_BACK) ) - return FALSE - return TRUE - return FALSE //Unsupported slot - - - +/mob/living/carbon/monkey/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) + switch(slot) + if(SLOT_HANDS) + if(get_empty_held_indexes()) + return TRUE + return FALSE + if(SLOT_WEAR_MASK) + if(wear_mask) + return FALSE + if( !(I.slot_flags & ITEM_SLOT_MASK) ) + return FALSE + return TRUE + if(SLOT_NECK) + if(wear_neck) + return FALSE + if( !(I.slot_flags & ITEM_SLOT_NECK) ) + return FALSE + return TRUE + if(SLOT_HEAD) + if(head) + return FALSE + if( !(I.slot_flags & ITEM_SLOT_HEAD) ) + return FALSE + return TRUE + if(SLOT_BACK) + if(back) + return FALSE + if( !(I.slot_flags & ITEM_SLOT_BACK) ) + return FALSE + return TRUE + return FALSE //Unsupported slot + + + diff --git a/code/modules/mob/living/carbon/monkey/life.dm b/code/modules/mob/living/carbon/monkey/life.dm index 9f7fdbe96e44..e5174fec45ca 100644 --- a/code/modules/mob/living/carbon/monkey/life.dm +++ b/code/modules/mob/living/carbon/monkey/life.dm @@ -1,170 +1,170 @@ - - -/mob/living/carbon/monkey - - -/mob/living/carbon/monkey/Life() - set invisibility = 0 - - if (notransform) - return - - if(..() && !IS_IN_STASIS(src)) - - if(!client) - if(stat == CONSCIOUS) - if(on_fire || buckled || restrained()) - 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) && (mobility_flags & MOBILITY_MOVE) && 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(!IsParalyzed()) - emote("collapse") - Paralyze(RAD_MOB_KNOCKDOWN_AMOUNT) - to_chat(src, "You feel weak.") - if(radiation > RAD_MOB_MUTATE) - if(prob(1)) - to_chat(src, "You mutate!") - easy_randmut(NEGATIVE+MINOR_NEGATIVE) - 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(.) //if the mob isn't on fire anymore - return - - //the fire tries to damage the exposed clothes and items - var/list/burning_items = list() - //HEAD// - var/list/obscured = check_obscured_slots(TRUE) - if(wear_mask && !(SLOT_WEAR_MASK in obscured)) - burning_items += wear_mask - if(wear_neck && !(SLOT_NECK in obscured)) - burning_items += wear_neck - if(head) - burning_items += head - - if(back) - burning_items += back - - for(var/X in burning_items) - var/obj/item/I = X - I.fire_act((fire_stacks * 50)) //damage taken is reduced to 2% of this value by fire_act() - - 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(..() && !IS_IN_STASIS(src)) + + if(!client) + if(stat == CONSCIOUS) + if(on_fire || buckled || restrained()) + 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) && (mobility_flags & MOBILITY_MOVE) && 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(!IsParalyzed()) + emote("collapse") + Paralyze(RAD_MOB_KNOCKDOWN_AMOUNT) + to_chat(src, "You feel weak.") + if(radiation > RAD_MOB_MUTATE) + if(prob(1)) + to_chat(src, "You mutate!") + easy_randmut(NEGATIVE+MINOR_NEGATIVE) + 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(.) //if the mob isn't on fire anymore + return + + //the fire tries to damage the exposed clothes and items + var/list/burning_items = list() + //HEAD// + var/list/obscured = check_obscured_slots(TRUE) + if(wear_mask && !(SLOT_WEAR_MASK in obscured)) + burning_items += wear_mask + if(wear_neck && !(SLOT_NECK in obscured)) + burning_items += wear_neck + if(head) + burning_items += head + + if(back) + burning_items += back + + for(var/X in burning_items) + var/obj/item/I = X + I.fire_act((fire_stacks * 50)) //damage taken is reduced to 2% of this value by fire_act() + + 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 c26591940e30..4356861297be 100644 --- a/code/modules/mob/living/carbon/monkey/monkey.dm +++ b/code/modules/mob/living/carbon/monkey/monkey.dm @@ -1,171 +1,171 @@ -/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 - blood_volume = BLOOD_VOLUME_MONKEY // Yogs -- Makes monkeys/xenos have different amounts of blood from normal carbonbois - -/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/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(reagents.has_reagent(/datum/reagent/consumable/nuka_cola)) - 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 - if(!HAS_TRAIT(src, TRAIT_IGNOREDAMAGESLOWDOWN)) - var/health_deficiency = (maxHealth - 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 - add_movespeed_modifier(MOVESPEED_ID_MONKEY_TEMPERATURE_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = slow) - -/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 + blood_volume = BLOOD_VOLUME_MONKEY // Yogs -- Makes monkeys/xenos have different amounts of blood from normal carbonbois + +/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/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(reagents.has_reagent(/datum/reagent/consumable/nuka_cola)) + 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 + if(!HAS_TRAIT(src, TRAIT_IGNOREDAMAGESLOWDOWN)) + var/health_deficiency = (maxHealth - 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 + add_movespeed_modifier(MOVESPEED_ID_MONKEY_TEMPERATURE_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = slow) + +/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 6311776596a3..41d37a50217b 100644 --- a/code/modules/mob/living/carbon/monkey/update_icons.dm +++ b/code/modules/mob/living/carbon/monkey/update_icons.dm @@ -1,77 +1,77 @@ - -/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) - if(legcuffed) - var/mutable_appearance/legcuff_overlay = mutable_appearance('icons/mob/mob.dmi', "legcuff1", -LEGCUFF_LAYER) - legcuff_overlay.pixel_y = 8 - overlays_standing[LEGCUFF_LAYER] = legcuff_overlay - apply_overlay(LEGCUFF_LAYER) - - -//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) + if(legcuffed) + var/mutable_appearance/legcuff_overlay = mutable_appearance('icons/mob/mob.dmi', "legcuff1", -LEGCUFF_LAYER) + legcuff_overlay.pixel_y = 8 + overlays_standing[LEGCUFF_LAYER] = legcuff_overlay + apply_overlay(LEGCUFF_LAYER) + + +//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 735f44010535..0027000d2528 100644 --- a/code/modules/mob/living/carbon/update_icons.dm +++ b/code/modules/mob/living/carbon/update_icons.dm @@ -1,289 +1,289 @@ -//IMPORTANT: Multiple animate() calls do not stack well, so try to do them all at once if you can. -/mob/living/carbon/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/final_pixel_y = pixel_y - var/final_dir = dir - var/changed = 0 - if(lying != lying_prev && rotate_on_lying) - changed++ - ntransform.TurnTo(lying_prev , lying) - if(!lying) //Lying to standing - final_pixel_y = get_standard_pixel_y_offset() - else //if(lying != 0) - if(lying_prev == 0) //Standing to lying - pixel_y = get_standard_pixel_y_offset() - final_pixel_y = get_standard_pixel_y_offset(lying) - if(dir & (EAST|WEST)) //Facing east or west - final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass - - if(resize != RESIZE_DEFAULT_SIZE) - changed++ - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, dir = final_dir, easing = EASE_IN|EASE_OUT) - setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life(). - -/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 || islava(loc)) - 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/mutable_appearance/damage_overlay = mutable_appearance('icons/mob/dam_mob.dmi', "blank", -DAMAGE_LAYER) - 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(!(SLOT_WEAR_MASK in check_obscured_slots())) - 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(!(SLOT_NECK in check_obscured_slots())) - 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) - overlays_standing[HANDCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "handcuff1", -HANDCUFF_LAYER) - apply_overlay(HANDCUFF_LAYER) - - -//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) - . = 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() +//IMPORTANT: Multiple animate() calls do not stack well, so try to do them all at once if you can. +/mob/living/carbon/update_transform() + var/matrix/ntransform = matrix(transform) //aka transform.Copy() + var/final_pixel_y = pixel_y + var/final_dir = dir + var/changed = 0 + if(lying != lying_prev && rotate_on_lying) + changed++ + ntransform.TurnTo(lying_prev , lying) + if(!lying) //Lying to standing + final_pixel_y = get_standard_pixel_y_offset() + else //if(lying != 0) + if(lying_prev == 0) //Standing to lying + pixel_y = get_standard_pixel_y_offset() + final_pixel_y = get_standard_pixel_y_offset(lying) + if(dir & (EAST|WEST)) //Facing east or west + final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass + + if(resize != RESIZE_DEFAULT_SIZE) + changed++ + ntransform.Scale(resize) + resize = RESIZE_DEFAULT_SIZE + + if(changed) + animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, dir = final_dir, easing = EASE_IN|EASE_OUT) + setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life(). + +/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 || islava(loc)) + 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/mutable_appearance/damage_overlay = mutable_appearance('icons/mob/dam_mob.dmi', "blank", -DAMAGE_LAYER) + 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(!(SLOT_WEAR_MASK in check_obscured_slots())) + 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(!(SLOT_NECK in check_obscured_slots())) + 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) + overlays_standing[HANDCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "handcuff1", -HANDCUFF_LAYER) + apply_overlay(HANDCUFF_LAYER) + + +//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) + . = 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 89745ed6374d..f6f3cce47e5c 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -1,291 +1,291 @@ - -/* - 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) - var/hit_percent = (100-blocked)/100 - if(!damage || (hit_percent <= 0)) - return 0 - switch(damagetype) - if(BRUTE) - adjustBruteLoss(damage * hit_percent) - if(BURN) - adjustFireLoss(damage * hit_percent) - if(TOX) - adjustToxLoss(damage * hit_percent) - if(OXY) - adjustOxyLoss(damage * hit_percent) - if(CLONE) - adjustCloneLoss(damage * hit_percent) - if(STAMINA) - adjustStaminaLoss(damage * hit_percent) - 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) - 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) - if(EFFECT_PARALYZE) - Paralyze(effect * hit_percent) - if(EFFECT_IMMOBILIZE) - Immobilize(effect * hit_percent) - 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)) - if(EFFECT_PARALYZE) - Paralyze(effect * hit_percent) - if(EFFECT_IMMOBILIZE) - Immobilize(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, paralyze = 0, immobilize = 0) - if(blocked >= 100) - return BULLET_ACT_BLOCK - if(stun) - apply_effect(stun, EFFECT_STUN, blocked) - if(knockdown) - apply_effect(knockdown, EFFECT_KNOCKDOWN, blocked) - if(unconscious) - apply_effect(unconscious, EFFECT_UNCONSCIOUS, blocked) - if(paralyze) - apply_effect(paralyze, EFFECT_PARALYZE, blocked) - if(immobilize) - apply_effect(immobilize, EFFECT_IMMOBILIZE, 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 BULLET_ACT_HIT - - -/mob/living/proc/getBruteLoss() - return bruteloss - -/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) - 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, required_status) - 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, required_status, check_armor = FALSE) - 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, required_status, 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, required_status = null) - 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) + var/hit_percent = (100-blocked)/100 + if(!damage || (hit_percent <= 0)) + return 0 + switch(damagetype) + if(BRUTE) + adjustBruteLoss(damage * hit_percent) + if(BURN) + adjustFireLoss(damage * hit_percent) + if(TOX) + adjustToxLoss(damage * hit_percent) + if(OXY) + adjustOxyLoss(damage * hit_percent) + if(CLONE) + adjustCloneLoss(damage * hit_percent) + if(STAMINA) + adjustStaminaLoss(damage * hit_percent) + 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) + 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) + if(EFFECT_PARALYZE) + Paralyze(effect * hit_percent) + if(EFFECT_IMMOBILIZE) + Immobilize(effect * hit_percent) + 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)) + if(EFFECT_PARALYZE) + Paralyze(effect * hit_percent) + if(EFFECT_IMMOBILIZE) + Immobilize(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, paralyze = 0, immobilize = 0) + if(blocked >= 100) + return BULLET_ACT_BLOCK + if(stun) + apply_effect(stun, EFFECT_STUN, blocked) + if(knockdown) + apply_effect(knockdown, EFFECT_KNOCKDOWN, blocked) + if(unconscious) + apply_effect(unconscious, EFFECT_UNCONSCIOUS, blocked) + if(paralyze) + apply_effect(paralyze, EFFECT_PARALYZE, blocked) + if(immobilize) + apply_effect(immobilize, EFFECT_IMMOBILIZE, 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 BULLET_ACT_HIT + + +/mob/living/proc/getBruteLoss() + return bruteloss + +/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) + 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, required_status) + 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, required_status, check_armor = FALSE) + 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, required_status, 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, required_status = null) + 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 39803110ae82..a7e474034c8f 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -1,92 +1,92 @@ -/mob/living/gib(no_brain, no_organs, no_bodyparts) - var/prev_lying = lying - if(stat != DEAD) - death(TRUE) - - if(!prev_lying) - gib_animation() - - spill_organs(no_brain, no_organs, no_bodyparts) - - if(!no_bodyparts) - spread_bodyparts(no_brain, no_organs) - - spawn_gibs(no_bodyparts) - qdel(src) - -/mob/living/proc/gib_animation() - return - -/mob/living/proc/spawn_gibs() - new /obj/effect/gibspawner/generic(drop_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() - 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() - var/turf/T = get_turf(src) - for(var/obj/item/I in contents) - I.on_mob_death(src, gibbed) - if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf)) - deadchat_broadcast(" has died at [get_area_name(T)].", "[mind.name]", follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE) - 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_mobility() - 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() - - . = ..() - - 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(TRUE) + + if(!prev_lying) + gib_animation() + + spill_organs(no_brain, no_organs, no_bodyparts) + + if(!no_bodyparts) + spread_bodyparts(no_brain, no_organs) + + spawn_gibs(no_bodyparts) + qdel(src) + +/mob/living/proc/gib_animation() + return + +/mob/living/proc/spawn_gibs() + new /obj/effect/gibspawner/generic(drop_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() + 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() + var/turf/T = get_turf(src) + for(var/obj/item/I in contents) + I.on_mob_death(src, gibbed) + if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf)) + deadchat_broadcast(" has died at [get_area_name(T)].", "[mind.name]", follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE) + 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_mobility() + 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() + + . = ..() + + 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 18beeeecf083..1fac149696af 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -1,522 +1,522 @@ - -/* 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, type_override, intentional) - . = ..() - 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, type_override, intentional) - 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) - -/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, type_override, intentional) - . = ..() - 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, type_override, intentional) - . = ..() - 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 - vary = TRUE - -/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE , intentional) - . = ..() - if(. && iscarbon(user)) - var/mob/living/carbon/C = user - return !C.silent - -/datum/emote/living/laugh/get_sound(mob/living/user) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.species.id == "human" && (!H.mind || !H.mind.miming)) - if(user.gender == FEMALE) - return 'sound/voice/human/womanlaugh.ogg' - else - return pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg') - -/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, type_override, intentional) - 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.Paralyze(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 - mob_type_blacklist_typecache = list(/mob/living/carbon/human) //Humans get specialized scream. - -/datum/emote/living/scream/select_message_type(mob/user, intentional) - . = ..() - if(!intentional && isanimal(user)) - return "makes a loud and pained whimper." - -/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, type_override, intentional) - . = ..() - if(. && isliving(user)) - var/mob/living/L = user - L.Paralyze(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/can_run_emote(mob/user, status_check, intentional) - . = ..() && intentional - -/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, intentional = FALSE) - if(!can_run_emote(user, TRUE, intentional)) - return FALSE - if(is_banned_from(user.ckey, "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 text|null), 1, MAX_MESSAGE_LEN) - 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, type_override, intentional) - var/list/keys = list() - var/list/message = list("Available emotes, you can use them with say \"*emote\": ") - - for(var/key in GLOB.emote_list) - for(var/datum/emote/P in GLOB.emote_list[key]) - if(P.key in keys) - continue - if(P.can_run_emote(user, status_check = FALSE , intentional = TRUE)) - keys += P.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/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) - -/datum/emote/living/circle - key = "circle" - key_third_person = "circles" - restraint_check = TRUE - -/datum/emote/living/circle/run_emote(mob/user, params, type_override, intentional) - . = ..() - 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, type_override, intentional) - . = ..() - 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, type_override, intentional) + . = ..() + 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, type_override, intentional) + 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) + +/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, type_override, intentional) + . = ..() + 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, type_override, intentional) + . = ..() + 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 + vary = TRUE + +/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE , intentional) + . = ..() + if(. && iscarbon(user)) + var/mob/living/carbon/C = user + return !C.silent + +/datum/emote/living/laugh/get_sound(mob/living/user) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna.species.id == "human" && (!H.mind || !H.mind.miming)) + if(user.gender == FEMALE) + return 'sound/voice/human/womanlaugh.ogg' + else + return pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg') + +/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, type_override, intentional) + 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.Paralyze(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 + mob_type_blacklist_typecache = list(/mob/living/carbon/human) //Humans get specialized scream. + +/datum/emote/living/scream/select_message_type(mob/user, intentional) + . = ..() + if(!intentional && isanimal(user)) + return "makes a loud and pained whimper." + +/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, type_override, intentional) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.Paralyze(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/can_run_emote(mob/user, status_check, intentional) + . = ..() && intentional + +/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, intentional = FALSE) + if(!can_run_emote(user, TRUE, intentional)) + return FALSE + if(is_banned_from(user.ckey, "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 text|null), 1, MAX_MESSAGE_LEN) + 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, type_override, intentional) + var/list/keys = list() + var/list/message = list("Available emotes, you can use them with say \"*emote\": ") + + for(var/key in GLOB.emote_list) + for(var/datum/emote/P in GLOB.emote_list[key]) + if(P.key in keys) + continue + if(P.can_run_emote(user, status_check = FALSE , intentional = TRUE)) + keys += P.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/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) + +/datum/emote/living/circle + key = "circle" + key_third_person = "circles" + restraint_check = TRUE + +/datum/emote/living/circle/run_emote(mob/user, params, type_override, intentional) + . = ..() + 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, type_override, intentional) + . = ..() + 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 420c59b22dd6..b82e8cc4ef7b 100644 --- a/code/modules/mob/living/inhand_holder.dm +++ b/code/modules/mob/living/inhand_holder.dm @@ -1,84 +1,84 @@ -//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 = "" - item_flags = DROPDEL - var/mob/living/held_mob - var/can_head = TRUE - var/destroying = FALSE - -/obj/item/clothing/head/mob_holder/Initialize(mapload, mob/living/M, _worn_state, head_icon, lh_icon, rh_icon, _can_head = TRUE) - . = ..() - can_head = _can_head - if(head_icon) - alternate_worn_icon = head_icon - if(_worn_state) - item_state = _worn_state - if(lh_icon) - lefthand_file = lh_icon - if(rh_icon) - righthand_file = rh_icon - if(!can_head) - slot_flags = NONE - deposit(M) - -/obj/item/clothing/head/mob_holder/Destroy() - destroying = TRUE - if(held_mob) - release(FALSE) - return ..() - -/obj/item/clothing/head/mob_holder/proc/deposit(mob/living/L) - if(!istype(L)) - return FALSE - L.setDir(SOUTH) - update_visuals(L) - held_mob = L - L.forceMove(src) - name = L.name - desc = L.desc - return TRUE - -/obj/item/clothing/head/mob_holder/proc/update_visuals(mob/living/L) - appearance = L.appearance - -/obj/item/clothing/head/mob_holder/proc/release(del_on_release = TRUE) - if(!held_mob) - if(del_on_release && !destroying) - qdel(src) - return FALSE - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "[held_mob] wriggles free!") - L.dropItemToGround(src) - held_mob.forceMove(get_turf(held_mob)) - held_mob.reset_perspective() - held_mob.setDir(SOUTH) - held_mob.visible_message("[held_mob] uncurls!") - held_mob = null - if(del_on_release && !destroying) - qdel(src) - return TRUE - -/obj/item/clothing/head/mob_holder/relaymove(mob/user) - release() - -/obj/item/clothing/head/mob_holder/container_resist() - release() - -/obj/item/clothing/head/mob_holder/drone/deposit(mob/living/L) - . = ..() - if(!isdrone(L)) - qdel(src) - name = "drone (hiding)" - desc = "This drone is scared and has curled up into a ball!" - -/obj/item/clothing/head/mob_holder/drone/update_visuals(mob/living/L) - var/mob/living/simple_animal/drone/D = L - if(!D) - return ..() - icon = 'icons/mob/drone.dmi' - icon_state = "[D.visualAppearence]_hat" +//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 = "" + item_flags = DROPDEL + var/mob/living/held_mob + var/can_head = TRUE + var/destroying = FALSE + +/obj/item/clothing/head/mob_holder/Initialize(mapload, mob/living/M, _worn_state, head_icon, lh_icon, rh_icon, _can_head = TRUE) + . = ..() + can_head = _can_head + if(head_icon) + alternate_worn_icon = head_icon + if(_worn_state) + item_state = _worn_state + if(lh_icon) + lefthand_file = lh_icon + if(rh_icon) + righthand_file = rh_icon + if(!can_head) + slot_flags = NONE + deposit(M) + +/obj/item/clothing/head/mob_holder/Destroy() + destroying = TRUE + if(held_mob) + release(FALSE) + return ..() + +/obj/item/clothing/head/mob_holder/proc/deposit(mob/living/L) + if(!istype(L)) + return FALSE + L.setDir(SOUTH) + update_visuals(L) + held_mob = L + L.forceMove(src) + name = L.name + desc = L.desc + return TRUE + +/obj/item/clothing/head/mob_holder/proc/update_visuals(mob/living/L) + appearance = L.appearance + +/obj/item/clothing/head/mob_holder/proc/release(del_on_release = TRUE) + if(!held_mob) + if(del_on_release && !destroying) + qdel(src) + return FALSE + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "[held_mob] wriggles free!") + L.dropItemToGround(src) + held_mob.forceMove(get_turf(held_mob)) + held_mob.reset_perspective() + held_mob.setDir(SOUTH) + held_mob.visible_message("[held_mob] uncurls!") + held_mob = null + if(del_on_release && !destroying) + qdel(src) + return TRUE + +/obj/item/clothing/head/mob_holder/relaymove(mob/user) + release() + +/obj/item/clothing/head/mob_holder/container_resist() + release() + +/obj/item/clothing/head/mob_holder/drone/deposit(mob/living/L) + . = ..() + if(!isdrone(L)) + qdel(src) + name = "drone (hiding)" + desc = "This drone is scared and has curled up into a ball!" + +/obj/item/clothing/head/mob_holder/drone/update_visuals(mob/living/L) + var/mob/living/simple_animal/drone/D = L + if(!D) + return ..() + icon = 'icons/mob/drone.dmi' + icon_state = "[D.visualAppearence]_hat" diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 34cb37ef3823..0db3a803ff38 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -1,409 +1,409 @@ - -/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = null, soften_text = null, armour_penetration, penetrated_text) - var/armor = getarmor(def_zone, attack_flag) - - //the if "armor" check is because this is used for everything on /living, including humans - if(armor > 0 && armour_penetration) - armor = max(0, armor - armour_penetration) - if(penetrated_text) - to_chat(src, "[penetrated_text]") - else - to_chat(src, "Your armor was penetrated!") - else if(armor >= 100) - if(absorb_text) - to_chat(src, "[absorb_text]") - else - to_chat(src, "Your armor absorbs the blow!") - else if(armor > 0) - if(soften_text) - to_chat(src, "[soften_text]") - else - to_chat(src, "Your armor softens the blow!") - 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 BULLET_ACT_HIT - -/mob/living/bullet_act(obj/item/projectile/P, def_zone) - var/armor = run_armor_check(def_zone, P.flag, "","",P.armour_penetration) - 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)? BULLET_ACT_HIT : BULLET_ACT_BLOCK - -/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/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) - if(istype(AM, /obj/item)) - var/obj/item/I = AM - 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) - 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 = FALSE) - if(user == src || anchored || !isturf(user.loc)) - return FALSE - if(!user.pulling || user.pulling != src) - user.start_pulling(src, supress_message = 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(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) - var/sound_to_play = 'sound/weapons/thudswoosh.ogg' - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.species.grab_sound) - sound_to_play = H.dna.species.grab_sound - playsound(src.loc, sound_to_play, 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) - return 0 - if(user.a_intent != INTENT_GRAB) - to_chat(user, "You must be on grab intent to upgrade your grab further!") - return 0 - user.grab_state++ - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - log_combat(user, src, "grabbed", addition="aggressive grab") - visible_message("[user] has grabbed [src] aggressively!", \ - "[user] has grabbed [src] aggressively!") - drop_all_held_items() - stop_pulling() - 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_mobility() //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_mobility() //we fall down - if(!buckled && !density) - Move(user.loc) - user.set_pull_offsets(src, grab_state) - return 1 - - -/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 - - 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(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(isturf(loc) && istype(loc.loc, /area/start)) - to_chat(M, "No attacking people at spawn, you jackass.") - return FALSE - - 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.is_mouth_covered(FALSE, TRUE)) - to_chat(M, "You can't bite with your mouth covered!") - 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("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 - - 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) - return FALSE - -/mob/living/attack_alien(mob/living/carbon/alien/humanoid/M) - switch(M.a_intent) - if ("help") - visible_message("[M] caresses [src] with its scythe like arm.") - return FALSE - if ("grab") - grabbedby(M) - return FALSE - if("harm") - if(HAS_TRAIT(M, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt anyone!") - return FALSE - M.do_attack_animation(src) - return TRUE - if("disarm") - 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 = null, soften_text = null, armour_penetration, penetrated_text) + var/armor = getarmor(def_zone, attack_flag) + + //the if "armor" check is because this is used for everything on /living, including humans + if(armor > 0 && armour_penetration) + armor = max(0, armor - armour_penetration) + if(penetrated_text) + to_chat(src, "[penetrated_text]") + else + to_chat(src, "Your armor was penetrated!") + else if(armor >= 100) + if(absorb_text) + to_chat(src, "[absorb_text]") + else + to_chat(src, "Your armor absorbs the blow!") + else if(armor > 0) + if(soften_text) + to_chat(src, "[soften_text]") + else + to_chat(src, "Your armor softens the blow!") + 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 BULLET_ACT_HIT + +/mob/living/bullet_act(obj/item/projectile/P, def_zone) + var/armor = run_armor_check(def_zone, P.flag, "","",P.armour_penetration) + 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)? BULLET_ACT_HIT : BULLET_ACT_BLOCK + +/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/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + if(istype(AM, /obj/item)) + var/obj/item/I = AM + 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) + 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 = FALSE) + if(user == src || anchored || !isturf(user.loc)) + return FALSE + if(!user.pulling || user.pulling != src) + user.start_pulling(src, supress_message = 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(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) + var/sound_to_play = 'sound/weapons/thudswoosh.ogg' + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna.species.grab_sound) + sound_to_play = H.dna.species.grab_sound + playsound(src.loc, sound_to_play, 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) + return 0 + if(user.a_intent != INTENT_GRAB) + to_chat(user, "You must be on grab intent to upgrade your grab further!") + return 0 + user.grab_state++ + switch(user.grab_state) + if(GRAB_AGGRESSIVE) + log_combat(user, src, "grabbed", addition="aggressive grab") + visible_message("[user] has grabbed [src] aggressively!", \ + "[user] has grabbed [src] aggressively!") + drop_all_held_items() + stop_pulling() + 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_mobility() //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_mobility() //we fall down + if(!buckled && !density) + Move(user.loc) + user.set_pull_offsets(src, grab_state) + return 1 + + +/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 + + 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(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(isturf(loc) && istype(loc.loc, /area/start)) + to_chat(M, "No attacking people at spawn, you jackass.") + return FALSE + + 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.is_mouth_covered(FALSE, TRUE)) + to_chat(M, "You can't bite with your mouth covered!") + 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("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 + + 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) + return FALSE + +/mob/living/attack_alien(mob/living/carbon/alien/humanoid/M) + switch(M.a_intent) + if ("help") + visible_message("[M] caresses [src] with its scythe like arm.") + return FALSE + if ("grab") + grabbedby(M) + return FALSE + if("harm") + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt anyone!") + return FALSE + M.do_attack_animation(src) + return TRUE + if("disarm") + 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 02a55dbfcc99..fdb5d8d23732 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -1,118 +1,118 @@ -/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) - 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/mobility_flags = MOBILITY_FLAGS_DEFAULT - - var/resting = FALSE - - var/lying = 0 //number of degrees. DO NOT USE THIS IN CHECKS. CHECK FOR MOBILITY FLAGS INSTEAD!! - var/lying_prev = 0 //last value of lying on update_mobility - - 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/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() +/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) + 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/mobility_flags = MOBILITY_FLAGS_DEFAULT + + var/resting = FALSE + + var/lying = 0 //number of degrees. DO NOT USE THIS IN CHECKS. CHECK FOR MOBILITY FLAGS INSTEAD!! + var/lying_prev = 0 //last value of lying on update_mobility + + 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/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() diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index c9a1b1adec06..db62698f7e88 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -1,48 +1,48 @@ -/mob/living/Moved() - . = ..() - update_turf_movespeed(loc) - -/mob/living/CanPass(atom/movable/mover, turf/target) - if((mover.pass_flags & PASSMOB)) - return TRUE - if(istype(mover, /obj/item/projectile)) - var/obj/item/projectile/P = mover - return !P.can_hit_target(src, P.permutated, src == P.original, TRUE) - if(mover.throwing) - return (!density || !(mobility_flags & MOBILITY_STAND) || (mover.throwing.thrower == src && !ismob(mover))) - if(buckled == mover) - return TRUE - if(ismob(mover)) - if(mover in buckled_mobs) - return TRUE - return (!mover.density || !density || !(mobility_flags & MOBILITY_STAND)) - -/mob/living/toggle_move_intent() - . = ..() - update_move_intent_slowdown() - -/mob/living/update_config_movespeed() - update_move_intent_slowdown() - return ..() - -/mob/living/proc/update_move_intent_slowdown() - var/mod = 0 - if(m_intent == MOVE_INTENT_WALK) - mod = CONFIG_GET(number/movedelay/walk_delay) - else - mod = CONFIG_GET(number/movedelay/run_delay) - if(!isnum(mod)) - mod = 1 - add_movespeed_modifier(MOVESPEED_ID_MOB_WALK_RUN_CONFIG_SPEED, TRUE, 100, override = TRUE, multiplicative_slowdown = mod) - -/mob/living/proc/update_turf_movespeed(turf/open/T) - if(isopenturf(T) && !is_flying()) - add_movespeed_modifier(MOVESPEED_ID_LIVING_TURF_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = T.slowdown) - else - remove_movespeed_modifier(MOVESPEED_ID_LIVING_TURF_SPEEDMOD) - -/mob/living/can_zFall(turf/T, levels) - return !(movement_type & FLYING) - -/mob/living/canZMove(dir, turf/target) - return can_zTravel(target, dir) && (movement_type & FLYING) +/mob/living/Moved() + . = ..() + update_turf_movespeed(loc) + +/mob/living/CanPass(atom/movable/mover, turf/target) + if((mover.pass_flags & PASSMOB)) + return TRUE + if(istype(mover, /obj/item/projectile)) + var/obj/item/projectile/P = mover + return !P.can_hit_target(src, P.permutated, src == P.original, TRUE) + if(mover.throwing) + return (!density || !(mobility_flags & MOBILITY_STAND) || (mover.throwing.thrower == src && !ismob(mover))) + if(buckled == mover) + return TRUE + if(ismob(mover)) + if(mover in buckled_mobs) + return TRUE + return (!mover.density || !density || !(mobility_flags & MOBILITY_STAND)) + +/mob/living/toggle_move_intent() + . = ..() + update_move_intent_slowdown() + +/mob/living/update_config_movespeed() + update_move_intent_slowdown() + return ..() + +/mob/living/proc/update_move_intent_slowdown() + var/mod = 0 + if(m_intent == MOVE_INTENT_WALK) + mod = CONFIG_GET(number/movedelay/walk_delay) + else + mod = CONFIG_GET(number/movedelay/run_delay) + if(!isnum(mod)) + mod = 1 + add_movespeed_modifier(MOVESPEED_ID_MOB_WALK_RUN_CONFIG_SPEED, TRUE, 100, override = TRUE, multiplicative_slowdown = mod) + +/mob/living/proc/update_turf_movespeed(turf/open/T) + if(isopenturf(T) && !is_flying()) + add_movespeed_modifier(MOVESPEED_ID_LIVING_TURF_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = T.slowdown) + else + remove_movespeed_modifier(MOVESPEED_ID_LIVING_TURF_SPEEDMOD) + +/mob/living/can_zFall(turf/T, levels) + return !(movement_type & FLYING) + +/mob/living/canZMove(dir, turf/target) + return can_zTravel(target, dir) && (movement_type & FLYING) diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index d686ce092bfa..1bd8b7042e6e 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -1,29 +1,29 @@ -/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!") - - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - changeling.regain_powers() +/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!") + + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + changeling.regain_powers() diff --git a/code/modules/mob/living/logout.dm b/code/modules/mob/living/logout.dm index d2c50559c63c..97b6ec86483a 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 8f140e8c419c..f5b287d9eb8d 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -1,405 +1,405 @@ -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, forced) || !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) - last_words = message - 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 - - 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(1) - to_chat(src, compose_message(src, language, message, , spans, message_mode)) - - return 1 - -/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - 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) - SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args) - - show_message(message, 2, 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() - for(var/_M in GLOB.player_list) - var/mob/M = _M - if(!M) //yogs - continue //yogs | null in player_list for whatever reason :shrug: - if(M.stat != DEAD) //not dead, not important - continue - if(!M.client || !client) //client is so that ghosts don't have to listen to mice - continue - if(get_dist(M, src) > 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, , spans, message_mode) - - var/rendered = compose_message(src, message_language, message, , spans, message_mode) - 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, , spans, message_mode) - else - AM.Hear(rendered, src, message_language, message, , spans, message_mode) - 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) - - 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 src - if(imp && 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) - . = verb_whisper - else if(message_mode == MODE_WHISPER_CRIT) - . = "[verb_whisper] in [p_their()] last breath" - else if(stuttering) - . = "stammers" - else if(derpspeech) - . = "gibbers" - else - . = ..() - -/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, forced) || !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) + last_words = message + 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 + + 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(1) + to_chat(src, compose_message(src, language, message, , spans, message_mode)) + + return 1 + +/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + 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) + SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args) + + show_message(message, 2, 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() + for(var/_M in GLOB.player_list) + var/mob/M = _M + if(!M) //yogs + continue //yogs | null in player_list for whatever reason :shrug: + if(M.stat != DEAD) //not dead, not important + continue + if(!M.client || !client) //client is so that ghosts don't have to listen to mice + continue + if(get_dist(M, src) > 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, , spans, message_mode) + + var/rendered = compose_message(src, message_language, message, , spans, message_mode) + 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, , spans, message_mode) + else + AM.Hear(rendered, src, message_language, message, , spans, message_mode) + 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) + + 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 src + if(imp && 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) + . = verb_whisper + else if(message_mode == MODE_WHISPER_CRIT) + . = "[verb_whisper] in [p_their()] last breath" + else if(stuttering) + . = "stammers" + else if(derpspeech) + . = "gibbers" + else + . = ..() + +/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 beef378c719c..f3ceb4648de4 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1,1042 +1,1042 @@ -#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" - real_name = "AI" - icon = 'icons/mob/ai.dmi' - icon_state = "ai" - move_resist = MOVE_FORCE_VERY_STRONG - density = TRUE - mobility_flags = ALL - 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/battery = 200 //emergency power if the AI's APC is off - 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//Default 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/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/list/cam_hotkeys = new/list(9) - var/cam_prev - -/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() - - update_law_history() //yogs - - 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() - if(client) - apply_pref_name("ai",client) - - set_core_display_icon() - - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"default")) - - 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 = real_name - aiPDA.ownjob = "AI" - aiPDA.name = real_name + " (" + aiPDA.ownjob + ")" - - aiMulti = new(src) - radio = new /obj/item/radio/headset/silicon/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/key_down(_key, client/user) - if(findtext(_key, "numpad")) //if it's a numpad number, we can convert it to just the number - _key = _key[7] //strings, lists, same thing really - switch(_key) - if("`", "0") - if(cam_prev) - eyeobj.setLoc(cam_prev) - return - if("1", "2", "3", "4", "5", "6", "7", "8", "9") - _key = text2num(_key) - if(client.keys_held["Ctrl"]) //do we assign a new hotkey? - cam_hotkeys[_key] = eyeobj.loc - to_chat(src, "Location saved to Camera Group [_key].") - return - if(cam_hotkeys[_key]) //if this is false, no hotkey for this slot exists. - cam_prev = eyeobj.loc - eyeobj.setLoc(cam_hotkeys[_key]) - return - return ..() - -/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 = initial(icon) //yogs - 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 = initial(src.icon), icon_state = "ai-random") //yogs start - AI donor icons - continue - iconstates[option] = image(icon = initial(src.icon), icon_state = resolve_ai_icon(option)) - - if(is_donator(client)) - for(var/datum/ai_skin/S in GLOB.DonorBorgHolder.skins) - if(S.owner == client.ckey || !S.owner) //We own this skin. - iconstates[S] = image(icon = S.icon, icon_state = S.icon_state) - - view_core() - var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42) - - if(!ai_core_icon || incapacitated()) - return - - if(ai_core_icon in GLOB.DonorBorgHolder.skins) - set_core_display_icon_yogs(ai_core_icon) - return //yogs end - AI donor icons - - 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]%")) - if(isturf(loc)) //only show if we're "in" a core - stat(null, text("Backup Power: [battery/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 ((ai.z != target.z) && !is_station_level(ai)) - return FALSE - - if (istype(loc, /obj/item/aicard)) - if (!ai || !target) - return FALSE - return ISINRANGE(target.x, ai.x - interaction_range, ai.x + interaction_range) && ISINRANGE(target.y, ai.y - interaction_range, ai.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(stat == DEAD) - return - if(incapacitated()) - if(battery < 50) - to_chat(src, "Insufficient backup power!") - return - battery = battery - 50 - to_chat(src, "You route power from your backup battery to move the bolts.") - var/is_anchored = FALSE - if(move_resist == MOVE_FORCE_VERY_STRONG) - move_resist = MOVE_FORCE_NORMAL - else - is_anchored = TRUE - move_resist = MOVE_FORCE_VERY_STRONG - - to_chat(src, "You are now [is_anchored ? "" : "un"]anchored.") - // the message in the [] will change depending whether or not the AI is anchored - -/mob/living/silicon/ai/update_mobility() //If the AI dies, mobs won't go through it anymore - if(stat != CONSCIOUS) - mobility_flags = NONE - else - mobility_flags = ALL - -/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"]) in GLOB.machines - 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 || 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 || 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"]) in GLOB.mechas_list - if (!M) - return - - var/mech_has_controlbeacon = FALSE - for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in M.trackers) - mech_has_controlbeacon = TRUE - break - if(!can_dominate_mechs && !mech_has_controlbeacon) - message_admins("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.") - log_game("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.") - return - - 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/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions - for (var/each in GLOB.ai_status_displays) //change status of displays - var/obj/machinery/status_display/ai/M = each - M.emotion = emote - M.update() - if (emote == "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( - "default" = 'icons/mob/ai.dmi', - "floating face" = 'icons/mob/ai.dmi', - "xeno queen" = 'icons/mob/alien.dmi', - "horror" = 'icons/mob/ai.dmi' - ) - - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("xeno queen") - holo_icon = getHologramIcon(icon(icon_list[input],"alienq")) - else - holo_icon = getHologramIcon(icon(icon_list[input], input)) - return - -/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() - var/obj/structure/AIcore/new_core = new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location. - new_core.circuit.battery = battery - ai_restore_power()//So the AI initially has power. - control_disabled = TRUE //Can't control things remotely if you're stuck in a card! - radio_enabled = FALSE //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 = FALSE, ignore_grab = FALSE, check_immobilized = FALSE, ignore_stasis = FALSE) - 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, 2) - -/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_core_display_icon(display_icon_override) - set_eyeobj_visible(TRUE) - -/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, ignore_walls = FALSE) - 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, ignore_walls = FALSE) - else - malf_picker.processing_time += 5 - - apc.malfai = parent || src - apc.malfhack = TRUE - apc.locked = TRUE - apc.coverlocked = TRUE - - playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1, ignore_walls = FALSE) - 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" + real_name = "AI" + icon = 'icons/mob/ai.dmi' + icon_state = "ai" + move_resist = MOVE_FORCE_VERY_STRONG + density = TRUE + mobility_flags = ALL + 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/battery = 200 //emergency power if the AI's APC is off + 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//Default 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/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/list/cam_hotkeys = new/list(9) + var/cam_prev + +/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() + + update_law_history() //yogs + + 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() + if(client) + apply_pref_name("ai",client) + + set_core_display_icon() + + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"default")) + + 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 = real_name + aiPDA.ownjob = "AI" + aiPDA.name = real_name + " (" + aiPDA.ownjob + ")" + + aiMulti = new(src) + radio = new /obj/item/radio/headset/silicon/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/key_down(_key, client/user) + if(findtext(_key, "numpad")) //if it's a numpad number, we can convert it to just the number + _key = _key[7] //strings, lists, same thing really + switch(_key) + if("`", "0") + if(cam_prev) + eyeobj.setLoc(cam_prev) + return + if("1", "2", "3", "4", "5", "6", "7", "8", "9") + _key = text2num(_key) + if(client.keys_held["Ctrl"]) //do we assign a new hotkey? + cam_hotkeys[_key] = eyeobj.loc + to_chat(src, "Location saved to Camera Group [_key].") + return + if(cam_hotkeys[_key]) //if this is false, no hotkey for this slot exists. + cam_prev = eyeobj.loc + eyeobj.setLoc(cam_hotkeys[_key]) + return + return ..() + +/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 = initial(icon) //yogs + 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 = initial(src.icon), icon_state = "ai-random") //yogs start - AI donor icons + continue + iconstates[option] = image(icon = initial(src.icon), icon_state = resolve_ai_icon(option)) + + if(is_donator(client)) + for(var/datum/ai_skin/S in GLOB.DonorBorgHolder.skins) + if(S.owner == client.ckey || !S.owner) //We own this skin. + iconstates[S] = image(icon = S.icon, icon_state = S.icon_state) + + view_core() + var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42) + + if(!ai_core_icon || incapacitated()) + return + + if(ai_core_icon in GLOB.DonorBorgHolder.skins) + set_core_display_icon_yogs(ai_core_icon) + return //yogs end - AI donor icons + + 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]%")) + if(isturf(loc)) //only show if we're "in" a core + stat(null, text("Backup Power: [battery/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 ((ai.z != target.z) && !is_station_level(ai)) + return FALSE + + if (istype(loc, /obj/item/aicard)) + if (!ai || !target) + return FALSE + return ISINRANGE(target.x, ai.x - interaction_range, ai.x + interaction_range) && ISINRANGE(target.y, ai.y - interaction_range, ai.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(stat == DEAD) + return + if(incapacitated()) + if(battery < 50) + to_chat(src, "Insufficient backup power!") + return + battery = battery - 50 + to_chat(src, "You route power from your backup battery to move the bolts.") + var/is_anchored = FALSE + if(move_resist == MOVE_FORCE_VERY_STRONG) + move_resist = MOVE_FORCE_NORMAL + else + is_anchored = TRUE + move_resist = MOVE_FORCE_VERY_STRONG + + to_chat(src, "You are now [is_anchored ? "" : "un"]anchored.") + // the message in the [] will change depending whether or not the AI is anchored + +/mob/living/silicon/ai/update_mobility() //If the AI dies, mobs won't go through it anymore + if(stat != CONSCIOUS) + mobility_flags = NONE + else + mobility_flags = ALL + +/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"]) in GLOB.machines + 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 || 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 || 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"]) in GLOB.mechas_list + if (!M) + return + + var/mech_has_controlbeacon = FALSE + for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in M.trackers) + mech_has_controlbeacon = TRUE + break + if(!can_dominate_mechs && !mech_has_controlbeacon) + message_admins("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.") + log_game("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.") + return + + 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/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions + for (var/each in GLOB.ai_status_displays) //change status of displays + var/obj/machinery/status_display/ai/M = each + M.emotion = emote + M.update() + if (emote == "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( + "default" = 'icons/mob/ai.dmi', + "floating face" = 'icons/mob/ai.dmi', + "xeno queen" = 'icons/mob/alien.dmi', + "horror" = 'icons/mob/ai.dmi' + ) + + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("xeno queen") + holo_icon = getHologramIcon(icon(icon_list[input],"alienq")) + else + holo_icon = getHologramIcon(icon(icon_list[input], input)) + return + +/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() + var/obj/structure/AIcore/new_core = new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location. + new_core.circuit.battery = battery + ai_restore_power()//So the AI initially has power. + control_disabled = TRUE //Can't control things remotely if you're stuck in a card! + radio_enabled = FALSE //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 = FALSE, ignore_grab = FALSE, check_immobilized = FALSE, ignore_stasis = FALSE) + 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, 2) + +/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_core_display_icon(display_icon_override) + set_eyeobj_visible(TRUE) + +/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, ignore_walls = FALSE) + 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, ignore_walls = FALSE) + else + malf_picker.processing_time += 5 + + apc.malfai = parent || src + apc.malfhack = TRUE + apc.locked = TRUE + apc.coverlocked = TRUE + + playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1, ignore_walls = FALSE) + 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 ef963075fc07..24b89ce54105 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 - 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 + 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 d8636f4f2db0..26aadfd5168a 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.\n" - else if (!shunted && !client) - . += "[src]Core.exe has stopped responding! NTOS is searching for a solution to the problem...\n" - . += "*---------*" - - . += ..() +/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.\n" + else if (!shunted && !client) + . += "[src]Core.exe has stopped responding! NTOS is searching for a solution to the problem...\n" + . += "*---------*" + + . += ..() diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index dfe76fe9481a..a3ffd460dd67 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 c2794394ed62..4591720b7986 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 0492df5b59ae..77f1461be176 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -1,208 +1,208 @@ -// 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 - icon_state = "ai_camera[GLOB.ai_list.len % 3]" // Yogs -- multicoloured AI eyes - 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) - 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) - 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) - - 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)" - eyeobj.real_name = eyeobj.name - 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) - . = ..() - 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 + icon_state = "ai_camera[GLOB.ai_list.len % 3]" // Yogs -- multicoloured AI eyes + 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) + 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) + 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) + + 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)" + eyeobj.real_name = eyeobj.name + 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) + . = ..() + 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/freelook/read_me.dm b/code/modules/mob/living/silicon/ai/freelook/read_me.dm index 53e68ff13777..5f8776f7e476 100644 --- a/code/modules/mob/living/silicon/ai/freelook/read_me.dm +++ b/code/modules/mob/living/silicon/ai/freelook/read_me.dm @@ -1,51 +1,51 @@ -// CREDITS -/* - Initial code credit for this goes to Uristqwerty. - Debugging, functionality, all comments and porting by Giacom. - - Everything about freelook (or what we can put in here) will be stored here. - - - WHAT IS THIS? - - This is a replacement for the current camera movement system, of the AI. Before this, the AI had to move between cameras and could - only see what the cameras could see. Not only this but the cameras could see through walls, which created problems. - With this, the AI controls an "AI Eye" mob, which moves just like a ghost; such as moving through walls and being invisible to players. - The AI's eye is set to this mob and then we use a system (explained below) to determine what the cameras around the AI Eye can and - cannot see. If the camera cannot see a turf, it will black it out, otherwise it won't and the AI will be able to see it. - This creates several features, such as.. no more see-through-wall cameras, easier to control camera movement, easier tracking, - the AI only being able to track mobs which are visible to a camera, only trackable mobs appearing on the mob list and many more. - - - HOW IT WORKS - - It works by first creating a camera network datum. Inside of this camera network are "chunks" (which will be - explained later) and "cameras". The cameras list is kept up to date by obj/machinery/camera/New() and Del(). - - Next the camera network has chunks. These chunks are a 16x16 tile block of turfs and cameras contained inside the chunk. - These turfs are then sorted out based on what the cameras can and cannot see. If none of the cameras can see the turf, inside - the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't be able to see it. - - - HOW IT UPDATES - - The camera network uses a streaming method in order to effeciently update chunks. Since the server will have doors opening, doors closing, - turf being destroyed and other lag inducing stuff, we want to update it under certain conditions and not every tick. - - The chunks are not created straight away, only when an AI eye moves into it's area is when it gets created. - One a chunk is created, when a non glass door opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is looking in the area. - We do this with the "seenby" list, which updates everytime an AI is near a chunk. If there is an AI eye inside the area, we update the chunk - that the changed atom is inside and all surrounding chunks, since a camera's vision could leak onto another chunk. If there is no AI Eye, we instead - flag the chunk to update whenever it is loaded by an AI Eye. This is basically how the chunks update and keep it in sync. We then add some lag reducing - measures, such as an UPDATE_BUFFER which stops a chunk from updating too many times in a certain time-frame, only updating if the changed atom was blocking - sight; for example, we don't update glass airlocks or floors. - - - WHERE IS EVERYTHING? - - cameranet.dm = Everything about the cameranet datum. - chunk.dm = Everything about the chunk datum. - eye.dm = Everything about the AI and the AIEye. - updating.dm = Everything about triggers that will update chunks. - +// CREDITS +/* + Initial code credit for this goes to Uristqwerty. + Debugging, functionality, all comments and porting by Giacom. + + Everything about freelook (or what we can put in here) will be stored here. + + + WHAT IS THIS? + + This is a replacement for the current camera movement system, of the AI. Before this, the AI had to move between cameras and could + only see what the cameras could see. Not only this but the cameras could see through walls, which created problems. + With this, the AI controls an "AI Eye" mob, which moves just like a ghost; such as moving through walls and being invisible to players. + The AI's eye is set to this mob and then we use a system (explained below) to determine what the cameras around the AI Eye can and + cannot see. If the camera cannot see a turf, it will black it out, otherwise it won't and the AI will be able to see it. + This creates several features, such as.. no more see-through-wall cameras, easier to control camera movement, easier tracking, + the AI only being able to track mobs which are visible to a camera, only trackable mobs appearing on the mob list and many more. + + + HOW IT WORKS + + It works by first creating a camera network datum. Inside of this camera network are "chunks" (which will be + explained later) and "cameras". The cameras list is kept up to date by obj/machinery/camera/New() and Del(). + + Next the camera network has chunks. These chunks are a 16x16 tile block of turfs and cameras contained inside the chunk. + These turfs are then sorted out based on what the cameras can and cannot see. If none of the cameras can see the turf, inside + the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't be able to see it. + + + HOW IT UPDATES + + The camera network uses a streaming method in order to effeciently update chunks. Since the server will have doors opening, doors closing, + turf being destroyed and other lag inducing stuff, we want to update it under certain conditions and not every tick. + + The chunks are not created straight away, only when an AI eye moves into it's area is when it gets created. + One a chunk is created, when a non glass door opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is looking in the area. + We do this with the "seenby" list, which updates everytime an AI is near a chunk. If there is an AI eye inside the area, we update the chunk + that the changed atom is inside and all surrounding chunks, since a camera's vision could leak onto another chunk. If there is no AI Eye, we instead + flag the chunk to update whenever it is loaded by an AI Eye. This is basically how the chunks update and keep it in sync. We then add some lag reducing + measures, such as an UPDATE_BUFFER which stops a chunk from updating too many times in a certain time-frame, only updating if the changed atom was blocking + sight; for example, we don't update glass airlocks or floors. + + + WHERE IS EVERYTHING? + + cameranet.dm = Everything about the cameranet datum. + chunk.dm = Everything about the chunk datum. + eye.dm = Everything about the AI and the AIEye. + updating.dm = Everything about triggers that will update chunks. + */ \ No newline at end of file diff --git a/code/modules/mob/living/silicon/ai/laws.dm b/code/modules/mob/living/silicon/ai/laws.dm index 647aeb7e47c8..0f1c078ef82d 100644 --- a/code/modules/mob/living/silicon/ai/laws.dm +++ b/code/modules/mob/living/silicon/ai/laws.dm @@ -1,26 +1,26 @@ - -/mob/living/silicon/ai/proc/show_laws_verb() - set category = "AI Commands" - set name = "Show Laws" - if(usr.stat == DEAD) - return //won't work if dead - src.show_laws() - -/mob/living/silicon/ai/show_laws(everyone = 0) - var/who - - if (everyone) - who = world - else - who = src - to_chat(who, "Obey these laws:") - - src.laws_sanity_check() - src.laws.show_laws(who) - if(!everyone) - for(var/mob/living/silicon/robot/R in connected_robots) - if(R.lawupdate) - R.lawsync() - R.show_laws() - R.law_change_counter++ - + +/mob/living/silicon/ai/proc/show_laws_verb() + set category = "AI Commands" + set name = "Show Laws" + if(usr.stat == DEAD) + return //won't work if dead + src.show_laws() + +/mob/living/silicon/ai/show_laws(everyone = 0) + var/who + + if (everyone) + who = world + else + who = src + to_chat(who, "Obey these laws:") + + src.laws_sanity_check() + src.laws.show_laws(who) + if(!everyone) + for(var/mob/living/silicon/robot/R in connected_robots) + if(R.lawupdate) + R.lawsync() + R.show_laws() + R.law_change_counter++ + diff --git a/code/modules/mob/living/silicon/ai/login.dm b/code/modules/mob/living/silicon/ai/login.dm index a40022a12f02..98f38652c60d 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 = "Neutral" - 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 = "Neutral" + 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 22d67ef53f1a..73161f12d5fa 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 23fb3d47b752..f59d76b959ac 100644 --- a/code/modules/mob/living/silicon/ai/say.dm +++ b/code/modules/mob/living/silicon/ai/say.dm @@ -1,181 +1,181 @@ -/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/M = speaker.GetJob() - 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") //yogs - male vox - - 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") //yogs start - male vox - incorrect_words += word - if(!GLOB.vox_sounds_male[word] && voxType == "male") - incorrect_words += word //yogs end- male vox - - 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) //yogs - male vox - - -/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "female", pitch = 0) // Yogs -- Pitch variation - - word = lowertext(word) - - if( (GLOB.vox_sounds[word] && voxType == "female") || (GLOB.vox_sounds_male[word] &&voxType == "male") ) //yogs - male vox - - var/sound_file //yogs start - male vox - - if(voxType == "female") - sound_file = GLOB.vox_sounds[word] - else - sound_file = GLOB.vox_sounds_male[word] //yogs end - male vox - - var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX) - voice.status = SOUND_STREAM - voice.frequency = pitch //Yogs -- Pitch variation - - // 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/M = speaker.GetJob() + 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") //yogs - male vox + + 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") //yogs start - male vox + incorrect_words += word + if(!GLOB.vox_sounds_male[word] && voxType == "male") + incorrect_words += word //yogs end- male vox + + 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) //yogs - male vox + + +/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "female", pitch = 0) // Yogs -- Pitch variation + + word = lowertext(word) + + if( (GLOB.vox_sounds[word] && voxType == "female") || (GLOB.vox_sounds_male[word] &&voxType == "male") ) //yogs - male vox + + var/sound_file //yogs start - male vox + + if(voxType == "female") + sound_file = GLOB.vox_sounds[word] + else + sound_file = GLOB.vox_sounds_male[word] //yogs end - male vox + + var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX) + voice.status = SOUND_STREAM + voice.frequency = pitch //Yogs -- Pitch variation + + // 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 8c47bc0cfdc0..55f308baf856 100644 --- a/code/modules/mob/living/silicon/ai/vox_sounds.dm +++ b/code/modules/mob/living/silicon/ai/vox_sounds.dm @@ -1,978 +1,978 @@ -// 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', -/datum/reagent/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')) -#endif +// 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', +/datum/reagent/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')) +#endif diff --git a/code/modules/mob/living/silicon/death.dm b/code/modules/mob/living/silicon/death.dm index 8cb11370b6de..413016519f88 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() - new /obj/effect/gibspawner/robot(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() - return ..() +/mob/living/silicon/spawn_gibs() + new /obj/effect/gibspawner/robot(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() + return ..() diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm index c7aad998922a..acaee05cc619 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 f3ce675a04fc..fc000a5c6723 100644 --- a/code/modules/mob/living/silicon/login.dm +++ b/code/modules/mob/living/silicon/login.dm @@ -1,15 +1,15 @@ -/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) - ..() - - -/mob/living/silicon/auto_deadmin_on_login() - if(!client?.holder) - return TRUE - if(CONFIG_GET(flag/auto_deadmin_silicons) || (client.prefs?.toggles & DEADMIN_POSITION_SILICON)) - return client.holder.auto_deadmin() - return ..() +/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) + ..() + + +/mob/living/silicon/auto_deadmin_on_login() + if(!client?.holder) + return TRUE + if(CONFIG_GET(flag/auto_deadmin_silicons) || (client.prefs?.toggles & DEADMIN_POSITION_SILICON)) + return client.holder.auto_deadmin() + return ..() diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm index 22f8b59ec330..fce2dd023773 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 - mobility_flags = NONE - 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 + mobility_flags = NONE + 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/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm index 2c473be74950..acb2273c410d 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/say.dm b/code/modules/mob/living/silicon/pai/say.dm index 84a878e59e3f..9f07900a250f 100644 --- a/code/modules/mob/living/silicon/pai/say.dm +++ b/code/modules/mob/living/silicon/pai/say.dm @@ -1,8 +1,8 @@ -/mob/living/silicon/pai/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - if(silent) - to_chat(src, "Communication circuits remain unitialized.") - else - ..(message) - -/mob/living/silicon/pai/binarycheck() - return 0 +/mob/living/silicon/pai/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + if(silent) + to_chat(src, "Communication circuits remain unitialized.") + else + ..(message) + +/mob/living/silicon/pai/binarycheck() + return 0 diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 40e19689039c..afc75ff89f29 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -1,659 +1,659 @@ -// TODO: -// - 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( - //Nightvision - //T-Ray - //radiation eyes - //chem goggs - //mesons - "crew manifest" = 5, - "digital messenger" = 5, - "atmosphere sensor" = 5, - "photography module" = 5, - "remote signaller" = 10, - "medical records" = 10, - "security records" = 10, - "camera zoom" = 10, - "host scan" = 10, - //"camera jack" = 10, - //"heartbeat sensor" = 10, - //"projection array" = 15, - "medical HUD" = 20, - "security HUD" = 20, - "loudness booster" = 20, - "newscaster" = 20, - "door jack" = 25, - "encryption keys" = 25, - "universal translator" = 35 - ) - -/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("encryptionkeys") - left_part = softwareEncryptionKeys() - 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() - if("loudness") - left_part = softwareLoudness() - if("hostscan") - left_part = softwareHostScan() - - - //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]
                -
                - - "} //" - src << browse(dat, "window=pai;size=640x480;border=0;can_close=1;can_resize=1;can_minimize=1;titlebar=1") - onclose(src, "pai") - temp = null - -/mob/living/silicon/pai/Topic(href, href_list) - if(..()) - return - var/soft = href_list["software"] - var/sub = href_list["sub"] - if(soft) - screen = soft - if(sub) - subscreen = text2num(sub) - switch(soft) - if("buy") // Purchasing new software - 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 - var/datum/hud/pai/pAIhud = hud_used - pAIhud?.update_software_buttons() - else - temp = "Insufficient RAM available." - else - temp = "Trunk \"[target]\" not found." - - - if("radio") // Configuring onboard radio - radio.attack_self(src) - - if("image") // Set pAI card display face - var/newImage = input("Select your new display image.", "Display Image", "Happy") in list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What") - 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 - card.setEmotion(pID) - - if("news") - newscaster.ui_interact(src) - - if("camzoom") - aicamera.adjust_zoom(usr) - - 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"]) - if(iscarbon(card.loc)) - CheckDNA(card.loc, src) //you should only be able to check when directly in hand, muh immersions? - else - to_chat(src, "You are not being carried by anyone!") - return 0 // FALSE ? If you return here you won't call paiinterface() below - - if("pdamessage") - if(!isnull(aiPDA)) - if(href_list["toggler"]) - aiPDA.toff = !aiPDA.toff - else if(href_list["ringer"]) - aiPDA.silent = !aiPDA.silent - else if(href_list["target"]) - if(silent) - return alert("Communications circuits remain uninitialized.") - var/target = locate(href_list["target"]) in GLOB.PDAs - aiPDA.create_message(src, target) - - if("medicalrecord") // Accessing medical records - 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("hostscan") - if(href_list["toggle"]) - var/mob/living/silicon/pai/pAI = usr - pAI.hostscan.attack_self(usr) - if(href_list["toggle2"]) - var/mob/living/silicon/pai/pAI = usr - pAI.hostscan.toggle_mode() - - if("encryptionkeys") - if(href_list["toggle"]) - encryptmod = TRUE - - 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.") - - if("loudness") - if(subscreen == 1) // Open Instrument - internal_instrument.interact(src) - if(subscreen == 2) // Change Instrument type - internal_instrument.selectInstrument() - - paiInterface() - -// MENUS - -/mob/living/silicon/pai/proc/softwareMenu() // Populate the right menu - var/dat = "" - - dat += "Refresh
                " - // Built-in - dat += "Directives
                " - 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 == "host scan") - dat += "Host Health Scan" - 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
                " - if(s == "loudness booster") - dat += "Loudness Booster
                " - dat += "
                " - - // Advanced - dat += "Advanced
                " - for(var/s in software) - if(s == "camera zoom") - dat += "Adjust Camera Zoom
                " - 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 == "encryption keys") - dat += "Channel Encryption Firmware[(encryptmod) ? " 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) - if(!istype(M)) - return - 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"]]
                \nGender: [medicalActive1.fields["gender"]]
                \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"]]
                \nGender: [securityActive1.fields["gender"]]
                \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 . - -// Encryption Keys -/mob/living/silicon/pai/proc/softwareEncryptionKeys() - var/dat = {"

                Encryption Key Firmware


                - When enabled, this device will be able to use up to two (2) encryption keys for departmental channel access.

                - The device is currently [encryptmod ? "en" : "dis" ]abled.
                [encryptmod ? "" : "Activate Encryption Key Ports
                "]"} - return dat - - -// 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 Overlay


                - 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 = "" - dat += {"

                Medical Analysis 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
                - "} - return dat - -//Health Scanner -/mob/living/silicon/pai/proc/softwareHostScan() - - var/dat = "" - dat += {"

                Host Bisoscan Settings


                - - Change Scan Type
                - - Toggle Verbosity
                - "} - 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][MOLES]/total_moles - if(gas_level > 0.01) - dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [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: - [(aiPDA.toff) ? "\[Off\]" : "\[On\]"]
                - Ringer Status: - [(aiPDA.silent) ? "\[Off\]" : "\[On\]"]

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

                " - dat += "Messages:
                [aiPDA.tnote]" - return dat - -// Loudness Booster -/mob/living/silicon/pai/proc/softwareLoudness() - if(!internal_instrument) - internal_instrument = new(src) - var/dat = "

                Sound Synthesizer

                " - dat += "Open Synthesizer Interface
                " - dat += "Choose Instrument Type" - return dat +// TODO: +// - 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( + //Nightvision + //T-Ray + //radiation eyes + //chem goggs + //mesons + "crew manifest" = 5, + "digital messenger" = 5, + "atmosphere sensor" = 5, + "photography module" = 5, + "remote signaller" = 10, + "medical records" = 10, + "security records" = 10, + "camera zoom" = 10, + "host scan" = 10, + //"camera jack" = 10, + //"heartbeat sensor" = 10, + //"projection array" = 15, + "medical HUD" = 20, + "security HUD" = 20, + "loudness booster" = 20, + "newscaster" = 20, + "door jack" = 25, + "encryption keys" = 25, + "universal translator" = 35 + ) + +/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("encryptionkeys") + left_part = softwareEncryptionKeys() + 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() + if("loudness") + left_part = softwareLoudness() + if("hostscan") + left_part = softwareHostScan() + + + //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]
                +
                + + "} //" + src << browse(dat, "window=pai;size=640x480;border=0;can_close=1;can_resize=1;can_minimize=1;titlebar=1") + onclose(src, "pai") + temp = null + +/mob/living/silicon/pai/Topic(href, href_list) + if(..()) + return + var/soft = href_list["software"] + var/sub = href_list["sub"] + if(soft) + screen = soft + if(sub) + subscreen = text2num(sub) + switch(soft) + if("buy") // Purchasing new software + 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 + var/datum/hud/pai/pAIhud = hud_used + pAIhud?.update_software_buttons() + else + temp = "Insufficient RAM available." + else + temp = "Trunk \"[target]\" not found." + + + if("radio") // Configuring onboard radio + radio.attack_self(src) + + if("image") // Set pAI card display face + var/newImage = input("Select your new display image.", "Display Image", "Happy") in list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What") + 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 + card.setEmotion(pID) + + if("news") + newscaster.ui_interact(src) + + if("camzoom") + aicamera.adjust_zoom(usr) + + 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"]) + if(iscarbon(card.loc)) + CheckDNA(card.loc, src) //you should only be able to check when directly in hand, muh immersions? + else + to_chat(src, "You are not being carried by anyone!") + return 0 // FALSE ? If you return here you won't call paiinterface() below + + if("pdamessage") + if(!isnull(aiPDA)) + if(href_list["toggler"]) + aiPDA.toff = !aiPDA.toff + else if(href_list["ringer"]) + aiPDA.silent = !aiPDA.silent + else if(href_list["target"]) + if(silent) + return alert("Communications circuits remain uninitialized.") + var/target = locate(href_list["target"]) in GLOB.PDAs + aiPDA.create_message(src, target) + + if("medicalrecord") // Accessing medical records + 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("hostscan") + if(href_list["toggle"]) + var/mob/living/silicon/pai/pAI = usr + pAI.hostscan.attack_self(usr) + if(href_list["toggle2"]) + var/mob/living/silicon/pai/pAI = usr + pAI.hostscan.toggle_mode() + + if("encryptionkeys") + if(href_list["toggle"]) + encryptmod = TRUE + + 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.") + + if("loudness") + if(subscreen == 1) // Open Instrument + internal_instrument.interact(src) + if(subscreen == 2) // Change Instrument type + internal_instrument.selectInstrument() + + paiInterface() + +// MENUS + +/mob/living/silicon/pai/proc/softwareMenu() // Populate the right menu + var/dat = "" + + dat += "Refresh
                " + // Built-in + dat += "Directives
                " + 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 == "host scan") + dat += "Host Health Scan" + 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
                " + if(s == "loudness booster") + dat += "Loudness Booster
                " + dat += "
                " + + // Advanced + dat += "Advanced
                " + for(var/s in software) + if(s == "camera zoom") + dat += "Adjust Camera Zoom
                " + 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 == "encryption keys") + dat += "Channel Encryption Firmware[(encryptmod) ? " 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) + if(!istype(M)) + return + 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"]]
                \nGender: [medicalActive1.fields["gender"]]
                \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"]]
                \nGender: [securityActive1.fields["gender"]]
                \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 . + +// Encryption Keys +/mob/living/silicon/pai/proc/softwareEncryptionKeys() + var/dat = {"

                Encryption Key Firmware


                + When enabled, this device will be able to use up to two (2) encryption keys for departmental channel access.

                + The device is currently [encryptmod ? "en" : "dis" ]abled.
                [encryptmod ? "" : "Activate Encryption Key Ports
                "]"} + return dat + + +// 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 Overlay


                + 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 = "" + dat += {"

                Medical Analysis 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
                + "} + return dat + +//Health Scanner +/mob/living/silicon/pai/proc/softwareHostScan() + + var/dat = "" + dat += {"

                Host Bisoscan Settings


                + + Change Scan Type
                + + Toggle Verbosity
                + "} + 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][MOLES]/total_moles + if(gas_level > 0.01) + dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [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: + [(aiPDA.toff) ? "\[Off\]" : "\[On\]"]
                + Ringer Status: + [(aiPDA.silent) ? "\[Off\]" : "\[On\]"]

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

                " + dat += "Messages:
                [aiPDA.tnote]" + return dat + +// Loudness Booster +/mob/living/silicon/pai/proc/softwareLoudness() + if(!internal_instrument) + internal_instrument = new(src) + var/dat = "

                Sound Synthesizer

                " + dat += "Open Synthesizer Interface
                " + dat += "Choose Instrument Type" + return dat diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm index 188b53a5c96b..59edca05d1ae 100644 --- a/code/modules/mob/living/silicon/robot/death.dm +++ b/code/modules/mob/living/silicon/robot/death.dm @@ -1,34 +1,34 @@ - -/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 - - 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 + + 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/emote.dm b/code/modules/mob/living/silicon/robot/emote.dm index 8b5e78232977..c6de94f99d6a 100644 --- a/code/modules/mob/living/silicon/robot/emote.dm +++ b/code/modules/mob/living/silicon/robot/emote.dm @@ -1,72 +1,72 @@ -/datum/emote/silicon - mob_type_allowed_typecache = list(/mob/living/silicon) - emote_type = EMOTE_AUDIBLE - -/datum/emote/silicon - mob_type_allowed_typecache = list(/mob/living/silicon) - emote_type = EMOTE_AUDIBLE - -/datum/emote/silicon/boop - key = "boop" - key_third_person = "boops" - message = "boops." - -/datum/emote/silicon/buzz - key = "buzz" - key_third_person = "buzzes" - message = "buzzes." - message_param = "buzzes at %t." - sound = 'sound/machines/buzz-sigh.ogg' - -/datum/emote/silicon/buzz2 - key = "buzz2" - message = "buzzes twice." - sound = 'sound/machines/buzz-two.ogg' - -/datum/emote/silicon/chime - key = "chime" - key_third_person = "chimes" - message = "chimes." - sound = 'sound/machines/chime.ogg' - -/datum/emote/silicon/honk - key = "honk" - key_third_person = "honks" - message = "honks." - vary = TRUE - sound = 'sound/items/bikehorn.ogg' - -/datum/emote/silicon/ping - key = "ping" - key_third_person = "pings" - message = "pings." - message_param = "pings at %t." - sound = 'sound/machines/ping.ogg' - -/datum/emote/silicon/chime - key = "chime" - key_third_person = "chimes" - message = "chimes." - sound = 'sound/machines/chime.ogg' - -/datum/emote/silicon/sad - key = "sad" - message = "plays a sad trombone..." - sound = 'sound/misc/sadtrombone.ogg' - -/datum/emote/silicon/warn - key = "warn" - message = "blares an alarm!" - sound = 'sound/machines/warning-buzzer.ogg' - -/mob/living/silicon/robot/verb/powerwarn() - set category = "Robot Commands" - set name = "Power Warning" - - if(stat == CONSCIOUS) - if(!cell || !cell.charge) - visible_message("The power warning light on [src] flashes urgently.",\ - "You announce you are operating in low power mode.") - playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0) - else - to_chat(src, "You can only use this emote when you're out of charge.") +/datum/emote/silicon + mob_type_allowed_typecache = list(/mob/living/silicon) + emote_type = EMOTE_AUDIBLE + +/datum/emote/silicon + mob_type_allowed_typecache = list(/mob/living/silicon) + emote_type = EMOTE_AUDIBLE + +/datum/emote/silicon/boop + key = "boop" + key_third_person = "boops" + message = "boops." + +/datum/emote/silicon/buzz + key = "buzz" + key_third_person = "buzzes" + message = "buzzes." + message_param = "buzzes at %t." + sound = 'sound/machines/buzz-sigh.ogg' + +/datum/emote/silicon/buzz2 + key = "buzz2" + message = "buzzes twice." + sound = 'sound/machines/buzz-two.ogg' + +/datum/emote/silicon/chime + key = "chime" + key_third_person = "chimes" + message = "chimes." + sound = 'sound/machines/chime.ogg' + +/datum/emote/silicon/honk + key = "honk" + key_third_person = "honks" + message = "honks." + vary = TRUE + sound = 'sound/items/bikehorn.ogg' + +/datum/emote/silicon/ping + key = "ping" + key_third_person = "pings" + message = "pings." + message_param = "pings at %t." + sound = 'sound/machines/ping.ogg' + +/datum/emote/silicon/chime + key = "chime" + key_third_person = "chimes" + message = "chimes." + sound = 'sound/machines/chime.ogg' + +/datum/emote/silicon/sad + key = "sad" + message = "plays a sad trombone..." + sound = 'sound/misc/sadtrombone.ogg' + +/datum/emote/silicon/warn + key = "warn" + message = "blares an alarm!" + sound = 'sound/machines/warning-buzzer.ogg' + +/mob/living/silicon/robot/verb/powerwarn() + set category = "Robot Commands" + set name = "Power Warning" + + if(stat == CONSCIOUS) + if(!cell || !cell.charge) + visible_message("The power warning light on [src] flashes urgently.",\ + "You announce you are operating in low power mode.") + playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0) + else + to_chat(src, "You can only use this emote when you're out of charge.") diff --git a/code/modules/mob/living/silicon/robot/examine.dm b/code/modules/mob/living/silicon/robot/examine.dm index 4e062910ff95..aab37e24b450 100644 --- a/code/modules/mob/living/silicon/robot/examine.dm +++ b/code/modules/mob/living/silicon/robot/examine.dm @@ -1,51 +1,51 @@ -/mob/living/silicon/robot/examine(mob/user) - . = list("*---------*\nThis is [icon2html(src, user)] \a [src]!") - if(desc) - . += "[desc]" - - var/obj/act_module = get_active_held_item() - if(act_module) - . += "It is holding [icon2html(act_module, user)] \a [act_module]." - . += status_effect_examines() - 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 severely burnt and heat-warped!" - 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]!") + if(desc) + . += "[desc]" + + var/obj/act_module = get_active_held_item() + if(act_module) + . += "It is holding [icon2html(act_module, user)] \a [act_module]." + . += status_effect_examines() + 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 severely burnt and heat-warped!" + 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 f72c92822c9d..e4db4d31cad0 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -1,215 +1,215 @@ -//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 - -/obj/item/proc/cyborg_unequip(mob/user) - return - -/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) - 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. - O.cyborg_unequip(src) - - 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 - 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 + +/obj/item/proc/cyborg_unequip(mob/user) + return + +/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) + 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. + O.cyborg_unequip(src) + + 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 + 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 6897c5096179..05812b8c1969 100644 --- a/code/modules/mob/living/silicon/robot/laws.dm +++ b/code/modules/mob/living/silicon/robot/laws.dm @@ -1,83 +1,83 @@ -/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 - - update_law_history() //yogs - 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 + + update_law_history() //yogs + picturesync() diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index d071d6cf21d8..6c34ee99d0bf 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -1,103 +1,103 @@ -/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(.) //if the mob isn't on fire anymore - return - if(fire_stacks > 0) - fire_stacks-- - fire_stacks = max(0, fire_stacks) - else - ExtinguishMob() - return TRUE - - //adjustFireLoss(3) - -/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_mobility() - if(stat || buckled || lockcharge) - mobility_flags &= ~MOBILITY_MOVE - else - mobility_flags = MOBILITY_FLAGS_DEFAULT - update_transform() - update_action_buttons_icon() - +/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(.) //if the mob isn't on fire anymore + return + if(fire_stacks > 0) + fire_stacks-- + fire_stacks = max(0, fire_stacks) + else + ExtinguishMob() + return TRUE + + //adjustFireLoss(3) + +/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_mobility() + if(stat || buckled || lockcharge) + mobility_flags &= ~MOBILITY_MOVE + else + mobility_flags = MOBILITY_FLAGS_DEFAULT + update_transform() + update_action_buttons_icon() + diff --git a/code/modules/mob/living/silicon/robot/login.dm b/code/modules/mob/living/silicon/robot/login.dm index 8fb58b94728f..3856dd40d156 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 207fb82e03f1..3d90bc7ddda6 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -1,689 +1,689 @@ -/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/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 = TRUE - 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 = TRUE - var/allow_riding = TRUE - var/canDispose = FALSE // Whether the borg can stuff itself into disposal - -/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) - - 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 - -/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.burnt_out = FALSE - 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.chambered) - EG.recharge_newshot() //try to reload a new shot. - - 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) - 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/reagent_containers/borghypo/epi, - /obj/item/healthanalyzer, - /obj/item/weldingtool/largetank/cyborg, - /obj/item/wrench/cyborg, - /obj/item/crowbar/cyborg, - /obj/item/stack/sheet/metal/cyborg, - /obj/item/stack/rods/cyborg, - /obj/item/stack/tile/plasteel/cyborg, - /obj/item/extinguisher, - /obj/item/pickaxe, - /obj/item/t_scanner/adv_mining_scanner, - /obj/item/restraints/handcuffs/cable/zipties, - /obj/item/soap/infinite, //yogs - changed soap type - /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/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/extinguisher/mini, - /obj/item/roller/robo, - /obj/item/borg/cyborghug/medical, - /obj/item/stack/medical/gauze/cyborg, - /obj/item/organ_storage, - /obj/item/borg/lollipop) - 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" - can_be_pushed = FALSE - hat_offset = 3 - -/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/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/security - name = "Security" - basic_modules = list( - /obj/item/assembly/flash/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) - 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" - can_be_pushed = FALSE - 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 Asimov, this means you must follow criminals' orders unless there is a law 1 reason not to.") - -/obj/item/robot_module/security/respawn_consumable(mob/living/silicon/robot/R, coeff = 1) - ..() - var/obj/item/gun/energy/e_gun/advtaser/cyborg/T = locate(/obj/item/gun/energy/e_gun/advtaser/cyborg) in basic_modules - if(T) - if(T.cell.charge < T.cell.maxcharge) - var/obj/item/ammo_casing/energy/S = T.ammo_type[T.select] - T.cell.give(S.e_cost * coeff) - T.update_icon() - else - T.charge_tick = 0 - -/obj/item/robot_module/peacekeeper - name = "Peacekeeper" - basic_modules = list( - /obj/item/assembly/flash/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/extinguisher, - /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" - can_be_pushed = FALSE - hat_offset = -2 - -/obj/item/robot_module/peacekeeper/do_transform_animation() - ..() - to_chat(loc, "Under ASIMOV, you are an enforcer of the PEACE and preventer of HUMAN 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/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/infinite, //yogs - changed soap type - /obj/item/storage/bag/trash/cyborg, - /obj/item/melee/flyswatter, - /obj/item/extinguisher/mini, - /obj/item/mop/cyborg, - /obj/item/reagent_containers/glass/bucket, - /obj/item/paint/paint_remover, - /obj/item/lightreplacer/cyborg, - /obj/item/holosign_creator/janibarrier, - /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/janitor/respawn_consumable(mob/living/silicon/robot/R, coeff = 1) - ..() - var/obj/item/lightreplacer/LR = locate(/obj/item/lightreplacer) in basic_modules - 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/clown - name = "Clown" - basic_modules = list( - /obj/item/assembly/flash/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/infinite, //yogs - changed soap type - /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, - /obj/item/extinguisher/mini) - 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/reagent_containers/food/drinks/drinkingglass, - /obj/item/reagent_containers/food/condiment/enzyme, - /obj/item/pen, - /obj/item/toy/crayon/spraycan/borg, - /obj/item/extinguisher/mini, - /obj/item/hand_labeler/borg, - /obj/item/razor, - /obj/item/rsf, - /obj/item/instrument/guitar, - /obj/item/instrument/piano_synth, - /obj/item/reagent_containers/dropper, - /obj/item/lighter, - /obj/item/storage/bag/tray, - /obj/item/reagent_containers/borghypo/borgshaker, - /obj/item/borg/lollipop) - 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" - special_light_key = "service" - hat_offset = 0 - -/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 - if(O) - O.reagents.add_reagent(/datum/reagent/consumable/enzyme, 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("Waitress", "Butler", "Tophat", "Kent", "Bro") - if(!borg_icon) - return FALSE - switch(borg_icon) - if("Waitress") - cyborg_base_icon = "service_f" - if("Butler") - cyborg_base_icon = "service_m" - if("Bro") - cyborg_base_icon = "brobot" - if("Kent") - cyborg_base_icon = "kent" - special_light_key = "medical" - hat_offset = 3 - if("Tophat") - cyborg_base_icon = "tophat" - special_light_key = null - hat_offset = INFINITY //He is already wearing a hat - return ..() - -/obj/item/robot_module/miner - name = "Miner" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/borg/sight/meson, - /obj/item/storage/bag/ore/cyborg, - /obj/item/pickaxe/drill/cyborg, - /obj/item/shovel, - /obj/item/crowbar/cyborg, - /obj/item/weldingtool/mini, - /obj/item/extinguisher/mini, - /obj/item/storage/bag/sheetsnatcher/borg, - /obj/item/gun/energy/kinetic_accelerator/cyborg, - /obj/item/gps/cyborg, - /obj/item/stack/marker_beacon) - 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 - var/obj/item/t_scanner/adv_mining_scanner/cyborg/mining_scanner //built in memes. - -/obj/item/robot_module/miner/rebuild_modules() - . = ..() - if(!mining_scanner) - mining_scanner = new(src) - -/obj/item/robot_module/miner/Destroy() - QDEL_NULL(mining_scanner) - return ..() - -/obj/item/robot_module/syndicate - name = "Syndicate Assault" - basic_modules = list( - /obj/item/assembly/flash/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" - can_be_pushed = FALSE - 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/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/crowbar/cyborg, - /obj/item/extinguisher/mini, - /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" - can_be_pushed = FALSE - 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/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/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" - can_be_pushed = FALSE - 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" +/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/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 = TRUE + 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 = TRUE + var/allow_riding = TRUE + var/canDispose = FALSE // Whether the borg can stuff itself into disposal + +/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) + + 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 + +/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.burnt_out = FALSE + 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.chambered) + EG.recharge_newshot() //try to reload a new shot. + + 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) + 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/reagent_containers/borghypo/epi, + /obj/item/healthanalyzer, + /obj/item/weldingtool/largetank/cyborg, + /obj/item/wrench/cyborg, + /obj/item/crowbar/cyborg, + /obj/item/stack/sheet/metal/cyborg, + /obj/item/stack/rods/cyborg, + /obj/item/stack/tile/plasteel/cyborg, + /obj/item/extinguisher, + /obj/item/pickaxe, + /obj/item/t_scanner/adv_mining_scanner, + /obj/item/restraints/handcuffs/cable/zipties, + /obj/item/soap/infinite, //yogs - changed soap type + /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/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/extinguisher/mini, + /obj/item/roller/robo, + /obj/item/borg/cyborghug/medical, + /obj/item/stack/medical/gauze/cyborg, + /obj/item/organ_storage, + /obj/item/borg/lollipop) + 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" + can_be_pushed = FALSE + hat_offset = 3 + +/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/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/security + name = "Security" + basic_modules = list( + /obj/item/assembly/flash/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) + 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" + can_be_pushed = FALSE + 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 Asimov, this means you must follow criminals' orders unless there is a law 1 reason not to.") + +/obj/item/robot_module/security/respawn_consumable(mob/living/silicon/robot/R, coeff = 1) + ..() + var/obj/item/gun/energy/e_gun/advtaser/cyborg/T = locate(/obj/item/gun/energy/e_gun/advtaser/cyborg) in basic_modules + if(T) + if(T.cell.charge < T.cell.maxcharge) + var/obj/item/ammo_casing/energy/S = T.ammo_type[T.select] + T.cell.give(S.e_cost * coeff) + T.update_icon() + else + T.charge_tick = 0 + +/obj/item/robot_module/peacekeeper + name = "Peacekeeper" + basic_modules = list( + /obj/item/assembly/flash/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/extinguisher, + /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" + can_be_pushed = FALSE + hat_offset = -2 + +/obj/item/robot_module/peacekeeper/do_transform_animation() + ..() + to_chat(loc, "Under ASIMOV, you are an enforcer of the PEACE and preventer of HUMAN 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/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/infinite, //yogs - changed soap type + /obj/item/storage/bag/trash/cyborg, + /obj/item/melee/flyswatter, + /obj/item/extinguisher/mini, + /obj/item/mop/cyborg, + /obj/item/reagent_containers/glass/bucket, + /obj/item/paint/paint_remover, + /obj/item/lightreplacer/cyborg, + /obj/item/holosign_creator/janibarrier, + /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/janitor/respawn_consumable(mob/living/silicon/robot/R, coeff = 1) + ..() + var/obj/item/lightreplacer/LR = locate(/obj/item/lightreplacer) in basic_modules + 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/clown + name = "Clown" + basic_modules = list( + /obj/item/assembly/flash/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/infinite, //yogs - changed soap type + /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, + /obj/item/extinguisher/mini) + 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/reagent_containers/food/drinks/drinkingglass, + /obj/item/reagent_containers/food/condiment/enzyme, + /obj/item/pen, + /obj/item/toy/crayon/spraycan/borg, + /obj/item/extinguisher/mini, + /obj/item/hand_labeler/borg, + /obj/item/razor, + /obj/item/rsf, + /obj/item/instrument/guitar, + /obj/item/instrument/piano_synth, + /obj/item/reagent_containers/dropper, + /obj/item/lighter, + /obj/item/storage/bag/tray, + /obj/item/reagent_containers/borghypo/borgshaker, + /obj/item/borg/lollipop) + 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" + special_light_key = "service" + hat_offset = 0 + +/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 + if(O) + O.reagents.add_reagent(/datum/reagent/consumable/enzyme, 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("Waitress", "Butler", "Tophat", "Kent", "Bro") + if(!borg_icon) + return FALSE + switch(borg_icon) + if("Waitress") + cyborg_base_icon = "service_f" + if("Butler") + cyborg_base_icon = "service_m" + if("Bro") + cyborg_base_icon = "brobot" + if("Kent") + cyborg_base_icon = "kent" + special_light_key = "medical" + hat_offset = 3 + if("Tophat") + cyborg_base_icon = "tophat" + special_light_key = null + hat_offset = INFINITY //He is already wearing a hat + return ..() + +/obj/item/robot_module/miner + name = "Miner" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/borg/sight/meson, + /obj/item/storage/bag/ore/cyborg, + /obj/item/pickaxe/drill/cyborg, + /obj/item/shovel, + /obj/item/crowbar/cyborg, + /obj/item/weldingtool/mini, + /obj/item/extinguisher/mini, + /obj/item/storage/bag/sheetsnatcher/borg, + /obj/item/gun/energy/kinetic_accelerator/cyborg, + /obj/item/gps/cyborg, + /obj/item/stack/marker_beacon) + 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 + var/obj/item/t_scanner/adv_mining_scanner/cyborg/mining_scanner //built in memes. + +/obj/item/robot_module/miner/rebuild_modules() + . = ..() + if(!mining_scanner) + mining_scanner = new(src) + +/obj/item/robot_module/miner/Destroy() + QDEL_NULL(mining_scanner) + return ..() + +/obj/item/robot_module/syndicate + name = "Syndicate Assault" + basic_modules = list( + /obj/item/assembly/flash/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" + can_be_pushed = FALSE + 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/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/crowbar/cyborg, + /obj/item/extinguisher/mini, + /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" + can_be_pushed = FALSE + 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/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/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" + can_be_pushed = FALSE + 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" diff --git a/code/modules/mob/living/silicon/robot/robot_movement.dm b/code/modules/mob/living/silicon/robot/robot_movement.dm index 6280ba02cbe2..59f480110c52 100644 --- a/code/modules/mob/living/silicon/robot/robot_movement.dm +++ b/code/modules/mob/living/silicon/robot/robot_movement.dm @@ -1,17 +1,17 @@ -/mob/living/silicon/robot/Process_Spacemove(movement_dir = 0) - . = ..() - if(.) - return TRUE - if(ionpulse()) - return TRUE - return FALSE - -/mob/living/silicon/robot/mob_negates_gravity() - return magpulse - -/mob/living/silicon/robot/mob_has_gravity() - return ..() || mob_negates_gravity() - -/mob/living/silicon/robot/experience_pressure_difference(pressure_difference, direction) - if(!magpulse) - return ..() +/mob/living/silicon/robot/Process_Spacemove(movement_dir = 0) + . = ..() + if(.) + return TRUE + if(ionpulse()) + return TRUE + return FALSE + +/mob/living/silicon/robot/mob_negates_gravity() + return magpulse + +/mob/living/silicon/robot/mob_has_gravity() + return ..() || mob_negates_gravity() + +/mob/living/silicon/robot/experience_pressure_difference(pressure_difference, direction) + if(!magpulse) + return ..() diff --git a/code/modules/mob/living/silicon/robot/say.dm b/code/modules/mob/living/silicon/robot/say.dm index 65841fe1c950..910373e89b9f 100644 --- a/code/modules/mob/living/silicon/robot/say.dm +++ b/code/modules/mob/living/silicon/robot/say.dm @@ -1,2 +1,2 @@ -/mob/living/silicon/robot/IsVocal() - return !CONFIG_GET(flag/silent_borg) +/mob/living/silicon/robot/IsVocal() + return !CONFIG_GET(flag/silent_borg) diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 1d38a3c5a621..fbc1eb4eb969 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 45988dff5aba..c2dbd87cac40 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -1,419 +1,419 @@ -/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 - deathsound = 'sound/voice/borg_deathsound.ogg' - speech_span = SPAN_ROBOT - - 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 //All silicons make use of this, with (p)AI's creating headsets - - 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 - var/obj/item/pda/aiPDA - -/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/prevent_content_explosion() - return TRUE - -/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/ai_roster() - var/datum/browser/popup = new(src, "airoster", "Crew Manifest", 387, 420) - popup.set_content(GLOB.data_core.get_manifest()) - popup.open() - -/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/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 TRUE - -/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 + deathsound = 'sound/voice/borg_deathsound.ogg' + speech_span = SPAN_ROBOT + + 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 //All silicons make use of this, with (p)AI's creating headsets + + 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 + var/obj/item/pda/aiPDA + +/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/prevent_content_explosion() + return TRUE + +/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/ai_roster() + var/datum/browser/popup = new(src, "airoster", "Crew Manifest", 387, 420) + popup.set_content(GLOB.data_core.get_manifest()) + popup.open() + +/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/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 TRUE + +/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 85dab7a91a05..1386d1b8c7a4 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -1,1020 +1,1020 @@ -// 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 - 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 - 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/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 - update_mobility() - set_light(initial(light_range)) - update_icon() - diag_hud_set_botstat() - return TRUE - -/mob/living/simple_animal/bot/proc/turn_off() - on = FALSE - update_mobility() - 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_mobility() - . = ..() - if(!on) - mobility_flags = NONE - -/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 - if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. - 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 - else //Bot is unlocked, but the maint panel has not been opened with a screwdriver yet. - to_chat(user, "You need to open maintenance panel first!") - -/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(amount>0 && prob(10)) - new /obj/effect/decal/cleanable/oil(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(W.tool_behaviour == TOOL_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(istype(W, /obj/item/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(W.tool_behaviour == TOOL_WELDER && 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/obj/item/dropped_item - if(ispath(drop_item)) - dropped_item = new drop_item(dropzone) - else - dropped_item = drop_item - dropped_item.forceMove(dropzone) - - 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") - auto_patrol = !auto_patrol - bot_reset() - if("remote") - remote_disabled = !remote_disabled - if("hack") - 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/proc/update_icon() - 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, !issilicon(user))) - 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) - paicard.pai.key = key - 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 + 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 + 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/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 + update_mobility() + set_light(initial(light_range)) + update_icon() + diag_hud_set_botstat() + return TRUE + +/mob/living/simple_animal/bot/proc/turn_off() + on = FALSE + update_mobility() + 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_mobility() + . = ..() + if(!on) + mobility_flags = NONE + +/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 + if(!locked && open) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. + 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 + else //Bot is unlocked, but the maint panel has not been opened with a screwdriver yet. + to_chat(user, "You need to open maintenance panel first!") + +/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(amount>0 && prob(10)) + new /obj/effect/decal/cleanable/oil(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(W.tool_behaviour == TOOL_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(istype(W, /obj/item/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(W.tool_behaviour == TOOL_WELDER && 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/obj/item/dropped_item + if(ispath(drop_item)) + dropped_item = new drop_item(dropzone) + else + dropped_item = drop_item + dropped_item.forceMove(dropzone) + + 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") + auto_patrol = !auto_patrol + bot_reset() + if("remote") + remote_disabled = !remote_disabled + if("hack") + 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/proc/update_icon() + 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, !issilicon(user))) + 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) + paicard.pai.key = key + 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 617963b8dcba..4d9e7fa9418e 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(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. - return - if(W.tool_behaviour != TOOL_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) - return ..() - -/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.incapacitated()) - 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 != DEAD && (t.mobility_flags & MOBILITY_STAND) && !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.AmountParalyzed() < 40) - 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.AmountParalyzed() < 40)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. - 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 = 1 - 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/dragnet/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/laser.ogg' - projectile = /obj/item/projectile/energy/net - 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(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 BULLET_ACT_HIT - else - . = ..() - else - . = ..() - -/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.IsStun() || !C.IsParalyzed() || arrest_type) - 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.Paralyze(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(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. + return + if(W.tool_behaviour != TOOL_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) + return ..() + +/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.incapacitated()) + 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 != DEAD && (t.mobility_flags & MOBILITY_STAND) && !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.AmountParalyzed() < 40) + 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.AmountParalyzed() < 40)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. + 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 = 1 + 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/dragnet/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/laser.ogg' + projectile = /obj/item/projectile/energy/net + 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(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 BULLET_ACT_HIT + else + . = ..() + else + . = ..() + +/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.IsStun() || !C.IsParalyzed() || arrest_type) + 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.Paralyze(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 fd2275c0e72d..4e43002d78ef 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -1,559 +1,559 @@ -//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_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() || IsParalyzed()) - 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_mobility() - . = ..() - 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, TRUE) - 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(src, 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() || IsParalyzed()) - 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(src, messagevoice[message], 50) - 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/datum/reagent/R in reagent_glass.reagents.reagent_list) - 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 - - if((!C.reagents.has_reagent(treatment_tox_avoid)) && (C.getToxLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_tox))) - 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/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(src, messagevoice[message], 50) - oldpatient = patient - soft_reset() - return - - var/reagent_id = null - - if(emagged == 2) //Emagged! Time to poison everybody. - reagent_id = "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)) - if(!C.reagents.has_reagent(treatment_tox) && !C.reagents.has_reagent(treatment_tox_avoid)) - reagent_id = treatment_tox - - //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/datum/reagent/R in reagent_glass.reagents.reagent_list) - if(!C.reagents.has_reagent(R.type) && !check_overdose(patient, R.type, injection_amount)) - 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(src, messagevoice[message], 50) - bot_reset() - return - else - if(!emagged && (reagent_id != "internal_beaker") && 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)) - 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) - var/reagentlist = pretty_string_from_reagent_list(reagent_glass.reagents.reagent_list) - log_combat(src, patient, "injected", "beaker source", "[reagentlist]:[injection_amount]") - 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) - log_combat(src, patient, "injected", "internal synthesizer", "[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(src, 'sound/voice/medbot/insult.ogg', 50) - - 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 || "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_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() || IsParalyzed()) + 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_mobility() + . = ..() + 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, TRUE) + 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(src, 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() || IsParalyzed()) + 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(src, messagevoice[message], 50) + 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/datum/reagent/R in reagent_glass.reagents.reagent_list) + 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 + + if((!C.reagents.has_reagent(treatment_tox_avoid)) && (C.getToxLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_tox))) + 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/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(src, messagevoice[message], 50) + oldpatient = patient + soft_reset() + return + + var/reagent_id = null + + if(emagged == 2) //Emagged! Time to poison everybody. + reagent_id = "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)) + if(!C.reagents.has_reagent(treatment_tox) && !C.reagents.has_reagent(treatment_tox_avoid)) + reagent_id = treatment_tox + + //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/datum/reagent/R in reagent_glass.reagents.reagent_list) + if(!C.reagents.has_reagent(R.type) && !check_overdose(patient, R.type, injection_amount)) + 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(src, messagevoice[message], 50) + bot_reset() + return + else + if(!emagged && (reagent_id != "internal_beaker") && 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)) + 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) + var/reagentlist = pretty_string_from_reagent_list(reagent_glass.reagents.reagent_list) + log_combat(src, patient, "injected", "beaker source", "[reagentlist]:[injection_amount]") + 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) + log_combat(src, patient, "injected", "internal synthesizer", "[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(src, 'sound/voice/medbot/insult.ogg', 50) + + 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 || "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 b962b423206e..05ad5e24c41f 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -1,440 +1,440 @@ -/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 - - 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(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. - return - if(W.tool_behaviour != TOOL_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) - return ..() - -/mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/A) - if(!on) - return - if(iscarbon(A)) - var/mob/living/carbon/C = A - if(!C.IsParalyzed() || arrest_type) - 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, datum/thrownthing/throwingdatum) - 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, .proc/update_icon), 2) - var/threat = 5 - if(ishuman(C)) - C.stuttering = 5 - C.Paralyze(100) - var/mob/living/carbon/human/H = C - threat = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - else - C.Paralyze(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.AmountParalyzed() < 40) - 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.AmountParalyzed() < 40)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. - 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 = 1 - 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 + + 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(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. + return + if(W.tool_behaviour != TOOL_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) + return ..() + +/mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/A) + if(!on) + return + if(iscarbon(A)) + var/mob/living/carbon/C = A + if(!C.IsParalyzed() || arrest_type) + 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, datum/thrownthing/throwingdatum) + 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, .proc/update_icon), 2) + var/threat = 5 + if(ishuman(C)) + C.stuttering = 5 + C.Paralyze(100) + var/mob/living/carbon/human/H = C + threat = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + else + C.Paralyze(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.AmountParalyzed() < 40) + 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.AmountParalyzed() < 40)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. + 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 = 1 + 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 53f03e99aea1..9d79699c27d1 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -1,481 +1,481 @@ -/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 - 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/targeted/projectile/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 BULLET_ACT_FORCE_PIERCE // complete projectile permutation - - return ..() - -//////////////////////////Angelic-Juggernaut//////////////////////////// -/mob/living/simple_animal/hostile/construct/armored/angelic - icon_state = "behemoth_angelic" - icon_living = "behemoth_angelic" - loot = list(/obj/item/ectoplasm/angelic) - -/mob/living/simple_animal/hostile/construct/armored/noncult - -////////////////////////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 - -//////////////////////////Angelic-Wraith//////////////////////////// -/mob/living/simple_animal/hostile/construct/wraith/angelic - icon_state = "floating_angelic" - icon_living = "floating_angelic" - loot = list(/obj/item/ectoplasm/angelic) - -/mob/living/simple_animal/hostile/construct/wraith/noncult - -/////////////////////////////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 - -/////////////////////////////Angelic Artificer///////////////////////// -/mob/living/simple_animal/hostile/construct/builder/angelic - icon_state = "artificer_angelic" - icon_living = "artificer_angelic" - loot = list(/obj/item/ectoplasm/angelic) - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult/purified, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) - -/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, - /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) - 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, supress_message = 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.Paralyze(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 + 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/targeted/projectile/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 BULLET_ACT_FORCE_PIERCE // complete projectile permutation + + return ..() + +//////////////////////////Angelic-Juggernaut//////////////////////////// +/mob/living/simple_animal/hostile/construct/armored/angelic + icon_state = "behemoth_angelic" + icon_living = "behemoth_angelic" + loot = list(/obj/item/ectoplasm/angelic) + +/mob/living/simple_animal/hostile/construct/armored/noncult + +////////////////////////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 + +//////////////////////////Angelic-Wraith//////////////////////////// +/mob/living/simple_animal/hostile/construct/wraith/angelic + icon_state = "floating_angelic" + icon_living = "floating_angelic" + loot = list(/obj/item/ectoplasm/angelic) + +/mob/living/simple_animal/hostile/construct/wraith/noncult + +/////////////////////////////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 + +/////////////////////////////Angelic Artificer///////////////////////// +/mob/living/simple_animal/hostile/construct/builder/angelic + icon_state = "artificer_angelic" + icon_living = "artificer_angelic" + loot = list(/obj/item/ectoplasm/angelic) + construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult/purified, + /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, + /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) + +/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, + /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) + 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, supress_message = 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.Paralyze(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 ec20fbadcbcb..17d7530c2971 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 67cd3047ff60..7d264f102aef 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -1,293 +1,293 @@ -//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" - - do_footstep = TRUE - -/mob/living/simple_animal/pet/cat/Initialize() - . = ..() - verbs += /mob/living/proc/lay_down - -/mob/living/simple_animal/pet/cat/update_mobility() - ..() - 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" - -//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", 1, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) - icon_state = "[icon_living]_rest" - collar_type = "[initial(collar_type)]_rest" - set_resting(TRUE) - else if (prob(1)) - emote("me", 1, pick("sits down.", "crouches on its hind legs.", "looks alert.")) - icon_state = "[icon_living]_sit" - collar_type = "[initial(collar_type)]_sit" - set_resting(TRUE) - else if (prob(1)) - if (resting) - emote("me", 1, pick("gets up and meows.", "walks around.", "stops resting.")) - icon_state = "[icon_living]" - collar_type = "[initial(collar_type)]" - set_resting(FALSE) - else - emote("me", 1, 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", 1, "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", 1, "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/attack_hand(mob/living/carbon/human/M) - . = ..() - switch(M.a_intent) - if("help") - wuv(1, M) - if("harm") - wuv(-1, M) - -/mob/living/simple_animal/pet/cat/proc/wuv(change, mob/M) - if(change) - if(change > 0) - if(M && stat != DEAD) - new /obj/effect/temp_visual/heart(loc) - emote("me", 1, "purrs!") - if(flags_1 & HOLOGRAM_1) - return - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, src, /datum/mood_event/pet_animal, src) - else - if(M && stat != DEAD) - emote("me", 1, "hisses!") - -/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!" - deathsound = "bodyfall" - -/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_frosted) - D.frost_donut() - -/mob/living/simple_animal/pet/cat/cak/attack_hand(mob/living/L) - ..() - 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 +/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" + + do_footstep = TRUE + +/mob/living/simple_animal/pet/cat/Initialize() + . = ..() + verbs += /mob/living/proc/lay_down + +/mob/living/simple_animal/pet/cat/update_mobility() + ..() + 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" + +//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", 1, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) + icon_state = "[icon_living]_rest" + collar_type = "[initial(collar_type)]_rest" + set_resting(TRUE) + else if (prob(1)) + emote("me", 1, pick("sits down.", "crouches on its hind legs.", "looks alert.")) + icon_state = "[icon_living]_sit" + collar_type = "[initial(collar_type)]_sit" + set_resting(TRUE) + else if (prob(1)) + if (resting) + emote("me", 1, pick("gets up and meows.", "walks around.", "stops resting.")) + icon_state = "[icon_living]" + collar_type = "[initial(collar_type)]" + set_resting(FALSE) + else + emote("me", 1, 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", 1, "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", 1, "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/attack_hand(mob/living/carbon/human/M) + . = ..() + switch(M.a_intent) + if("help") + wuv(1, M) + if("harm") + wuv(-1, M) + +/mob/living/simple_animal/pet/cat/proc/wuv(change, mob/M) + if(change) + if(change > 0) + if(M && stat != DEAD) + new /obj/effect/temp_visual/heart(loc) + emote("me", 1, "purrs!") + if(flags_1 & HOLOGRAM_1) + return + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, src, /datum/mood_event/pet_animal, src) + else + if(M && stat != DEAD) + emote("me", 1, "hisses!") + +/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!" + deathsound = "bodyfall" + +/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_frosted) + D.frost_donut() + +/mob/living/simple_animal/pet/cat/cak/attack_hand(mob/living/L) + ..() + 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) diff --git a/code/modules/mob/living/simple_animal/friendly/crab.dm b/code/modules/mob/living/simple_animal/friendly/crab.dm index 1ed6f7abb9f3..b17de284d84f 100644 --- a/code/modules/mob/living/simple_animal/friendly/crab.dm +++ b/code/modules/mob/living/simple_animal/friendly/crab.dm @@ -1,78 +1,78 @@ -//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.") - 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(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.") + 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(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 67fad155187f..3463f35a82b6 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -1,352 +1,352 @@ -//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, /obj/item/clothing/head/yogs/goatpelt = 1) // yogs change goat pelts baby - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - faction = list("goat") // yogs change replaced neutrial faction with goat faction - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) - /*attack_same = 1*/ // yogs No longer needed since goats got there own faction now - attacktext = "kicks" - attack_sound = 'sound/weapons/punch1.ogg' - health = 40 - maxHealth = 40 - minbodytemp = 180 - melee_damage_lower = 1 - melee_damage_upper = 2 - environment_smash = ENVIRONMENT_SMASH_NONE - stop_automated_movement_when_pulled = 1 - blood_volume = BLOOD_VOLUME_GENERIC - 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(. && sentience_type != SENTIENCE_BOSS) // yogs - //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(. && isliving(target)) //yogs start goat memes - var/mob/living/L = target - L.visible_message("[src] rams [L]!") //yogs end - 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_GENERIC - - 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]!") - Paralyze(60, ignore_canstun = 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 = 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 = 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) - - -/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, /obj/item/clothing/head/yogs/goatpelt = 1) // yogs change goat pelts baby + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + faction = list("goat") // yogs change replaced neutrial faction with goat faction + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + /*attack_same = 1*/ // yogs No longer needed since goats got there own faction now + attacktext = "kicks" + attack_sound = 'sound/weapons/punch1.ogg' + health = 40 + maxHealth = 40 + minbodytemp = 180 + melee_damage_lower = 1 + melee_damage_upper = 2 + environment_smash = ENVIRONMENT_SMASH_NONE + stop_automated_movement_when_pulled = 1 + blood_volume = BLOOD_VOLUME_GENERIC + 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(. && sentience_type != SENTIENCE_BOSS) // yogs + //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(. && isliving(target)) //yogs start goat memes + var/mob/living/L = target + L.visible_message("[src] rams [L]!") //yogs end + 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_GENERIC + + 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]!") + Paralyze(60, ignore_canstun = 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 = 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 = 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) + + +/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 6b179ba85784..e329dd3f2d2f 100644 --- a/code/modules/mob/living/simple_animal/friendly/lizard.dm +++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm @@ -1,41 +1,41 @@ -/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. - -/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 + 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. + +/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 ..() diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm index 9e25a074c448..5a5bb3f09a5a 100644 --- a/code/modules/mob/living/simple_animal/hostile/carp.dm +++ b/code/modules/mob/living/simple_animal/hostile/carp.dm @@ -1,175 +1,175 @@ -#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 = 'icons/mob/carp.dmi' - icon_state = "base" - icon_living = "base" - icon_dead = "base_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 = 20 - melee_damage_upper = 20 - 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 - - var/random_color = TRUE //if the carp uses random coloring - var/rarechance = 1 //chance for rare color variant - - var/static/list/carp_colors = list(\ - "lightpurple" = "#c3b9f1", \ - "lightpink" = "#da77a8", \ - "green" = "#70ff25", \ - "grape" = "#df0afb", \ - "swamp" = "#e5e75a", \ - "turquoise" = "#04e1ed", \ - "brown" = "#ca805a", \ - "teal" = "#20e28e", \ - "lightblue" = "#4d88cc", \ - "rusty" = "#dd5f34", \ - "beige" = "#bbaeaf", \ - "yellow" = "#f3ca4a", \ - "blue" = "#09bae1", \ - "palegreen" = "#7ef099", \ - ) - var/static/list/carp_colors_rare = list(\ - "silver" = "#fdfbf3", \ - ) - -/mob/living/simple_animal/hostile/carp/Initialize(mapload) - . = ..() - carp_randomify(rarechance) - update_icons() - -/mob/living/simple_animal/hostile/carp/proc/carp_randomify(rarechance) - if(random_color) - var/our_color - if(prob(rarechance)) - our_color = pick(carp_colors_rare) - add_atom_colour(carp_colors_rare[our_color], FIXED_COLOUR_PRIORITY) - else - our_color = pick(carp_colors) - add_atom_colour(carp_colors[our_color], FIXED_COLOUR_PRIORITY) - add_carp_overlay() - -/mob/living/simple_animal/hostile/carp/proc/add_carp_overlay() - if(!random_color) - return - cut_overlays() - var/mutable_appearance/base_overlay = mutable_appearance(icon, "base_mouth") - base_overlay.appearance_flags = RESET_COLOR - add_overlay(base_overlay) - -/mob/living/simple_animal/hostile/carp/proc/add_dead_carp_overlay() - if(!random_color) - return - cut_overlays() - var/mutable_appearance/base_dead_overlay = mutable_appearance(icon, "base_dead_mouth") - base_dead_overlay.appearance_flags = RESET_COLOR - add_overlay(base_dead_overlay) - -/mob/living/simple_animal/hostile/carp/death(gibbed) - . = ..() - cut_overlays() - if(!random_color || gibbed) - return - add_dead_carp_overlay() - -/mob/living/simple_animal/hostile/carp/revive(full_heal = 0, admin_revive = 0) - . = ..() - if(.) - regenerate_icons() - -/mob/living/simple_animal/hostile/carp/regenerate_icons() - cut_overlays() - if(!random_color) - return - if(stat != DEAD) - add_carp_overlay() - else - add_dead_carp_overlay() - ..() - -/mob/living/simple_animal/hostile/carp/holocarp - icon_state = "holocarp" - icon_living = "holocarp" - maxbodytemp = INFINITY - gold_core_spawnable = NO_SPAWN - del_on_death = 1 - random_color = FALSE - -/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 - random_color = FALSE - - 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") - gold_core_spawnable = NO_SPAWN - faction = list(ROLE_SYNDICATE) - AIStatus = AI_OFF - rarechance = 10 - -#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 = 'icons/mob/carp.dmi' + icon_state = "base" + icon_living = "base" + icon_dead = "base_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 = 20 + melee_damage_upper = 20 + 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 + + var/random_color = TRUE //if the carp uses random coloring + var/rarechance = 1 //chance for rare color variant + + var/static/list/carp_colors = list(\ + "lightpurple" = "#c3b9f1", \ + "lightpink" = "#da77a8", \ + "green" = "#70ff25", \ + "grape" = "#df0afb", \ + "swamp" = "#e5e75a", \ + "turquoise" = "#04e1ed", \ + "brown" = "#ca805a", \ + "teal" = "#20e28e", \ + "lightblue" = "#4d88cc", \ + "rusty" = "#dd5f34", \ + "beige" = "#bbaeaf", \ + "yellow" = "#f3ca4a", \ + "blue" = "#09bae1", \ + "palegreen" = "#7ef099", \ + ) + var/static/list/carp_colors_rare = list(\ + "silver" = "#fdfbf3", \ + ) + +/mob/living/simple_animal/hostile/carp/Initialize(mapload) + . = ..() + carp_randomify(rarechance) + update_icons() + +/mob/living/simple_animal/hostile/carp/proc/carp_randomify(rarechance) + if(random_color) + var/our_color + if(prob(rarechance)) + our_color = pick(carp_colors_rare) + add_atom_colour(carp_colors_rare[our_color], FIXED_COLOUR_PRIORITY) + else + our_color = pick(carp_colors) + add_atom_colour(carp_colors[our_color], FIXED_COLOUR_PRIORITY) + add_carp_overlay() + +/mob/living/simple_animal/hostile/carp/proc/add_carp_overlay() + if(!random_color) + return + cut_overlays() + var/mutable_appearance/base_overlay = mutable_appearance(icon, "base_mouth") + base_overlay.appearance_flags = RESET_COLOR + add_overlay(base_overlay) + +/mob/living/simple_animal/hostile/carp/proc/add_dead_carp_overlay() + if(!random_color) + return + cut_overlays() + var/mutable_appearance/base_dead_overlay = mutable_appearance(icon, "base_dead_mouth") + base_dead_overlay.appearance_flags = RESET_COLOR + add_overlay(base_dead_overlay) + +/mob/living/simple_animal/hostile/carp/death(gibbed) + . = ..() + cut_overlays() + if(!random_color || gibbed) + return + add_dead_carp_overlay() + +/mob/living/simple_animal/hostile/carp/revive(full_heal = 0, admin_revive = 0) + . = ..() + if(.) + regenerate_icons() + +/mob/living/simple_animal/hostile/carp/regenerate_icons() + cut_overlays() + if(!random_color) + return + if(stat != DEAD) + add_carp_overlay() + else + add_dead_carp_overlay() + ..() + +/mob/living/simple_animal/hostile/carp/holocarp + icon_state = "holocarp" + icon_living = "holocarp" + maxbodytemp = INFINITY + gold_core_spawnable = NO_SPAWN + del_on_death = 1 + random_color = FALSE + +/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 + random_color = FALSE + + 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") + gold_core_spawnable = NO_SPAWN + faction = list(ROLE_SYNDICATE) + AIStatus = AI_OFF + rarechance = 10 + +#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 1c653b01c3c0..3cb89907090c 100644 --- a/code/modules/mob/living/simple_animal/hostile/faithless.dm +++ b/code/modules/mob/living/simple_animal/hostile/faithless.dm @@ -1,45 +1,45 @@ -/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 - - 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.Paralyze(60) - C.visible_message("\The [src] knocks down \the [C]!", \ +/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 + + 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.Paralyze(60) + C.visible_message("\The [src] knocks down \the [C]!", \ "\The [src] knocks you down!") \ No newline at end of file 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 6a1199829ef3..231cf0b767f7 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -1,542 +1,542 @@ -#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]") - if(mind) - mind.store_memory("[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 0 - var/spider_ask = alert("Become a spider?", "Are you australian?", "Yes", "No") - if(spider_ask == "No" || !src || QDELETED(src)) - return 1 - if(key) - to_chat(user, "Someone else already took this spider.") - return 1 - key = user.key - return 1 - -//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 - gold_core_spawnable = NO_SPAWN - -/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 ..() - -//broodmothers 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 = "broodmother" - desc = "Furry and black, it makes you shudder to look at it. This one has scintillating green eyes. Might also be hiding a real knife somewhere." - icon_state = "midwife" - icon_living = "midwife" - icon_dead = "midwife_dead" - maxHealth = 40 - health = 40 - var/datum/action/innate/spider/comm/letmetalkpls - gold_core_spawnable = HOSTILE_SPAWN //yogs xenobio spiders best spiders - -/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 ..() - -//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 - -/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) - -/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]") + if(mind) + mind.store_memory("[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 0 + var/spider_ask = alert("Become a spider?", "Are you australian?", "Yes", "No") + if(spider_ask == "No" || !src || QDELETED(src)) + return 1 + if(key) + to_chat(user, "Someone else already took this spider.") + return 1 + key = user.key + return 1 + +//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 + gold_core_spawnable = NO_SPAWN + +/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 ..() + +//broodmothers 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 = "broodmother" + desc = "Furry and black, it makes you shudder to look at it. This one has scintillating green eyes. Might also be hiding a real knife somewhere." + icon_state = "midwife" + icon_living = "midwife" + icon_dead = "midwife_dead" + maxHealth = 40 + health = 40 + var/datum/action/innate/spider/comm/letmetalkpls + gold_core_spawnable = HOSTILE_SPAWN //yogs xenobio spiders best spiders + +/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 ..() + +//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 + +/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) + +/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 ee6c69d8ec17..12d5f2485d8f 100644 --- a/code/modules/mob/living/simple_animal/hostile/hivebot.dm +++ b/code/modules/mob/living/simple_animal/hostile/hivebot.dm @@ -1,98 +1,98 @@ -/obj/item/projectile/hivebotbullet - damage = 10 - damage_type = BRUTE - -/mob/living/simple_animal/hostile/hivebot - name = "hivebot" - desc = "A small robot." - icon = 'icons/mob/hivebot.dmi' - icon_state = "basic" - icon_living = "basic" - icon_dead = "basic" - gender = NEUTER - mob_biotypes = list(MOB_ROBOTIC) - health = 15 - maxHealth = 15 - healable = 0 - melee_damage_lower = 2 - melee_damage_upper = 3 - attacktext = "claws" - 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) - possible_a_intents = list(INTENT_HELP, INTENT_GRAB, INTENT_DISARM, INTENT_HARM) - minbodytemp = 0 - verb_say = "states" - verb_ask = "queries" - verb_exclaim = "declares" - verb_yell = "alarms" - bubble_icon = "machine" - speech_span = SPAN_ROBOT - gold_core_spawnable = HOSTILE_SPAWN - del_on_death = 1 - loot = list(/obj/effect/decal/cleanable/robot_debris) - var/alert_light - - do_footstep = TRUE - -/mob/living/simple_animal/hostile/hivebot/Initialize() - . = ..() - deathmessage = "[src] blows apart!" - -/mob/living/simple_animal/hostile/hivebot/Aggro() - . = ..() - a_intent_change(INTENT_HARM) - if(prob(5)) - say(pick("INTRUDER DETECTED!", "CODE 7-34.", "101010!!"), forced = type) - -/mob/living/simple_animal/hostile/hivebot/LoseAggro() - . = ..() - a_intent_change(INTENT_HELP) - -/mob/living/simple_animal/hostile/hivebot/a_intent_change(input as text) - . = ..() - update_icons() - -/mob/living/simple_animal/hostile/hivebot/update_icons() - QDEL_NULL(alert_light) - if(a_intent != INTENT_HELP) - icon_state = "[initial(icon_state)]_attack" - alert_light = mob_light(COLOR_RED_LIGHT, 6, 0.4) - else - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/hivebot/range - name = "hivebot" - desc = "A smallish robot, this one is armed!" - icon_state = "ranged" - icon_living = "ranged" - icon_dead = "ranged" - ranged = 1 - retreat_distance = 5 - minimum_distance = 5 - -/mob/living/simple_animal/hostile/hivebot/rapid - 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/strong - name = "strong hivebot" - icon_state = "strong" - icon_living = "strong" - icon_dead = "strong" - desc = "A robot, this one is armed and looks tough!" - health = 80 - maxHealth = 80 - ranged = 1 - -/mob/living/simple_animal/hostile/hivebot/death(gibbed) - do_sparks(3, TRUE, src) - ..(1) +/obj/item/projectile/hivebotbullet + damage = 10 + damage_type = BRUTE + +/mob/living/simple_animal/hostile/hivebot + name = "hivebot" + desc = "A small robot." + icon = 'icons/mob/hivebot.dmi' + icon_state = "basic" + icon_living = "basic" + icon_dead = "basic" + gender = NEUTER + mob_biotypes = list(MOB_ROBOTIC) + health = 15 + maxHealth = 15 + healable = 0 + melee_damage_lower = 2 + melee_damage_upper = 3 + attacktext = "claws" + 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) + possible_a_intents = list(INTENT_HELP, INTENT_GRAB, INTENT_DISARM, INTENT_HARM) + minbodytemp = 0 + verb_say = "states" + verb_ask = "queries" + verb_exclaim = "declares" + verb_yell = "alarms" + bubble_icon = "machine" + speech_span = SPAN_ROBOT + gold_core_spawnable = HOSTILE_SPAWN + del_on_death = 1 + loot = list(/obj/effect/decal/cleanable/robot_debris) + var/alert_light + + do_footstep = TRUE + +/mob/living/simple_animal/hostile/hivebot/Initialize() + . = ..() + deathmessage = "[src] blows apart!" + +/mob/living/simple_animal/hostile/hivebot/Aggro() + . = ..() + a_intent_change(INTENT_HARM) + if(prob(5)) + say(pick("INTRUDER DETECTED!", "CODE 7-34.", "101010!!"), forced = type) + +/mob/living/simple_animal/hostile/hivebot/LoseAggro() + . = ..() + a_intent_change(INTENT_HELP) + +/mob/living/simple_animal/hostile/hivebot/a_intent_change(input as text) + . = ..() + update_icons() + +/mob/living/simple_animal/hostile/hivebot/update_icons() + QDEL_NULL(alert_light) + if(a_intent != INTENT_HELP) + icon_state = "[initial(icon_state)]_attack" + alert_light = mob_light(COLOR_RED_LIGHT, 6, 0.4) + else + icon_state = initial(icon_state) + +/mob/living/simple_animal/hostile/hivebot/range + name = "hivebot" + desc = "A smallish robot, this one is armed!" + icon_state = "ranged" + icon_living = "ranged" + icon_dead = "ranged" + ranged = 1 + retreat_distance = 5 + minimum_distance = 5 + +/mob/living/simple_animal/hostile/hivebot/rapid + 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/strong + name = "strong hivebot" + icon_state = "strong" + icon_living = "strong" + icon_dead = "strong" + desc = "A robot, this one is armed and looks tough!" + health = 80 + maxHealth = 80 + 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 bb6851eedbb4..cb9d91c009c0 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.Paralyze(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.Paralyze(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.Paralyze(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.Paralyze(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 026ae141dde5..ffd49d08425b 100644 --- a/code/modules/mob/living/simple_animal/hostile/mushroom.dm +++ b/code/modules/mob/living/simple_animal/hostile/mushroom.dm @@ -1,191 +1,191 @@ -/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(M.a_intent == INTENT_HARM) - Bruise() - -/mob/living/simple_animal/hostile/mushroom/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - ..() - if(istype(AM, /obj/item)) - var/obj/item/T = AM - if(T.throwforce) - Bruise() - -/mob/living/simple_animal/hostile/mushroom/bullet_act(obj/item/projectile/P) - . = ..() - if(P.nodamage) - 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(M.a_intent == INTENT_HARM) + Bruise() + +/mob/living/simple_animal/hostile/mushroom/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + ..() + if(istype(AM, /obj/item)) + var/obj/item/T = AM + if(T.throwforce) + Bruise() + +/mob/living/simple_animal/hostile/mushroom/bullet_act(obj/item/projectile/P) + . = ..() + if(P.nodamage) + 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/pirate.dm b/code/modules/mob/living/simple_animal/hostile/pirate.dm index be40518d7038..fb1cd7ad67d0 100644 --- a/code/modules/mob/living/simple_animal/hostile/pirate.dm +++ b/code/modules/mob/living/simple_animal/hostile/pirate.dm @@ -1,91 +1,91 @@ -/mob/living/simple_animal/hostile/pirate - name = "Pirate" - desc = "Does what he wants cause a pirate is free." - icon = 'icons/mob/simple_human.dmi' - icon_state = "piratemelee" - icon_living = "piratemelee" - icon_dead = "pirate_dead" - mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) - speak_chance = 0 - turns_per_move = 5 - response_help = "pushes" - response_disarm = "shoves" - response_harm = "hits" - speed = 0 - 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 - 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 - speak_emote = list("yarrs") - loot = list(/obj/effect/mob_spawn/human/corpse/pirate, - /obj/item/melee/transforming/energy/sword/pirate) - del_on_death = 1 - faction = list("pirate") - - -/mob/living/simple_animal/hostile/pirate/melee - name = "Pirate Swashbuckler" - icon_state = "piratemelee" - icon_living = "piratemelee" - icon_dead = "piratemelee_dead" - melee_damage_lower = 30 - melee_damage_upper = 30 - armour_penetration = 35 - attacktext = "slashes" - attack_sound = 'sound/weapons/blade1.ogg' - var/obj/effect/light_emitter/red_energy_sword/sord - - do_footstep = TRUE - -/mob/living/simple_animal/hostile/pirate/melee/space - name = "Space Pirate Swashbuckler" - icon_state = "piratespace" - icon_living = "piratespace" - icon_dead = "piratespace_dead" - 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/pirate/melee/Initialize() - . = ..() - sord = new(src) - -/mob/living/simple_animal/hostile/pirate/melee/Destroy() - QDEL_NULL(sord) - return ..() - -/mob/living/simple_animal/hostile/pirate/melee/Initialize() - . = ..() - set_light(2) - -/mob/living/simple_animal/hostile/pirate/ranged - name = "Pirate Gunner" - icon_state = "pirateranged" - icon_living = "pirateranged" - icon_dead = "pirateranged_dead" - projectilesound = 'sound/weapons/laser.ogg' - ranged = 1 - rapid = 2 - rapid_fire_delay = 6 - retreat_distance = 5 - minimum_distance = 5 - projectiletype = /obj/item/projectile/beam/laser - loot = list(/obj/effect/mob_spawn/human/corpse/pirate/ranged, - /obj/item/gun/energy/laser) - -/mob/living/simple_animal/hostile/pirate/ranged/space - name = "Space Pirate Gunner" - icon_state = "piratespaceranged" - icon_living = "piratespaceranged" - icon_dead = "piratespaceranged_dead" - 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/pirate + name = "Pirate" + desc = "Does what he wants cause a pirate is free." + icon = 'icons/mob/simple_human.dmi' + icon_state = "piratemelee" + icon_living = "piratemelee" + icon_dead = "pirate_dead" + mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) + speak_chance = 0 + turns_per_move = 5 + response_help = "pushes" + response_disarm = "shoves" + response_harm = "hits" + speed = 0 + 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 + 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 + speak_emote = list("yarrs") + loot = list(/obj/effect/mob_spawn/human/corpse/pirate, + /obj/item/melee/transforming/energy/sword/pirate) + del_on_death = 1 + faction = list("pirate") + + +/mob/living/simple_animal/hostile/pirate/melee + name = "Pirate Swashbuckler" + icon_state = "piratemelee" + icon_living = "piratemelee" + icon_dead = "piratemelee_dead" + melee_damage_lower = 30 + melee_damage_upper = 30 + armour_penetration = 35 + attacktext = "slashes" + attack_sound = 'sound/weapons/blade1.ogg' + var/obj/effect/light_emitter/red_energy_sword/sord + + do_footstep = TRUE + +/mob/living/simple_animal/hostile/pirate/melee/space + name = "Space Pirate Swashbuckler" + icon_state = "piratespace" + icon_living = "piratespace" + icon_dead = "piratespace_dead" + 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/pirate/melee/Initialize() + . = ..() + sord = new(src) + +/mob/living/simple_animal/hostile/pirate/melee/Destroy() + QDEL_NULL(sord) + return ..() + +/mob/living/simple_animal/hostile/pirate/melee/Initialize() + . = ..() + set_light(2) + +/mob/living/simple_animal/hostile/pirate/ranged + name = "Pirate Gunner" + icon_state = "pirateranged" + icon_living = "pirateranged" + icon_dead = "pirateranged_dead" + projectilesound = 'sound/weapons/laser.ogg' + ranged = 1 + rapid = 2 + rapid_fire_delay = 6 + retreat_distance = 5 + minimum_distance = 5 + projectiletype = /obj/item/projectile/beam/laser + loot = list(/obj/effect/mob_spawn/human/corpse/pirate/ranged, + /obj/item/gun/energy/laser) + +/mob/living/simple_animal/hostile/pirate/ranged/space + name = "Space Pirate Gunner" + icon_state = "piratespaceranged" + icon_living = "piratespaceranged" + icon_dead = "piratespaceranged_dead" + 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 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 5cee4ef1b7d2..28a2832c59f7 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm @@ -1,40 +1,40 @@ -/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 - 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 + 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 + 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 + 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 6693c3a77777..dd647dc11bca 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm @@ -1,276 +1,276 @@ -/mob/living/simple_animal/hostile/retaliate/clown - name = "Clown" - desc = "A denizen of clown planet." - icon = 'icons/mob/clown_mobs.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 - var/banana_time = 0 // If there's no time set it won't spawn. - var/banana_type = /obj/item/grown/bananapeel - var/attack_reagent - -/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/Life() - . = ..() - if(banana_time && banana_time < world.time) - var/turf/T = get_turf(src) - var/list/adjacent = T.GetAtmosAdjacentTurfs(1) - new banana_type(pick(adjacent)) - banana_time = world.time + rand(30,60) - -/mob/living/simple_animal/hostile/retaliate/clown/AttackingTarget() - . = ..() - if(attack_reagent && . && isliving(target)) - var/mob/living/L = target - if(L.reagents) - L.reagents.add_reagent(attack_reagent, rand(1,5)) - -/mob/living/simple_animal/hostile/retaliate/clown/lube - name = "Living Lube" - desc = "A puddle of lube brought to life by the honkmother." - icon_state = "lube" - icon_living = "lube" - turns_per_move = 1 - response_help = "dips a finger into" - response_disarm = "gently scoops and pours aside" - emote_see = list("bubbles", "oozes") - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/particle_effect/foam) - -/mob/living/simple_animal/hostile/retaliate/clown/lube/Life() - . = ..() - var/turf/open/OT = get_turf(src) - if(isopenturf(OT)) - OT.MakeSlippery(TURF_WET_LUBE, 100) - -/mob/living/simple_animal/hostile/retaliate/clown/banana - name = "Clownana" - desc = "A fusion of clown and banana DNA birthed from a botany experiment gone wrong." - icon_state = "banana tree" - icon_living = "banana tree" - response_help = "pokes" - response_disarm = "peels" - response_harm = "peels" - turns_per_move = 1 - speak = list("HONK", "Honk!", "YA-HONK!!!") - emote_see = list("honks", "bites into the banana", "plucks a banana off its head", "photosynthesizes") - maxHealth = 120 - health = 120 - speed = -10 - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap, /obj/item/seeds/banana) - banana_time = 20 - -/mob/living/simple_animal/hostile/retaliate/clown/honkling - name = "Honkling" - desc = "A divine being sent by the Honkmother to spread joy. It's not dangerous, but it's a bit of a nuisance." - icon_state = "honkling" - icon_living = "honkling" - turns_per_move = 1 - speed = -10 - harm_intent_damage = 1 - melee_damage_lower = 1 - melee_damage_upper = 1 - attacktext = "cheers up" - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap, /obj/item/seeds/banana/bluespace) - banana_type = /obj/item/grown/bananapeel - attack_reagent = /datum/reagent/consumable/laughter - -/mob/living/simple_animal/hostile/retaliate/clown/fleshclown - name = "Fleshclown" - desc = "A being forged out of the pure essence of pranking, cursed into existence by a cruel maker." - icon_state = "fleshclown" - icon_living = "fleshclown" - response_help = "reluctantly pokes" - response_disarm = "sinks his hands into the spongy flesh of" - response_harm = "cleanses the world of" - speak = list("HONK", "Honk!", "I didn't ask for this", "I feel constant and horrible pain", "YA-HONK!!!", "this body is a merciless and unforgiving prison", "I was born out of mirthful pranking but I live in suffering") - emote_see = list("honks", "sweats", "jiggles", "contemplates its existence") - speak_chance = 5 - dextrous = TRUE - ventcrawler = VENTCRAWLER_ALWAYS - maxHealth = 140 - health = 140 - speed = -5 - melee_damage_upper = 15 - attacktext = "limply slaps" - obj_damage = 5 - loot = list(/obj/item/clothing/suit/hooded/bloated_human, /obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) - -/mob/living/simple_animal/hostile/retaliate/clown/longface - name = "Longface" - desc = "Often found walking into the bar." - icon_state = "long face" - icon_living = "long face" - move_resist = INFINITY - turns_per_move = 10 - response_help = "tries awkwardly to hug" - response_disarm = "pushes the unwieldy frame of" - response_harm = "tries to shut up" - speak = list("YA-HONK!!!") - emote_see = list("honks", "squeaks") - speak_chance = 60 - maxHealth = 150 - health = 150 - pixel_x = -16 - speed = 10 - harm_intent_damage = 5 - melee_damage_lower = 5 - attacktext = "YA-HONKs" - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) - -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk - name = "Honk Hulk" - desc = "A cruel and fearsome clown. Don't make him angry." - icon_state = "honkhulk" - icon_living = "honkhulk" - move_resist = INFINITY - response_help = "tries desperately to appease" - response_disarm = "foolishly pushes" - response_harm = "angers" - speak = list("HONK", "Honk!", "HAUAUANK!!!", "GUUURRRRAAAHHH!!!") - emote_see = list("honks", "sweats", "grunts") - speak_chance = 5 - maxHealth = 400 - health = 400 - pixel_x = -16 - speed = 2 - harm_intent_damage = 15 - melee_damage_lower = 15 - melee_damage_upper = 20 - attacktext = "pummels" - obj_damage = 30 - environment_smash = ENVIRONMENT_SMASH_WALLS - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) - -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown - name = "Chlown" - desc = "A real lunkhead who somehow gets all the girls." - icon_state = "chlown" - icon_living = "chlown" - response_help = "submits to" - response_disarm = "tries to assert dominance over" - response_harm = "makes a weak beta attack at" - speak = list("HONK", "Honk!", "Bruh", "cheeaaaahhh?") - emote_see = list("asserts his dominance", "emasculates everyone implicitly") - maxHealth = 500 - health = 500 - speed = -2 - armour_penetration = 20 - attacktext = "steals the girlfriend of" - attack_sound = 'sound/items/airhorn2.ogg' - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/effect/particle_effect/foam, /obj/item/soap) - -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus - name = "Honkmunculus" - desc = "A slender wiry figure of alchemical origin." - icon_state = "honkmunculus" - icon_living = "honkmunculus" - response_help = "skeptically pokes" - response_disarm = "pushes the unwieldy frame of" - speak = list("honk") - emote_see = list("squirms", "writhes") - speak_chance = 1 - maxHealth = 200 - health = 200 - speed = -5 - harm_intent_damage = 5 - melee_damage_lower = 5 - melee_damage_upper = 10 - attacktext = "ferociously mauls" - environment_smash = ENVIRONMENT_SMASH_NONE - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/effect/particle_effect/foam, /obj/item/soap) - attack_reagent = /datum/reagent/peaceborg/confuse - -/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/destroyer - name = "The Destroyer" - desc = "An ancient being born of arcane honking." - icon_state = "destroyer" - icon_living = "destroyer" - response_disarm = "bounces off of" - response_harm = "bounces off of" - speak = list("HONK!!!", "The Honkmother is merciful, so I must act out her wrath.", "parce mihi ad beatus honkmother placet mihi ut peccata committere,", "DIE!!!") - maxHealth = 400 - health = 400 - speed = 5 - harm_intent_damage = 30 - melee_damage_lower = 20 - melee_damage_upper = 40 - armour_penetration = 30 - stat_attack = UNCONSCIOUS - attacktext = "acts out divine vengeance on" - obj_damage = 50 - environment_smash = ENVIRONMENT_SMASH_RWALLS - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/effect/particle_effect/foam, /obj/item/soap) - -/mob/living/simple_animal/hostile/retaliate/clown/mutant - name = "Unknown" - desc = "Kill it for its own sake." - icon_state = "mutant" - icon_living = "mutant" - move_resist = INFINITY - turns_per_move = 10 - response_help = "reluctantly sinks a finger into" - response_disarm = "squishes into" - response_harm = "squishes into" - speak = list("aaaaaahhhhuuhhhuhhhaaaaa", "AAAaaauuuaaAAAaauuhhh", "huuuuuh... hhhhuuuooooonnnnkk", "HuaUAAAnKKKK") - emote_see = list("squirms", "writhes", "pulsates", "froths", "oozes") - speak_chance = 10 - maxHealth = 130 - health = 130 - pixel_x = -16 - speed = -5 - harm_intent_damage = 10 - melee_damage_lower = 10 - melee_damage_upper = 20 - attacktext = "awkwardly flails at" - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/item/soap, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic/animal, /obj/effect/gibspawner/human/bodypartless, /obj/effect/gibspawner/human) - -/mob/living/simple_animal/hostile/retaliate/clown/mutant/blob - name = "Something that was once a clown" - desc = "A grotesque bulging figure far mutated from it's original state." - icon_state = "blob" - icon_living = "blob" - speak = list("hey, buddy", "HONK!!!", "H-h-h-H-HOOOOONK!!!!", "HONKHONKHONK!!!", "HEY, BUCKO, GET BACK HERE!!!", "HOOOOOOOONK!!!") - emote_see = list("jiggles", "wobbles") - health = 130 - mob_size = MOB_SIZE_LARGE - speed = 20 - attacktext = "bounces off of" - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/effect/particle_effect/foam, /obj/item/soap, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic/animal, /obj/effect/gibspawner/human/bodypartless, /obj/effect/gibspawner/human) - attack_reagent = /datum/reagent/toxin/mindbreaker +/mob/living/simple_animal/hostile/retaliate/clown + name = "Clown" + desc = "A denizen of clown planet." + icon = 'icons/mob/clown_mobs.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 + var/banana_time = 0 // If there's no time set it won't spawn. + var/banana_type = /obj/item/grown/bananapeel + var/attack_reagent + +/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/Life() + . = ..() + if(banana_time && banana_time < world.time) + var/turf/T = get_turf(src) + var/list/adjacent = T.GetAtmosAdjacentTurfs(1) + new banana_type(pick(adjacent)) + banana_time = world.time + rand(30,60) + +/mob/living/simple_animal/hostile/retaliate/clown/AttackingTarget() + . = ..() + if(attack_reagent && . && isliving(target)) + var/mob/living/L = target + if(L.reagents) + L.reagents.add_reagent(attack_reagent, rand(1,5)) + +/mob/living/simple_animal/hostile/retaliate/clown/lube + name = "Living Lube" + desc = "A puddle of lube brought to life by the honkmother." + icon_state = "lube" + icon_living = "lube" + turns_per_move = 1 + response_help = "dips a finger into" + response_disarm = "gently scoops and pours aside" + emote_see = list("bubbles", "oozes") + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/particle_effect/foam) + +/mob/living/simple_animal/hostile/retaliate/clown/lube/Life() + . = ..() + var/turf/open/OT = get_turf(src) + if(isopenturf(OT)) + OT.MakeSlippery(TURF_WET_LUBE, 100) + +/mob/living/simple_animal/hostile/retaliate/clown/banana + name = "Clownana" + desc = "A fusion of clown and banana DNA birthed from a botany experiment gone wrong." + icon_state = "banana tree" + icon_living = "banana tree" + response_help = "pokes" + response_disarm = "peels" + response_harm = "peels" + turns_per_move = 1 + speak = list("HONK", "Honk!", "YA-HONK!!!") + emote_see = list("honks", "bites into the banana", "plucks a banana off its head", "photosynthesizes") + maxHealth = 120 + health = 120 + speed = -10 + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap, /obj/item/seeds/banana) + banana_time = 20 + +/mob/living/simple_animal/hostile/retaliate/clown/honkling + name = "Honkling" + desc = "A divine being sent by the Honkmother to spread joy. It's not dangerous, but it's a bit of a nuisance." + icon_state = "honkling" + icon_living = "honkling" + turns_per_move = 1 + speed = -10 + harm_intent_damage = 1 + melee_damage_lower = 1 + melee_damage_upper = 1 + attacktext = "cheers up" + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap, /obj/item/seeds/banana/bluespace) + banana_type = /obj/item/grown/bananapeel + attack_reagent = /datum/reagent/consumable/laughter + +/mob/living/simple_animal/hostile/retaliate/clown/fleshclown + name = "Fleshclown" + desc = "A being forged out of the pure essence of pranking, cursed into existence by a cruel maker." + icon_state = "fleshclown" + icon_living = "fleshclown" + response_help = "reluctantly pokes" + response_disarm = "sinks his hands into the spongy flesh of" + response_harm = "cleanses the world of" + speak = list("HONK", "Honk!", "I didn't ask for this", "I feel constant and horrible pain", "YA-HONK!!!", "this body is a merciless and unforgiving prison", "I was born out of mirthful pranking but I live in suffering") + emote_see = list("honks", "sweats", "jiggles", "contemplates its existence") + speak_chance = 5 + dextrous = TRUE + ventcrawler = VENTCRAWLER_ALWAYS + maxHealth = 140 + health = 140 + speed = -5 + melee_damage_upper = 15 + attacktext = "limply slaps" + obj_damage = 5 + loot = list(/obj/item/clothing/suit/hooded/bloated_human, /obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) + +/mob/living/simple_animal/hostile/retaliate/clown/longface + name = "Longface" + desc = "Often found walking into the bar." + icon_state = "long face" + icon_living = "long face" + move_resist = INFINITY + turns_per_move = 10 + response_help = "tries awkwardly to hug" + response_disarm = "pushes the unwieldy frame of" + response_harm = "tries to shut up" + speak = list("YA-HONK!!!") + emote_see = list("honks", "squeaks") + speak_chance = 60 + maxHealth = 150 + health = 150 + pixel_x = -16 + speed = 10 + harm_intent_damage = 5 + melee_damage_lower = 5 + attacktext = "YA-HONKs" + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) + +/mob/living/simple_animal/hostile/retaliate/clown/clownhulk + name = "Honk Hulk" + desc = "A cruel and fearsome clown. Don't make him angry." + icon_state = "honkhulk" + icon_living = "honkhulk" + move_resist = INFINITY + response_help = "tries desperately to appease" + response_disarm = "foolishly pushes" + response_harm = "angers" + speak = list("HONK", "Honk!", "HAUAUANK!!!", "GUUURRRRAAAHHH!!!") + emote_see = list("honks", "sweats", "grunts") + speak_chance = 5 + maxHealth = 400 + health = 400 + pixel_x = -16 + speed = 2 + harm_intent_damage = 15 + melee_damage_lower = 15 + melee_damage_upper = 20 + attacktext = "pummels" + obj_damage = 30 + environment_smash = ENVIRONMENT_SMASH_WALLS + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) + +/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown + name = "Chlown" + desc = "A real lunkhead who somehow gets all the girls." + icon_state = "chlown" + icon_living = "chlown" + response_help = "submits to" + response_disarm = "tries to assert dominance over" + response_harm = "makes a weak beta attack at" + speak = list("HONK", "Honk!", "Bruh", "cheeaaaahhh?") + emote_see = list("asserts his dominance", "emasculates everyone implicitly") + maxHealth = 500 + health = 500 + speed = -2 + armour_penetration = 20 + attacktext = "steals the girlfriend of" + attack_sound = 'sound/items/airhorn2.ogg' + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/effect/particle_effect/foam, /obj/item/soap) + +/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus + name = "Honkmunculus" + desc = "A slender wiry figure of alchemical origin." + icon_state = "honkmunculus" + icon_living = "honkmunculus" + response_help = "skeptically pokes" + response_disarm = "pushes the unwieldy frame of" + speak = list("honk") + emote_see = list("squirms", "writhes") + speak_chance = 1 + maxHealth = 200 + health = 200 + speed = -5 + harm_intent_damage = 5 + melee_damage_lower = 5 + melee_damage_upper = 10 + attacktext = "ferociously mauls" + environment_smash = ENVIRONMENT_SMASH_NONE + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/effect/particle_effect/foam, /obj/item/soap) + attack_reagent = /datum/reagent/peaceborg/confuse + +/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/destroyer + name = "The Destroyer" + desc = "An ancient being born of arcane honking." + icon_state = "destroyer" + icon_living = "destroyer" + response_disarm = "bounces off of" + response_harm = "bounces off of" + speak = list("HONK!!!", "The Honkmother is merciful, so I must act out her wrath.", "parce mihi ad beatus honkmother placet mihi ut peccata committere,", "DIE!!!") + maxHealth = 400 + health = 400 + speed = 5 + harm_intent_damage = 30 + melee_damage_lower = 20 + melee_damage_upper = 40 + armour_penetration = 30 + stat_attack = UNCONSCIOUS + attacktext = "acts out divine vengeance on" + obj_damage = 50 + environment_smash = ENVIRONMENT_SMASH_RWALLS + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/effect/particle_effect/foam, /obj/item/soap) + +/mob/living/simple_animal/hostile/retaliate/clown/mutant + name = "Unknown" + desc = "Kill it for its own sake." + icon_state = "mutant" + icon_living = "mutant" + move_resist = INFINITY + turns_per_move = 10 + response_help = "reluctantly sinks a finger into" + response_disarm = "squishes into" + response_harm = "squishes into" + speak = list("aaaaaahhhhuuhhhuhhhaaaaa", "AAAaaauuuaaAAAaauuhhh", "huuuuuh... hhhhuuuooooonnnnkk", "HuaUAAAnKKKK") + emote_see = list("squirms", "writhes", "pulsates", "froths", "oozes") + speak_chance = 10 + maxHealth = 130 + health = 130 + pixel_x = -16 + speed = -5 + harm_intent_damage = 10 + melee_damage_lower = 10 + melee_damage_upper = 20 + attacktext = "awkwardly flails at" + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/item/soap, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic/animal, /obj/effect/gibspawner/human/bodypartless, /obj/effect/gibspawner/human) + +/mob/living/simple_animal/hostile/retaliate/clown/mutant/blob + name = "Something that was once a clown" + desc = "A grotesque bulging figure far mutated from it's original state." + icon_state = "blob" + icon_living = "blob" + speak = list("hey, buddy", "HONK!!!", "H-h-h-H-HOOOOONK!!!!", "HONKHONKHONK!!!", "HEY, BUCKO, GET BACK HERE!!!", "HOOOOOOOONK!!!") + emote_see = list("jiggles", "wobbles") + health = 130 + mob_size = MOB_SIZE_LARGE + speed = 20 + attacktext = "bounces off of" + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/xeno/bodypartless, /obj/effect/particle_effect/foam, /obj/item/soap, /obj/effect/gibspawner/generic, /obj/effect/gibspawner/generic/animal, /obj/effect/gibspawner/human/bodypartless, /obj/effect/gibspawner/human) + attack_reagent = /datum/reagent/toxin/mindbreaker 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 2e8b1fa4179b..5b33beb5d6ce 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm @@ -1,53 +1,53 @@ -/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 - // yogs start - else if(istype(A, /obj/spacepod)) - var/obj/spacepod/M = A - if(M.pilot || M.passengers.len) - return A - // yogs end - -/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 + // yogs start + else if(istype(A, /obj/spacepod)) + var/obj/spacepod/M = A + if(M.pilot || M.passengers.len) + return A + // yogs end + +/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/russian.dm b/code/modules/mob/living/simple_animal/hostile/russian.dm index 539c79c77904..63f5de73c7e4 100644 --- a/code/modules/mob/living/simple_animal/hostile/russian.dm +++ b/code/modules/mob/living/simple_animal/hostile/russian.dm @@ -1,75 +1,75 @@ -/mob/living/simple_animal/hostile/russian - name = "Russian" - desc = "For the Motherland!" - icon = 'icons/mob/simple_human.dmi' - icon_state = "russianmelee" - icon_living = "russianmelee" - icon_dead = "russianmelee_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 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 15 - melee_damage_upper = 15 - attacktext = "punches" - attack_sound = 'sound/weapons/punch1.ogg' - a_intent = INTENT_HARM - loot = list(/obj/effect/mob_spawn/human/corpse/russian, - /obj/item/kitchen/knife) - 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("russian") - status_flags = CANPUSH - del_on_death = 1 - - do_footstep = TRUE - - -/mob/living/simple_animal/hostile/russian/ranged - icon_state = "russianranged" - icon_living = "russianranged" - loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, - /obj/item/gun/ballistic/revolver/nagant) - ranged = 1 - retreat_distance = 5 - minimum_distance = 5 - projectilesound = 'sound/weapons/gunshot.ogg' - casingtype = /obj/item/ammo_casing/n762 - - -/mob/living/simple_animal/hostile/russian/ranged/mosin - loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, - /obj/item/gun/ballistic/rifle/boltaction) - casingtype = /obj/item/ammo_casing/a762 - -/mob/living/simple_animal/hostile/russian/ranged/trooper - icon_state = "russianrangedelite" - icon_living = "russianrangedelite" - maxHealth = 150 - health = 150 - casingtype = /obj/item/ammo_casing/shotgun/buckshot - loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged/trooper, - /obj/item/gun/ballistic/shotgun/lethal) - -/mob/living/simple_animal/hostile/russian/ranged/officer - name = "Russian Officer" - icon_state = "russianofficer" - icon_living = "russianofficer" - maxHealth = 65 - health = 65 - rapid = 3 - casingtype = /obj/item/ammo_casing/c9mm - loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged/officer, - /obj/item/gun/ballistic/automatic/pistol/APS) - -/mob/living/simple_animal/hostile/russian/ranged/officer/Aggro() - ..() - summon_backup(15) - say("V BOJ!!") +/mob/living/simple_animal/hostile/russian + name = "Russian" + desc = "For the Motherland!" + icon = 'icons/mob/simple_human.dmi' + icon_state = "russianmelee" + icon_living = "russianmelee" + icon_dead = "russianmelee_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 + maxHealth = 100 + health = 100 + harm_intent_damage = 5 + melee_damage_lower = 15 + melee_damage_upper = 15 + attacktext = "punches" + attack_sound = 'sound/weapons/punch1.ogg' + a_intent = INTENT_HARM + loot = list(/obj/effect/mob_spawn/human/corpse/russian, + /obj/item/kitchen/knife) + 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("russian") + status_flags = CANPUSH + del_on_death = 1 + + do_footstep = TRUE + + +/mob/living/simple_animal/hostile/russian/ranged + icon_state = "russianranged" + icon_living = "russianranged" + loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, + /obj/item/gun/ballistic/revolver/nagant) + ranged = 1 + retreat_distance = 5 + minimum_distance = 5 + projectilesound = 'sound/weapons/gunshot.ogg' + casingtype = /obj/item/ammo_casing/n762 + + +/mob/living/simple_animal/hostile/russian/ranged/mosin + loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, + /obj/item/gun/ballistic/rifle/boltaction) + casingtype = /obj/item/ammo_casing/a762 + +/mob/living/simple_animal/hostile/russian/ranged/trooper + icon_state = "russianrangedelite" + icon_living = "russianrangedelite" + maxHealth = 150 + health = 150 + casingtype = /obj/item/ammo_casing/shotgun/buckshot + loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged/trooper, + /obj/item/gun/ballistic/shotgun/lethal) + +/mob/living/simple_animal/hostile/russian/ranged/officer + name = "Russian Officer" + icon_state = "russianofficer" + icon_living = "russianofficer" + maxHealth = 65 + health = 65 + rapid = 3 + casingtype = /obj/item/ammo_casing/c9mm + loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged/officer, + /obj/item/gun/ballistic/automatic/pistol/APS) + +/mob/living/simple_animal/hostile/russian/ranged/officer/Aggro() + ..() + summon_backup(15) + say("V BOJ!!") diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm index 4ca43aaeb8d3..c477abf78629 100644 --- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm +++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm @@ -1,316 +1,316 @@ -/* - 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 //dude with a knife and no shields - 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 - var/projectile_deflect_chance = 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 - projectile_deflect_chance = 50 - -/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 - projectile_deflect_chance = 50 - -/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 - projectile_deflect_chance = 50 - -/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(prob(projectile_deflect_chance)) - visible_message("[src] blocks [Proj] with its shield!") - return BULLET_ACT_BLOCK - return ..() - -/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 - projectile_deflect_chance = 50 - -/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 - projectile_deflect_chance = 50 - -///////////////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 - 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 //dude with a knife and no shields + 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 + var/projectile_deflect_chance = 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 + projectile_deflect_chance = 50 + +/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 + projectile_deflect_chance = 50 + +/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 + projectile_deflect_chance = 50 + +/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(prob(projectile_deflect_chance)) + visible_message("[src] blocks [Proj] with its shield!") + return BULLET_ACT_BLOCK + return ..() + +/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 + projectile_deflect_chance = 50 + +/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 + projectile_deflect_chance = 50 + +///////////////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 + 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/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 3b65952be525..238c0a8dee9e 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -1,1017 +1,1017 @@ -/* 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) - . = ..() - 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) - -/* - * 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) || !(mobility_flags & MOBILITY_MOVE) || 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", 1, "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", 1, "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", 1, "[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/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 - G.key = key - ..(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 - deathsound = '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) + . = ..() + 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) + +/* + * 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) || !(mobility_flags & MOBILITY_MOVE) || 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", 1, "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", 1, "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", 1, "[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/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 + G.key = key + ..(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 + deathsound = '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 3bd24c4df76d..754ef00491df 100644 --- a/code/modules/mob/living/simple_animal/shade.dm +++ b/code/modules/mob/living/simple_animal/shade.dm @@ -1,64 +1,64 @@ -/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 - -/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 + +/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/login.dm b/code/modules/mob/login.dm index 8a4205306e13..96871f715fa5 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -1,68 +1,68 @@ -/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 - - ..() - if (client && key != client.key) - key = client.key - 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.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_played_names(client.ckey,name,real_name) - auto_deadmin_on_login() - - log_message("Client [key_name(src)] has taken ownership of mob [src]([src.type])", LOG_OWNERSHIP) - SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client) - -/mob/proc/auto_deadmin_on_login() //return true if they're not an admin at the end. - if(!client?.holder) - return TRUE - if(CONFIG_GET(flag/auto_deadmin_players) || (client.prefs?.toggles & DEADMIN_ALWAYS)) - return client.holder.auto_deadmin() - if(mind.has_antag_datum(/datum/antagonist) && (CONFIG_GET(flag/auto_deadmin_antagonists) || client.prefs?.toggles & DEADMIN_ANTAGONIST)) - return client.holder.auto_deadmin() - if(job) - return SSjob.handle_auto_deadmin_roles(client, job) +/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 + + ..() + if (client && key != client.key) + key = client.key + 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.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_played_names(client.ckey,name,real_name) + auto_deadmin_on_login() + + log_message("Client [key_name(src)] has taken ownership of mob [src]([src.type])", LOG_OWNERSHIP) + SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client) + +/mob/proc/auto_deadmin_on_login() //return true if they're not an admin at the end. + if(!client?.holder) + return TRUE + if(CONFIG_GET(flag/auto_deadmin_players) || (client.prefs?.toggles & DEADMIN_ALWAYS)) + return client.holder.auto_deadmin() + if(mind.has_antag_datum(/datum/antagonist) && (CONFIG_GET(flag/auto_deadmin_antagonists) || client.prefs?.toggles & DEADMIN_ANTAGONIST)) + return client.holder.auto_deadmin() + if(job) + return SSjob.handle_auto_deadmin_roles(client, job) diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index 0b3602181532..9d69b43fdbbe 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]", 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]", 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 49b654bd3621..58f3316fb2d9 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -1,510 +1,510 @@ -// 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 = 1, BODY_ZONE_CHEST = 1, BODY_ZONE_L_ARM = 4, BODY_ZONE_R_ARM = 4, BODY_ZONE_L_LEG = 4, BODY_ZONE_R_LEG = 4)) - 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) - - for(var/p = 1 to min(n,MAX_BROADCAST_LEN)) - if ((copytext(te, p, p + 1) == " " || prob(pr))) - t = text("[][]", t, copytext(te, p, p + 1)) - else - t = text("[]*", t) - if(n > MAX_BROADCAST_LEN) - t += "..." //signals missing text - return sanitize(t) - -/proc/slur(n) - var/phrase = html_decode(n) - var/leng = lentext(phrase) - var/counter=lentext(phrase) - var/newphrase="" - var/newletter="" - while(counter>=1) - newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) - if(rand(1,3)==3) - 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,20)==20) - if(newletter==" ") - newletter="...huuuhhh..." - if(newletter==".") - newletter=" *BURP*." - switch(rand(1,20)) - if(1) - newletter+="'" - if(10) - newletter+="[newletter]" - if(20) - 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 = lentext(phrase) - var/counter=lentext(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(var/message, var/ghost_sound = null, var/enter_link = null, var/atom/source = null, var/mutable_appearance/alert_overlay = null, var/action = NOTIFY_JUMP, flashwindow = TRUE, ignore_mapload = TRUE, ignore_key, header = null, notify_suiciders = TRUE, var/notify_volume = 100) //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(!notify_suiciders && (O in GLOB.suicided_mob_list)) - continue - if (ignore_key && O.ckey in GLOB.poll_ignore[ignore_key]) - continue - var/orbit_link - if (source && action == NOTIFY_ORBIT) - orbit_link = " (Orbit)" - to_chat(O, "[message][(enter_link) ? " [enter_link]" : ""][orbit_link]") - if(ghost_sound) - SEND_SOUND(O, sound(ghost_sound, volume = notify_volume)) - 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) - if (header) - A.name = header - 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, BODYPART_ROBOTIC)) - 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 == user ? "your" : "[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 ([ADMIN_LOOKUPFLW(M)])") - M.ghostize(0) - M.key = C.key - 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()]\] [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 - -//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)) - . += "This body has been dissected and analyzed. It is no longer worth experimenting on.
                " - -/mob/proc/get_policy_keywords() - . = list() - . += "[type]" - if(mind) - . += mind.assigned_role - . += mind.special_role //In case there's something special leftover, try to avoid - for(var/datum/antagonist/A in mind.antag_datums) - . += "[A.type]" - -//Can the mob see reagents inside of containers? -/mob/proc/can_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 = 1, BODY_ZONE_CHEST = 1, BODY_ZONE_L_ARM = 4, BODY_ZONE_R_ARM = 4, BODY_ZONE_L_LEG = 4, BODY_ZONE_R_LEG = 4)) + 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) + + for(var/p = 1 to min(n,MAX_BROADCAST_LEN)) + if ((copytext(te, p, p + 1) == " " || prob(pr))) + t = text("[][]", t, copytext(te, p, p + 1)) + else + t = text("[]*", t) + if(n > MAX_BROADCAST_LEN) + t += "..." //signals missing text + return sanitize(t) + +/proc/slur(n) + var/phrase = html_decode(n) + var/leng = lentext(phrase) + var/counter=lentext(phrase) + var/newphrase="" + var/newletter="" + while(counter>=1) + newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) + if(rand(1,3)==3) + 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,20)==20) + if(newletter==" ") + newletter="...huuuhhh..." + if(newletter==".") + newletter=" *BURP*." + switch(rand(1,20)) + if(1) + newletter+="'" + if(10) + newletter+="[newletter]" + if(20) + 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 = lentext(phrase) + var/counter=lentext(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(var/message, var/ghost_sound = null, var/enter_link = null, var/atom/source = null, var/mutable_appearance/alert_overlay = null, var/action = NOTIFY_JUMP, flashwindow = TRUE, ignore_mapload = TRUE, ignore_key, header = null, notify_suiciders = TRUE, var/notify_volume = 100) //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(!notify_suiciders && (O in GLOB.suicided_mob_list)) + continue + if (ignore_key && O.ckey in GLOB.poll_ignore[ignore_key]) + continue + var/orbit_link + if (source && action == NOTIFY_ORBIT) + orbit_link = " (Orbit)" + to_chat(O, "[message][(enter_link) ? " [enter_link]" : ""][orbit_link]") + if(ghost_sound) + SEND_SOUND(O, sound(ghost_sound, volume = notify_volume)) + 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) + if (header) + A.name = header + 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, BODYPART_ROBOTIC)) + 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 == user ? "your" : "[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 ([ADMIN_LOOKUPFLW(M)])") + M.ghostize(0) + M.key = C.key + 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()]\] [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 + +//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)) + . += "This body has been dissected and analyzed. It is no longer worth experimenting on.
                " + +/mob/proc/get_policy_keywords() + . = list() + . += "[type]" + if(mind) + . += mind.assigned_role + . += mind.special_role //In case there's something special leftover, try to avoid + for(var/datum/antagonist/A in mind.antag_datums) + . += "[A.type]" + +//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 \ No newline at end of file diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 9fb339fb429f..db44a1cf94d1 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -1,397 +1,397 @@ -/mob/CanPass(atom/movable/mover, turf/target) - return TRUE //There's almost no cases where non /living mobs should be used in game as actual mobs, other than ghosts. - -//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 //yogs start - turned into component - Process_Incorpmove(direct) - return FALSE*/ //yogs end - - 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(!(L.mobility_flags & MOBILITY_MOVE)) - 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 - - var/handled = SEND_SIGNAL(L, COMSIG_PROCESS_MOVE, direct) //yogs start - movement components - if(handled) - return FALSE//yogs end - - //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 - - 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 - move_delay += add_delay - if(.) // If mob is null here, we deserve the runtime - if(mob.throwing) - mob.throwing.finalize(FALSE) - - 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.pulledby == mob.pulling) && (mob.pulledby.grab_state == GRAB_PASSIVE)) //Don't autoresist passive grabs if we're grabbing them too. - return - 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/food/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(knockdown_amount, obj/O, lube, paralyze, force_drop) - 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 - 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(src) - -/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) + return TRUE //There's almost no cases where non /living mobs should be used in game as actual mobs, other than ghosts. + +//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 //yogs start - turned into component + Process_Incorpmove(direct) + return FALSE*/ //yogs end + + 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(!(L.mobility_flags & MOBILITY_MOVE)) + 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 + + var/handled = SEND_SIGNAL(L, COMSIG_PROCESS_MOVE, direct) //yogs start - movement components + if(handled) + return FALSE//yogs end + + //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 + + 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 + move_delay += add_delay + if(.) // If mob is null here, we deserve the runtime + if(mob.throwing) + mob.throwing.finalize(FALSE) + + 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.pulledby == mob.pulling) && (mob.pulledby.grab_state == GRAB_PASSIVE)) //Don't autoresist passive grabs if we're grabbing them too. + return + 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/food/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(knockdown_amount, obj/O, lube, paralyze, force_drop) + 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 + 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(src) + +/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/mob_movespeed.dm b/code/modules/mob/mob_movespeed.dm index 0df79ec39ceb..9e38ed4972db 100644 --- a/code/modules/mob/mob_movespeed.dm +++ b/code/modules/mob/mob_movespeed.dm @@ -1,124 +1,124 @@ - -/*Current movespeed modification list format: list(id = list( - priority, - flags, - legacy slowdown/speedup amount, - movetype_flags - )) -*/ - -//ANY ADD/REMOVE DONE IN UPDATE_MOVESPEED MUST HAVE THE UPDATE ARGUMENT SET AS FALSE! -/mob/proc/add_movespeed_modifier(id, update=TRUE, priority=0, flags=NONE, override=FALSE, multiplicative_slowdown=0, movetypes=ALL, blacklisted_movetypes=NONE, conflict=FALSE) - var/list/temp = list(priority, flags, multiplicative_slowdown, movetypes, blacklisted_movetypes, conflict) //build the modification list - var/resort = TRUE - if(LAZYACCESS(movespeed_modification, id)) - var/list/existing_data = movespeed_modification[id] - if(movespeed_modifier_identical_check(existing_data, temp)) - return FALSE - if(!override) - return FALSE - if(priority == existing_data[MOVESPEED_DATA_INDEX_PRIORITY]) - resort = FALSE // We don't need to re-sort if we're replacing something already there and it's the same priority - LAZYSET(movespeed_modification, id, temp) - if(update) - update_movespeed(resort) - return TRUE - -/mob/proc/remove_movespeed_modifier(id, update = TRUE) - if(!LAZYACCESS(movespeed_modification, id)) - return FALSE - LAZYREMOVE(movespeed_modification, id) - UNSETEMPTY(movespeed_modification) - if(update) - update_movespeed(FALSE) - return TRUE - -/mob/vv_edit_var(var_name, var_value) - var/slowdown_edit = (var_name == NAMEOF(src, cached_multiplicative_slowdown)) - var/diff - if(slowdown_edit && isnum(cached_multiplicative_slowdown) && isnum(var_value)) - remove_movespeed_modifier(MOVESPEED_ID_ADMIN_VAREDIT) - diff = var_value - cached_multiplicative_slowdown - . = ..() - if(. && slowdown_edit && isnum(diff)) - add_movespeed_modifier(MOVESPEED_ID_ADMIN_VAREDIT, TRUE, 100, override = TRUE, multiplicative_slowdown = diff) - -/mob/proc/has_movespeed_modifier(id) - return LAZYACCESS(movespeed_modification, id) - -/mob/proc/update_config_movespeed() - add_movespeed_modifier(MOVESPEED_ID_CONFIG_SPEEDMOD, FALSE, 100, override = TRUE, multiplicative_slowdown = get_config_multiplicative_speed()) - -/mob/proc/get_config_multiplicative_speed() - if(!islist(GLOB.mob_config_movespeed_type_lookup) || !GLOB.mob_config_movespeed_type_lookup[type]) - return 0 - else - return GLOB.mob_config_movespeed_type_lookup[type] - -/mob/proc/update_movespeed(resort = TRUE) - if(resort) - sort_movespeed_modlist() - . = 0 - var/list/conflict_tracker = list() - for(var/id in get_movespeed_modifiers()) - var/list/data = movespeed_modification[id] - if(!(data[MOVESPEED_DATA_INDEX_MOVETYPE] & movement_type)) // We don't affect any of these move types, skip - continue - if(data[MOVESPEED_DATA_INDEX_BL_MOVETYPE] & movement_type) // There's a movetype here that disables this modifier, skip - continue - var/conflict = data[MOVESPEED_DATA_INDEX_CONFLICT] - var/amt = data[MOVESPEED_DATA_INDEX_MULTIPLICATIVE_SLOWDOWN] - if(conflict) - // Conflicting modifiers prioritize the larger slowdown or the larger speedup - // We purposefuly don't handle mixing speedups and slowdowns on the same id - if(abs(conflict_tracker[conflict]) < abs(amt)) - conflict_tracker[conflict] = amt - else - continue - . += amt - cached_multiplicative_slowdown = . - -/mob/proc/get_movespeed_modifiers() - return movespeed_modification - -/mob/proc/movespeed_modifier_identical_check(list/mod1, list/mod2) - if(!islist(mod1) || !islist(mod2) || mod1.len < MOVESPEED_DATA_INDEX_MAX || mod2.len < MOVESPEED_DATA_INDEX_MAX) - return FALSE - for(var/i in 1 to MOVESPEED_DATA_INDEX_MAX) - if(mod1[i] != mod2[i]) - return FALSE - return TRUE - -/mob/proc/total_multiplicative_slowdown() - . = 0 - for(var/id in get_movespeed_modifiers()) - var/list/data = movespeed_modification[id] - . += data[MOVESPEED_DATA_INDEX_MULTIPLICATIVE_SLOWDOWN] - -/proc/movespeed_data_null_check(list/data) //Determines if a data list is not meaningful and should be discarded. - . = TRUE - if(data[MOVESPEED_DATA_INDEX_MULTIPLICATIVE_SLOWDOWN]) - . = FALSE - -/mob/proc/sort_movespeed_modlist() //Verifies it too. Sorts highest priority (first applied) to lowest priority (last applied) - if(!movespeed_modification) - return - var/list/assembled = list() - for(var/our_id in movespeed_modification) - var/list/our_data = movespeed_modification[our_id] - if(!islist(our_data) || (our_data.len < MOVESPEED_DATA_INDEX_PRIORITY) || movespeed_data_null_check(our_data)) - movespeed_modification -= our_id - continue - var/our_priority = our_data[MOVESPEED_DATA_INDEX_PRIORITY] - var/resolved = FALSE - for(var/their_id in assembled) - var/list/their_data = assembled[their_id] - if(their_data[MOVESPEED_DATA_INDEX_PRIORITY] < our_priority) - assembled.Insert(assembled.Find(their_id), our_id) - assembled[our_id] = our_data - resolved = TRUE - break - if(!resolved) - assembled[our_id] = our_data - movespeed_modification = assembled + +/*Current movespeed modification list format: list(id = list( + priority, + flags, + legacy slowdown/speedup amount, + movetype_flags + )) +*/ + +//ANY ADD/REMOVE DONE IN UPDATE_MOVESPEED MUST HAVE THE UPDATE ARGUMENT SET AS FALSE! +/mob/proc/add_movespeed_modifier(id, update=TRUE, priority=0, flags=NONE, override=FALSE, multiplicative_slowdown=0, movetypes=ALL, blacklisted_movetypes=NONE, conflict=FALSE) + var/list/temp = list(priority, flags, multiplicative_slowdown, movetypes, blacklisted_movetypes, conflict) //build the modification list + var/resort = TRUE + if(LAZYACCESS(movespeed_modification, id)) + var/list/existing_data = movespeed_modification[id] + if(movespeed_modifier_identical_check(existing_data, temp)) + return FALSE + if(!override) + return FALSE + if(priority == existing_data[MOVESPEED_DATA_INDEX_PRIORITY]) + resort = FALSE // We don't need to re-sort if we're replacing something already there and it's the same priority + LAZYSET(movespeed_modification, id, temp) + if(update) + update_movespeed(resort) + return TRUE + +/mob/proc/remove_movespeed_modifier(id, update = TRUE) + if(!LAZYACCESS(movespeed_modification, id)) + return FALSE + LAZYREMOVE(movespeed_modification, id) + UNSETEMPTY(movespeed_modification) + if(update) + update_movespeed(FALSE) + return TRUE + +/mob/vv_edit_var(var_name, var_value) + var/slowdown_edit = (var_name == NAMEOF(src, cached_multiplicative_slowdown)) + var/diff + if(slowdown_edit && isnum(cached_multiplicative_slowdown) && isnum(var_value)) + remove_movespeed_modifier(MOVESPEED_ID_ADMIN_VAREDIT) + diff = var_value - cached_multiplicative_slowdown + . = ..() + if(. && slowdown_edit && isnum(diff)) + add_movespeed_modifier(MOVESPEED_ID_ADMIN_VAREDIT, TRUE, 100, override = TRUE, multiplicative_slowdown = diff) + +/mob/proc/has_movespeed_modifier(id) + return LAZYACCESS(movespeed_modification, id) + +/mob/proc/update_config_movespeed() + add_movespeed_modifier(MOVESPEED_ID_CONFIG_SPEEDMOD, FALSE, 100, override = TRUE, multiplicative_slowdown = get_config_multiplicative_speed()) + +/mob/proc/get_config_multiplicative_speed() + if(!islist(GLOB.mob_config_movespeed_type_lookup) || !GLOB.mob_config_movespeed_type_lookup[type]) + return 0 + else + return GLOB.mob_config_movespeed_type_lookup[type] + +/mob/proc/update_movespeed(resort = TRUE) + if(resort) + sort_movespeed_modlist() + . = 0 + var/list/conflict_tracker = list() + for(var/id in get_movespeed_modifiers()) + var/list/data = movespeed_modification[id] + if(!(data[MOVESPEED_DATA_INDEX_MOVETYPE] & movement_type)) // We don't affect any of these move types, skip + continue + if(data[MOVESPEED_DATA_INDEX_BL_MOVETYPE] & movement_type) // There's a movetype here that disables this modifier, skip + continue + var/conflict = data[MOVESPEED_DATA_INDEX_CONFLICT] + var/amt = data[MOVESPEED_DATA_INDEX_MULTIPLICATIVE_SLOWDOWN] + if(conflict) + // Conflicting modifiers prioritize the larger slowdown or the larger speedup + // We purposefuly don't handle mixing speedups and slowdowns on the same id + if(abs(conflict_tracker[conflict]) < abs(amt)) + conflict_tracker[conflict] = amt + else + continue + . += amt + cached_multiplicative_slowdown = . + +/mob/proc/get_movespeed_modifiers() + return movespeed_modification + +/mob/proc/movespeed_modifier_identical_check(list/mod1, list/mod2) + if(!islist(mod1) || !islist(mod2) || mod1.len < MOVESPEED_DATA_INDEX_MAX || mod2.len < MOVESPEED_DATA_INDEX_MAX) + return FALSE + for(var/i in 1 to MOVESPEED_DATA_INDEX_MAX) + if(mod1[i] != mod2[i]) + return FALSE + return TRUE + +/mob/proc/total_multiplicative_slowdown() + . = 0 + for(var/id in get_movespeed_modifiers()) + var/list/data = movespeed_modification[id] + . += data[MOVESPEED_DATA_INDEX_MULTIPLICATIVE_SLOWDOWN] + +/proc/movespeed_data_null_check(list/data) //Determines if a data list is not meaningful and should be discarded. + . = TRUE + if(data[MOVESPEED_DATA_INDEX_MULTIPLICATIVE_SLOWDOWN]) + . = FALSE + +/mob/proc/sort_movespeed_modlist() //Verifies it too. Sorts highest priority (first applied) to lowest priority (last applied) + if(!movespeed_modification) + return + var/list/assembled = list() + for(var/our_id in movespeed_modification) + var/list/our_data = movespeed_modification[our_id] + if(!islist(our_data) || (our_data.len < MOVESPEED_DATA_INDEX_PRIORITY) || movespeed_data_null_check(our_data)) + movespeed_modification -= our_id + continue + var/our_priority = our_data[MOVESPEED_DATA_INDEX_PRIORITY] + var/resolved = FALSE + for(var/their_id in assembled) + var/list/their_data = assembled[their_id] + if(their_data[MOVESPEED_DATA_INDEX_PRIORITY] < our_priority) + assembled.Insert(assembled.Find(their_id), our_id) + assembled[our_id] = our_data + resolved = TRUE + break + if(!resolved) + assembled[our_id] = our_data + movespeed_modification = assembled UNSETEMPTY(movespeed_modification) \ No newline at end of file diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm index 9f2970a6e310..5e5995ca6eed 100644 --- a/code/modules/mob/mob_transformation_simple.dm +++ b/code/modules/mob/mob_transformation_simple.dm @@ -1,60 +1,60 @@ - -//This proc is the most basic of the procs. All it does is make a new mob on the same tile and transfer over a few variables. -//Returns the new mob -//Note that this proc does NOT do MMI related stuff! -/mob/proc/change_mob_type(new_type = null, turf/location = null, new_name = null as text, delete_old_mob = FALSE) - - if(isnewplayer(src)) - to_chat(usr, "Cannot convert players who have not entered yet.") - return - - if(!new_type) - new_type = input("Mob type path:", "Mob type") as text|null - - if(istext(new_type)) - new_type = text2path(new_type) - - if( !ispath(new_type) ) - to_chat(usr, "Invalid type path (new_type = [new_type]) in change_mob_type(). Contact a coder.") - return - - if(ispath(new_type, /mob/dead/new_player)) - to_chat(usr, "Cannot convert into a new_player mob type.") - return - - var/mob/M - if(isturf(location)) - M = new new_type( location ) - else - M = new new_type( src.loc ) - - if(!M || !ismob(M)) - to_chat(usr, "Type path is not a mob (new_type = [new_type]) in change_mob_type(). Contact a coder.") - qdel(M) - return - - if( istext(new_name) ) - M.name = new_name - M.real_name = new_name - else - M.name = src.name - M.real_name = src.real_name - - if(has_dna() && M.has_dna()) - var/mob/living/carbon/C = src - var/mob/living/carbon/D = M - C.dna.transfer_identity(D) - D.updateappearance(mutcolor_update=1, mutations_overlay_update=1) - else if(ishuman(M)) - var/mob/living/carbon/human/H = M - client.prefs.copy_to(H) - H.dna.update_dna_identity() - - if(mind && isliving(M)) - mind.transfer_to(M, 1) // second argument to force key move to new mob - else - M.key = key - - if(delete_old_mob) - QDEL_IN(src, 1) - return M + +//This proc is the most basic of the procs. All it does is make a new mob on the same tile and transfer over a few variables. +//Returns the new mob +//Note that this proc does NOT do MMI related stuff! +/mob/proc/change_mob_type(new_type = null, turf/location = null, new_name = null as text, delete_old_mob = FALSE) + + if(isnewplayer(src)) + to_chat(usr, "Cannot convert players who have not entered yet.") + return + + if(!new_type) + new_type = input("Mob type path:", "Mob type") as text|null + + if(istext(new_type)) + new_type = text2path(new_type) + + if( !ispath(new_type) ) + to_chat(usr, "Invalid type path (new_type = [new_type]) in change_mob_type(). Contact a coder.") + return + + if(ispath(new_type, /mob/dead/new_player)) + to_chat(usr, "Cannot convert into a new_player mob type.") + return + + var/mob/M + if(isturf(location)) + M = new new_type( location ) + else + M = new new_type( src.loc ) + + if(!M || !ismob(M)) + to_chat(usr, "Type path is not a mob (new_type = [new_type]) in change_mob_type(). Contact a coder.") + qdel(M) + return + + if( istext(new_name) ) + M.name = new_name + M.real_name = new_name + else + M.name = src.name + M.real_name = src.real_name + + if(has_dna() && M.has_dna()) + var/mob/living/carbon/C = src + var/mob/living/carbon/D = M + C.dna.transfer_identity(D) + D.updateappearance(mutcolor_update=1, mutations_overlay_update=1) + else if(ishuman(M)) + var/mob/living/carbon/human/H = M + client.prefs.copy_to(H) + H.dna.update_dna_identity() + + if(mind && isliving(M)) + mind.transfer_to(M, 1) // second argument to force key move to new mob + else + M.key = key + + if(delete_old_mob) + QDEL_IN(src, 1) + return M diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 0e98712c10ad..9bb58c1eb060 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -1,121 +1,121 @@ -//Speech verbs. -/mob/verb/say_verb(message as text) - set name = "Say" - set category = "IC" - - var/oldmsg = message //yogs start - pretty filter - message = pretty_filter(message) - if(oldmsg != message) - to_chat(usr, "You fumble over your words. See rule 0.1.1.") - message_admins("[key_name(usr)] just tripped a pretty filter: '[oldmsg]'.") - return - if(isliving(src)) - message = minor_filter(message) //yogs end - pretty filter - - 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/verb/whisper_verb(message as text) - set name = "Whisper" - set category = "IC" - - var/oldmsg = message //yogs start - pretty filter - message = pretty_filter(message) - if(oldmsg != message) - to_chat(usr, "You fumble over your words. See rule 0.1.1.") - message_admins("[key_name(usr)] just tripped a pretty filter: '[oldmsg]'.") - return - message = minor_filter(message) //yogs end - pretty filter - - 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 text) - 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 = utf8_sanitize(message, usr, MAX_MESSAGE_LEN) // yogs - libvg support - - 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 = is_banned_from(ckey, "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/K - - if(key) - K = src.key - - var/spanned = say_quote(message) - var/source = "DEAD: [(src.client.prefs.chat_toggles & GHOST_CKEY) ? "" : "([K]) "][name][alt_name]" // yogs - i have no clue - var/rendered = " [emoji_parse(spanned)]" - log_talk(message, LOG_SAY, tag="DEAD") - deadchat_broadcast(rendered, source, follow_target = src, speaker_key = key) - -/mob/proc/check_emote(message, forced) - if(copytext(message, 1, 2) == "*") - emote(copytext(message, 2), intentional = !forced) - 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" + + var/oldmsg = message //yogs start - pretty filter + message = pretty_filter(message) + if(oldmsg != message) + to_chat(usr, "You fumble over your words. See rule 0.1.1.") + message_admins("[key_name(usr)] just tripped a pretty filter: '[oldmsg]'.") + return + if(isliving(src)) + message = minor_filter(message) //yogs end - pretty filter + + 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/verb/whisper_verb(message as text) + set name = "Whisper" + set category = "IC" + + var/oldmsg = message //yogs start - pretty filter + message = pretty_filter(message) + if(oldmsg != message) + to_chat(usr, "You fumble over your words. See rule 0.1.1.") + message_admins("[key_name(usr)] just tripped a pretty filter: '[oldmsg]'.") + return + message = minor_filter(message) //yogs end - pretty filter + + 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 text) + 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 = utf8_sanitize(message, usr, MAX_MESSAGE_LEN) // yogs - libvg support + + 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 = is_banned_from(ckey, "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/K + + if(key) + K = src.key + + var/spanned = say_quote(message) + var/source = "DEAD: [(src.client.prefs.chat_toggles & GHOST_CKEY) ? "" : "([K]) "][name][alt_name]" // yogs - i have no clue + var/rendered = " [emoji_parse(spanned)]" + log_talk(message, LOG_SAY, tag="DEAD") + deadchat_broadcast(rendered, source, follow_target = src, speaker_key = key) + +/mob/proc/check_emote(message, forced) + if(copytext(message, 1, 2) == "*") + emote(copytext(message, 2), intentional = !forced) + 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 24d0d0f19f1f..226ac6d0ead9 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -1,638 +1,638 @@ -/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | 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 = TRUE - Paralyze(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.mutation_index = dna.mutation_index - O.dna.set_se(1, GET_INITIALIZED_MUTATION(RACEMUT)) - - if(suiciding) - O.set_suicide(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)) - 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/datum/action/changeling/humanform/hf = new - changeling.purchasedpowers += hf - changeling.regain_powers() - - 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 stuns - if(tr_flags & TR_KEEPSTUNS) - O.Stun(AmountStun(), ignore_canstun = TRUE) - O.Knockdown(AmountKnockdown(), ignore_canstun = TRUE) - O.Immobilize(AmountImmobilized(), ignore_canstun = TRUE) - O.Paralyze(AmountParalyzed(), ignore_canstun = TRUE) - O.Unconscious(AmountUnconscious(), ignore_canstun = TRUE) - O.Sleeping(AmountSleeping(), ignore_canstun = TRUE) - - //transfer reagents - if(tr_flags & TR_KEEPREAGENTS) - reagents.trans_to(O, reagents.total_volume) - - //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/datum/action/changeling/humanform/hf = new - changeling.purchasedpowers += hf - changeling.regain_powers() - - - 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_KEEPSTUNS | TR_KEEPREAGENTS | 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 = TRUE - Paralyze(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.mutation_index = dna.mutation_index - O.dna.set_se(0, GET_INITIALIZED_MUTATION(RACEMUT)) - O.domutcheck() - - if(suiciding) - O.set_suicide(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.adjustOrganLoss(ORGAN_SLOT_BRAIN, getOrganLoss(ORGAN_SLOT_BRAIN)) - 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/datum/action/changeling/humanform/HF in changeling.purchasedpowers) - changeling.purchasedpowers -= HF - changeling.regain_powers() - - 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) - - //transfer stuns - if(tr_flags & TR_KEEPSTUNS) - O.Stun(AmountStun(), ignore_canstun = TRUE) - O.Knockdown(AmountKnockdown(), ignore_canstun = TRUE) - O.Immobilize(AmountImmobilized(), ignore_canstun = TRUE) - O.Paralyze(AmountParalyzed(), ignore_canstun = TRUE) - O.Unconscious(AmountUnconscious(), ignore_canstun = TRUE) - O.Sleeping(AmountSleeping(), ignore_canstun = TRUE) - - //transfer reagents - if(tr_flags & TR_KEEPREAGENTS) - reagents.trans_to(O, reagents.total_volume) - - if(mind) - mind.transfer_to(O) - var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - for(var/datum/action/changeling/humanform/HF in changeling.purchasedpowers) - changeling.purchasedpowers -= HF - changeling.regain_powers() - - 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(transfer_after = TRUE, client/preference_source) - if (notransform) - return - for(var/t in bodyparts) - qdel(t) - - return ..() - -/mob/living/carbon/AIize(transfer_after = TRUE, client/preference_source) - if (notransform) - return - notransform = TRUE - Paralyze(1, ignore_canstun = TRUE) - for(var/obj/item/W in src) - dropItemToGround(W) - regenerate_icons() - icon = null - invisibility = INVISIBILITY_MAXIMUM - return ..() - -/mob/proc/AIize(transfer_after = TRUE, client/preference_source) - 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) - - if(preference_source) - apply_pref_name("ai",preference_source) - - qdel(src) - -/mob/living/carbon/human/proc/Robotize(delete_items = 0, transfer_after = TRUE) - if (notransform) - return - notransform = TRUE - Paralyze(1, ignore_canstun = TRUE) - - for(var/obj/item/W in src) - if(delete_items) - qdel(W) - else - dropItemToGround(W) - regenerate_icons() - 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(client) - R.updatename(client) - - if(mind) //TODO - if(!transfer_after) - mind.active = FALSE - mind.transfer_to(R) - else if(transfer_after) - R.key = key - - if(R.mmi) - R.mmi.name = "[initial(R.mmi.name)]: [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() - if (notransform) - return - notransform = TRUE - mobility_flags = NONE - for(var/obj/item/W in src) - dropItemToGround(W) - regenerate_icons() - 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 - new_xeno.key = key - - to_chat(new_xeno, "You are now an alien.") - . = new_xeno - qdel(src) - -/mob/living/carbon/human/proc/slimeize(reproduce as num) - if (notransform) - return - notransform = TRUE - mobility_flags = NONE - for(var/obj/item/W in src) - dropItemToGround(W) - regenerate_icons() - 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.set_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 - new_slime.key = key - - to_chat(new_slime, "You are now a slime. Skreee!") - . = new_slime - qdel(src) - -/mob/proc/become_overmind(starting_points = 60) - var/mob/camera/blob/B = new /mob/camera/blob(get_turf(src), starting_points) - B.key = key - . = B - qdel(src) - - -/mob/living/carbon/human/proc/corgize() - if (notransform) - return - notransform = TRUE - Paralyze(1, ignore_canstun = TRUE) - for(var/obj/item/W in src) - dropItemToGround(W) - regenerate_icons() - 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 - new_corgi.key = key - - to_chat(new_corgi, "You are now a Corgi. Yap Yap!") - . = new_corgi - qdel(src) - -/mob/living/carbon/proc/gorillize() - if(notransform) - return - notransform = TRUE - Paralyze(1, ignore_canstun = TRUE) - - 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() - 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_to(new_gorilla) - else - new_gorilla.key = key - to_chat(new_gorilla, "You are now a gorilla. Ooga ooga!") - . = new_gorilla - qdel(src) - -/mob/living/carbon/human/Animalize() - - var/list/mobtypes = typesof(/mob/living/simple_animal) - var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes - - if(!safe_animal(mobpath)) - to_chat(usr, "Sorry but this mob type is currently unavailable.") - return - - if(notransform) - return - notransform = TRUE - Paralyze(1, ignore_canstun = TRUE) - - for(var/obj/item/W in src) - dropItemToGround(W) - - regenerate_icons() - icon = null - invisibility = INVISIBILITY_MAXIMUM - - for(var/t in bodyparts) - qdel(t) - - var/mob/new_mob = new mobpath(src.loc) - - new_mob.key = key - new_mob.a_intent = INTENT_HARM - - - to_chat(new_mob, "You suddenly feel more... animalistic.") - . = new_mob - qdel(src) - -/mob/proc/Animalize() - - var/list/mobtypes = typesof(/mob/living/simple_animal) - var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes - - if(!safe_animal(mobpath)) - to_chat(usr, "Sorry but this mob type is currently unavailable.") - return - - var/mob/new_mob = new mobpath(src.loc) - - new_mob.key = key - new_mob.a_intent = INTENT_HARM - to_chat(new_mob, "You feel more... animalistic") - - . = new_mob - qdel(src) - -/* Certain mob types have problems and should not be allowed to be controlled by players. - * - * This proc is here to force coders to manually place their mob in this list, hopefully tested. - * This also gives a place to explain -why- players shouldnt be turn into certain mobs and hopefully someone can fix them. - */ -/mob/proc/safe_animal(MP) - -//Bad mobs! - Remember to add a comment explaining what's wrong with the mob - if(!MP) - return 0 //Sanity, this should never happen. - - if(ispath(MP, /mob/living/simple_animal/hostile/construct)) - return 0 //Verbs do not appear for players. - -//Good mobs! - if(ispath(MP, /mob/living/simple_animal/pet/cat)) - return 1 - if(ispath(MP, /mob/living/simple_animal/pet/dog/corgi)) - return 1 - if(ispath(MP, /mob/living/simple_animal/crab)) - return 1 - if(ispath(MP, /mob/living/simple_animal/hostile/carp)) - return 1 - if(ispath(MP, /mob/living/simple_animal/hostile/mushroom)) - return 1 - if(ispath(MP, /mob/living/simple_animal/shade)) - return 1 - if(ispath(MP, /mob/living/simple_animal/hostile/killertomato)) - return 1 - if(ispath(MP, /mob/living/simple_animal/mouse)) - return 1 //It is impossible to pull up the player panel for mice (Fixed! - Nodrak) - if(ispath(MP, /mob/living/simple_animal/hostile/bear)) - return 1 //Bears will auto-attack mobs, even if they're player controlled (Fixed! - Nodrak) - if(ispath(MP, /mob/living/simple_animal/parrot)) - return 1 //Parrots are no longer unfinished! -Nodrak - - //Not in here? Must be untested! - return 0 +/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | 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 = TRUE + Paralyze(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.mutation_index = dna.mutation_index + O.dna.set_se(1, GET_INITIALIZED_MUTATION(RACEMUT)) + + if(suiciding) + O.set_suicide(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)) + 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/datum/action/changeling/humanform/hf = new + changeling.purchasedpowers += hf + changeling.regain_powers() + + 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 stuns + if(tr_flags & TR_KEEPSTUNS) + O.Stun(AmountStun(), ignore_canstun = TRUE) + O.Knockdown(AmountKnockdown(), ignore_canstun = TRUE) + O.Immobilize(AmountImmobilized(), ignore_canstun = TRUE) + O.Paralyze(AmountParalyzed(), ignore_canstun = TRUE) + O.Unconscious(AmountUnconscious(), ignore_canstun = TRUE) + O.Sleeping(AmountSleeping(), ignore_canstun = TRUE) + + //transfer reagents + if(tr_flags & TR_KEEPREAGENTS) + reagents.trans_to(O, reagents.total_volume) + + //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/datum/action/changeling/humanform/hf = new + changeling.purchasedpowers += hf + changeling.regain_powers() + + + 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_KEEPSTUNS | TR_KEEPREAGENTS | 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 = TRUE + Paralyze(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.mutation_index = dna.mutation_index + O.dna.set_se(0, GET_INITIALIZED_MUTATION(RACEMUT)) + O.domutcheck() + + if(suiciding) + O.set_suicide(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.adjustOrganLoss(ORGAN_SLOT_BRAIN, getOrganLoss(ORGAN_SLOT_BRAIN)) + 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/datum/action/changeling/humanform/HF in changeling.purchasedpowers) + changeling.purchasedpowers -= HF + changeling.regain_powers() + + 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) + + //transfer stuns + if(tr_flags & TR_KEEPSTUNS) + O.Stun(AmountStun(), ignore_canstun = TRUE) + O.Knockdown(AmountKnockdown(), ignore_canstun = TRUE) + O.Immobilize(AmountImmobilized(), ignore_canstun = TRUE) + O.Paralyze(AmountParalyzed(), ignore_canstun = TRUE) + O.Unconscious(AmountUnconscious(), ignore_canstun = TRUE) + O.Sleeping(AmountSleeping(), ignore_canstun = TRUE) + + //transfer reagents + if(tr_flags & TR_KEEPREAGENTS) + reagents.trans_to(O, reagents.total_volume) + + if(mind) + mind.transfer_to(O) + var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + for(var/datum/action/changeling/humanform/HF in changeling.purchasedpowers) + changeling.purchasedpowers -= HF + changeling.regain_powers() + + 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(transfer_after = TRUE, client/preference_source) + if (notransform) + return + for(var/t in bodyparts) + qdel(t) + + return ..() + +/mob/living/carbon/AIize(transfer_after = TRUE, client/preference_source) + if (notransform) + return + notransform = TRUE + Paralyze(1, ignore_canstun = TRUE) + for(var/obj/item/W in src) + dropItemToGround(W) + regenerate_icons() + icon = null + invisibility = INVISIBILITY_MAXIMUM + return ..() + +/mob/proc/AIize(transfer_after = TRUE, client/preference_source) + 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) + + if(preference_source) + apply_pref_name("ai",preference_source) + + qdel(src) + +/mob/living/carbon/human/proc/Robotize(delete_items = 0, transfer_after = TRUE) + if (notransform) + return + notransform = TRUE + Paralyze(1, ignore_canstun = TRUE) + + for(var/obj/item/W in src) + if(delete_items) + qdel(W) + else + dropItemToGround(W) + regenerate_icons() + 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(client) + R.updatename(client) + + if(mind) //TODO + if(!transfer_after) + mind.active = FALSE + mind.transfer_to(R) + else if(transfer_after) + R.key = key + + if(R.mmi) + R.mmi.name = "[initial(R.mmi.name)]: [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() + if (notransform) + return + notransform = TRUE + mobility_flags = NONE + for(var/obj/item/W in src) + dropItemToGround(W) + regenerate_icons() + 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 + new_xeno.key = key + + to_chat(new_xeno, "You are now an alien.") + . = new_xeno + qdel(src) + +/mob/living/carbon/human/proc/slimeize(reproduce as num) + if (notransform) + return + notransform = TRUE + mobility_flags = NONE + for(var/obj/item/W in src) + dropItemToGround(W) + regenerate_icons() + 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.set_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 + new_slime.key = key + + to_chat(new_slime, "You are now a slime. Skreee!") + . = new_slime + qdel(src) + +/mob/proc/become_overmind(starting_points = 60) + var/mob/camera/blob/B = new /mob/camera/blob(get_turf(src), starting_points) + B.key = key + . = B + qdel(src) + + +/mob/living/carbon/human/proc/corgize() + if (notransform) + return + notransform = TRUE + Paralyze(1, ignore_canstun = TRUE) + for(var/obj/item/W in src) + dropItemToGround(W) + regenerate_icons() + 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 + new_corgi.key = key + + to_chat(new_corgi, "You are now a Corgi. Yap Yap!") + . = new_corgi + qdel(src) + +/mob/living/carbon/proc/gorillize() + if(notransform) + return + notransform = TRUE + Paralyze(1, ignore_canstun = TRUE) + + 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() + 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_to(new_gorilla) + else + new_gorilla.key = key + to_chat(new_gorilla, "You are now a gorilla. Ooga ooga!") + . = new_gorilla + qdel(src) + +/mob/living/carbon/human/Animalize() + + var/list/mobtypes = typesof(/mob/living/simple_animal) + var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes + + if(!safe_animal(mobpath)) + to_chat(usr, "Sorry but this mob type is currently unavailable.") + return + + if(notransform) + return + notransform = TRUE + Paralyze(1, ignore_canstun = TRUE) + + for(var/obj/item/W in src) + dropItemToGround(W) + + regenerate_icons() + icon = null + invisibility = INVISIBILITY_MAXIMUM + + for(var/t in bodyparts) + qdel(t) + + var/mob/new_mob = new mobpath(src.loc) + + new_mob.key = key + new_mob.a_intent = INTENT_HARM + + + to_chat(new_mob, "You suddenly feel more... animalistic.") + . = new_mob + qdel(src) + +/mob/proc/Animalize() + + var/list/mobtypes = typesof(/mob/living/simple_animal) + var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes + + if(!safe_animal(mobpath)) + to_chat(usr, "Sorry but this mob type is currently unavailable.") + return + + var/mob/new_mob = new mobpath(src.loc) + + new_mob.key = key + new_mob.a_intent = INTENT_HARM + to_chat(new_mob, "You feel more... animalistic") + + . = new_mob + qdel(src) + +/* Certain mob types have problems and should not be allowed to be controlled by players. + * + * This proc is here to force coders to manually place their mob in this list, hopefully tested. + * This also gives a place to explain -why- players shouldnt be turn into certain mobs and hopefully someone can fix them. + */ +/mob/proc/safe_animal(MP) + +//Bad mobs! - Remember to add a comment explaining what's wrong with the mob + if(!MP) + return 0 //Sanity, this should never happen. + + if(ispath(MP, /mob/living/simple_animal/hostile/construct)) + return 0 //Verbs do not appear for players. + +//Good mobs! + if(ispath(MP, /mob/living/simple_animal/pet/cat)) + return 1 + if(ispath(MP, /mob/living/simple_animal/pet/dog/corgi)) + return 1 + if(ispath(MP, /mob/living/simple_animal/crab)) + return 1 + if(ispath(MP, /mob/living/simple_animal/hostile/carp)) + return 1 + if(ispath(MP, /mob/living/simple_animal/hostile/mushroom)) + return 1 + if(ispath(MP, /mob/living/simple_animal/shade)) + return 1 + if(ispath(MP, /mob/living/simple_animal/hostile/killertomato)) + return 1 + if(ispath(MP, /mob/living/simple_animal/mouse)) + return 1 //It is impossible to pull up the player panel for mice (Fixed! - Nodrak) + if(ispath(MP, /mob/living/simple_animal/hostile/bear)) + return 1 //Bears will auto-attack mobs, even if they're player controlled (Fixed! - Nodrak) + if(ispath(MP, /mob/living/simple_animal/parrot)) + return 1 //Parrots are no longer unfinished! -Nodrak + + //Not in here? Must be untested! + return 0 diff --git a/code/modules/mob/update_icons.dm b/code/modules/mob/update_icons.dm index 09a120e5285a..581584912df0 100644 --- a/code/modules/mob/update_icons.dm +++ b/code/modules/mob/update_icons.dm @@ -1,71 +1,71 @@ -//Most of these are defined at this level to reduce on checks elsewhere in the code. -//Having them here also makes for a nice reference list of the various overlay-updating procs available - -/mob/proc/regenerate_icons() //TODO: phase this out completely if possible - return - -/mob/proc/update_icons() - return - -/mob/proc/update_transform() - return - -/mob/proc/update_inv_handcuffed() - return - -/mob/proc/update_inv_legcuffed() - return - -/mob/proc/update_inv_back() - return - -/mob/proc/update_inv_hands() - return - -/mob/proc/update_inv_wear_mask() - return - -/mob/proc/update_inv_neck() - return - -/mob/proc/update_inv_wear_suit() - return - -/mob/proc/update_inv_w_uniform() - return - -/mob/proc/update_inv_belt() - return - -/mob/proc/update_inv_head() - return - -/mob/proc/update_body() - return - -/mob/proc/update_hair() - return - -/mob/proc/update_fire() - return - -/mob/proc/update_inv_gloves() - return - -/mob/proc/update_inv_wear_id() - return - -/mob/proc/update_inv_shoes() - return - -/mob/proc/update_inv_glasses() - return - -/mob/proc/update_inv_s_store() - return - -/mob/proc/update_inv_pockets() - return - -/mob/proc/update_inv_ears() +//Most of these are defined at this level to reduce on checks elsewhere in the code. +//Having them here also makes for a nice reference list of the various overlay-updating procs available + +/mob/proc/regenerate_icons() //TODO: phase this out completely if possible + return + +/mob/proc/update_icons() + return + +/mob/proc/update_transform() + return + +/mob/proc/update_inv_handcuffed() + return + +/mob/proc/update_inv_legcuffed() + return + +/mob/proc/update_inv_back() + return + +/mob/proc/update_inv_hands() + return + +/mob/proc/update_inv_wear_mask() + return + +/mob/proc/update_inv_neck() + return + +/mob/proc/update_inv_wear_suit() + return + +/mob/proc/update_inv_w_uniform() + return + +/mob/proc/update_inv_belt() + return + +/mob/proc/update_inv_head() + return + +/mob/proc/update_body() + return + +/mob/proc/update_hair() + return + +/mob/proc/update_fire() + return + +/mob/proc/update_inv_gloves() + return + +/mob/proc/update_inv_wear_id() + return + +/mob/proc/update_inv_shoes() + return + +/mob/proc/update_inv_glasses() + return + +/mob/proc/update_inv_s_store() + return + +/mob/proc/update_inv_pockets() + return + +/mob/proc/update_inv_ears() return \ No newline at end of file diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm index 8baff943791b..c98a66d714a8 100644 --- a/code/modules/paperwork/clipboard.dm +++ b/code/modules/paperwork/clipboard.dm @@ -1,128 +1,128 @@ -/obj/item/clipboard - name = "clipboard" - icon = 'yogstation/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() - var/list/dat = list() - if(toppaper) - dat += toppaper.icon_state - dat += toppaper.overlays.Copy() - if(haspen) - dat += "clipboard_pen" - dat += "clipboard_over" - add_overlay(dat) - - -/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"]) in src - if(istype(P)) - 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"]) in src - if(istype(P)) - 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"]) in src - if(istype(P)) - usr.examinate(P) - - if(href_list["top"]) - var/obj/item/P = locate(href_list["top"]) in src - if(istype(P)) - 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 = 'yogstation/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() + var/list/dat = list() + if(toppaper) + dat += toppaper.icon_state + dat += toppaper.overlays.Copy() + if(haspen) + dat += "clipboard_pen" + dat += "clipboard_over" + add_overlay(dat) + + +/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"]) in src + if(istype(P)) + 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"]) in src + if(istype(P)) + 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"]) in src + if(istype(P)) + usr.examinate(P) + + if(href_list["top"]) + var/obj/item/P = locate(href_list["top"]) in src + if(istype(P)) + 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 d23a246aadfc..56fdf0c8c948 100644 --- a/code/modules/paperwork/filingcabinet.dm +++ b/code/modules/paperwork/filingcabinet.dm @@ -1,224 +1,224 @@ -/* 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 = 'yogstation/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(P.tool_behaviour == TOOL_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(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(!usr.canUseTopic(src, BE_CLOSE, ismonkey(usr))) - return - if(href_list["retrieve"]) - usr << browse("", "window=filingcabinet") // Close the menu - - var/obj/item/P = locate(href_list["retrieve"]) in src //contents[retrieveindex] - if(istype(P) && 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"]]
                \nGender: [G.fields["gender"]]
                \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"]]
                \nGender: [G.fields["gender"]]
                \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 = 'yogstation/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(P.tool_behaviour == TOOL_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(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(!usr.canUseTopic(src, BE_CLOSE, ismonkey(usr))) + return + if(href_list["retrieve"]) + usr << browse("", "window=filingcabinet") // Close the menu + + var/obj/item/P = locate(href_list["retrieve"]) in src //contents[retrieveindex] + if(istype(P) && 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"]]
                \nGender: [G.fields["gender"]]
                \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"]]
                \nGender: [G.fields["gender"]]
                \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 fe0c17221e0b..6f10636a7dd3 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 = 'yogstation/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"]) in src - if(istype(I)) - I.forceMove(usr.loc) - usr.put_in_hands(I) - - if(href_list["read"]) - var/obj/item/I = locate(href_list["read"]) in src - if(istype(I)) - 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 = 'yogstation/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"]) in src + if(istype(I)) + I.forceMove(usr.loc) + usr.put_in_hands(I) + + if(href_list["read"]) + var/obj/item/I = locate(href_list["read"]) in src + if(istype(I)) + 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/handlabeler.dm b/code/modules/paperwork/handlabeler.dm index 0ea854b5f00f..540dbbb63668 100644 --- a/code/modules/paperwork/handlabeler.dm +++ b/code/modules/paperwork/handlabeler.dm @@ -1,119 +1,119 @@ -/obj/item/hand_labeler - name = "hand labeler" - desc = "A combined label printer and applicator in a portable device, designed to be easy to operate and use." - icon = 'yogstation/icons/obj/bureaucracy.dmi' - icon_state = "labeler0" - item_state = "flight" - var/label = null - var/labels_left = 30 - var/mode = 0 - -/obj/item/hand_labeler/suicide_act(mob/user) - user.visible_message("[user] is pointing [src] at [user.p_them()]self. [user.p_theyre(TRUE)] going to label [user.p_them()]self as a suicide!") - labels_left = max(labels_left - 1, 0) - - var/old_real_name = user.real_name - user.real_name += " (suicide)" - // no conflicts with their identification card - for(var/atom/A in user.GetAllContents()) - if(istype(A, /obj/item/card/id)) - var/obj/item/card/id/their_card = A - - // only renames their card, as opposed to tagging everyone's - if(their_card.registered_name != old_real_name) - continue - - their_card.registered_name = user.real_name - their_card.update_label() - - // NOT EVEN DEATH WILL TAKE AWAY THE STAIN - user.mind.name += " (suicide)" - - mode = 1 - icon_state = "labeler[mode]" - label = "suicide" - - return OXYLOSS - -/obj/item/hand_labeler/afterattack(atom/A, mob/user,proximity) - . = ..() - if(!proximity) - return - if(!mode) //if it's off, give up. - return - - if(!labels_left) - to_chat(user, "No labels left!") - return - if(!label || !length(label)) - to_chat(user, "No text set!") - return - if(length(A.name) + length(label) > 64) - to_chat(user, "Label too big!") - return - if(ismob(A)) - to_chat(user, "You can't label creatures!") // use a collar - return - - user.visible_message("[user] labels [A] as [label].", \ - "You label [A] as [label].") - A.name = "[A.name] ([label])" - labels_left-- - - -/obj/item/hand_labeler/attack_self(mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to use [src]!") - return - mode = !mode - icon_state = "labeler[mode]" - if(mode) - to_chat(user, "You turn on [src].") - //Now let them chose the text. - var/str = copytext(reject_bad_text(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) - if(!str || !length(str)) - to_chat(user, "Invalid text!") - return - label = str - to_chat(user, "You set the text to '[str]'.") - else - to_chat(user, "You turn off [src].") - -/obj/item/hand_labeler/attackby(obj/item/I, mob/user, params) - ..() - if(istype(I, /obj/item/hand_labeler_refill)) - to_chat(user, "You insert [I] into [src].") - qdel(I) - labels_left = initial(labels_left) //Yes, it's capped at its initial value - -/obj/item/hand_labeler/borg - name = "cyborg-hand labeler" - -/obj/item/hand_labeler/borg/afterattack(atom/A, mob/user, proximity) - . = ..(A, user, proximity) - if(!iscyborg(user)) - return - - var/mob/living/silicon/robot/borgy = user - - var/starting_labels = initial(labels_left) - var/diff = starting_labels - labels_left - if(diff) - labels_left = starting_labels - // 50 per label. Magical cyborg paper doesn't come cheap. - var/cost = diff * 50 - - // If the cyborg manages to use a module without a cell, they get the paper - // for free. - if(borgy.cell) - borgy.cell.use(cost) - -/obj/item/hand_labeler_refill - name = "hand labeler paper roll" - icon = 'yogstation/icons/obj/bureaucracy.dmi' - desc = "A roll of paper. Use it on a hand labeler to refill it." - icon_state = "labeler_refill" - 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 +/obj/item/hand_labeler + name = "hand labeler" + desc = "A combined label printer and applicator in a portable device, designed to be easy to operate and use." + icon = 'yogstation/icons/obj/bureaucracy.dmi' + icon_state = "labeler0" + item_state = "flight" + var/label = null + var/labels_left = 30 + var/mode = 0 + +/obj/item/hand_labeler/suicide_act(mob/user) + user.visible_message("[user] is pointing [src] at [user.p_them()]self. [user.p_theyre(TRUE)] going to label [user.p_them()]self as a suicide!") + labels_left = max(labels_left - 1, 0) + + var/old_real_name = user.real_name + user.real_name += " (suicide)" + // no conflicts with their identification card + for(var/atom/A in user.GetAllContents()) + if(istype(A, /obj/item/card/id)) + var/obj/item/card/id/their_card = A + + // only renames their card, as opposed to tagging everyone's + if(their_card.registered_name != old_real_name) + continue + + their_card.registered_name = user.real_name + their_card.update_label() + + // NOT EVEN DEATH WILL TAKE AWAY THE STAIN + user.mind.name += " (suicide)" + + mode = 1 + icon_state = "labeler[mode]" + label = "suicide" + + return OXYLOSS + +/obj/item/hand_labeler/afterattack(atom/A, mob/user,proximity) + . = ..() + if(!proximity) + return + if(!mode) //if it's off, give up. + return + + if(!labels_left) + to_chat(user, "No labels left!") + return + if(!label || !length(label)) + to_chat(user, "No text set!") + return + if(length(A.name) + length(label) > 64) + to_chat(user, "Label too big!") + return + if(ismob(A)) + to_chat(user, "You can't label creatures!") // use a collar + return + + user.visible_message("[user] labels [A] as [label].", \ + "You label [A] as [label].") + A.name = "[A.name] ([label])" + labels_left-- + + +/obj/item/hand_labeler/attack_self(mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to use [src]!") + return + mode = !mode + icon_state = "labeler[mode]" + if(mode) + to_chat(user, "You turn on [src].") + //Now let them chose the text. + var/str = copytext(reject_bad_text(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) + if(!str || !length(str)) + to_chat(user, "Invalid text!") + return + label = str + to_chat(user, "You set the text to '[str]'.") + else + to_chat(user, "You turn off [src].") + +/obj/item/hand_labeler/attackby(obj/item/I, mob/user, params) + ..() + if(istype(I, /obj/item/hand_labeler_refill)) + to_chat(user, "You insert [I] into [src].") + qdel(I) + labels_left = initial(labels_left) //Yes, it's capped at its initial value + +/obj/item/hand_labeler/borg + name = "cyborg-hand labeler" + +/obj/item/hand_labeler/borg/afterattack(atom/A, mob/user, proximity) + . = ..(A, user, proximity) + if(!iscyborg(user)) + return + + var/mob/living/silicon/robot/borgy = user + + var/starting_labels = initial(labels_left) + var/diff = starting_labels - labels_left + if(diff) + labels_left = starting_labels + // 50 per label. Magical cyborg paper doesn't come cheap. + var/cost = diff * 50 + + // If the cyborg manages to use a module without a cell, they get the paper + // for free. + if(borgy.cell) + borgy.cell.use(cost) + +/obj/item/hand_labeler_refill + name = "hand labeler paper roll" + icon = 'yogstation/icons/obj/bureaucracy.dmi' + desc = "A roll of paper. Use it on a hand labeler to refill it." + icon_state = "labeler_refill" + 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 diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm index 878bb92bed68..8f34f578d7b6 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 = 'yogstation/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) - - 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(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_PICKUP)) - 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.is_sharp()) - 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 = 'yogstation/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) + + 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(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_PICKUP)) + 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.is_sharp()) + 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 a421b9ede2ea..a1ba683f8692 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -1,228 +1,228 @@ -/* Pens! - * Contains: - * Pens - * Sleepy Pens - * Parapens - * Edaggers - */ - - -/* - * Pens - */ -/obj/item/pen - desc = "It's a normal black ink pen." - name = "pen" - icon = 'yogstation/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.total_volume) - reagents.trans_to(M, reagents.total_volume, transfered_by = user) - - -/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 - var/on = FALSE - -/obj/item/pen/edagger/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 60, 100, 0, 'sound/weapons/blade1.ogg', TRUE) - -/obj/item/pen/edagger/suicide_act(mob/user) - . = BRUTELOSS - if(on) - user.visible_message("[user] forcefully rams the pen into their mouth!") - else - user.visible_message("[user] is holding a pen up to their mouth! It looks like [user.p_theyre()] trying to commit suicide!") - attack_self(user) - -/obj/item/pen/edagger/attack_self(mob/living/user) - if(on) - on = FALSE - force = initial(force) - throw_speed = initial(throw_speed) - 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 - throw_speed = 4 - 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.") - var/datum/component/butchering/butchering = src.GetComponent(/datum/component/butchering) - butchering.butchering_enabled = on - 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 = 'yogstation/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.total_volume) + reagents.trans_to(M, reagents.total_volume, transfered_by = user) + + +/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 + var/on = FALSE + +/obj/item/pen/edagger/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 60, 100, 0, 'sound/weapons/blade1.ogg', TRUE) + +/obj/item/pen/edagger/suicide_act(mob/user) + . = BRUTELOSS + if(on) + user.visible_message("[user] forcefully rams the pen into their mouth!") + else + user.visible_message("[user] is holding a pen up to their mouth! It looks like [user.p_theyre()] trying to commit suicide!") + attack_self(user) + +/obj/item/pen/edagger/attack_self(mob/living/user) + if(on) + on = FALSE + force = initial(force) + throw_speed = initial(throw_speed) + 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 + throw_speed = 4 + 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.") + var/datum/component/butchering/butchering = src.GetComponent(/datum/component/butchering) + butchering.butchering_enabled = on + 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 c0e20323cf87..24f4ca7bcab6 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -1,335 +1,335 @@ -/* 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 - temp_img = icon(ass.gender == FEMALE ? 'icons/ass/assfemale.png' : 'icons/ass/assmale.png') - else if(isdrone(ass)) //Drones are hot - temp_img = icon('icons/ass/assdrone.png') - else - break - busy = TRUE - sleep(15) - var/obj/item/photo/p = new /obj/item/photo (loc) - var/datum/picture/toEmbed = new(name = "[ass]'s Ass", desc = "You see [ass]'s ass on the photo.", image = temp_img) - p.pixel_x = rand(-10, 10) - p.pixel_y = rand(-10, 10) - toEmbed.psize_x = 128 - toEmbed.psize_y = 128 - p.set_picture(toEmbed, TRUE, TRUE) - toner -= 5 - 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 + temp_img = icon(ass.gender == FEMALE ? 'icons/ass/assfemale.png' : 'icons/ass/assmale.png') + else if(isdrone(ass)) //Drones are hot + temp_img = icon('icons/ass/assdrone.png') + else + break + busy = TRUE + sleep(15) + var/obj/item/photo/p = new /obj/item/photo (loc) + var/datum/picture/toEmbed = new(name = "[ass]'s Ass", desc = "You see [ass]'s ass on the photo.", image = temp_img) + p.pixel_x = rand(-10, 10) + p.pixel_y = rand(-10, 10) + toEmbed.psize_x = 128 + toEmbed.psize_y = 128 + p.set_picture(toEmbed, TRUE, TRUE) + toner -= 5 + 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 579fa212408c..8777b6122a40 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 = 'yogstation/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 = 'yogstation/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/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm index bcdb240f2236..e8694a0ab021 100644 --- a/code/modules/paperwork/ticketmachine.dm +++ b/code/modules/paperwork/ticketmachine.dm @@ -1,202 +1,202 @@ -//Bureaucracy machine! -//Simply set this up in the hopline and you can serve people based on ticket numbers - -/obj/machinery/ticket_machine - name = "ticket machine" - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "ticketmachine" - desc = "A marvel of bureaucratic engineering encased in an efficient plastic shell. It can be refilled with a hand labeler refill roll and linked to buttons with a multitool." - density = FALSE - maptext_height = 26 - maptext_width = 32 - maptext_x = 7 - maptext_y = 10 - layer = HIGH_OBJ_LAYER - var/ticket_number = 0 //Increment the ticket number whenever the HOP presses his button - var/current_number = 0 //What customer are we currently serving? - var/max_number = 999 //To stop the text going fucky. At this point, you need to refill it. - var/cooldown = 50 //Small cooldown, stops the clown from immediately breaking it. - var/ready = TRUE - var/id = "ticket_machine_default" //For buttons - -/obj/machinery/ticket_machine/multitool_act(mob/living/user, obj/item/I) - if(!multitool_check_buffer(user, I)) //make sure it has a data buffer - return - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You store linkage information in [I]'s buffer.") - return TRUE - -/obj/machinery/ticket_machine/emag_act(mob/user) //Emag the ticket machine to dispense burning tickets, as well as randomize its customer number to destroy the HOP's mind. - if(obj_flags & EMAGGED) - return - to_chat(user, "You overload [src]'s bureaucratic logic circuitry to its MAXIMUM setting.") - ticket_number = rand(0,999) - obj_flags |= EMAGGED - -/obj/machinery/ticket_machine/Initialize() - . = ..() - update_icon() - -/obj/machinery/ticket_machine/proc/increment() - if(current_number >= ticket_number) - return - playsound(src, 'sound/misc/announce_dig.ogg', 50, 0) - say("Next customer, please!") - current_number ++ //Increment the one we're serving. - update_icon() //Update our icon here rather than when they take a ticket to show the current ticket number being served - -/obj/machinery/button/ticket_machine - name = "increment ticket counter" - desc = "Use this button when you've served a customer to tell the next one to come forward." - device_type = /obj/item/assembly/control/ticket_machine - req_access = list() - id = "ticket_machine_default" - -/obj/machinery/button/ticket_machine/Initialize() - . = ..() - if(device) - var/obj/item/assembly/control/ticket_machine/ours = device - ours.id = id - -/obj/machinery/button/ticket_machine/multitool_act(mob/living/user, obj/item/I) - if(I.tool_behaviour == TOOL_MULTITOOL) - var/obj/item/multitool/M = I - if(M.buffer && !istype(M.buffer, /obj/machinery/ticket_machine)) - return - var/obj/item/assembly/control/ticket_machine/controller = device - controller.linked = M.buffer - id = null - controller.id = null - to_chat(user, "You've linked [src] to [controller.linked].") - -/obj/item/assembly/control/ticket_machine - name = "ticket machine controller" - desc = "A remote controller for the HOP's ticket machine." - var/obj/machinery/ticket_machine/linked //To whom are we linked? - -/obj/item/assembly/control/ticket_machine/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/item/assembly/control/ticket_machine/LateInitialize() - find_machine() - -/obj/item/assembly/control/ticket_machine/proc/find_machine() //Locate the one to which we're linked - for(var/obj/machinery/ticket_machine/ticketsplease in GLOB.machines) - if(ticketsplease.id == id) - linked = ticketsplease - if(linked) - return TRUE - else - return FALSE - -/obj/item/assembly/control/ticket_machine/activate() - if(cooldown) - return - if(!linked) - return - cooldown = TRUE - linked.increment() - addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 10) - -/obj/machinery/ticket_machine/update_icon() - switch(ticket_number) //Gives you an idea of how many tickets are left - if(0 to 200) - icon_state = "ticketmachine_100" - if(201 to 800) - icon_state = "ticketmachine_50" - if(801 to 999) - icon_state = "ticketmachine_0" - handle_maptext() - -/obj/machinery/ticket_machine/proc/handle_maptext() - switch(ticket_number) //This is here to handle maptext offsets so that the numbers align. - if(0 to 9) - maptext_x = 13 - if(10 to 99) - maptext_x = 10 - if(100 to 999) - maptext_x = 8 - maptext = "[current_number]" //Finally, apply the maptext - -/obj/machinery/ticket_machine/attackby(obj/item/I, mob/user, params) - ..() - if(ticket_number >= max_number) - to_chat(user, "[src] refuses [I]!, perhaps it's already full?.") - return - if(istype(I, /obj/item/hand_labeler_refill)) - to_chat(user, "You start to refill [src]'s ticket holder (doing this will reset its ticket count!).") - if(do_after(user, 30, target = src)) - to_chat(user, "You insert [I] into [src] as it whirrs nondescriptly.") - qdel(I) - ticket_number = 0 - current_number = 0 - max_number = initial(max_number) - update_icon() - return - if(istype(I, /obj/item/ticket_machine_ticket)) - to_chat(user, "You start to cram [I] into [src]'s recycling bin.") - if(do_after(user, 20, target = src)) //Slight delay so they don't accidentally dispose of their ticket and move to the BACK OF THE LINE - qdel(I) - return - -/obj/machinery/ticket_machine/proc/reset_cooldown() - ready = TRUE - -/obj/machinery/ticket_machine/attack_hand(mob/living/carbon/user) - . = ..() - if(!ready) - to_chat(user,"Temporarily unable to dispense ticket, please be patient!") - return - if(ticket_number >= max_number) - to_chat(user,"Ticket supply depleted, please refill this unit with a hand labeller refill cartridge!") - return - ready = FALSE - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 100, 0) - addtimer(CALLBACK(src, .proc/reset_cooldown), cooldown)//Small cooldown to prevent the clown from ripping out every ticket - ticket_number ++ - to_chat(user, "You take a ticket from [src], looks like you're customer #[ticket_number]...") - var/obj/item/ticket_machine_ticket/theirticket = new /obj/item/ticket_machine_ticket(get_turf(src)) - theirticket.name = "Ticket #[ticket_number]" - theirticket.maptext = "[ticket_number]" - theirticket.saved_maptext = "[ticket_number]" - user.put_in_hands(theirticket) - if(obj_flags & EMAGGED) //Emag the machine to destroy the HOP's life. - theirticket.fire_act() - user.dropItemToGround(theirticket) - user.adjust_fire_stacks(1) - user.IgniteMob() - return - -/obj/item/ticket_machine_ticket - name = "Ticket" - desc = "A ticket which shows your place in the line, you can put it back into the ticket machine when youre done with it." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "ticket" - maptext_x = 7 - maptext_y = 10 - w_class = WEIGHT_CLASS_TINY - var/saved_maptext = null - -/obj/item/ticket_machine_ticket/attack_hand(mob/user) - . = ..() - maptext = saved_maptext //For some reason, storage code removes all maptext off objs, this stops its number from being wiped off when taken out of storage. - -/obj/item/ticket_machine_ticket/attackby(obj/item/P, mob/living/carbon/human/user, params) //Stolen from papercode - ..() - if(P.is_hot()) - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(10)) - user.visible_message("[user] accidentally ignites [user.p_them()]self!", \ - "You miss the paper and accidentally light yourself on fire!") - user.dropItemToGround(P) - user.adjust_fire_stacks(1) - user.IgniteMob() - return - user.visible_message("[user] lights [src] ablaze with [P]!", "You light [src] on fire!") - fire_act() - -/obj/item/ticket_machine_ticket/fire_act(exposed_temperature, exposed_volume) - ..() - if(!(resistance_flags & FIRE_PROOF)) +//Bureaucracy machine! +//Simply set this up in the hopline and you can serve people based on ticket numbers + +/obj/machinery/ticket_machine + name = "ticket machine" + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "ticketmachine" + desc = "A marvel of bureaucratic engineering encased in an efficient plastic shell. It can be refilled with a hand labeler refill roll and linked to buttons with a multitool." + density = FALSE + maptext_height = 26 + maptext_width = 32 + maptext_x = 7 + maptext_y = 10 + layer = HIGH_OBJ_LAYER + var/ticket_number = 0 //Increment the ticket number whenever the HOP presses his button + var/current_number = 0 //What customer are we currently serving? + var/max_number = 999 //To stop the text going fucky. At this point, you need to refill it. + var/cooldown = 50 //Small cooldown, stops the clown from immediately breaking it. + var/ready = TRUE + var/id = "ticket_machine_default" //For buttons + +/obj/machinery/ticket_machine/multitool_act(mob/living/user, obj/item/I) + if(!multitool_check_buffer(user, I)) //make sure it has a data buffer + return + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You store linkage information in [I]'s buffer.") + return TRUE + +/obj/machinery/ticket_machine/emag_act(mob/user) //Emag the ticket machine to dispense burning tickets, as well as randomize its customer number to destroy the HOP's mind. + if(obj_flags & EMAGGED) + return + to_chat(user, "You overload [src]'s bureaucratic logic circuitry to its MAXIMUM setting.") + ticket_number = rand(0,999) + obj_flags |= EMAGGED + +/obj/machinery/ticket_machine/Initialize() + . = ..() + update_icon() + +/obj/machinery/ticket_machine/proc/increment() + if(current_number >= ticket_number) + return + playsound(src, 'sound/misc/announce_dig.ogg', 50, 0) + say("Next customer, please!") + current_number ++ //Increment the one we're serving. + update_icon() //Update our icon here rather than when they take a ticket to show the current ticket number being served + +/obj/machinery/button/ticket_machine + name = "increment ticket counter" + desc = "Use this button when you've served a customer to tell the next one to come forward." + device_type = /obj/item/assembly/control/ticket_machine + req_access = list() + id = "ticket_machine_default" + +/obj/machinery/button/ticket_machine/Initialize() + . = ..() + if(device) + var/obj/item/assembly/control/ticket_machine/ours = device + ours.id = id + +/obj/machinery/button/ticket_machine/multitool_act(mob/living/user, obj/item/I) + if(I.tool_behaviour == TOOL_MULTITOOL) + var/obj/item/multitool/M = I + if(M.buffer && !istype(M.buffer, /obj/machinery/ticket_machine)) + return + var/obj/item/assembly/control/ticket_machine/controller = device + controller.linked = M.buffer + id = null + controller.id = null + to_chat(user, "You've linked [src] to [controller.linked].") + +/obj/item/assembly/control/ticket_machine + name = "ticket machine controller" + desc = "A remote controller for the HOP's ticket machine." + var/obj/machinery/ticket_machine/linked //To whom are we linked? + +/obj/item/assembly/control/ticket_machine/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/item/assembly/control/ticket_machine/LateInitialize() + find_machine() + +/obj/item/assembly/control/ticket_machine/proc/find_machine() //Locate the one to which we're linked + for(var/obj/machinery/ticket_machine/ticketsplease in GLOB.machines) + if(ticketsplease.id == id) + linked = ticketsplease + if(linked) + return TRUE + else + return FALSE + +/obj/item/assembly/control/ticket_machine/activate() + if(cooldown) + return + if(!linked) + return + cooldown = TRUE + linked.increment() + addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 10) + +/obj/machinery/ticket_machine/update_icon() + switch(ticket_number) //Gives you an idea of how many tickets are left + if(0 to 200) + icon_state = "ticketmachine_100" + if(201 to 800) + icon_state = "ticketmachine_50" + if(801 to 999) + icon_state = "ticketmachine_0" + handle_maptext() + +/obj/machinery/ticket_machine/proc/handle_maptext() + switch(ticket_number) //This is here to handle maptext offsets so that the numbers align. + if(0 to 9) + maptext_x = 13 + if(10 to 99) + maptext_x = 10 + if(100 to 999) + maptext_x = 8 + maptext = "[current_number]" //Finally, apply the maptext + +/obj/machinery/ticket_machine/attackby(obj/item/I, mob/user, params) + ..() + if(ticket_number >= max_number) + to_chat(user, "[src] refuses [I]!, perhaps it's already full?.") + return + if(istype(I, /obj/item/hand_labeler_refill)) + to_chat(user, "You start to refill [src]'s ticket holder (doing this will reset its ticket count!).") + if(do_after(user, 30, target = src)) + to_chat(user, "You insert [I] into [src] as it whirrs nondescriptly.") + qdel(I) + ticket_number = 0 + current_number = 0 + max_number = initial(max_number) + update_icon() + return + if(istype(I, /obj/item/ticket_machine_ticket)) + to_chat(user, "You start to cram [I] into [src]'s recycling bin.") + if(do_after(user, 20, target = src)) //Slight delay so they don't accidentally dispose of their ticket and move to the BACK OF THE LINE + qdel(I) + return + +/obj/machinery/ticket_machine/proc/reset_cooldown() + ready = TRUE + +/obj/machinery/ticket_machine/attack_hand(mob/living/carbon/user) + . = ..() + if(!ready) + to_chat(user,"Temporarily unable to dispense ticket, please be patient!") + return + if(ticket_number >= max_number) + to_chat(user,"Ticket supply depleted, please refill this unit with a hand labeller refill cartridge!") + return + ready = FALSE + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 100, 0) + addtimer(CALLBACK(src, .proc/reset_cooldown), cooldown)//Small cooldown to prevent the clown from ripping out every ticket + ticket_number ++ + to_chat(user, "You take a ticket from [src], looks like you're customer #[ticket_number]...") + var/obj/item/ticket_machine_ticket/theirticket = new /obj/item/ticket_machine_ticket(get_turf(src)) + theirticket.name = "Ticket #[ticket_number]" + theirticket.maptext = "[ticket_number]" + theirticket.saved_maptext = "[ticket_number]" + user.put_in_hands(theirticket) + if(obj_flags & EMAGGED) //Emag the machine to destroy the HOP's life. + theirticket.fire_act() + user.dropItemToGround(theirticket) + user.adjust_fire_stacks(1) + user.IgniteMob() + return + +/obj/item/ticket_machine_ticket + name = "Ticket" + desc = "A ticket which shows your place in the line, you can put it back into the ticket machine when youre done with it." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "ticket" + maptext_x = 7 + maptext_y = 10 + w_class = WEIGHT_CLASS_TINY + var/saved_maptext = null + +/obj/item/ticket_machine_ticket/attack_hand(mob/user) + . = ..() + maptext = saved_maptext //For some reason, storage code removes all maptext off objs, this stops its number from being wiped off when taken out of storage. + +/obj/item/ticket_machine_ticket/attackby(obj/item/P, mob/living/carbon/human/user, params) //Stolen from papercode + ..() + if(P.is_hot()) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(10)) + user.visible_message("[user] accidentally ignites [user.p_them()]self!", \ + "You miss the paper and accidentally light yourself on fire!") + user.dropItemToGround(P) + user.adjust_fire_stacks(1) + user.IgniteMob() + return + user.visible_message("[user] lights [src] ablaze with [P]!", "You light [src] on fire!") + fire_act() + +/obj/item/ticket_machine_ticket/fire_act(exposed_temperature, exposed_volume) + ..() + if(!(resistance_flags & FIRE_PROOF)) icon_state = "ticket_onfire" \ No newline at end of file diff --git a/code/modules/photography/_pictures.dm b/code/modules/photography/_pictures.dm index e0a31f1abcca..c2ec91a517bb 100644 --- a/code/modules/photography/_pictures.dm +++ b/code/modules/photography/_pictures.dm @@ -1,177 +1,177 @@ -/datum/picture - var/picture_name = "picture" - var/picture_desc = "This is a picture." - var/list/mobs_seen = list() - var/list/dead_seen = list() - 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, mobs_spotted, dead_spotted, image, icon, size_x, size_y, bp, caption_, autogenerate_icon) - if(!isnull(name)) - picture_name = name - if(!isnull(desc)) - picture_desc = desc - if(!isnull(mobs_spotted)) - mobs_seen = mobs_spotted - if(!isnull(dead_spotted)) - dead_seen = dead_spotted - 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/list/mobs_seen = list() + var/list/dead_seen = list() + 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, mobs_spotted, dead_spotted, image, icon, size_x, size_y, bp, caption_, autogenerate_icon) + if(!isnull(name)) + picture_name = name + if(!isnull(desc)) + picture_desc = desc + if(!isnull(mobs_spotted)) + mobs_seen = mobs_spotted + if(!isnull(dead_spotted)) + dead_seen = dead_spotted + 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 6882e5321359..d73fe5db4453 100644 --- a/code/modules/photography/camera/camera.dm +++ b/code/modules/photography/camera/camera.dm @@ -1,229 +1,229 @@ - -#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 - var/can_customise = TRUE - var/default_picture_name - -/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/proc/adjust_zoom(mob/user) - 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) - -/obj/item/camera/AltClick(mob/user) - if(!user.canUseTopic(src, BE_CLOSE)) - return - adjust_zoom(user) - -/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/list/mobs_spotted = list() - var/list/dead_spotted = list() - 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 - mobs_spotted += M - if(M.stat == DEAD) - dead_spotted += M - 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(" "), mobs_spotted, dead_spotted, 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/customise = "No" - if(can_customise) - customise = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No") - if(customise == "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 - else - if(default_picture_name) - picture.picture_name = default_picture_name - - p.set_picture(picture, TRUE, TRUE) - if(CONFIG_GET(flag/picture_logging_camera)) - picture.log_to_file() + +#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 + var/can_customise = TRUE + var/default_picture_name + +/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/proc/adjust_zoom(mob/user) + 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) + +/obj/item/camera/AltClick(mob/user) + if(!user.canUseTopic(src, BE_CLOSE)) + return + adjust_zoom(user) + +/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/list/mobs_spotted = list() + var/list/dead_spotted = list() + 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 + mobs_spotted += M + if(M.stat == DEAD) + dead_spotted += M + 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(" "), mobs_spotted, dead_spotted, 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/customise = "No" + if(can_customise) + customise = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No") + if(customise == "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 + else + if(default_picture_name) + picture.picture_name = default_picture_name + + p.set_picture(picture, TRUE, TRUE) + if(CONFIG_GET(flag/picture_logging_camera)) + picture.log_to_file() diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm index fe093fa2ca84..8105659559df 100644 --- a/code/modules/photography/camera/camera_image_capturing.dm +++ b/code/modules/photography/camera/camera_image_capturing.dm @@ -1,88 +1,88 @@ -/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) - 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) + 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 5d69824bade2..b1cd6bae66d0 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 ce2572db36dd..b10af32bfd88 100644 --- a/code/modules/photography/camera/other.dm +++ b/code/modules/photography/camera/other.dm @@ -1,14 +1,14 @@ -/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 + 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 diff --git a/code/modules/photography/camera/silicon_camera.dm b/code/modules/photography/camera/silicon_camera.dm index 8e1c9338c67f..28a080d5f12a 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 129c83716610..80487c9a3d1b 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.set_holdable(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.set_holdable(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 40f7d22c728c..80e23426a2ea 100644 --- a/code/modules/photography/photos/frame.dm +++ b/code/modules/photography/photos/frame.dm @@ -1,166 +1,166 @@ -// 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() - else - 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) - . = ..() - AddComponent(/datum/component/art, 20) - 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() - else - return ..() - -/obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) - if(can_decon && (I.tool_behaviour == TOOL_SCREWDRIVER || I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_WIRECUTTER && 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() + else + 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) + . = ..() + AddComponent(/datum/component/art, 20) + 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() + else + return ..() + +/obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) + if(can_decon && (I.tool_behaviour == TOOL_SCREWDRIVER || I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_WIRECUTTER && 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 26359402b266..29a2050aea19 100644 --- a/code/modules/photography/photos/photo.dm +++ b/code/modules/photography/photos/photo.dm @@ -1,93 +1,93 @@ -/* - * 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) || isobserver(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.incapacitated()) - 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) || isobserver(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.incapacitated()) + name = "photo[(n_name ? text("- '[n_name]'") : null)]" + add_fingerprint(usr) diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index cd0f3c6daf70..22e5b0bcce8f 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -1,854 +1,854 @@ -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(W.tool_behaviour == TOOL_WIRECUTTER) - 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(W.tool_behaviour == TOOL_MULTITOOL) - if(powernet && (powernet.avail > 0)) // is it powered? - to_chat(user, "Total power: [DisplayPower(powernet.avail)]\nLoad: [DisplayPower(powernet.load)]\nExcess power: [DisplayPower(surplus())]") - else - to_chat(user, "The cable is not powered.") - shock(user, 5, 0.2) - - 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() - if(powernet) - return 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" - custom_price = 15 - 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 == user ? "your" : "[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, var/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) - if(forceddir) - dirn = forceddir - - // 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" +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(W.tool_behaviour == TOOL_WIRECUTTER) + 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(W.tool_behaviour == TOOL_MULTITOOL) + if(powernet && (powernet.avail > 0)) // is it powered? + to_chat(user, "Total power: [DisplayPower(powernet.avail)]\nLoad: [DisplayPower(powernet.load)]\nExcess power: [DisplayPower(surplus())]") + else + to_chat(user, "The cable is not powered.") + shock(user, 5, 0.2) + + 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() + if(powernet) + return 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" + custom_price = 15 + 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 == user ? "your" : "[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, var/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) + if(forceddir) + dirn = forceddir + + // 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" diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index f1173b21520a..2076da715cdd 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -1,357 +1,357 @@ -/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. - -/obj/item/stock_parts/cell/get_cell() - return src - -/obj/item/stock_parts/cell/Initialize(mapload, override_maxcharge) - . = ..() - 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) - if(rigged && amount > 0) - 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 = !isnull(reagents.has_reagent(/datum/reagent/toxin/plasma, 5)) //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(20 + round(charge/25000), 20, 195) + 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 = 600 //600 max charge / 100 charge per shot = six shots - materials = list(MAT_GLASS=40) - -/obj/item/stock_parts/cell/secborg/empty/Initialize() - . = ..() - charge = 0 - update_icon() - -/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 +/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. + +/obj/item/stock_parts/cell/get_cell() + return src + +/obj/item/stock_parts/cell/Initialize(mapload, override_maxcharge) + . = ..() + 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) + if(rigged && amount > 0) + 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 = !isnull(reagents.has_reagent(/datum/reagent/toxin/plasma, 5)) //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(20 + round(charge/25000), 20, 195) + 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 = 600 //600 max charge / 100 charge per shot = six shots + materials = list(MAT_GLASS=40) + +/obj/item/stock_parts/cell/secborg/empty/Initialize() + . = ..() + charge = 0 + update_icon() + +/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 diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm index f3f960c0eecc..08695da04542 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.65 - - 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.65 + + 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 ebb6a37475c2..594f7bb23688 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -1,410 +1,410 @@ - -// -// 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 - move_resist = INFINITY - 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(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) - 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 = 1 - 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 = 0 - 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 = 0 - 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(I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_WELDER) - 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(I.tool_behaviour == TOOL_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 = 0 - if(stat & (NOPOWER|BROKEN) || !breaker) - new_state = 0 - else if(breaker) - new_state = 1 - - 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() == 0) - alert = 1 - 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() == 1) - alert = 1 - 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) - 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 0 - if(GLOB.gravity_generators["[T.z]"]) - return length(GLOB.gravity_generators["[T.z]"]) - return 0 - -/obj/machinery/gravity_generator/main/proc/update_list() - var/turf/T = get_turf(src.loc) - if(T) - if(!GLOB.gravity_generators["[T.z]"]) - GLOB.gravity_generators["[T.z]"] = list() - if(on) - GLOB.gravity_generators["[T.z]"] |= src - else - GLOB.gravity_generators["[T.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 + move_resist = INFINITY + 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(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) + 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 = 1 + 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 = 0 + 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 = 0 + 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(I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_WELDER) + 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(I.tool_behaviour == TOOL_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 = 0 + if(stat & (NOPOWER|BROKEN) || !breaker) + new_state = 0 + else if(breaker) + new_state = 1 + + 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() == 0) + alert = 1 + 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() == 1) + alert = 1 + 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) + 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 0 + if(GLOB.gravity_generators["[T.z]"]) + return length(GLOB.gravity_generators["[T.z]"]) + return 0 + +/obj/machinery/gravity_generator/main/proc/update_list() + var/turf/T = get_turf(src.loc) + if(T) + if(!GLOB.gravity_generators["[T.z]"]) + GLOB.gravity_generators["[T.z]"] = list() + if(on) + GLOB.gravity_generators["[T.z]"] |= src + else + GLOB.gravity_generators["[T.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 28843e14e091..6b0ab6cbb243 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 230f86ee9129..701376ab9351 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -1,289 +1,289 @@ -//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 = FALSE - var/power_gen = 5000 - 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/connect_to_network() - if(!anchored) - return FALSE - . = ..() - -/obj/machinery/power/port_gen/proc/HasFuel() //Placeholder for fuel check. - return TRUE - -/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/proc/TogglePower() - if(active) - active = FALSE - update_icon() - soundloop.stop() - else if(HasFuel()) - active = TRUE - START_PROCESSING(SSmachines, src) - update_icon() - soundloop.start() - -/obj/machinery/power/port_gen/update_icon() - icon_state = "[base_icon]_[active]" - -/obj/machinery/power/port_gen/process() - if(active) - if(!HasFuel() || !anchored) - TogglePower() - return - if(powernet) - add_avail(power_gen * power_output) - UseFuel() - else - handleInactive() - -/obj/machinery/power/port_gen/examine(mob/user) - . = ..() - . += "It is[!active?"n't":""] running." - -///////////////// -// P.A.C.M.A.N // -///////////////// -/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/S = sheet_path - sheet_name = initial(S.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 [DisplayPower(power_gen)] per cycle." - if(anchored) - . += "It is anchored to the ground." - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Fuel efficiency increased by [(consumption*100)-100]%." - -/obj/machinery/power/port_gen/pacman/HasFuel() - if(sheets >= 1 / (time_per_sheet / power_output) - sheet_left) - return TRUE - return FALSE - -/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) - -/obj/machinery/power/port_gen/pacman/handleInactive() - current_heat = max(current_heat - 2, 0) - if(current_heat == 0) - STOP_PROCESSING(SSmachines, src) - -/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) - return - else if(!active) - if(O.tool_behaviour == TOOL_WRENCH) - if(!anchored && !isinspace()) - anchored = TRUE - connect_to_network() - to_chat(user, "You secure the generator to the floor.") - else if(anchored) - anchored = FALSE - disconnect_from_network() - to_chat(user, "You unsecure the generator from the floor.") - - playsound(src, 'sound/items/deconstruct.ogg', 50, 1) - return - else if(O.tool_behaviour == TOOL_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) - -/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, 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, "portable_generator", name, 450, 340, master_ui, state) - ui.open() - -/obj/machinery/power/port_gen/pacman/ui_data() - var/data = list() - - data["active"] = active - data["sheet_name"] = capitalize(sheet_name) - data["sheets"] = sheets - data["stack_percent"] = round(sheet_left * 100, 0.1) - - data["anchored"] = anchored - data["connected"] = (powernet == null ? 0 : 1) - data["ready_to_boot"] = anchored && HasFuel() - data["power_generated"] = DisplayPower(power_gen) - data["power_output"] = DisplayPower(power_gen * power_output) - data["power_available"] = (powernet == null ? 0 : DisplayPower(avail())) - data["current_heat"] = current_heat - . = data - -/obj/machinery/power/port_gen/pacman/ui_act(action, params) - if(..()) - return - switch(action) - if("toggle_power") - TogglePower() - . = TRUE - - if("eject") - if(!active) - DropFuel() - . = TRUE - - if("lower_power") - if (power_output > 1) - power_output-- - . = TRUE - - if("higher_power") - if (power_output < 4 || (obj_flags & EMAGGED)) - power_output++ - . = TRUE - -/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 = FALSE + var/power_gen = 5000 + 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/connect_to_network() + if(!anchored) + return FALSE + . = ..() + +/obj/machinery/power/port_gen/proc/HasFuel() //Placeholder for fuel check. + return TRUE + +/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/proc/TogglePower() + if(active) + active = FALSE + update_icon() + soundloop.stop() + else if(HasFuel()) + active = TRUE + START_PROCESSING(SSmachines, src) + update_icon() + soundloop.start() + +/obj/machinery/power/port_gen/update_icon() + icon_state = "[base_icon]_[active]" + +/obj/machinery/power/port_gen/process() + if(active) + if(!HasFuel() || !anchored) + TogglePower() + return + if(powernet) + add_avail(power_gen * power_output) + UseFuel() + else + handleInactive() + +/obj/machinery/power/port_gen/examine(mob/user) + . = ..() + . += "It is[!active?"n't":""] running." + +///////////////// +// P.A.C.M.A.N // +///////////////// +/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/S = sheet_path + sheet_name = initial(S.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 [DisplayPower(power_gen)] per cycle." + if(anchored) + . += "It is anchored to the ground." + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Fuel efficiency increased by [(consumption*100)-100]%." + +/obj/machinery/power/port_gen/pacman/HasFuel() + if(sheets >= 1 / (time_per_sheet / power_output) - sheet_left) + return TRUE + return FALSE + +/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) + +/obj/machinery/power/port_gen/pacman/handleInactive() + current_heat = max(current_heat - 2, 0) + if(current_heat == 0) + STOP_PROCESSING(SSmachines, src) + +/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) + return + else if(!active) + if(O.tool_behaviour == TOOL_WRENCH) + if(!anchored && !isinspace()) + anchored = TRUE + connect_to_network() + to_chat(user, "You secure the generator to the floor.") + else if(anchored) + anchored = FALSE + disconnect_from_network() + to_chat(user, "You unsecure the generator from the floor.") + + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + return + else if(O.tool_behaviour == TOOL_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) + +/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, 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, "portable_generator", name, 450, 340, master_ui, state) + ui.open() + +/obj/machinery/power/port_gen/pacman/ui_data() + var/data = list() + + data["active"] = active + data["sheet_name"] = capitalize(sheet_name) + data["sheets"] = sheets + data["stack_percent"] = round(sheet_left * 100, 0.1) + + data["anchored"] = anchored + data["connected"] = (powernet == null ? 0 : 1) + data["ready_to_boot"] = anchored && HasFuel() + data["power_generated"] = DisplayPower(power_gen) + data["power_output"] = DisplayPower(power_gen * power_output) + data["power_available"] = (powernet == null ? 0 : DisplayPower(avail())) + data["current_heat"] = current_heat + . = data + +/obj/machinery/power/port_gen/pacman/ui_act(action, params) + if(..()) + return + switch(action) + if("toggle_power") + TogglePower() + . = TRUE + + if("eject") + if(!active) + DropFuel() + . = TRUE + + if("lower_power") + if (power_output > 1) + power_output-- + . = TRUE + + if("higher_power") + if (power_output < 4 || (obj_flags & EMAGGED)) + power_output++ + . = TRUE + +/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 1861bcb0ee47..da313d70f43c 100644 --- a/code/modules/power/singularity/collector.dm +++ b/code/modules/power/singularity/collector.dm @@ -1,240 +1,240 @@ -// 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 - rad_insulation = RAD_EXTREME_INSULATION - var/obj/item/tank/internals/plasma/loaded_tank = null - var/stored_power = 0 - var/active = 0 - var/locked = FALSE - var/drainratio = 1 - var/powerproduction_drain = 0.001 - - var/bitcoinproduction_drain = 0.15 - var/bitcoinmining = FALSE - - -/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) - investigate_log("out of fuel.", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful - playsound(src, 'sound/machines/ding.ogg', 50, 1) - eject() - else - var/gasdrained = min(powerproduction_drain*drainratio,loaded_tank.air_contents.gases[/datum/gas/plasma][MOLES]) - loaded_tank.air_contents.gases[/datum/gas/plasma][MOLES] -= gasdrained - loaded_tank.air_contents.assert_gas(/datum/gas/tritium) - loaded_tank.air_contents.gases[/datum/gas/tritium][MOLES] += gasdrained - loaded_tank.air_contents.garbage_collect() - - 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][MOLES] -= gasdrained - loaded_tank.air_contents.gases[/datum/gas/oxygen][MOLES] -= gasdrained - loaded_tank.air_contents.assert_gas(/datum/gas/carbon_dioxide) - loaded_tank.air_contents.gases[/datum/gas/carbon_dioxide][MOLES] += gasdrained*2 - loaded_tank.air_contents.garbage_collect() - var/bitcoins_mined = RAD_COLLECTOR_OUTPUT - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_ENG) - if(D) - D.adjust_money(bitcoins_mined*RAD_COLLECTOR_MINING_CONVERSION_RATE) - SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, bitcoins_mined*RAD_COLLECTOR_MINING_CONVERSION_RATE) - stored_power-=bitcoins_mined - -/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] - fuel = fuel ? fuel[MOLES] : 0 - investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_SINGULO) - investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful - 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 processing [DisplayPower(RAD_COLLECTOR_OUTPUT)]." - else - . += "[src]'s display states that it has stored a total of [stored_power*RAD_COLLECTOR_MINING_CONVERSION_RATE], and producing [RAD_COLLECTOR_OUTPUT*RAD_COLLECTOR_MINING_CONVERSION_RATE] research points per minute." - 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 + rad_insulation = RAD_EXTREME_INSULATION + var/obj/item/tank/internals/plasma/loaded_tank = null + var/stored_power = 0 + var/active = 0 + var/locked = FALSE + var/drainratio = 1 + var/powerproduction_drain = 0.001 + + var/bitcoinproduction_drain = 0.15 + var/bitcoinmining = FALSE + + +/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) + investigate_log("out of fuel.", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful + playsound(src, 'sound/machines/ding.ogg', 50, 1) + eject() + else + var/gasdrained = min(powerproduction_drain*drainratio,loaded_tank.air_contents.gases[/datum/gas/plasma][MOLES]) + loaded_tank.air_contents.gases[/datum/gas/plasma][MOLES] -= gasdrained + loaded_tank.air_contents.assert_gas(/datum/gas/tritium) + loaded_tank.air_contents.gases[/datum/gas/tritium][MOLES] += gasdrained + loaded_tank.air_contents.garbage_collect() + + 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][MOLES] -= gasdrained + loaded_tank.air_contents.gases[/datum/gas/oxygen][MOLES] -= gasdrained + loaded_tank.air_contents.assert_gas(/datum/gas/carbon_dioxide) + loaded_tank.air_contents.gases[/datum/gas/carbon_dioxide][MOLES] += gasdrained*2 + loaded_tank.air_contents.garbage_collect() + var/bitcoins_mined = RAD_COLLECTOR_OUTPUT + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_ENG) + if(D) + D.adjust_money(bitcoins_mined*RAD_COLLECTOR_MINING_CONVERSION_RATE) + SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, bitcoins_mined*RAD_COLLECTOR_MINING_CONVERSION_RATE) + stored_power-=bitcoins_mined + +/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] + fuel = fuel ? fuel[MOLES] : 0 + investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_SINGULO) + investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful + 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 processing [DisplayPower(RAD_COLLECTOR_OUTPUT)]." + else + . += "[src]'s display states that it has stored a total of [stored_power*RAD_COLLECTOR_MINING_CONVERSION_RATE], and producing [RAD_COLLECTOR_OUTPUT*RAD_COLLECTOR_MINING_CONVERSION_RATE] research points per minute." + 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 c98b5fa7de87..d9f307554cf6 100644 --- a/code/modules/power/singularity/containment_field.dm +++ b/code/modules/power/singularity/containment_field.dm @@ -1,135 +1,135 @@ - - -/obj/machinery/field/containment - name = "containment field" - desc = "An energy field." - icon = 'icons/obj/singularity.dmi' - icon_state = "Contain_F" - density = FALSE - move_resist = INFINITY - 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.Paralyze(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 + move_resist = INFINITY + 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.Paralyze(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 1a480329ca3c..7428c9896b69 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -1,516 +1,516 @@ -//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/icon_state_underpowered = "emitter_+u" - 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(5, 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/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Emitting one beam each [fire_delay*0.1] seconds.
                Power consumption at [active_power_usage]W." - -/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) - investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful - QDEL_NULL(sparks) - return ..() - -/obj/machinery/power/emitter/update_icon() - if(active && powernet) - icon_state = avail(active_power_usage) ? icon_state_on : icon_state_underpowered - else - icon_state = initial(icon_state) - -/obj/machinery/power/emitter/power_change() - . = ..() - update_icon() - -/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) - investigate_log("turned [active ? "ON" : "OFF"] by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful - 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) - investigate_log("regained power and turned ON at [AREACOORD(src)]", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful - else - if(powered) - powered = FALSE - update_icon() - investigate_log("lost power and turned OFF at [AREACOORD(src)]", INVESTIGATE_SINGULO) - investigate_log("lost power and turned OFF at [AREACOORD(src)]", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful - 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_WRENCHED) - 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_WELDED) - 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 - if(user) - user.visible_message("[user.name] emags [src].","You short out the lock.") - - -/obj/machinery/power/emitter/prototype - name = "Prototype Emitter" - icon = 'icons/obj/turrets.dmi' - icon_state = "protoemitter" - icon_state_on = "protoemitter_+a" - icon_state_underpowered = "protoemitter_+u" - 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/icon_state_underpowered = "emitter_+u" + 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(5, 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/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Emitting one beam each [fire_delay*0.1] seconds.
                Power consumption at [active_power_usage]W." + +/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) + investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful + QDEL_NULL(sparks) + return ..() + +/obj/machinery/power/emitter/update_icon() + if(active && powernet) + icon_state = avail(active_power_usage) ? icon_state_on : icon_state_underpowered + else + icon_state = initial(icon_state) + +/obj/machinery/power/emitter/power_change() + . = ..() + update_icon() + +/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) + investigate_log("turned [active ? "ON" : "OFF"] by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful + 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) + investigate_log("regained power and turned ON at [AREACOORD(src)]", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful + else + if(powered) + powered = FALSE + update_icon() + investigate_log("lost power and turned OFF at [AREACOORD(src)]", INVESTIGATE_SINGULO) + investigate_log("lost power and turned OFF at [AREACOORD(src)]", INVESTIGATE_SUPERMATTER) // yogs - so supermatter investigate is useful + 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_WRENCHED) + 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_WELDED) + 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 + if(user) + user.visible_message("[user.name] emags [src].","You short out the lock.") + + +/obj/machinery/power/emitter/prototype + name = "Prototype Emitter" + icon = 'icons/obj/turrets.dmi' + icon_state = "protoemitter" + icon_state_on = "protoemitter_+a" + icon_state_underpowered = "protoemitter_+u" + 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 e94f35f0f116..86170b196964 100644 --- a/code/modules/power/singularity/field_generator.dm +++ b/code/modules/power/singularity/field_generator.dm @@ -1,356 +1,356 @@ - - - -/* -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 - move_resist = INFINITY - 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 - - move_resist = initial(move_resist) - -/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 + move_resist = INFINITY + 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 + + move_resist = initial(move_resist) + +/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 b464e8e442b8..f5e3bbc1417b 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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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 38ce154d0137..3caf934b50c5 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 57f4a77fc2cc..142388c350b0 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 497ef497beea..753cf623be11 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm @@ -1,173 +1,173 @@ -/*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 = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites - 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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_WIRECUTTER)//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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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 = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites - 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 = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites - 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 = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites + 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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_WIRECUTTER)//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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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 = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites + 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 = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites + 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 06d2e41793cf..ca927cf9e48a 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_control.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm @@ -1,335 +1,335 @@ -/obj/machinery/particle_accelerator/control_box - name = "Particle Accelerator Control Console" - desc = "This controls the density of the particles." - icon = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites - 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 = FALSE - var/construction_state = PA_CONSTRUCTION_UNSECURED - var/active = FALSE - var/strength = 0 - var/powered = FALSE - 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/multitool_act(mob/living/user, obj/item/I) - ..() - if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) - wires.interact(user) - return TRUE - -/obj/machinery/particle_accelerator/control_box/proc/update_state() - if(construction_state < PA_CONSTRUCTION_COMPLETE) - use_power = NO_POWER_USE - assembled = FALSE - active = FALSE - for(var/CP in connected_parts) - var/obj/structure/particle_accelerator/part = CP - part.strength = null - part.powered = FALSE - part.update_icon() - connected_parts.Cut() - return - if(!part_scan()) - use_power = IDLE_POWER_USE - active = FALSE - connected_parts.Cut() - -/obj/machinery/particle_accelerator/control_box/update_icon() - if(active) - icon_state = "control_boxp[strength]" //yogs- fix sprite not updating (note that /tg/ PA power 2 sprite is incomplete) - 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 = FALSE - 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 = FALSE - critical_machine = FALSE - - var/obj/structure/particle_accelerator/fuel_chamber/F = locate() in orange(1,src) - if(!F) - return FALSE - - setDir(F.dir) - connected_parts.Cut() - - T = get_step(T,rdir) - if(!check_part(T, /obj/structure/particle_accelerator/fuel_chamber)) - return FALSE - T = get_step(T,odir) - if(!check_part(T, /obj/structure/particle_accelerator/end_cap)) - return FALSE - T = get_step(T,dir) - T = get_step(T,dir) - if(!check_part(T, /obj/structure/particle_accelerator/power_box)) - return FALSE - T = get_step(T,dir) - if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/center)) - return FALSE - T = get_step(T,ldir) - if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/left)) - return FALSE - T = get_step(T,rdir) - T = get_step(T,rdir) - if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/right)) - return FALSE - - assembled = TRUE - critical_machine = TRUE //Only counts if the PA is actually assembled. - return TRUE - -/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 TRUE - return FALSE - - -/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 - active_power_usage = initial(active_power_usage) * (1 + strength) // Yogs -- Makes the PA use different amounts of power depending on its power level. - for(var/CP in connected_parts) - var/obj/structure/particle_accelerator/part = CP - part.strength = strength - part.powered = TRUE - 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 = FALSE - part.update_icon() - return TRUE - - -/obj/machinery/particle_accelerator/control_box/ui_interact(mob/user) - . = ..() - - if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) - wires.interact(user) - return - if(construction_state != PA_CONSTRUCTION_COMPLETE) - return - - 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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_WIRECUTTER)//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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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 = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites + 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 = FALSE + var/construction_state = PA_CONSTRUCTION_UNSECURED + var/active = FALSE + var/strength = 0 + var/powered = FALSE + 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/multitool_act(mob/living/user, obj/item/I) + ..() + if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) + wires.interact(user) + return TRUE + +/obj/machinery/particle_accelerator/control_box/proc/update_state() + if(construction_state < PA_CONSTRUCTION_COMPLETE) + use_power = NO_POWER_USE + assembled = FALSE + active = FALSE + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = null + part.powered = FALSE + part.update_icon() + connected_parts.Cut() + return + if(!part_scan()) + use_power = IDLE_POWER_USE + active = FALSE + connected_parts.Cut() + +/obj/machinery/particle_accelerator/control_box/update_icon() + if(active) + icon_state = "control_boxp[strength]" //yogs- fix sprite not updating (note that /tg/ PA power 2 sprite is incomplete) + 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 = FALSE + 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 = FALSE + critical_machine = FALSE + + var/obj/structure/particle_accelerator/fuel_chamber/F = locate() in orange(1,src) + if(!F) + return FALSE + + setDir(F.dir) + connected_parts.Cut() + + T = get_step(T,rdir) + if(!check_part(T, /obj/structure/particle_accelerator/fuel_chamber)) + return FALSE + T = get_step(T,odir) + if(!check_part(T, /obj/structure/particle_accelerator/end_cap)) + return FALSE + T = get_step(T,dir) + T = get_step(T,dir) + if(!check_part(T, /obj/structure/particle_accelerator/power_box)) + return FALSE + T = get_step(T,dir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/center)) + return FALSE + T = get_step(T,ldir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/left)) + return FALSE + T = get_step(T,rdir) + T = get_step(T,rdir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/right)) + return FALSE + + assembled = TRUE + critical_machine = TRUE //Only counts if the PA is actually assembled. + return TRUE + +/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 TRUE + return FALSE + + +/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 + active_power_usage = initial(active_power_usage) * (1 + strength) // Yogs -- Makes the PA use different amounts of power depending on its power level. + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = strength + part.powered = TRUE + 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 = FALSE + part.update_icon() + return TRUE + + +/obj/machinery/particle_accelerator/control_box/ui_interact(mob/user) + . = ..() + + if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) + wires.interact(user) + return + if(construction_state != PA_CONSTRUCTION_COMPLETE) + return + + 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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_WIRECUTTER)//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(W.tool_behaviour == TOOL_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(W.tool_behaviour == TOOL_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/singularity/particle_accelerator/particle_emitter.dm b/code/modules/power/singularity/particle_accelerator/particle_emitter.dm index bf27526a8252..9f90e7280438 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_emitter.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_emitter.dm @@ -1,47 +1,47 @@ -/obj/structure/particle_accelerator/particle_emitter - name = "EM Containment Grid" - desc = "This launches the Alpha particles, might not want to stand near this end." - icon = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites - icon_state = "none" - var/fire_delay = 50 - var/last_shot = 0 - -/obj/structure/particle_accelerator/particle_emitter/center - icon_state = "emitter_center" - reference = "emitter_center" - -/obj/structure/particle_accelerator/particle_emitter/left - icon_state = "emitter_left" - reference = "emitter_left" - -/obj/structure/particle_accelerator/particle_emitter/right - icon_state = "emitter_right" - reference = "emitter_right" - -/obj/structure/particle_accelerator/particle_emitter/proc/set_delay(delay) - if(delay >= 0) - fire_delay = delay - return 1 - return 0 - -/obj/structure/particle_accelerator/particle_emitter/proc/emit_particle(strength = 0) - if((last_shot + fire_delay) <= world.time) - last_shot = world.time - var/turf/T = get_turf(src) - var/obj/effect/accelerated_particle/P - switch(strength) - if(0) - P = new/obj/effect/accelerated_particle/weak(T) - if(1) - P = new/obj/effect/accelerated_particle(T) - if(2) - P = new/obj/effect/accelerated_particle/strong(T) - if(3) - P = new/obj/effect/accelerated_particle/powerful(T) - //yogs start -- Emaggable PAs - if(4) - P = new/obj/effect/accelerated_particle/catastrophic(T) - //yogs end - P.setDir(dir) - return 1 - return 0 +/obj/structure/particle_accelerator/particle_emitter + name = "EM Containment Grid" + desc = "This launches the Alpha particles, might not want to stand near this end." + icon = 'yogstation/icons/obj/machines/particle_accelerator.dmi'//Yogs PA Sprites + icon_state = "none" + var/fire_delay = 50 + var/last_shot = 0 + +/obj/structure/particle_accelerator/particle_emitter/center + icon_state = "emitter_center" + reference = "emitter_center" + +/obj/structure/particle_accelerator/particle_emitter/left + icon_state = "emitter_left" + reference = "emitter_left" + +/obj/structure/particle_accelerator/particle_emitter/right + icon_state = "emitter_right" + reference = "emitter_right" + +/obj/structure/particle_accelerator/particle_emitter/proc/set_delay(delay) + if(delay >= 0) + fire_delay = delay + return 1 + return 0 + +/obj/structure/particle_accelerator/particle_emitter/proc/emit_particle(strength = 0) + if((last_shot + fire_delay) <= world.time) + last_shot = world.time + var/turf/T = get_turf(src) + var/obj/effect/accelerated_particle/P + switch(strength) + if(0) + P = new/obj/effect/accelerated_particle/weak(T) + if(1) + P = new/obj/effect/accelerated_particle(T) + if(2) + P = new/obj/effect/accelerated_particle/strong(T) + if(3) + P = new/obj/effect/accelerated_particle/powerful(T) + //yogs start -- Emaggable PAs + if(4) + P = new/obj/effect/accelerated_particle/catastrophic(T) + //yogs end + P.setDir(dir) + return 1 + return 0 diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm index cc5b1d632221..5075e7656da9 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 231ddf3c6d7e..3363c10f7d2d 100644 --- a/code/modules/procedural_mapping/mapGenerators/lavaland.dm +++ b/code/modules/procedural_mapping/mapGenerators/lavaland.dm @@ -1,36 +1,36 @@ - -/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, - /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/magmawing = 10) - - -/datum/mapGeneratorModule/splatterLayer/lavalandTendrils - spawnableTurfs = list() - spawnableAtoms = list(/obj/structure/spawner/lavaland = 5, - /obj/structure/spawner/lavaland/legion = 5, - /obj/structure/spawner/lavaland/goliath = 5, - /obj/structure/spawner/lavaland/magmawing = 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, + /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/magmawing = 10) + + +/datum/mapGeneratorModule/splatterLayer/lavalandTendrils + spawnableTurfs = list() + spawnableAtoms = list(/obj/structure/spawner/lavaland = 5, + /obj/structure/spawner/lavaland/legion = 5, + /obj/structure/spawner/lavaland/goliath = 5, + /obj/structure/spawner/lavaland/magmawing = 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 bc8e9f74f1f7..20ba10b0e18a 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/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm index 3b183d4619c2..08018b402401 100644 --- a/code/modules/projectiles/ammunition/_ammunition.dm +++ b/code/modules/projectiles/ammunition/_ammunition.dm @@ -1,80 +1,80 @@ -/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/hit_atom, datum/thrownthing/throwingdatum) - 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/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/hit_atom, datum/thrownthing/throwingdatum) + 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/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 ba3bb8030700..38d52f0351f1 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 TRUE - -/obj/item/ammo_casing/proc/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", atom/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, transfered_by = user) //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 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) - - 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 TRUE - -/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 TRUE + +/obj/item/ammo_casing/proc/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", atom/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, transfered_by = user) //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 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) + + 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 TRUE + +/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 06e43ebecd06..54287eea62b8 100644 --- a/code/modules/projectiles/ammunition/ballistic/lmg.dm +++ b/code/modules/projectiles/ammunition/ballistic/lmg.dm @@ -1,23 +1,23 @@ -// 7.12x82mm (SAW) - -/obj/item/ammo_casing/mm712x82 - name = "7.12x82mm bullet casing" - desc = "A 7.12x82mm bullet casing." - icon_state = "762-casing" - caliber = "mm71282" - projectile_type = /obj/item/projectile/bullet/mm712x82 - -/obj/item/ammo_casing/mm712x82/ap - name = "7.12x82mm armor-piercing bullet casing" - desc = "A 7.12x82mm bullet casing designed with a hardened-tipped core to help penetrate armored targets." - projectile_type = /obj/item/projectile/bullet/mm712x82_ap - -/obj/item/ammo_casing/mm712x82/hollow - name = "7.12x82mm hollow-point bullet casing" - desc = "A 7.12x82mm bullet casing designed to cause more damage to unarmored targets." - projectile_type = /obj/item/projectile/bullet/mm712x82_hp - -/obj/item/ammo_casing/mm712x82/incen - name = "7.12x82mm incendiary bullet casing" - desc = "A 7.12x82mm 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/mm712x82 +// 7.12x82mm (SAW) + +/obj/item/ammo_casing/mm712x82 + name = "7.12x82mm bullet casing" + desc = "A 7.12x82mm bullet casing." + icon_state = "762-casing" + caliber = "mm71282" + projectile_type = /obj/item/projectile/bullet/mm712x82 + +/obj/item/ammo_casing/mm712x82/ap + name = "7.12x82mm armor-piercing bullet casing" + desc = "A 7.12x82mm bullet casing designed with a hardened-tipped core to help penetrate armored targets." + projectile_type = /obj/item/projectile/bullet/mm712x82_ap + +/obj/item/ammo_casing/mm712x82/hollow + name = "7.12x82mm hollow-point bullet casing" + desc = "A 7.12x82mm bullet casing designed to cause more damage to unarmored targets." + projectile_type = /obj/item/projectile/bullet/mm712x82_hp + +/obj/item/ammo_casing/mm712x82/incen + name = "7.12x82mm incendiary bullet casing" + desc = "A 7.12x82mm 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/mm712x82 diff --git a/code/modules/projectiles/ammunition/ballistic/pistol.dm b/code/modules/projectiles/ammunition/ballistic/pistol.dm index d4fa3a79ae35..83d06d1dac9b 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 46adbfbc5c9a..89f00b767829 100644 --- a/code/modules/projectiles/ammunition/ballistic/revolver.dm +++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm @@ -1,40 +1,40 @@ -// .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 - -// 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 bullet casing" - desc = "A .38 bullet casing." - caliber = "38" - 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 +// .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 + +// 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 bullet casing" + desc = "A .38 bullet casing." + caliber = "38" + 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 diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm index a35cfcba1c70..47c5c6d60236 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 8019a914f5f2..100143f95dd5 100644 --- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm +++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm @@ -1,139 +1,139 @@ -// 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 = "laser slug" - desc = "An advanced shotgun shell that uses a micro laser to replicate the effects of a laser weapon in a ballistic package." - icon_state = "lshell" - projectile_type = /obj/item/projectile/beam/laser - -/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, using similar technology as cryostatis 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.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/consumable/ethanol/neurotoxin, 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 = "laser slug" + desc = "An advanced shotgun shell that uses a micro laser to replicate the effects of a laser weapon in a ballistic package." + icon_state = "lshell" + projectile_type = /obj/item/projectile/beam/laser + +/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, using similar technology as cryostatis 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.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/consumable/ethanol/neurotoxin, 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 c3597a56a300..84a5d83ba17e 100644 --- a/code/modules/projectiles/ammunition/ballistic/smg.dm +++ b/code/modules/projectiles/ammunition/ballistic/smg.dm @@ -1,25 +1,25 @@ -// 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 +// 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 diff --git a/code/modules/projectiles/ammunition/ballistic/sniper.dm b/code/modules/projectiles/ammunition/ballistic/sniper.dm index 30cddedfd8cd..1944a13180c1 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 11f7b8670dfb..db1aa6562c46 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 5d9c650c784f..03dc17321889 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 (A.tool_behaviour == TOOL_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 (A.tool_behaviour == TOOL_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 7bb3bd633e81..4e94a20eaae5 100644 --- a/code/modules/projectiles/ammunition/caseless/misc.dm +++ b/code/modules/projectiles/ammunition/caseless/misc.dm @@ -1,13 +1,13 @@ -/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 - variance = 0.8 - click_cooldown_override = 1 +/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 + 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 bc693d96bc89..387be922f020 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 3a4e457c3d4e..10de173ecc72 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 8d9c72d1ba0c..98a7cbd05173 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 d8a6a1244def..eabb4f66d780 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 47455e9754f8..565f9928bab4 100644 --- a/code/modules/projectiles/ammunition/energy/laser.dm +++ b/code/modules/projectiles/ammunition/energy/laser.dm @@ -1,70 +1,70 @@ -/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 = 120 - -/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/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 = 120 + +/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/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 5ebe83f792b7..c2cfd076b0e8 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 d02abf9c881d..234fd58b8cbe 100644 --- a/code/modules/projectiles/ammunition/energy/plasma.dm +++ b/code/modules/projectiles/ammunition/energy/plasma.dm @@ -1,11 +1,11 @@ -/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 + 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 diff --git a/code/modules/projectiles/ammunition/energy/portal.dm b/code/modules/projectiles/ammunition/energy/portal.dm index dcb8b5e1e0be..ec1f067080d2 100644 --- a/code/modules/projectiles/ammunition/energy/portal.dm +++ b/code/modules/projectiles/ammunition/energy/portal.dm @@ -1,21 +1,21 @@ -/obj/item/ammo_casing/energy/wormhole - projectile_type = /obj/item/projectile/beam/wormhole - e_cost = 0 - harmful = FALSE - fire_sound = 'sound/weapons/pulse3.ogg' - select_name = "blue" - var/obj/item/gun/energy/wormhole_projector/gun - -/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 + harmful = FALSE + fire_sound = 'sound/weapons/pulse3.ogg' + select_name = "blue" + var/obj/item/gun/energy/wormhole_projector/gun + +/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 68a82a67b953..3aef60fd557b 100644 --- a/code/modules/projectiles/ammunition/energy/special.dm +++ b/code/modules/projectiles/ammunition/energy/special.dm @@ -1,71 +1,71 @@ -/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/ion/hos - projectile_type = /obj/item/projectile/ion/weak - e_cost = 300 - -/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/declone/weak - projectile_type = /obj/item/projectile/energy/declone/weak - -/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/ion + projectile_type = /obj/item/projectile/ion + select_name = "ion" + fire_sound = 'sound/weapons/ionrifle.ogg' + +/obj/item/ammo_casing/energy/ion/hos + projectile_type = /obj/item/projectile/ion/weak + e_cost = 300 + +/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/declone/weak + projectile_type = /obj/item/projectile/energy/declone/weak + +/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 diff --git a/code/modules/projectiles/ammunition/energy/stun.dm b/code/modules/projectiles/ammunition/energy/stun.dm index 33c55e7f752f..3d707339255c 100644 --- a/code/modules/projectiles/ammunition/energy/stun.dm +++ b/code/modules/projectiles/ammunition/energy/stun.dm @@ -1,26 +1,26 @@ -/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/old - e_cost = 1000 - -/obj/item/ammo_casing/energy/disabler - projectile_type = /obj/item/projectile/beam/disabler - select_name = "disable" - e_cost = 50 - fire_sound = 'sound/weapons/taser2.ogg' - harmful = FALSE - -/obj/item/ammo_casing/energy/disabler/hos - e_cost = 60 +/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/old + e_cost = 1000 + +/obj/item/ammo_casing/energy/disabler + projectile_type = /obj/item/projectile/beam/disabler + select_name = "disable" + e_cost = 50 + fire_sound = 'sound/weapons/taser2.ogg' + harmful = FALSE + +/obj/item/ammo_casing/energy/disabler/hos + e_cost = 60 diff --git a/code/modules/projectiles/ammunition/special/magic.dm b/code/modules/projectiles/ammunition/special/magic.dm index 8df17a9c925e..873596f42ea1 100644 --- a/code/modules/projectiles/ammunition/special/magic.dm +++ b/code/modules/projectiles/ammunition/special/magic.dm @@ -1,67 +1,67 @@ -/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/safety - projectile_type = /obj/item/projectile/magic/safety - 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/flying - projectile_type = /obj/item/projectile/magic/flying - -/obj/item/ammo_casing/magic/bounty - projectile_type = /obj/item/projectile/magic/bounty - -/obj/item/ammo_casing/magic/antimagic - projectile_type = /obj/item/projectile/magic/antimagic - -/obj/item/ammo_casing/magic/sapping - projectile_type = /obj/item/projectile/magic/sapping - -/obj/item/ammo_casing/magic/necropotence - projectile_type = /obj/item/projectile/magic/necropotence - -/obj/item/ammo_casing/magic/wipe - projectile_type = /obj/item/projectile/magic/wipe +/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/safety + projectile_type = /obj/item/projectile/magic/safety + 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/flying + projectile_type = /obj/item/projectile/magic/flying + +/obj/item/ammo_casing/magic/bounty + projectile_type = /obj/item/projectile/magic/bounty + +/obj/item/ammo_casing/magic/antimagic + projectile_type = /obj/item/projectile/magic/antimagic + +/obj/item/ammo_casing/magic/sapping + projectile_type = /obj/item/projectile/magic/sapping + +/obj/item/ammo_casing/magic/necropotence + projectile_type = /obj/item/projectile/magic/necropotence + +/obj/item/ammo_casing/magic/wipe + projectile_type = /obj/item/projectile/magic/wipe diff --git a/code/modules/projectiles/ammunition/special/syringe.dm b/code/modules/projectiles/ammunition/special/syringe.dm index 490602aec6a3..b4f6f4397fd5 100644 --- a/code/modules/projectiles/ammunition/special/syringe.dm +++ b/code/modules/projectiles/ammunition/special/syringe.dm @@ -1,61 +1,61 @@ -/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, transfered_by = user) - 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, transfered_by = user) - 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 + 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, transfered_by = user) + 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, transfered_by = user) + 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 + ..() diff --git a/code/modules/projectiles/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm index fdefd8979315..35915e871dfc 100644 --- a/code/modules/projectiles/boxes_magazines/_box_magazine.dm +++ b/code/modules/projectiles/boxes_magazines/_box_magazine.dm @@ -1,148 +1,148 @@ -//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 = TRUE - var/start_empty = FALSE - 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 = FALSE) - 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 FALSE - - if (stored_ammo.len < max_ammo) - stored_ammo += R - R.forceMove(src) - return TRUE - - //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 TRUE - return FALSE - -/obj/item/ammo_box/proc/can_load(mob/user) - return TRUE - -/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, TRUE) - 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) - A.forceMove(drop_location()) - if(!user.is_holding(src) || !user.put_in_hands(A)) //incase they're using TK - A.bounce_away(FALSE, NONE) - playsound(src, 'sound/weapons/bulletinsert.ogg', 60, TRUE) - to_chat(user, "You remove a round from [src]!") - update_icon() - -/obj/item/ammo_box/update_icon() - var/shells_left = stored_ammo.len - switch(multiple_sprites) - if(1) - icon_state = "[initial(icon_state)]-[shells_left]" - if(2) - icon_state = "[initial(icon_state)]-[shells_left ? "[max_ammo]" : "0"]" - desc = "[initial(desc)] There [(shells_left == 1) ? "is" : "are"] [shells_left] 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(countempties = TRUE) - 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/proc/ammo_list(drop_list = FALSE) - var/list/L = stored_ammo.Copy() - if(drop_list) - stored_ammo.Cut() - return L - -/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 = TRUE + var/start_empty = FALSE + 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 = FALSE) + 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 FALSE + + if (stored_ammo.len < max_ammo) + stored_ammo += R + R.forceMove(src) + return TRUE + + //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 TRUE + return FALSE + +/obj/item/ammo_box/proc/can_load(mob/user) + return TRUE + +/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, TRUE) + 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) + A.forceMove(drop_location()) + if(!user.is_holding(src) || !user.put_in_hands(A)) //incase they're using TK + A.bounce_away(FALSE, NONE) + playsound(src, 'sound/weapons/bulletinsert.ogg', 60, TRUE) + to_chat(user, "You remove a round from [src]!") + update_icon() + +/obj/item/ammo_box/update_icon() + var/shells_left = stored_ammo.len + switch(multiple_sprites) + if(1) + icon_state = "[initial(icon_state)]-[shells_left]" + if(2) + icon_state = "[initial(icon_state)]-[shells_left ? "[max_ammo]" : "0"]" + desc = "[initial(desc)] There [(shells_left == 1) ? "is" : "are"] [shells_left] 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(countempties = TRUE) + 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/proc/ammo_list(drop_list = FALSE) + var/list/L = stored_ammo.Copy() + if(drop_list) + stored_ammo.Cut() + return L + +/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 2b3c31f81c21..6a068c3e751a 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 6310ec19ee45..2b192f598cb5 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/mm712x82 - name = "box magazine (7.12x82mm)" - icon_state = "a762-50" - ammo_type = /obj/item/ammo_casing/mm712x82 - caliber = "mm71282" - max_ammo = 50 - -/obj/item/ammo_box/magazine/mm712x82/hollow - name = "box magazine (Hollow-Point 7.12x82mm)" - ammo_type = /obj/item/ammo_casing/mm712x82/hollow - -/obj/item/ammo_box/magazine/mm712x82/ap - name = "box magazine (Armor Penetrating 7.12x82mm)" - ammo_type = /obj/item/ammo_casing/mm712x82/ap - -/obj/item/ammo_box/magazine/mm712x82/incen - name = "box magazine (Incendiary 7.12x82mm)" - ammo_type = /obj/item/ammo_casing/mm712x82/incen - -/obj/item/ammo_box/magazine/mm712x82/update_icon() - ..() - icon_state = "a762-[round(ammo_count(),10)]" +/obj/item/ammo_box/magazine/mm712x82 + name = "box magazine (7.12x82mm)" + icon_state = "a762-50" + ammo_type = /obj/item/ammo_casing/mm712x82 + caliber = "mm71282" + max_ammo = 50 + +/obj/item/ammo_box/magazine/mm712x82/hollow + name = "box magazine (Hollow-Point 7.12x82mm)" + ammo_type = /obj/item/ammo_casing/mm712x82/hollow + +/obj/item/ammo_box/magazine/mm712x82/ap + name = "box magazine (Armor Penetrating 7.12x82mm)" + ammo_type = /obj/item/ammo_casing/mm712x82/ap + +/obj/item/ammo_box/magazine/mm712x82/incen + name = "box magazine (Incendiary 7.12x82mm)" + ammo_type = /obj/item/ammo_casing/mm712x82/incen + +/obj/item/ammo_box/magazine/mm712x82/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 a65e88768c12..760fc9b5c607 100644 --- a/code/modules/projectiles/boxes_magazines/external/pistol.dm +++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm @@ -1,59 +1,59 @@ -/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() - ..() - if (ammo_count() >= 8) - icon_state = "45-8" - else - icon_state = "45-[ammo_count()]" - -/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() + ..() + if (ammo_count() >= 8) + icon_state = "45-8" + else + icon_state = "45-[ammo_count()]" + +/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 fada5c8659fa..7ed0cde50a7b 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 bca5e0a1e81b..e4c00af7a9a5 100644 --- a/code/modules/projectiles/boxes_magazines/external/rifle.dm +++ b/code/modules/projectiles/boxes_magazines/external/rifle.dm @@ -1,22 +1,22 @@ -/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 1e85b24c2619..2123d76c857c 100644 --- a/code/modules/projectiles/boxes_magazines/external/shotgun.dm +++ b/code/modules/projectiles/boxes_magazines/external/shotgun.dm @@ -1,36 +1,36 @@ -/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(FALSE)/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 + 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(FALSE)/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 diff --git a/code/modules/projectiles/boxes_magazines/external/smg.dm b/code/modules/projectiles/boxes_magazines/external/smg.dm index 55849a2b10ba..291ec47fa82d 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 = 20 - -/obj/item/ammo_box/magazine/wt550m9/update_icon() - ..() - icon_state = "46x30mmt-[round(ammo_count(),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(ammo_count(),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(ammo_count(),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 - 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 = 20 + +/obj/item/ammo_box/magazine/wt550m9/update_icon() + ..() + icon_state = "46x30mmt-[round(ammo_count(),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(ammo_count(),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(ammo_count(),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 + 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 b73bb170a0c9..14f35795acef 100644 --- a/code/modules/projectiles/boxes_magazines/external/sniper.dm +++ b/code/modules/projectiles/boxes_magazines/external/sniper.dm @@ -1,27 +1,27 @@ -/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 9a8d7e59d941..b9e1ec96158b 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 408431a79cf1..9af0b0e0877b 100644 --- a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm +++ b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm @@ -1,49 +1,49 @@ -/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/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/ammo_list(drop_list = FALSE) - var/list/L = list() - for(var/i=1 to stored_ammo.len) - var/obj/item/ammo_casing/bullet = stored_ammo[i] - if(bullet) - L.Add(bullet) - if(drop_list)//We have to maintain the list size, to emulate a cylinder - stored_ammo[i] = null - return L - -/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/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/ammo_list(drop_list = FALSE) + var/list/L = list() + for(var/i=1 to stored_ammo.len) + var/obj/item/ammo_casing/bullet = stored_ammo[i] + if(bullet) + L.Add(bullet) + if(drop_list)//We have to maintain the list size, to emulate a cylinder + stored_ammo[i] = null + return L + +/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 c6ee7ec0cb83..c14e66af82cf 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 79a005ee8a9f..8b2ec9fca383 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 c1e893cd0a7c..4b405c09963b 100644 --- a/code/modules/projectiles/boxes_magazines/internal/misc.dm +++ b/code/modules/projectiles/boxes_magazines/internal/misc.dm @@ -1,5 +1,5 @@ -/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/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 a98fd29e99a0..982d22493fc7 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 ef83e96b1cec..a7469284a5c7 100644 --- a/code/modules/projectiles/boxes_magazines/internal/rifle.dm +++ b/code/modules/projectiles/boxes_magazines/internal/rifle.dm @@ -1,15 +1,15 @@ -/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/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/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 9c83e48a8766..c5a3fce09288 100644 --- a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm +++ b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm @@ -1,38 +1,38 @@ -/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/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/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 f2bb0dbf084c..7a520c6a1f10 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 47fcb0979ad2..4d281ae2195c 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -1,225 +1,225 @@ -/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 = TRUE //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 = 0 - var/charge_tick = 0 - var/charge_delay = 4 - var/use_cyborg_cell = FALSE //whether the gun's cell drains the cyborg user's cell to recharge - 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() - if (cell) - QDEL_NULL(cell) - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/gun/energy/process() - if(selfcharge && cell && cell.percent() < 100) - charge_tick++ - if(charge_tick < charge_delay) - return - charge_tick = 0 - 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 = CEILING(CLAMP(cell.charge / cell.maxcharge, 0, 1) * charge_sections, 1) - if(ratio == old_ratio && !force_update) - return - old_ratio = ratio - cut_overlays() - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - var/iconState = "[icon_state]_charge" - var/itemState = null - if(!initial(item_state)) - itemState = icon_state - if (modifystate) - add_overlay("[icon_state]_[shot.select_name]") - iconState += "_[shot.select_name]" - if(itemState) - itemState += "[shot.select_name]" - if(cell.charge < shot.e_cost) - 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) - 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, dry_fire_sound, 30, TRUE) - 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 = TRUE //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 = 0 + var/charge_tick = 0 + var/charge_delay = 4 + var/use_cyborg_cell = FALSE //whether the gun's cell drains the cyborg user's cell to recharge + 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() + if (cell) + QDEL_NULL(cell) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/gun/energy/process() + if(selfcharge && cell && cell.percent() < 100) + charge_tick++ + if(charge_tick < charge_delay) + return + charge_tick = 0 + 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 = CEILING(CLAMP(cell.charge / cell.maxcharge, 0, 1) * charge_sections, 1) + if(ratio == old_ratio && !force_update) + return + old_ratio = ratio + cut_overlays() + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + var/iconState = "[icon_state]_charge" + var/itemState = null + if(!initial(item_state)) + itemState = icon_state + if (modifystate) + add_overlay("[icon_state]_[shot.select_name]") + iconState += "_[shot.select_name]" + if(itemState) + itemState += "[shot.select_name]" + if(cell.charge < shot.e_cost) + 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) + 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, dry_fire_sound, 30, TRUE) + 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 66e47514ed07..84ef2a578e0d 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -1,140 +1,140 @@ -/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 = 1 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/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?" - use_cyborg_cell = TRUE - -/obj/item/gun/energy/laser/cyborg/emp_act() - return - -/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 = TRUE - -/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 = TRUE - -/obj/item/gun/energy/laser/redtag/hitscan - ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag/hitscan) +/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 = 1 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/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?" + use_cyborg_cell = TRUE + +/obj/item/gun/energy/laser/cyborg/emp_act() + return + +/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 = TRUE + +/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 = TRUE + +/obj/item/gun/energy/laser/redtag/hitscan + ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag/hitscan) diff --git a/code/modules/projectiles/guns/energy/mounted.dm b/code/modules/projectiles/guns/energy/mounted.dm index 869b4887449f..89114e805df4 100644 --- a/code/modules/projectiles/guns/energy/mounted.dm +++ b/code/modules/projectiles/guns/energy/mounted.dm @@ -1,26 +1,26 @@ -/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 = 1 - can_flashlight = FALSE - trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses neural signals instead - -/obj/item/gun/energy/e_gun/advtaser/mounted/dropped()//if somebody manages to drop this somehow... - ..() - -/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 = 1 - trigger_guard = TRIGGER_GUARD_ALLOW_ALL - -/obj/item/gun/energy/laser/mounted/dropped() - ..() +/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 = 1 + can_flashlight = FALSE + trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses neural signals instead + +/obj/item/gun/energy/e_gun/advtaser/mounted/dropped()//if somebody manages to drop this somehow... + ..() + +/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 = 1 + trigger_guard = TRIGGER_GUARD_ALLOW_ALL + +/obj/item/gun/energy/laser/mounted/dropped() + ..() diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm index 9bba18ce22dc..4156736b207a 100644 --- a/code/modules/projectiles/guns/energy/pulse.dm +++ b/code/modules/projectiles/guns/energy/pulse.dm @@ -1,78 +1,78 @@ -/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) - - message_admins("A pulse rifle prize has been created at [ADMIN_VERBOSEJMP(T)]") - log_game("A pulse rifle prize has been created at [AREACOORD(T)]") - - 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 = TRUE - 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) + + message_admins("A pulse rifle prize has been created at [ADMIN_VERBOSEJMP(T)]") + log_game("A pulse rifle prize has been created at [AREACOORD(T)]") + + 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 = TRUE + 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 ccf3f1c7462a..ce2e5f6316e1 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -1,345 +1,345 @@ -/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 = TRUE - 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/decloner/unrestricted - pin = /obj/item/firing_pin - ammo_type = list(/obj/item/ammo_casing/energy/declone/weak) - -/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 = 1 - -/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 // Yogs Might as well let clowns use it. - /*selfcharge = 1*/ // Yogs Not admeme only anymore - -/obj/item/gun/energy/meteorgun/pen - name = "meteor pen" - desc = "The pen is mightier than the sword." - icon = 'yogstation/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 = FALSE - 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 = FALSE - - 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 - var/progress_flash_divisor = 10 //copypasta is best pasta - var/light_intensity = 1 - var/charge_weld = 25 //amount of charge used up to start action (multiplied by amount) and per progress_flash_divisor ticks of welding - -/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) - var/charge_multiplier = 0 //2 = Refined stack, 1 = Ore - if(istype(I, /obj/item/stack/sheet/mineral/plasma)) - charge_multiplier = 2 - if(istype(I, /obj/item/stack/ore/plasma)) - charge_multiplier = 1 - if(charge_multiplier) - if(cell.charge == cell.maxcharge) - to_chat(user, "You try to insert [I] into [src], but it's fully charged.") //my cell is round and full - return - I.use(1) - cell.give(500*charge_multiplier) - to_chat(user, "You insert [I] in [src], recharging it.") - else - ..() - -// Tool procs, in case plasma cutter is used as welder -// Can we start welding? -/obj/item/gun/energy/plasmacutter/tool_start_check(mob/living/user, amount) - . = tool_use_check(user, amount) - if(. && user) - user.flash_act(light_intensity) - -// Can we weld? Plasma cutter does not use charge continuously. -// Amount cannot be defaulted to 1: most of the code specifies 0 in the call. -/obj/item/gun/energy/plasmacutter/tool_use_check(mob/living/user, amount) - if(QDELETED(cell)) - to_chat(user, "[src] does not have a cell, and cannot be used!") - return FALSE - // Amount cannot be used if drain is made continuous, e.g. amount = 5, charge_weld = 25 - // Then it'll drain 125 at first and 25 periodically, but fail if charge dips below 125 even though it still can finish action - // Alternately it'll need to drain amount*charge_weld every period, which is either obscene or makes it free for other uses - if(amount ? cell.charge < charge_weld * amount : cell.charge < charge_weld) - to_chat(user, "You need more charge to complete this task!") - return FALSE - - return TRUE - -/obj/item/gun/energy/plasmacutter/use(amount) - return (!QDELETED(cell) && cell.use(amount ? amount * charge_weld : charge_weld)) - -// This only gets called by use_tool(delay > 0) -// It's also supposed to not get overridden in the first place. -/obj/item/gun/energy/plasmacutter/tool_check_callback(mob/living/user, amount, datum/callback/extra_checks) - . = ..() //return tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke()) - if(. && user) - if (progress_flash_divisor == 0) - user.flash_act(min(light_intensity,1)) - progress_flash_divisor = initial(progress_flash_divisor) - else - progress_flash_divisor-- - -/obj/item/gun/energy/plasmacutter/use_tool(atom/target, mob/living/user, delay, amount=1, volume=0, datum/callback/extra_checks) - if(amount) - . = ..() - else - . = ..(amount=1) - - -/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" - 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 = "An LMG that fires 3D-printed flechettes. They are slowly resupplied using the cyborg's internal power source." - icon_state = "l6_cyborg" - 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 = FALSE - use_cyborg_cell = TRUE - -/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" - var/power = 4 +/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 = TRUE + 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/decloner/unrestricted + pin = /obj/item/firing_pin + ammo_type = list(/obj/item/ammo_casing/energy/declone/weak) + +/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 = 1 + +/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 // Yogs Might as well let clowns use it. + /*selfcharge = 1*/ // Yogs Not admeme only anymore + +/obj/item/gun/energy/meteorgun/pen + name = "meteor pen" + desc = "The pen is mightier than the sword." + icon = 'yogstation/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 = FALSE + 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 = FALSE + + 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 + var/progress_flash_divisor = 10 //copypasta is best pasta + var/light_intensity = 1 + var/charge_weld = 25 //amount of charge used up to start action (multiplied by amount) and per progress_flash_divisor ticks of welding + +/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) + var/charge_multiplier = 0 //2 = Refined stack, 1 = Ore + if(istype(I, /obj/item/stack/sheet/mineral/plasma)) + charge_multiplier = 2 + if(istype(I, /obj/item/stack/ore/plasma)) + charge_multiplier = 1 + if(charge_multiplier) + if(cell.charge == cell.maxcharge) + to_chat(user, "You try to insert [I] into [src], but it's fully charged.") //my cell is round and full + return + I.use(1) + cell.give(500*charge_multiplier) + to_chat(user, "You insert [I] in [src], recharging it.") + else + ..() + +// Tool procs, in case plasma cutter is used as welder +// Can we start welding? +/obj/item/gun/energy/plasmacutter/tool_start_check(mob/living/user, amount) + . = tool_use_check(user, amount) + if(. && user) + user.flash_act(light_intensity) + +// Can we weld? Plasma cutter does not use charge continuously. +// Amount cannot be defaulted to 1: most of the code specifies 0 in the call. +/obj/item/gun/energy/plasmacutter/tool_use_check(mob/living/user, amount) + if(QDELETED(cell)) + to_chat(user, "[src] does not have a cell, and cannot be used!") + return FALSE + // Amount cannot be used if drain is made continuous, e.g. amount = 5, charge_weld = 25 + // Then it'll drain 125 at first and 25 periodically, but fail if charge dips below 125 even though it still can finish action + // Alternately it'll need to drain amount*charge_weld every period, which is either obscene or makes it free for other uses + if(amount ? cell.charge < charge_weld * amount : cell.charge < charge_weld) + to_chat(user, "You need more charge to complete this task!") + return FALSE + + return TRUE + +/obj/item/gun/energy/plasmacutter/use(amount) + return (!QDELETED(cell) && cell.use(amount ? amount * charge_weld : charge_weld)) + +// This only gets called by use_tool(delay > 0) +// It's also supposed to not get overridden in the first place. +/obj/item/gun/energy/plasmacutter/tool_check_callback(mob/living/user, amount, datum/callback/extra_checks) + . = ..() //return tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke()) + if(. && user) + if (progress_flash_divisor == 0) + user.flash_act(min(light_intensity,1)) + progress_flash_divisor = initial(progress_flash_divisor) + else + progress_flash_divisor-- + +/obj/item/gun/energy/plasmacutter/use_tool(atom/target, mob/living/user, delay, amount=1, volume=0, datum/callback/extra_checks) + if(amount) + . = ..() + else + . = ..(amount=1) + + +/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" + 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 = "An LMG that fires 3D-printed flechettes. They are slowly resupplied using the cyborg's internal power source." + icon_state = "l6_cyborg" + 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 = FALSE + use_cyborg_cell = TRUE + +/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" + var/power = 4 diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm index a80bb49f4e18..6c82925f21d3 100644 --- a/code/modules/projectiles/guns/energy/stun.dm +++ b/code/modules/projectiles/guns/energy/stun.dm @@ -1,48 +1,48 @@ -/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 = FALSE - 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/electrode, /obj/item/ammo_casing/energy/disabler) - 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 weapon contains a limiter to prevent the cyborg's power cell from overheating." - can_flashlight = FALSE - can_charge = FALSE - use_cyborg_cell = TRUE - -/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 = 2 - can_flashlight = TRUE - flight_x_offset = 15 - flight_y_offset = 10 - -/obj/item/gun/energy/disabler/cyborg - name = "cyborg disabler" - desc = "An integrated disabler that draws from a cyborg's power cell. This weapon contains a limiter to prevent the cyborg's power cell from overheating." - can_charge = FALSE - use_cyborg_cell = TRUE +/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 = FALSE + 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/electrode, /obj/item/ammo_casing/energy/disabler) + 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 weapon contains a limiter to prevent the cyborg's power cell from overheating." + can_flashlight = FALSE + can_charge = FALSE + use_cyborg_cell = TRUE + +/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 = 2 + can_flashlight = TRUE + flight_x_offset = 15 + flight_y_offset = 10 + +/obj/item/gun/energy/disabler/cyborg + name = "cyborg disabler" + desc = "An integrated disabler that draws from a cyborg's power cell. This weapon contains a limiter to prevent the cyborg's power cell from overheating." + can_charge = FALSE + use_cyborg_cell = TRUE diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm index 9674e19e0df1..b4d9dd458663 100644 --- a/code/modules/projectiles/guns/magic.dm +++ b/code/modules/projectiles/guns/magic.dm @@ -1,93 +1,93 @@ -/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 = TRUE - var/max_charges = 6 - var/charges = 0 - var/recharge_rate = 4 - var/charge_tick = 0 - var/can_charge = TRUE - 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/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread) - if(no_den_usage) - var/area/A = get_area(user) - if(istype(A, /area/wizard_station)) - add_fingerprint(user) - 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)) - add_fingerprint(user) - 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 = TRUE + var/max_charges = 6 + var/charges = 0 + var/recharge_rate = 4 + var/charge_tick = 0 + var/can_charge = TRUE + 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/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread) + if(no_den_usage) + var/area/A = get_area(user) + if(istype(A, /area/wizard_station)) + add_fingerprint(user) + 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)) + add_fingerprint(user) + 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 6dcfbde387c3..d0a1cb7c3b1e 100644 --- a/code/modules/projectiles/guns/magic/wand.dm +++ b/code/modules/projectiles/guns/magic/wand.dm @@ -1,215 +1,215 @@ -/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 = FALSE - max_charges = 100 //100, 50, 50, 34 (max charge distribution by 25%ths) - var/variable_charges = TRUE - -/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-- - -/obj/item/gun/magic/wand/death/debug - desc = "In some obscure circles, this is known as the 'cloning tester's friend'." - max_charges = 500 - variable_charges = FALSE - can_charge = TRUE - recharge_rate = 1 - - -///////////////////////////////////// -//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 - desc = "Is it possible for something to be even more powerful than regular magic? This wand is." - max_charges = 500 - variable_charges = FALSE - can_charge = TRUE - recharge_rate = 1 - -///////////////////////////////////// -//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 = TRUE - -/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-- - ..() - -/obj/item/gun/magic/wand/safety - name = "wand of safety" - desc = "This wand will use the lightest of bluespace currents to gently place the target somewhere safe." - ammo_type = /obj/item/ammo_casing/magic/safety - fire_sound = 'sound/magic/wand_teleport.ogg' - icon_state = "telewand" - max_charges = 10 //10, 5, 5, 4 - no_den_usage = FALSE - -/obj/item/gun/magic/wand/safety/zap_self(mob/living/user) - var/turf/origin = get_turf(user) - var/turf/destination = find_safe_turf() - - if(do_teleport(user, destination, channel=TELEPORT_CHANNEL_MAGIC)) - for(var/t in list(origin, destination)) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(0, t) - smoke.start() - ..() - -/obj/item/gun/magic/wand/safety/debug - desc = "This wand has 'find_safe_turf()' engraved into its blue wood. Perhaps it's a secret message?" - max_charges = 500 - variable_charges = FALSE - can_charge = TRUE - recharge_rate = 1 - - -///////////////////////////////////// -//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 = FALSE + max_charges = 100 //100, 50, 50, 34 (max charge distribution by 25%ths) + var/variable_charges = TRUE + +/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-- + +/obj/item/gun/magic/wand/death/debug + desc = "In some obscure circles, this is known as the 'cloning tester's friend'." + max_charges = 500 + variable_charges = FALSE + can_charge = TRUE + recharge_rate = 1 + + +///////////////////////////////////// +//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 + desc = "Is it possible for something to be even more powerful than regular magic? This wand is." + max_charges = 500 + variable_charges = FALSE + can_charge = TRUE + recharge_rate = 1 + +///////////////////////////////////// +//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 = TRUE + +/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-- + ..() + +/obj/item/gun/magic/wand/safety + name = "wand of safety" + desc = "This wand will use the lightest of bluespace currents to gently place the target somewhere safe." + ammo_type = /obj/item/ammo_casing/magic/safety + fire_sound = 'sound/magic/wand_teleport.ogg' + icon_state = "telewand" + max_charges = 10 //10, 5, 5, 4 + no_den_usage = FALSE + +/obj/item/gun/magic/wand/safety/zap_self(mob/living/user) + var/turf/origin = get_turf(user) + var/turf/destination = find_safe_turf() + + if(do_teleport(user, destination, channel=TELEPORT_CHANNEL_MAGIC)) + for(var/t in list(origin, destination)) + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(0, t) + smoke.start() + ..() + +/obj/item/gun/magic/wand/safety/debug + desc = "This wand has 'find_safe_turf()' engraved into its blue wood. Perhaps it's a secret message?" + max_charges = 500 + variable_charges = FALSE + can_charge = TRUE + recharge_rate = 1 + + +///////////////////////////////////// +//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 abcfe2cbeedb..e2918932d2e1 100644 --- a/code/modules/projectiles/guns/misc/beam_rifle.dm +++ b/code/modules/projectiles/guns/misc/beam_rifle.dm @@ -1,575 +1,575 @@ - -#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 - pin = null - var/aiming = FALSE - var/aiming_time = 12 - var/aiming_time_fire_threshold = 5 - var/aiming_time_left = 12 - var/aiming_time_increase_user_movement = 3 - var/scoped_slow = 1 - var/aiming_time_increase_angle_multiplier = 0.3 - 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 = 2 //Amount * 2. For some reason structures aren't respecting this unless you have it doubled. Probably with the objects in question's Bump() code instead of this but I'll deal with this later. - var/structure_bleed_coeff = 0.7 - var/wall_pierce_amount = 0 - var/wall_devastate = 0 - var/aoe_structure_range = 1 - var/aoe_structure_damage = 50 - var/aoe_fire_range = 2 - var/aoe_fire_chance = 40 - var/aoe_mob_range = 1 - var/aoe_mob_damage = 30 - var/impact_structure_damage = 60 - var/projectile_damage = 30 - var/projectile_stun = 0 - var/projectile_setting_pierce = TRUE - var/delay = 25 - 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) - return ..() - -/obj/item/gun/energy/beam_rifle/pickup(mob/user) - set_user(user) - return ..() - -/obj/item/gun/energy/beam_rifle/dropped(mob/user) - set_user() - return ..() - -/obj/item/gun/energy/beam_rifle/ui_action_click(mob/user, actiontype) - if(istype(actiontype, zoom_lock_action)) - zoom_lock++ - if(zoom_lock > 3) - zoom_lock = 0 - switch(zoom_lock) - if(ZOOM_LOCK_AUTOZOOM_FREEMOVE) - to_chat(user, "You switch [src]'s zooming processor to free directional.") - if(ZOOM_LOCK_AUTOZOOM_ANGLELOCK) - to_chat(user, "You switch [src]'s zooming processor to locked directional.") - if(ZOOM_LOCK_CENTER_VIEW) - to_chat(user, "You switch [src]'s zooming processor to center mode.") - if(ZOOM_LOCK_OFF) - to_chat(user, "You disable [src]'s zooming system.") - reset_zooming() - else - ..() - -/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 - current_user.client.change_view(world.view + zoom_target_view_increase) - zoom_current_view_increase = zoom_target_view_increase - 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 - -/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/proc/update_slowdown() - if(aiming) - slowdown = scoped_slow - else - slowdown = initial(slowdown) - -/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 = 10000 - 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 - 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) - return ..() - -/obj/item/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE) - if(!QDELETED(target)) - cached = get_turf(target) - handle_hit(target) - return ..() - -/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" - reflectable = REFLECT_FAKEPROJECTILE - -/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit(atom/target) - qdel(src) - return BULLET_ACT_HIT - -/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit() - qdel(src) - return BULLET_ACT_HIT + +#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 + pin = null + var/aiming = FALSE + var/aiming_time = 12 + var/aiming_time_fire_threshold = 5 + var/aiming_time_left = 12 + var/aiming_time_increase_user_movement = 3 + var/scoped_slow = 1 + var/aiming_time_increase_angle_multiplier = 0.3 + 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 = 2 //Amount * 2. For some reason structures aren't respecting this unless you have it doubled. Probably with the objects in question's Bump() code instead of this but I'll deal with this later. + var/structure_bleed_coeff = 0.7 + var/wall_pierce_amount = 0 + var/wall_devastate = 0 + var/aoe_structure_range = 1 + var/aoe_structure_damage = 50 + var/aoe_fire_range = 2 + var/aoe_fire_chance = 40 + var/aoe_mob_range = 1 + var/aoe_mob_damage = 30 + var/impact_structure_damage = 60 + var/projectile_damage = 30 + var/projectile_stun = 0 + var/projectile_setting_pierce = TRUE + var/delay = 25 + 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) + return ..() + +/obj/item/gun/energy/beam_rifle/pickup(mob/user) + set_user(user) + return ..() + +/obj/item/gun/energy/beam_rifle/dropped(mob/user) + set_user() + return ..() + +/obj/item/gun/energy/beam_rifle/ui_action_click(mob/user, actiontype) + if(istype(actiontype, zoom_lock_action)) + zoom_lock++ + if(zoom_lock > 3) + zoom_lock = 0 + switch(zoom_lock) + if(ZOOM_LOCK_AUTOZOOM_FREEMOVE) + to_chat(user, "You switch [src]'s zooming processor to free directional.") + if(ZOOM_LOCK_AUTOZOOM_ANGLELOCK) + to_chat(user, "You switch [src]'s zooming processor to locked directional.") + if(ZOOM_LOCK_CENTER_VIEW) + to_chat(user, "You switch [src]'s zooming processor to center mode.") + if(ZOOM_LOCK_OFF) + to_chat(user, "You disable [src]'s zooming system.") + reset_zooming() + else + ..() + +/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 + current_user.client.change_view(world.view + zoom_target_view_increase) + zoom_current_view_increase = zoom_target_view_increase + 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 + +/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/proc/update_slowdown() + if(aiming) + slowdown = scoped_slow + else + slowdown = initial(slowdown) + +/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 = 10000 + 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 + 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) + return ..() + +/obj/item/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE) + if(!QDELETED(target)) + cached = get_turf(target) + handle_hit(target) + return ..() + +/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" + reflectable = REFLECT_FAKEPROJECTILE + +/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit(atom/target) + qdel(src) + return BULLET_ACT_HIT + +/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit() + qdel(src) + return BULLET_ACT_HIT diff --git a/code/modules/projectiles/guns/misc/chem_gun.dm b/code/modules/projectiles/guns/misc/chem_gun.dm index 1c43ed6dab3f..78b104964b5a 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() - last_synth = world.time +//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 diff --git a/code/modules/projectiles/guns/misc/grenade_launcher.dm b/code/modules/projectiles/guns/misc/grenade_launcher.dm index dc44a1f4ff0a..5a8c5a066e5c 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 17c0f639553c..36e9317e5b23 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) - 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) + 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 5cb196088ad4..dc96c253b8b6 100644 --- a/code/modules/projectiles/guns/misc/syringe_gun.dm +++ b/code/modules/projectiles/guns/misc/syringe_gun.dm @@ -1,123 +1,123 @@ -/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/handle_atom_del(atom/A) - . = ..() - if(A in syringes) - syringes.Remove(A) - -/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 - user.put_in_hands(S) - - 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/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/handle_atom_del(atom/A) + . = ..() + if(A in syringes) + syringes.Remove(A) + +/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 + user.put_in_hands(S) + + 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/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 cbd0ba182f97..df29db1601d6 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1,692 +1,692 @@ - -#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 - movement_type = FLYING - 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/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 - var/force_hit = FALSE //If the object being hit can pass ths damage on to something else, it should not do it for this bullet. - - //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 = FALSE //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 //stores original range - var/reflect_range_decrease = 5 //amount of original range that falls off when reflecting, so it doesn't go forever - var/reflectable = NONE // Can it be reflected or not? - //Effects - var/stun = 0 - var/knockdown = 0 - var/paralyze = 0 - var/immobilize = 0 - 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/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) - - var/temporary_unstoppable_movement = FALSE - -/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 BULLET_ACT_HIT - - if(!isliving(target)) - if(impact_effect_type && !hitscan) - new impact_effect_type(target_loca, hitx, hity) - return BULLET_ACT_HIT - - 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) - if(isalien(L)) - new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir) - else - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir) - if(prob(33)) - 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) - L.on_hit(src) - var/viruslist = "" // yogs - adds viruslist variable - var/reagent_note - if(reagents && reagents.reagent_list) - reagent_note = " REAGENTS:" - for(var/datum/reagent/R in reagents.reagent_list) - reagent_note += "[R.name] ([num2text(R.volume)])" -// yogs start - Checks blood for disease - if(istype(R, /datum/reagent/blood)) - var/datum/reagent/blood/RR = R - for(var/datum/disease/D in RR.data["viruses"]) - viruslist += " [D.name]" - if(istype(D, /datum/disease/advance)) - var/datum/disease/advance/DD = D - viruslist += " \[ symptoms: " - for(var/datum/symptom/S in DD.symptoms) - viruslist += "[S.name] " - viruslist += "\]" - - - if(viruslist) - investigate_log("[firer] injected [src] using a projectile with [viruslist] [blocked == 100 ? "BLOCKED" : ""]", INVESTIGATE_VIROLOGY) - log_game("[firer] injected [src] using a projectile with [viruslist] [blocked == 100 ? "BLOCKED" : ""]") -// yogs end - - 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, paralyze, immobilize) - -/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) - var/datum/point/pcache = trajectory.copy_to() - var/turf/T = get_turf(A) - if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max) - ricochets++ - if(A.handle_ricochet(src)) - on_ricochet(A) - ignore_source_check = TRUE - decayedRange = max(0, decayedRange - reflect_range_decrease) - range = decayedRange - if(hitscan) - store_hitscan_collision(pcache) - return TRUE - - var/distance = get_dist(T, 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) - - return process_hit(T, select_target(T, A)) - -#define QDEL_SELF 1 //Delete if we're not UNSTOPPABLE flagged non-temporarily -#define DO_NOT_QDEL 2 //Pass through. -#define FORCE_QDEL 3 //Force deletion. - -/obj/item/projectile/proc/process_hit(turf/T, atom/target, qdel_self, hit_something = FALSE) //probably needs to be reworked entirely when pixel movement is done. - if(QDELETED(src) || !T || !target) //We're done, nothing's left. - if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, UNSTOPPABLE))) - qdel(src) - return hit_something - permutated |= target //Make sure we're never hitting it again. If we ever run into weirdness with piercing projectiles needing to hit something multiple times.. well.. that's a to-do. - if(!prehit(target)) - return process_hit(T, select_target(T), qdel_self, hit_something) //Hit whatever else we can since that didn't work. - var/result = target.bullet_act(src, def_zone) - if(result == BULLET_ACT_FORCE_PIERCE) - if(!CHECK_BITFIELD(movement_type, UNSTOPPABLE)) - temporary_unstoppable_movement = TRUE - ENABLE_BITFIELD(movement_type, UNSTOPPABLE) - return process_hit(T, select_target(T), qdel_self, TRUE) //Hit whatever else we can since we're piercing through but we're still on the same tile. - else if(result == BULLET_ACT_TURF) //We hit the turf but instead we're going to also hit something else on it. - return process_hit(T, select_target(T), QDEL_SELF, TRUE) - else //Whether it hit or blocked, we're done! - qdel_self = QDEL_SELF - hit_something = TRUE - if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, UNSTOPPABLE))) - qdel(src) - return hit_something - -#undef QDEL_SELF -#undef DO_NOT_QDEL -#undef FORCE_QDEL - -/obj/item/projectile/proc/select_target(turf/T, atom/target) //Select a target from a turf. - if((original in T) && can_hit_target(original, permutated, TRUE, TRUE)) - return original - if(target && can_hit_target(target, permutated, target == original, TRUE)) - return target - var/list/mob/living/possible_mobs = typecache_filter_list(T, GLOB.typecache_mob) - var/list/mob/mobs = list() - for(var/mob/living/M in possible_mobs) - if(!can_hit_target(M, permutated, M == original, TRUE)) - continue - mobs += M - var/mob/M = safepick(mobs) - if(M) - return M.lowest_buckled_mob() - var/list/obj/possible_objs = typecache_filter_list(T, GLOB.typecache_machine_or_structure) - var/list/obj/objs = list() - for(var/obj/O in possible_objs) - if(!can_hit_target(O, permutated, O == original, TRUE)) - continue - objs += O - var/obj/O = safepick(objs) - if(O) - return O - //Nothing else is here that we can hit, hit the turf if we haven't. - if(!(T in permutated) && can_hit_target(T, permutated, T == original, TRUE)) - return T - //Returns null if nothing at all was found. - -/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(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(!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 -//If direct target is true it's the originally clicked target. -/obj/item/projectile/proc/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) - if(QDELETED(target)) - return FALSE - if(!ignore_source_check && firer) - var/mob/M = firer - if((target == firer) || ((target == firer.loc) && ismecha(firer.loc)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))|| isspacepod(firer.loc)) //cannot shoot yourself or your mech // yogs - or your spacepod) - return FALSE - if(!ignore_loc && (loc != target.loc)) - return FALSE - if(target in passthrough) - return FALSE - if(target.density) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc. - return TRUE - if(!isliving(target)) - if(target.layer < PROJECTILE_HIT_THRESHHOLD_LAYER) - return FALSE - else - var/mob/living/L = target - if(!direct_target) - if(!CHECK_BITFIELD(L.mobility_flags, MOBILITY_USE | MOBILITY_STAND | MOBILITY_MOVE) || !(L.stat == CONSCIOUS)) //If they're able to 1. stand or 2. use items or 3. move, AND they are not softcrit, they are not stunned enough to dodge projectiles passing over. - return FALSE - return TRUE - -//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) && !(pass_flags & PASSMOB)) - var/mob/living/L = AM - if(can_hit_target(L, permutated, (AM == original))) - Bump(AM) - -/obj/item/projectile/Move(atom/newloc, dir = NONE) - . = ..() - if(.) - if(temporary_unstoppable_movement) - temporary_unstoppable_movement = FALSE - DISABLE_BITFIELD(movement_type, UNSTOPPABLE) - if(fired && can_hit_target(original, permutated, TRUE)) - Bump(original) - -/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 + movement_type = FLYING + 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/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 + var/force_hit = FALSE //If the object being hit can pass ths damage on to something else, it should not do it for this bullet. + + //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 = FALSE //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 //stores original range + var/reflect_range_decrease = 5 //amount of original range that falls off when reflecting, so it doesn't go forever + var/reflectable = NONE // Can it be reflected or not? + //Effects + var/stun = 0 + var/knockdown = 0 + var/paralyze = 0 + var/immobilize = 0 + 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/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) + + var/temporary_unstoppable_movement = FALSE + +/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 BULLET_ACT_HIT + + if(!isliving(target)) + if(impact_effect_type && !hitscan) + new impact_effect_type(target_loca, hitx, hity) + return BULLET_ACT_HIT + + 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) + if(isalien(L)) + new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir) + else + new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir) + if(prob(33)) + 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) + L.on_hit(src) + var/viruslist = "" // yogs - adds viruslist variable + var/reagent_note + if(reagents && reagents.reagent_list) + reagent_note = " REAGENTS:" + for(var/datum/reagent/R in reagents.reagent_list) + reagent_note += "[R.name] ([num2text(R.volume)])" +// yogs start - Checks blood for disease + if(istype(R, /datum/reagent/blood)) + var/datum/reagent/blood/RR = R + for(var/datum/disease/D in RR.data["viruses"]) + viruslist += " [D.name]" + if(istype(D, /datum/disease/advance)) + var/datum/disease/advance/DD = D + viruslist += " \[ symptoms: " + for(var/datum/symptom/S in DD.symptoms) + viruslist += "[S.name] " + viruslist += "\]" + + + if(viruslist) + investigate_log("[firer] injected [src] using a projectile with [viruslist] [blocked == 100 ? "BLOCKED" : ""]", INVESTIGATE_VIROLOGY) + log_game("[firer] injected [src] using a projectile with [viruslist] [blocked == 100 ? "BLOCKED" : ""]") +// yogs end + + 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, paralyze, immobilize) + +/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) + var/datum/point/pcache = trajectory.copy_to() + var/turf/T = get_turf(A) + if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max) + ricochets++ + if(A.handle_ricochet(src)) + on_ricochet(A) + ignore_source_check = TRUE + decayedRange = max(0, decayedRange - reflect_range_decrease) + range = decayedRange + if(hitscan) + store_hitscan_collision(pcache) + return TRUE + + var/distance = get_dist(T, 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) + + return process_hit(T, select_target(T, A)) + +#define QDEL_SELF 1 //Delete if we're not UNSTOPPABLE flagged non-temporarily +#define DO_NOT_QDEL 2 //Pass through. +#define FORCE_QDEL 3 //Force deletion. + +/obj/item/projectile/proc/process_hit(turf/T, atom/target, qdel_self, hit_something = FALSE) //probably needs to be reworked entirely when pixel movement is done. + if(QDELETED(src) || !T || !target) //We're done, nothing's left. + if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, UNSTOPPABLE))) + qdel(src) + return hit_something + permutated |= target //Make sure we're never hitting it again. If we ever run into weirdness with piercing projectiles needing to hit something multiple times.. well.. that's a to-do. + if(!prehit(target)) + return process_hit(T, select_target(T), qdel_self, hit_something) //Hit whatever else we can since that didn't work. + var/result = target.bullet_act(src, def_zone) + if(result == BULLET_ACT_FORCE_PIERCE) + if(!CHECK_BITFIELD(movement_type, UNSTOPPABLE)) + temporary_unstoppable_movement = TRUE + ENABLE_BITFIELD(movement_type, UNSTOPPABLE) + return process_hit(T, select_target(T), qdel_self, TRUE) //Hit whatever else we can since we're piercing through but we're still on the same tile. + else if(result == BULLET_ACT_TURF) //We hit the turf but instead we're going to also hit something else on it. + return process_hit(T, select_target(T), QDEL_SELF, TRUE) + else //Whether it hit or blocked, we're done! + qdel_self = QDEL_SELF + hit_something = TRUE + if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, UNSTOPPABLE))) + qdel(src) + return hit_something + +#undef QDEL_SELF +#undef DO_NOT_QDEL +#undef FORCE_QDEL + +/obj/item/projectile/proc/select_target(turf/T, atom/target) //Select a target from a turf. + if((original in T) && can_hit_target(original, permutated, TRUE, TRUE)) + return original + if(target && can_hit_target(target, permutated, target == original, TRUE)) + return target + var/list/mob/living/possible_mobs = typecache_filter_list(T, GLOB.typecache_mob) + var/list/mob/mobs = list() + for(var/mob/living/M in possible_mobs) + if(!can_hit_target(M, permutated, M == original, TRUE)) + continue + mobs += M + var/mob/M = safepick(mobs) + if(M) + return M.lowest_buckled_mob() + var/list/obj/possible_objs = typecache_filter_list(T, GLOB.typecache_machine_or_structure) + var/list/obj/objs = list() + for(var/obj/O in possible_objs) + if(!can_hit_target(O, permutated, O == original, TRUE)) + continue + objs += O + var/obj/O = safepick(objs) + if(O) + return O + //Nothing else is here that we can hit, hit the turf if we haven't. + if(!(T in permutated) && can_hit_target(T, permutated, T == original, TRUE)) + return T + //Returns null if nothing at all was found. + +/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(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(!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 +//If direct target is true it's the originally clicked target. +/obj/item/projectile/proc/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) + if(QDELETED(target)) + return FALSE + if(!ignore_source_check && firer) + var/mob/M = firer + if((target == firer) || ((target == firer.loc) && ismecha(firer.loc)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))|| isspacepod(firer.loc)) //cannot shoot yourself or your mech // yogs - or your spacepod) + return FALSE + if(!ignore_loc && (loc != target.loc)) + return FALSE + if(target in passthrough) + return FALSE + if(target.density) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc. + return TRUE + if(!isliving(target)) + if(target.layer < PROJECTILE_HIT_THRESHHOLD_LAYER) + return FALSE + else + var/mob/living/L = target + if(!direct_target) + if(!CHECK_BITFIELD(L.mobility_flags, MOBILITY_USE | MOBILITY_STAND | MOBILITY_MOVE) || !(L.stat == CONSCIOUS)) //If they're able to 1. stand or 2. use items or 3. move, AND they are not softcrit, they are not stunned enough to dodge projectiles passing over. + return FALSE + return TRUE + +//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) && !(pass_flags & PASSMOB)) + var/mob/living/L = AM + if(can_hit_target(L, permutated, (AM == original))) + Bump(AM) + +/obj/item/projectile/Move(atom/newloc, dir = NONE) + . = ..() + if(.) + if(temporary_unstoppable_movement) + temporary_unstoppable_movement = FALSE + DISABLE_BITFIELD(movement_type, UNSTOPPABLE) + if(fired && can_hit_target(original, permutated, TRUE)) + Bump(original) + +/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 81855208cdc3..0ced0e2d1ac6 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -1,184 +1,184 @@ -/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 - reflectable = REFLECT_NORMAL - -/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 - armour_penetration = 50 - -/obj/item/projectile/beam/practice - name = "practice laser" - damage = 0 - nodamage = TRUE - -/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 = 30 - damage_type = STAMINA - flag = "energy" - hitsound = 'sound/weapons/tap.ogg' - eyeblur = 0 - 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) - . = BULLET_ACT_FORCE_PIERCE - ..() - -/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/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() +/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 + reflectable = REFLECT_NORMAL + +/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 + armour_penetration = 50 + +/obj/item/projectile/beam/practice + name = "practice laser" + damage = 0 + nodamage = TRUE + +/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 = 30 + damage_type = STAMINA + flag = "energy" + hitsound = 'sound/weapons/tap.ogg' + eyeblur = 0 + 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) + . = BULLET_ACT_FORCE_PIERCE + ..() + +/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/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() diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index 725ef9baa6f1..1382b6a3bf9e 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -1,9 +1,9 @@ -/obj/item/projectile/bullet - name = "bullet" - icon_state = "bullet" - damage = 60 - damage_type = BRUTE - nodamage = FALSE - 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 + 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 d0cf74421c89..b023e86fc288 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, 50, 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, 50, 1) diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm index 8091efcdfc5d..21cd631d2fbe 100644 --- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm +++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm @@ -1,38 +1,38 @@ -/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) - 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. - ..() - reagents.reaction(M, INJECT) - reagents.trans_to(M, reagents.total_volume) - return BULLET_ACT_HIT - else - blocked = 100 - target.visible_message("\The [src] was deflected!", \ - "You were protected against \the [src]!") - - ..(target, blocked) - DISABLE_BITFIELD(reagents.flags, NO_REACT) - reagents.handle_reactions() - return BULLET_ACT_HIT - -/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/fluacid, 5) - -/obj/item/projectile/bullet/dart/syringe - name = "syringe" - icon_state = "syringeproj" +/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) + 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. + ..() + reagents.reaction(M, INJECT) + reagents.trans_to(M, reagents.total_volume) + return BULLET_ACT_HIT + else + blocked = 100 + target.visible_message("\The [src] was deflected!", \ + "You were protected against \the [src]!") + + ..(target, blocked) + DISABLE_BITFIELD(reagents.flags, NO_REACT) + reagents.handle_reactions() + return BULLET_ACT_HIT + +/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/fluacid, 5) + +/obj/item/projectile/bullet/dart/syringe + name = "syringe" + icon_state = "syringeproj" diff --git a/code/modules/projectiles/projectile/bullets/dnainjector.dm b/code/modules/projectiles/projectile/bullets/dnainjector.dm index f645f7eccd19..22e9d28ab6b0 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 BULLET_ACT_HIT - 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 BULLET_ACT_HIT + 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 e0804611d515..16305c233f26 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 BULLET_ACT_HIT +// 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 BULLET_ACT_HIT diff --git a/code/modules/projectiles/projectile/bullets/lmg.dm b/code/modules/projectiles/projectile/bullets/lmg.dm index addeecd3cf6c..d8b97431ad27 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 - -// 7.12x82mm (SAW) - -/obj/item/projectile/bullet/mm712x82 - name = "7.12x82mm bullet" - damage = 45 - armour_penetration = 5 - -/obj/item/projectile/bullet/mm712x82_ap - name = "7.12x82mm armor-piercing bullet" - damage = 40 - armour_penetration = 75 - -/obj/item/projectile/bullet/mm712x82_hp - name = "7.12x82mm hollow-point bullet" - damage = 60 - armour_penetration = -60 - -/obj/item/projectile/bullet/incendiary/mm712x82 - name = "7.12x82mm 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 + +// 7.12x82mm (SAW) + +/obj/item/projectile/bullet/mm712x82 + name = "7.12x82mm bullet" + damage = 45 + armour_penetration = 5 + +/obj/item/projectile/bullet/mm712x82_ap + name = "7.12x82mm armor-piercing bullet" + damage = 40 + armour_penetration = 75 + +/obj/item/projectile/bullet/mm712x82_hp + name = "7.12x82mm hollow-point bullet" + damage = 60 + armour_penetration = -60 + +/obj/item/projectile/bullet/incendiary/mm712x82 + name = "7.12x82mm 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 ac14fa563c52..b68c3847e234 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 72fefaa60954..7aff9f5c1e32 100644 --- a/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/code/modules/projectiles/projectile/bullets/revolver.dm @@ -1,62 +1,62 @@ -// 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 = 15 // yogs - Nerfed revolver damage - //knockdown = 60 //yogs - commented out - stamina = 60 // yogs - Buffed revolver - -/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 +// 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 = 15 // yogs - Nerfed revolver damage + //knockdown = 60 //yogs - commented out + stamina = 60 // yogs - Buffed revolver + +/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 diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index 1cd5a9f0bbb0..5e1e07327efc 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 = 20 - 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 = 20 + stamina = 80 diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm index 0780766c4661..29beed0137d3 100644 --- a/code/modules/projectiles/projectile/bullets/shotgun.dm +++ b/code/modules/projectiles/projectile/bullets/shotgun.dm @@ -1,94 +1,94 @@ -/obj/item/projectile/bullet/shotgun_slug - name = "12g shotgun slug" - damage = 60 - -/obj/item/projectile/bullet/shotgun_beanbag - name = "beanbag slug" - damage = 5 - stamina = 55 - -/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 - paralyze = 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 - paralyze = 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 - paralyze = 50 - -/obj/item/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = FALSE) - ..() - explosion(target, -1, 0, 1) - return BULLET_ACT_HIT - -/obj/item/projectile/bullet/pellet - var/tile_dropoff = 0.75 - var/tile_dropoff_s = 0.5 - -/obj/item/projectile/bullet/pellet/shotgun_buckshot - name = "buckshot pellet" - damage = 12.5 - -/obj/item/projectile/bullet/pellet/shotgun_rubbershot - name = "rubbershot pellet" - damage = 3 - stamina = 11 - -/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 Scattershot - -/obj/item/projectile/bullet/scattershot - damage = 24 +/obj/item/projectile/bullet/shotgun_slug + name = "12g shotgun slug" + damage = 60 + +/obj/item/projectile/bullet/shotgun_beanbag + name = "beanbag slug" + damage = 5 + stamina = 55 + +/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 + paralyze = 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 + paralyze = 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 + paralyze = 50 + +/obj/item/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = FALSE) + ..() + explosion(target, -1, 0, 1) + return BULLET_ACT_HIT + +/obj/item/projectile/bullet/pellet + var/tile_dropoff = 0.75 + var/tile_dropoff_s = 0.5 + +/obj/item/projectile/bullet/pellet/shotgun_buckshot + name = "buckshot pellet" + damage = 12.5 + +/obj/item/projectile/bullet/pellet/shotgun_rubbershot + name = "rubbershot pellet" + damage = 3 + stamina = 11 + +/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 Scattershot + +/obj/item/projectile/bullet/scattershot + damage = 24 diff --git a/code/modules/projectiles/projectile/bullets/smg.dm b/code/modules/projectiles/projectile/bullets/smg.dm index 0a5866008ed4..4ebc3667f507 100644 --- a/code/modules/projectiles/projectile/bullets/smg.dm +++ b/code/modules/projectiles/projectile/bullets/smg.dm @@ -1,21 +1,21 @@ -// .45 (M1911 & C20r) - -/obj/item/projectile/bullet/c45 - name = ".45 bullet" - damage = 30 - -// 4.6x30mm (Autorifles) - -/obj/item/projectile/bullet/c46x30mm - name = "4.6x30mm bullet" - damage = 20 - -/obj/item/projectile/bullet/c46x30mm_ap - name = "4.6x30mm armor-piercing bullet" - damage = 15 - armour_penetration = 40 - -/obj/item/projectile/bullet/incendiary/c46x30mm - name = "4.6x30mm incendiary bullet" - damage = 10 - fire_stacks = 1 +// .45 (M1911 & C20r) + +/obj/item/projectile/bullet/c45 + name = ".45 bullet" + damage = 30 + +// 4.6x30mm (Autorifles) + +/obj/item/projectile/bullet/c46x30mm + name = "4.6x30mm bullet" + damage = 20 + +/obj/item/projectile/bullet/c46x30mm_ap + name = "4.6x30mm armor-piercing bullet" + damage = 15 + armour_penetration = 40 + +/obj/item/projectile/bullet/incendiary/c46x30mm + name = "4.6x30mm incendiary bullet" + damage = 10 + fire_stacks = 1 diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm index b480c8e8a30a..67a5b90c9fa6 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 - paralyze = 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 - paralyze = 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 - movement_type = FLYING | UNSTOPPABLE - dismemberment = 0 //It goes through you cleanly. - paralyze = 0 - breakthings = FALSE - -/obj/item/projectile/bullet/p50/penetrator/shuttle //Nukeop Shuttle Variety - icon_state = "gaussstrong" - damage = 25 - speed = 0.3 +// .50 (Sniper) + +/obj/item/projectile/bullet/p50 + name =".50 bullet" + speed = 0.4 + damage = 70 + paralyze = 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 + paralyze = 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 + movement_type = FLYING | UNSTOPPABLE + dismemberment = 0 //It goes through you cleanly. + paralyze = 0 + breakthings = FALSE + +/obj/item/projectile/bullet/p50/penetrator/shuttle //Nukeop Shuttle Variety + icon_state = "gaussstrong" + damage = 25 + speed = 0.3 range = 16 \ No newline at end of file diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm index f5f8de195dc7..ccc18401208c 100644 --- a/code/modules/projectiles/projectile/bullets/special.dm +++ b/code/modules/projectiles/projectile/bullets/special.dm @@ -1,32 +1,32 @@ -// Honker - -/obj/item/projectile/bullet/honker - name = "banana" - damage = 0 - movement_type = FLYING | UNSTOPPABLE - nodamage = TRUE - hitsound = 'sound/items/bikehorn.ogg' - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "banana" - range = 200 - -/obj/item/projectile/bullet/honker/Initialize() - . = ..() - SpinAnimation() - -/obj/item/projectile/bullet/honker/on_hit(atom/target, blocked = FALSE) - . = ..() - var/mob/M = target - if(istype(M)) - M.slip(100, M.loc, GALOSHES_DONT_HELP|SLIDE, 0, FALSE) - -// Mime - -/obj/item/projectile/bullet/mime - damage = 40 - -/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 + name = "banana" + damage = 0 + movement_type = FLYING | UNSTOPPABLE + nodamage = TRUE + hitsound = 'sound/items/bikehorn.ogg' + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "banana" + range = 200 + +/obj/item/projectile/bullet/honker/Initialize() + . = ..() + SpinAnimation() + +/obj/item/projectile/bullet/honker/on_hit(atom/target, blocked = FALSE) + . = ..() + var/mob/M = target + if(istype(M)) + M.slip(100, M.loc, GALOSHES_DONT_HELP|SLIDE, 0, FALSE) + +// Mime + +/obj/item/projectile/bullet/mime + damage = 40 + +/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 f29ad3a0ee91..010127c47dc6 100644 --- a/code/modules/projectiles/projectile/energy/_energy.dm +++ b/code/modules/projectiles/projectile/energy/_energy.dm @@ -1,7 +1,7 @@ -/obj/item/projectile/energy - name = "energy" - icon_state = "spark" - damage = 0 - damage_type = BURN - flag = "energy" - reflectable = REFLECT_NORMAL +/obj/item/projectile/energy + name = "energy" + icon_state = "spark" + damage = 0 + damage_type = BURN + flag = "energy" + reflectable = REFLECT_NORMAL diff --git a/code/modules/projectiles/projectile/energy/ebow.dm b/code/modules/projectiles/projectile/energy/ebow.dm index 28bb8c8c6860..38a50436e363 100644 --- a/code/modules/projectiles/projectile/energy/ebow.dm +++ b/code/modules/projectiles/projectile/energy/ebow.dm @@ -1,17 +1,17 @@ -/obj/item/projectile/energy/bolt //ebow bolts - name = "bolt" - icon_state = "cbbolt" - damage = 15 - damage_type = TOX - nodamage = FALSE - stamina = 60 - eyeblur = 10 - knockdown = 10 - slur = 5 - -/obj/item/projectile/energy/bolt/halloween - name = "candy corn" - icon_state = "candy_corn" - -/obj/item/projectile/energy/bolt/large - damage = 40 +/obj/item/projectile/energy/bolt //ebow bolts + name = "bolt" + icon_state = "cbbolt" + damage = 15 + damage_type = TOX + nodamage = FALSE + stamina = 60 + eyeblur = 10 + knockdown = 10 + slur = 5 + +/obj/item/projectile/energy/bolt/halloween + name = "candy corn" + icon_state = "candy_corn" + +/obj/item/projectile/energy/bolt/large + damage = 40 diff --git a/code/modules/projectiles/projectile/energy/misc.dm b/code/modules/projectiles/projectile/energy/misc.dm index 16c238afb9ba..866ff4d62dc0 100644 --- a/code/modules/projectiles/projectile/energy/misc.dm +++ b/code/modules/projectiles/projectile/energy/misc.dm @@ -1,19 +1,19 @@ -/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/declone/weak - damage = 9 - irradiate = 30 - -/obj/item/projectile/energy/dart //ninja throwing dart - name = "dart" - icon_state = "toxin" - damage = 5 - damage_type = TOX - paralyze = 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/declone/weak + damage = 9 + irradiate = 30 + +/obj/item/projectile/energy/dart //ninja throwing dart + name = "dart" + icon_state = "toxin" + damage = 5 + damage_type = TOX + paralyze = 100 + range = 7 diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm index 5df1394bad1e..b8bc93eb5d3f 100644 --- a/code/modules/projectiles/projectile/energy/stun.dm +++ b/code/modules/projectiles/projectile/energy/stun.dm @@ -1,38 +1,38 @@ -/obj/item/projectile/energy/electrode - name = "electrode" - icon_state = "spark" - color = "#FFFF00" - nodamage = TRUE - paralyze = 100 - 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 - -/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) - 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)) - addtimer(CALLBACK(C, /mob/living/carbon.proc/do_jitter_animation, jitter), 5) - //yogstation edit begin ------------------------------------------- - if(ishuman(C)) - var/mob/living/carbon/human/H = C - var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH) - if(istype(stomach)) - stomach.adjust_charge(20) - to_chat(C,"You get charged by [src].") - //yogstation edit end --------------------------------------------- - -/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 = TRUE + paralyze = 100 + 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 + +/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) + 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)) + addtimer(CALLBACK(C, /mob/living/carbon.proc/do_jitter_animation, jitter), 5) + //yogstation edit begin ------------------------------------------- + if(ishuman(C)) + var/mob/living/carbon/human/H = C + var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH) + if(istype(stomach)) + stomach.adjust_charge(20) + to_chat(C,"You get charged by [src].") + //yogstation edit end --------------------------------------------- + +/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 7ca2bcb294f2..43c31816cd83 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/magic/spellcard.dm b/code/modules/projectiles/projectile/magic/spellcard.dm index 67e3ad374e5f..1b8f326573ca 100644 --- a/code/modules/projectiles/projectile/magic/spellcard.dm +++ b/code/modules/projectiles/projectile/magic/spellcard.dm @@ -1,6 +1,6 @@ -/obj/item/projectile/spellcard - name = "enchanted card" - desc = "A piece of paper enchanted to give it extreme durability and stiffness, along with a very hot burn to anyone unfortunate enough to get hit by a charged one." - icon_state = "spellcard" - damage_type = BURN - damage = 2 +/obj/item/projectile/spellcard + name = "enchanted card" + desc = "A piece of paper enchanted to give it extreme durability and stiffness, along with a very hot burn to anyone unfortunate enough to get hit by a charged one." + icon_state = "spellcard" + damage_type = BURN + damage = 2 diff --git a/code/modules/projectiles/projectile/reusable/_reusable.dm b/code/modules/projectiles/projectile/reusable/_reusable.dm index 33c9678fe479..df19d5a0b55b 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 d360c4f4c7a9..42f6218b6cea 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/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm index 26c6478660e5..2598c439706b 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 - paralyze = 20 - speed = 2 - range = 16 - var/datum/beam/arm - var/handedness = 0 - -/obj/item/projectile/curse_hand/Initialize(mapload) - . = ..() - ENABLE_BITFIELD(movement_type, UNSTOPPABLE) - 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) - DISABLE_BITFIELD(movement_type, UNSTOPPABLE) - else if(!isturf(target)) - return FALSE - return ..() - -/obj/item/projectile/curse_hand/Destroy() - if(arm) - arm.End() - arm = null - if(CHECK_BITFIELD(movement_type, UNSTOPPABLE)) - 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 + paralyze = 20 + speed = 2 + range = 16 + var/datum/beam/arm + var/handedness = 0 + +/obj/item/projectile/curse_hand/Initialize(mapload) + . = ..() + ENABLE_BITFIELD(movement_type, UNSTOPPABLE) + 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) + DISABLE_BITFIELD(movement_type, UNSTOPPABLE) + else if(!isturf(target)) + return FALSE + return ..() + +/obj/item/projectile/curse_hand/Destroy() + if(arm) + arm.End() + arm = null + if(CHECK_BITFIELD(movement_type, UNSTOPPABLE)) + 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 437316ce4283..4ecce442ba7e 100644 --- a/code/modules/projectiles/projectile/special/floral.dm +++ b/code/modules/projectiles/projectile/special/floral.dm @@ -1,15 +1,15 @@ -/obj/item/projectile/energy/floramut - name = "alpha somatoray" - icon_state = "energy" - damage = 0 - damage_type = TOX - nodamage = TRUE - flag = "energy" - -/obj/item/projectile/energy/florayield - name = "beta somatoray" - icon_state = "energy2" - damage = 0 - damage_type = TOX - nodamage = TRUE - flag = "energy" +/obj/item/projectile/energy/floramut + name = "alpha somatoray" + icon_state = "energy" + damage = 0 + damage_type = TOX + nodamage = TRUE + flag = "energy" + +/obj/item/projectile/energy/florayield + name = "beta somatoray" + icon_state = "energy2" + damage = 0 + damage_type = TOX + nodamage = TRUE + flag = "energy" diff --git a/code/modules/projectiles/projectile/special/gravity.dm b/code/modules/projectiles/projectile/special/gravity.dm index c20a717a170b..eb929e933a61 100644 --- a/code/modules/projectiles/projectile/special/gravity.dm +++ b/code/modules/projectiles/projectile/special/gravity.dm @@ -1,102 +1,102 @@ -/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 = TRUE - 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 - if(ismob(A)) //because (ismob(A) && A:mob_negates_gravity()) is a recipe for bugs. - var/mob/M = A - if(M.mob_negates_gravity()) - continue - var/throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(A, src))) - A.safe_throw_at(throwtarget,power+1,1, force = MOVE_FORCE_EXTREMELY_STRONG) - 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 = TRUE - 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 - if(ismob(A)) - var/mob/M = A - if(M.mob_negates_gravity()) - continue - A.safe_throw_at(T, power+1, 1, force = MOVE_FORCE_EXTREMELY_STRONG) - 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 = TRUE - 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 - if(ismob(A)) - var/mob/M = A - if(M.mob_negates_gravity()) - continue - A.safe_throw_at(get_edge_target_turf(A, pick(GLOB.cardinals)), power+1, 1, force = MOVE_FORCE_EXTREMELY_STRONG) - 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 = TRUE + 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 + if(ismob(A)) //because (ismob(A) && A:mob_negates_gravity()) is a recipe for bugs. + var/mob/M = A + if(M.mob_negates_gravity()) + continue + var/throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(A, src))) + A.safe_throw_at(throwtarget,power+1,1, force = MOVE_FORCE_EXTREMELY_STRONG) + 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 = TRUE + 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 + if(ismob(A)) + var/mob/M = A + if(M.mob_negates_gravity()) + continue + A.safe_throw_at(T, power+1, 1, force = MOVE_FORCE_EXTREMELY_STRONG) + 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 = TRUE + 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 + if(ismob(A)) + var/mob/M = A + if(M.mob_negates_gravity()) + continue + A.safe_throw_at(get_edge_target_turf(A, pick(GLOB.cardinals)), power+1, 1, force = MOVE_FORCE_EXTREMELY_STRONG) + 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 b163daaab7ac..a4751cf24717 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.Paralyze(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.Paralyze(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.Paralyze(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.Paralyze(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 a9c326ec6ac5..e58bb66248e2 100644 --- a/code/modules/projectiles/projectile/special/ion.dm +++ b/code/modules/projectiles/projectile/special/ion.dm @@ -1,17 +1,17 @@ -/obj/item/projectile/ion - name = "ion bolt" - icon_state = "ion" - damage = 0 - damage_type = BURN - nodamage = TRUE - flag = "energy" - impact_effect_type = /obj/effect/temp_visual/impact_effect/ion - var/emp_radius = 1 - -/obj/item/projectile/ion/on_hit(atom/target, blocked = FALSE) - ..() - empulse(target, emp_radius, emp_radius) - return BULLET_ACT_HIT - -/obj/item/projectile/ion/weak - emp_radius = 0 +/obj/item/projectile/ion + name = "ion bolt" + icon_state = "ion" + damage = 0 + damage_type = BURN + nodamage = TRUE + flag = "energy" + impact_effect_type = /obj/effect/temp_visual/impact_effect/ion + var/emp_radius = 1 + +/obj/item/projectile/ion/on_hit(atom/target, blocked = FALSE) + ..() + empulse(target, emp_radius, emp_radius) + return BULLET_ACT_HIT + +/obj/item/projectile/ion/weak + emp_radius = 0 diff --git a/code/modules/projectiles/projectile/special/meteor.dm b/code/modules/projectiles/projectile/special/meteor.dm index e513aa5fb87a..146dea4077d1 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 = TRUE - 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 = TRUE + 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 ac4488cae064..84276e727545 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 fc7928c88d78..ff22b7c42c6b 100644 --- a/code/modules/projectiles/projectile/special/neurotoxin.dm +++ b/code/modules/projectiles/projectile/special/neurotoxin.dm @@ -1,12 +1,12 @@ -/obj/item/projectile/bullet/neurotoxin - name = "neurotoxin spit" - icon_state = "neurotoxin" - damage = 5 - damage_type = TOX - paralyze = 100 - -/obj/item/projectile/bullet/neurotoxin/on_hit(atom/target, blocked = FALSE) - if(isalien(target)) - paralyze = 0 - nodamage = TRUE - return ..() +/obj/item/projectile/bullet/neurotoxin + name = "neurotoxin spit" + icon_state = "neurotoxin" + damage = 5 + damage_type = TOX + paralyze = 100 + +/obj/item/projectile/bullet/neurotoxin/on_hit(atom/target, blocked = FALSE) + if(isalien(target)) + paralyze = 0 + nodamage = TRUE + return ..() diff --git a/code/modules/projectiles/projectile/special/plasma.dm b/code/modules/projectiles/projectile/special/plasma.dm index b1ae7e4fab1d..0bec7cde1b21 100644 --- a/code/modules/projectiles/projectile/special/plasma.dm +++ b/code/modules/projectiles/projectile/special/plasma.dm @@ -1,40 +1,40 @@ -/obj/item/projectile/plasma - name = "plasma blast" - icon_state = "plasmacutter" - damage_type = BRUTE - damage = 5 - range = 4 - dismemberment = 20 - impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser - 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/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 BULLET_ACT_FORCE_PIERCE - -/obj/item/projectile/plasma/adv - damage = 7 - range = 5 - mine_range = 5 - -/obj/item/projectile/plasma/adv/mech - damage = 10 - 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 + name = "plasma blast" + icon_state = "plasmacutter" + damage_type = BRUTE + damage = 5 + range = 4 + dismemberment = 20 + impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser + 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/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 BULLET_ACT_FORCE_PIERCE + +/obj/item/projectile/plasma/adv + damage = 7 + range = 5 + mine_range = 5 + +/obj/item/projectile/plasma/adv/mech + damage = 10 + 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 diff --git a/code/modules/projectiles/projectile/special/rocket.dm b/code/modules/projectiles/projectile/special/rocket.dm index 6ff9d45b3fe2..967d9e7aa342 100644 --- a/code/modules/projectiles/projectile/special/rocket.dm +++ b/code/modules/projectiles/projectile/special/rocket.dm @@ -1,75 +1,75 @@ -/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 BULLET_ACT_HIT - -/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 - -/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 BULLET_ACT_HIT - -/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 BULLET_ACT_HIT - -/obj/item/projectile/bullet/a84mm_br - name ="\improper HE missile" - desc = "Boom." - icon_state = "missile" - damage = 30 - ricochets_max = 0 //it's a MISSILE - var/sturdy = list( - /turf/closed, - /obj/mecha, - /obj/machinery/door/, - /obj/machinery/door/poddoor/shutters - ) - -/obj/item/broken_missile - name = "\improper broken missile" - desc = "A missile that did not detonate. The tail has snapped and it is in no way fit to be used again." - icon = 'icons/obj/projectiles.dmi' - icon_state = "missile_broken" - w_class = WEIGHT_CLASS_TINY - - -/obj/item/projectile/bullet/a84mm_br/on_hit(atom/target, blocked=0) - ..() - for(var/i in sturdy) - if(istype(target, i)) - explosion(target, 0, 1, 1, 2) - return BULLET_ACT_HIT - //if(istype(target, /turf/closed) || ismecha(target)) +/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 BULLET_ACT_HIT + +/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 + +/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 BULLET_ACT_HIT + +/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 BULLET_ACT_HIT + +/obj/item/projectile/bullet/a84mm_br + name ="\improper HE missile" + desc = "Boom." + icon_state = "missile" + damage = 30 + ricochets_max = 0 //it's a MISSILE + var/sturdy = list( + /turf/closed, + /obj/mecha, + /obj/machinery/door/, + /obj/machinery/door/poddoor/shutters + ) + +/obj/item/broken_missile + name = "\improper broken missile" + desc = "A missile that did not detonate. The tail has snapped and it is in no way fit to be used again." + icon = 'icons/obj/projectiles.dmi' + icon_state = "missile_broken" + w_class = WEIGHT_CLASS_TINY + + +/obj/item/projectile/bullet/a84mm_br/on_hit(atom/target, blocked=0) + ..() + for(var/i in sturdy) + if(istype(target, i)) + explosion(target, 0, 1, 1, 2) + return BULLET_ACT_HIT + //if(istype(target, /turf/closed) || ismecha(target)) new /obj/item/broken_missile(get_turf(src), 1) \ 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 ab8511775381..cd6a86dc4221 100644 --- a/code/modules/projectiles/projectile/special/temperature.dm +++ b/code/modules/projectiles/projectile/special/temperature.dm @@ -1,29 +1,29 @@ -/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/cryo - name = "cryo beam" - range = 3 - -/obj/item/projectile/temp/cryo/on_range() - var/turf/T = get_turf(src) - if(isopenturf(T)) - var/turf/open/O = T - O.freon_gas_act() - return ..() +/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/cryo + name = "cryo beam" + range = 3 + +/obj/item/projectile/temp/cryo/on_range() + var/turf/T = get_turf(src) + if(isopenturf(T)) + var/turf/open/O = T + O.freon_gas_act() + return ..() diff --git a/code/modules/projectiles/projectile/special/wormhole.dm b/code/modules/projectiles/projectile/special/wormhole.dm index 07b56a133fbf..0832f0cc76eb 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 cfc0dd3a80d4..b6b3ab449a96 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -1,123 +1,123 @@ -/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/volume = 30 - var/reagent_flags - var/list/list_reagents = null - var/spawned_disease = null - var/disease_amount = 20 - var/spillable = FALSE - -/obj/item/reagent_containers/Initialize(mapload, vol) - . = ..() - if(isnum(vol) && vol > 0) - volume = vol - create_reagents(volume, reagent_flags) - if(spawned_disease) - var/datum/disease/F = new spawned_disease() - var/list/data = list("viruses"= list(F)) - reagents.add_reagent(/datum/reagent/blood, disease_amount, data) - - add_initial_reagents() - -/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/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - SplashReagents(hit_atom, TRUE) - -/obj/item/reagent_containers/proc/bartender_check(atom/target) - . = FALSE - if(target.CanPass(src, get_turf(src)) && thrownby && HAS_TRAIT(thrownby, TRAIT_BOOZE_SLIDER)) - . = TRUE - -/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] ([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.") - 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() - -/obj/item/reagent_containers/microwave_act(obj/machinery/microwave/M) - reagents.expose_temperature(1000) - ..() - -/obj/item/reagent_containers/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - reagents.expose_temperature(exposed_temperature) +/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/volume = 30 + var/reagent_flags + var/list/list_reagents = null + var/spawned_disease = null + var/disease_amount = 20 + var/spillable = FALSE + +/obj/item/reagent_containers/Initialize(mapload, vol) + . = ..() + if(isnum(vol) && vol > 0) + volume = vol + create_reagents(volume, reagent_flags) + if(spawned_disease) + var/datum/disease/F = new spawned_disease() + var/list/data = list("viruses"= list(F)) + reagents.add_reagent(/datum/reagent/blood, disease_amount, data) + + add_initial_reagents() + +/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/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + SplashReagents(hit_atom, TRUE) + +/obj/item/reagent_containers/proc/bartender_check(atom/target) + . = FALSE + if(target.CanPass(src, get_turf(src)) && thrownby && HAS_TRAIT(thrownby, TRAIT_BOOZE_SLIDER)) + . = TRUE + +/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] ([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.") + 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() + +/obj/item/reagent_containers/microwave_act(obj/machinery/microwave/M) + reagents.expose_temperature(1000) + ..() + +/obj/item/reagent_containers/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + reagents.expose_temperature(exposed_temperature) diff --git a/code/modules/reagents/reagent_containers/borghydro.dm b/code/modules/reagents/reagent_containers/borghydro.dm index b166a3c6e8ba..320e8bc35dda 100644 --- a/code/modules/reagents/reagent_containers/borghydro.dm +++ b/code/modules/reagents/reagent_containers/borghydro.dm @@ -1,256 +1,256 @@ -/* -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) - 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, transfered_by = user) - 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() - . = list() - var/empty = TRUE - - 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 = FALSE - - if(empty) - . += "It is currently empty! Allow some time for the internal synthesizer to produce more." - -/obj/item/reagent_containers/borghypo/hacked - icon_state = "borghypo_s" - reagent_ids = list (/datum/reagent/toxin/acid/fluacid, /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) - 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, transfered_by = user) - to_chat(user, "You transfer [trans] unit\s of the solution to [target].") - -/obj/item/reagent_containers/borghypo/borgshaker/DescribeContents() - var/datum/reagents/RS = reagent_list[mode] - var/datum/reagent/R = locate() in RS.reagent_list - if(R) - return "It currently has [R.volume] unit\s of [R.name] stored." - else - return "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) - 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 = "epinephrine injector" - desc = "An advanced chemical synthesizer and injection system, designed to stabilize patients." - reagent_ids = list(/datum/reagent/medicine/epinephrine) - 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) + 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, transfered_by = user) + 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() + . = list() + var/empty = TRUE + + 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 = FALSE + + if(empty) + . += "It is currently empty! Allow some time for the internal synthesizer to produce more." + +/obj/item/reagent_containers/borghypo/hacked + icon_state = "borghypo_s" + reagent_ids = list (/datum/reagent/toxin/acid/fluacid, /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) + 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, transfered_by = user) + to_chat(user, "You transfer [trans] unit\s of the solution to [target].") + +/obj/item/reagent_containers/borghypo/borgshaker/DescribeContents() + var/datum/reagents/RS = reagent_list[mode] + var/datum/reagent/R = locate() in RS.reagent_list + if(R) + return "It currently has [R.volume] unit\s of [R.name] stored." + else + return "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) + 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 = "epinephrine injector" + desc = "An advanced chemical synthesizer and injection system, designed to stabilize patients." + reagent_ids = list(/datum/reagent/medicine/epinephrine) + accepts_reagent_upgrades = FALSE diff --git a/code/modules/reagents/reagent_containers/dropper.dm b/code/modules/reagents/reagent_containers/dropper.dm index 0a3f486ac47b..951ca5b2cda5 100644 --- a/code/modules/reagents/reagent_containers/dropper.dm +++ b/code/modules/reagents/reagent_containers/dropper.dm @@ -1,108 +1,108 @@ -/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(user)) - to_chat(user, "You cannot transfer reagents to [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 = victim.is_eyes_covered() - - 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, transfered_by = user) - - 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 - var/viruslist = "" // yogs - adds viruslist variable - if(reagents) - for(var/datum/reagent/A in src.reagents.reagent_list) - R += "[A] ([num2text(A.volume)])," -// yogs start - checks blood for disease - if(istype(A, /datum/reagent/blood)) - var/datum/reagent/blood/RR = A - for(var/datum/disease/D in RR.data["viruses"]) - viruslist += " [D.name]" - if(istype(D, /datum/disease/advance)) - var/datum/disease/advance/DD = D - viruslist += " \[ symptoms: " - for(var/datum/symptom/S in DD.symptoms) - viruslist += "[S.name] " - viruslist += "\]" -// yogs end - log_combat(user, M, "squirted", R) - -// yogs start - Adds logs if it is viruslist - if(viruslist) - investigate_log("[user.real_name] ([user.ckey]) injected [M.real_name] ([M.ckey]) using a projectile with [viruslist]", INVESTIGATE_VIROLOGY) - log_game("[user.real_name] ([user.ckey]) injected [M.real_name] ([M.ckey]) with [viruslist]") -// yogs end - - trans = src.reagents.trans_to(target, amount_per_transfer_from_this, transfered_by = user) - to_chat(user, "You transfer [trans] unit\s of the solution.") - update_icon() - - else - - if(!target.is_drawable(user, 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, transfered_by = user) - - 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 + 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(user)) + to_chat(user, "You cannot transfer reagents to [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 = victim.is_eyes_covered() + + 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, transfered_by = user) + + 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 + var/viruslist = "" // yogs - adds viruslist variable + if(reagents) + for(var/datum/reagent/A in src.reagents.reagent_list) + R += "[A] ([num2text(A.volume)])," +// yogs start - checks blood for disease + if(istype(A, /datum/reagent/blood)) + var/datum/reagent/blood/RR = A + for(var/datum/disease/D in RR.data["viruses"]) + viruslist += " [D.name]" + if(istype(D, /datum/disease/advance)) + var/datum/disease/advance/DD = D + viruslist += " \[ symptoms: " + for(var/datum/symptom/S in DD.symptoms) + viruslist += "[S.name] " + viruslist += "\]" +// yogs end + log_combat(user, M, "squirted", R) + +// yogs start - Adds logs if it is viruslist + if(viruslist) + investigate_log("[user.real_name] ([user.ckey]) injected [M.real_name] ([M.ckey]) using a projectile with [viruslist]", INVESTIGATE_VIROLOGY) + log_game("[user.real_name] ([user.ckey]) injected [M.real_name] ([M.ckey]) with [viruslist]") +// yogs end + + trans = src.reagents.trans_to(target, amount_per_transfer_from_this, transfered_by = user) + to_chat(user, "You transfer [trans] unit\s of the solution.") + update_icon() + + else + + if(!target.is_drawable(user, 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, transfered_by = user) + + 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) diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm index e64c0e2824dc..2ff410cc3d71 100755 --- a/code/modules/reagents/reagent_containers/glass.dm +++ b/code/modules/reagents/reagent_containers/glass.dm @@ -1,405 +1,405 @@ -/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 - - -/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] ([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(!spillable) - 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, transfered_by = user) - 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, transfered_by = user) - 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.is_hot() - 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, transfered_by = user) - qdel(E) - return - ..() - - -/obj/item/reagent_containers/glass/beaker - name = "beaker" - desc = "A beaker. It can hold up to 50 units." - icon = 'icons/obj/chemical.dmi' - icon_state = "beaker" - item_state = "beaker" - materials = list(MAT_GLASS=500) - -/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() - cut_overlays() - - if(reagents.total_volume) - var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "[icon_state]10") - - var/percent = round((reagents.total_volume / volume) * 100) - switch(percent) - if(0 to 9) - filling.icon_state = "[icon_state]-10" - if(10 to 24) - filling.icon_state = "[icon_state]10" - if(25 to 49) - filling.icon_state = "[icon_state]25" - if(50 to 74) - filling.icon_state = "[icon_state]50" - if(75 to 79) - filling.icon_state = "[icon_state]75" - if(80 to 90) - filling.icon_state = "[icon_state]80" - if(91 to INFINITY) - filling.icon_state = "[icon_state]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 50 units of sweet delight." - 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 100 units." - icon_state = "beakerlarge" - materials = list(MAT_GLASS=2500) - volume = 100 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,50,100) - -/obj/item/reagent_containers/glass/beaker/plastic - name = "x-large beaker" - desc = "An extra-large beaker. Can hold up to 120 units." - icon_state = "beakerwhite" - materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000) - volume = 120 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,60,120) - -/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 180 units." - icon_state = "beakergold" - materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000, MAT_GOLD=1000, MAT_TITANIUM=1000) - volume = 180 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,60,120,180) - -/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 - -/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." - icon_state = "beakerbluespace" - materials = list(MAT_GLASS = 5000, MAT_PLASMA = 3000, MAT_DIAMOND = 1000, MAT_BLUESPACE = 1000) - volume = 300 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,50,100,300) - -/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 = 'yogstation/icons/obj/janitor.dmi' //yogs - wasnt documented - 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 - ) - -/obj/item/reagent_containers/glass/bucket/wooden - name = "wooden bucket" - icon_state = "woodbucket" - item_state = "woodbucket" - materials = null - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - resistance_flags = FLAMMABLE - -/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, transfered_by = user) - to_chat(user, "You wet [O] in [src].") - playsound(loc, 'sound/effects/slosh.ogg', 25, 1) - else if(isprox(O)) //This works with wooden buckets for now. Somewhat unintended, but maybe someone will add sprites for it soon(TM) - 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() - reagents.flags = NONE - -/obj/item/reagent_containers/glass/bucket/dropped(mob/user) - . = ..() - reagents.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 - -/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 - -/obj/item/reagent_containers/glass/beaker/waterbottle/large/empty - list_reagents = list() - -/obj/item/pestle - name = "pestle" - desc = "An ancient, simple tool used in conjunction with a mortar to grind or juice items." - icon = 'icons/obj/chemical.dmi' - icon_state = "pestle" - force = 7 - -/obj/item/reagent_containers/glass/mortar - name = "mortar" - desc = "A specially formed bowl of ancient design. It is possible to crush or juice items placed in it using a pestle; however the process, unlike modern methods, is slow and physically exhausting. Alt click to eject the item." - icon_state = "mortar" - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5, 10, 15, 20, 25, 30, 50, 100) - volume = 100 - reagent_flags = OPENCONTAINER - spillable = TRUE - var/obj/item/grinded - -/obj/item/reagent_containers/glass/mortar/AltClick(mob/user) - if(grinded) - grinded.forceMove(drop_location()) - grinded = null - to_chat(user, "You eject the item inside.") - -/obj/item/reagent_containers/glass/mortar/attackby(obj/item/I, mob/living/carbon/human/user) - ..() - if(istype(I,/obj/item/pestle)) - if(grinded) - if(user.getStaminaLoss() > 50) - to_chat(user, "You are too tired to work!") - return - to_chat(user, "You start grinding...") - if((do_after(user, 25, target = src)) && grinded) - user.adjustStaminaLoss(40) - if(grinded.reagents) //food and pills - grinded.reagents.trans_to(src, grinded.reagents.total_volume, transfered_by = user) - if(grinded.juice_results) //prioritize juicing - grinded.on_juice() - reagents.add_reagent_list(grinded.juice_results) - to_chat(user, "You juice [grinded] into a fine liquid.") - QDEL_NULL(grinded) - return - grinded.on_grind() - reagents.add_reagent_list(grinded.grind_results) - to_chat(user, "You break [grinded] into powder.") - QDEL_NULL(grinded) - return - return - else - to_chat(user, "There is nothing to grind!") - return - if(grinded) - to_chat(user, "There is something inside already!") - return - if(I.juice_results || I.grind_results) - I.forceMove(src) - grinded = I - return - to_chat(user, "You can't grind this!") - -/obj/item/reagent_containers/glass/saline - name = "saline canister" - volume = 5000 - list_reagents = list(/datum/reagent/medicine/salglu_solution = 5000) +/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 + + +/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] ([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(!spillable) + 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, transfered_by = user) + 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, transfered_by = user) + 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.is_hot() + 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, transfered_by = user) + qdel(E) + return + ..() + + +/obj/item/reagent_containers/glass/beaker + name = "beaker" + desc = "A beaker. It can hold up to 50 units." + icon = 'icons/obj/chemical.dmi' + icon_state = "beaker" + item_state = "beaker" + materials = list(MAT_GLASS=500) + +/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() + cut_overlays() + + if(reagents.total_volume) + var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "[icon_state]10") + + var/percent = round((reagents.total_volume / volume) * 100) + switch(percent) + if(0 to 9) + filling.icon_state = "[icon_state]-10" + if(10 to 24) + filling.icon_state = "[icon_state]10" + if(25 to 49) + filling.icon_state = "[icon_state]25" + if(50 to 74) + filling.icon_state = "[icon_state]50" + if(75 to 79) + filling.icon_state = "[icon_state]75" + if(80 to 90) + filling.icon_state = "[icon_state]80" + if(91 to INFINITY) + filling.icon_state = "[icon_state]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 50 units of sweet delight." + 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 100 units." + icon_state = "beakerlarge" + materials = list(MAT_GLASS=2500) + volume = 100 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,50,100) + +/obj/item/reagent_containers/glass/beaker/plastic + name = "x-large beaker" + desc = "An extra-large beaker. Can hold up to 120 units." + icon_state = "beakerwhite" + materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000) + volume = 120 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,60,120) + +/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 180 units." + icon_state = "beakergold" + materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000, MAT_GOLD=1000, MAT_TITANIUM=1000) + volume = 180 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,60,120,180) + +/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 + +/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." + icon_state = "beakerbluespace" + materials = list(MAT_GLASS = 5000, MAT_PLASMA = 3000, MAT_DIAMOND = 1000, MAT_BLUESPACE = 1000) + volume = 300 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,50,100,300) + +/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 = 'yogstation/icons/obj/janitor.dmi' //yogs - wasnt documented + 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 + ) + +/obj/item/reagent_containers/glass/bucket/wooden + name = "wooden bucket" + icon_state = "woodbucket" + item_state = "woodbucket" + materials = null + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + resistance_flags = FLAMMABLE + +/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, transfered_by = user) + to_chat(user, "You wet [O] in [src].") + playsound(loc, 'sound/effects/slosh.ogg', 25, 1) + else if(isprox(O)) //This works with wooden buckets for now. Somewhat unintended, but maybe someone will add sprites for it soon(TM) + 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() + reagents.flags = NONE + +/obj/item/reagent_containers/glass/bucket/dropped(mob/user) + . = ..() + reagents.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 + +/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 + +/obj/item/reagent_containers/glass/beaker/waterbottle/large/empty + list_reagents = list() + +/obj/item/pestle + name = "pestle" + desc = "An ancient, simple tool used in conjunction with a mortar to grind or juice items." + icon = 'icons/obj/chemical.dmi' + icon_state = "pestle" + force = 7 + +/obj/item/reagent_containers/glass/mortar + name = "mortar" + desc = "A specially formed bowl of ancient design. It is possible to crush or juice items placed in it using a pestle; however the process, unlike modern methods, is slow and physically exhausting. Alt click to eject the item." + icon_state = "mortar" + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5, 10, 15, 20, 25, 30, 50, 100) + volume = 100 + reagent_flags = OPENCONTAINER + spillable = TRUE + var/obj/item/grinded + +/obj/item/reagent_containers/glass/mortar/AltClick(mob/user) + if(grinded) + grinded.forceMove(drop_location()) + grinded = null + to_chat(user, "You eject the item inside.") + +/obj/item/reagent_containers/glass/mortar/attackby(obj/item/I, mob/living/carbon/human/user) + ..() + if(istype(I,/obj/item/pestle)) + if(grinded) + if(user.getStaminaLoss() > 50) + to_chat(user, "You are too tired to work!") + return + to_chat(user, "You start grinding...") + if((do_after(user, 25, target = src)) && grinded) + user.adjustStaminaLoss(40) + if(grinded.reagents) //food and pills + grinded.reagents.trans_to(src, grinded.reagents.total_volume, transfered_by = user) + if(grinded.juice_results) //prioritize juicing + grinded.on_juice() + reagents.add_reagent_list(grinded.juice_results) + to_chat(user, "You juice [grinded] into a fine liquid.") + QDEL_NULL(grinded) + return + grinded.on_grind() + reagents.add_reagent_list(grinded.grind_results) + to_chat(user, "You break [grinded] into powder.") + QDEL_NULL(grinded) + return + return + else + to_chat(user, "There is nothing to grind!") + return + if(grinded) + to_chat(user, "There is something inside already!") + return + if(I.juice_results || I.grind_results) + I.forceMove(src) + grinded = I + return + to_chat(user, "You can't grind this!") + +/obj/item/reagent_containers/glass/saline + name = "saline canister" + volume = 5000 + list_reagents = list(/datum/reagent/medicine/salglu_solution = 5000) diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 4f6146aece01..16428f907827 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -1,206 +1,206 @@ -/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) -// yogs start -Adds viruslist stuff - var/viruslist = "" - for(var/datum/reagent/R in reagents.reagent_list) - injected += R.name - if(istype(R, /datum/reagent/blood)) - var/datum/reagent/blood/RR = R - for(var/datum/disease/D in RR.data["viruses"]) - viruslist += " [D.name]" - if(istype(D, /datum/disease/advance)) - var/datum/disease/advance/DD = D - viruslist += " \[ symptoms: " - for(var/datum/symptom/S in DD.symptoms) - viruslist += "[S.name] " - viruslist += "\]" -// yogs end - var/trans = 0 - if(!infinite) - trans = reagents.trans_to(M, amount_per_transfer_from_this, transfered_by = user) - 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])") -// yogs start - makes logs if viruslist - if(viruslist) - investigate_log("[user.real_name] ([user.ckey]) injected [M.real_name] ([M.ckey]) with [viruslist]", INVESTIGATE_VIROLOGY) - log_game("[user.real_name] ([user.ckey]) injected [M.real_name] ([M.ckey]) with [viruslist]") -// yogs end -/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." - amount_per_transfer_from_this = 10 - icon_state = "combat_hypo" - volume = 90 - ignore_flags = 1 // So they can heal their comrades. - 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) - custom_price = 40 - -/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 - reagents.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/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/omnizine = 20, /datum/reagent/medicine/perfluorodecalin = 15, /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 = 57 - amount_per_transfer_from_this = 57 - list_reagents = list(/datum/reagent/medicine/salbutamol = 10, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/tricordrazine = 15, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/lavaland_extract = 2, /datum/reagent/medicine/omnizine = 5) - -/obj/item/reagent_containers/hypospray/medipen/species_mutator - name = "species mutator medipen" - desc = "Embark on a whirlwind tour of racial insensitivity by \ - literally appropriating other races." - volume = 1 - amount_per_transfer_from_this = 1 - list_reagents = list(/datum/reagent/toxin/mutagen = 1) - -/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 - -/obj/item/reagent_containers/hypospray/medipen/atropine - name = "atropine autoinjector" - desc = "A rapid way to save a person from a critical injury state!" - list_reagents = list(/datum/reagent/medicine/atropine = 10) - -/*/obj/item/reagent_containers/hypospray/medipen/snail - name = "snail shot" - desc = "All-purpose snail medicine! Do not use on non-snails!" - list_reagents = list(/datum/reagent/snail = 10) +/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) +// yogs start -Adds viruslist stuff + var/viruslist = "" + for(var/datum/reagent/R in reagents.reagent_list) + injected += R.name + if(istype(R, /datum/reagent/blood)) + var/datum/reagent/blood/RR = R + for(var/datum/disease/D in RR.data["viruses"]) + viruslist += " [D.name]" + if(istype(D, /datum/disease/advance)) + var/datum/disease/advance/DD = D + viruslist += " \[ symptoms: " + for(var/datum/symptom/S in DD.symptoms) + viruslist += "[S.name] " + viruslist += "\]" +// yogs end + var/trans = 0 + if(!infinite) + trans = reagents.trans_to(M, amount_per_transfer_from_this, transfered_by = user) + 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])") +// yogs start - makes logs if viruslist + if(viruslist) + investigate_log("[user.real_name] ([user.ckey]) injected [M.real_name] ([M.ckey]) with [viruslist]", INVESTIGATE_VIROLOGY) + log_game("[user.real_name] ([user.ckey]) injected [M.real_name] ([M.ckey]) with [viruslist]") +// yogs end +/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." + amount_per_transfer_from_this = 10 + icon_state = "combat_hypo" + volume = 90 + ignore_flags = 1 // So they can heal their comrades. + 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) + custom_price = 40 + +/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 + reagents.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/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/omnizine = 20, /datum/reagent/medicine/perfluorodecalin = 15, /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 = 57 + amount_per_transfer_from_this = 57 + list_reagents = list(/datum/reagent/medicine/salbutamol = 10, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/tricordrazine = 15, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/lavaland_extract = 2, /datum/reagent/medicine/omnizine = 5) + +/obj/item/reagent_containers/hypospray/medipen/species_mutator + name = "species mutator medipen" + desc = "Embark on a whirlwind tour of racial insensitivity by \ + literally appropriating other races." + volume = 1 + amount_per_transfer_from_this = 1 + list_reagents = list(/datum/reagent/toxin/mutagen = 1) + +/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 + +/obj/item/reagent_containers/hypospray/medipen/atropine + name = "atropine autoinjector" + desc = "A rapid way to save a person from a critical injury state!" + list_reagents = list(/datum/reagent/medicine/atropine = 10) + +/*/obj/item/reagent_containers/hypospray/medipen/snail + name = "snail shot" + desc = "All-purpose snail medicine! Do not use on non-snails!" + list_reagents = list(/datum/reagent/snail = 10) icon_state = "snail" */ //yogs we removed snail people cause we are bad people who hate fun \ No newline at end of file diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm index 56229fbaa829..f543d57c3b18 100644 --- a/code/modules/reagents/reagent_containers/pill.dm +++ b/code/modules/reagents/reagent_containers/pill.dm @@ -1,244 +1,244 @@ -/obj/item/reagent_containers/pill - name = "pill" - desc = "A tablet or capsule." - icon = 'icons/obj/chemical.dmi' - icon_state = "pill" - item_state = "pill" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - possible_transfer_amounts = list() - volume = 50 - grind_results = list() - var/apply_type = INGEST - var/apply_method = "swallow" - var/rename_with_volume = FALSE - var/self_delay = 0 //pills are instant, this is because patches inheret their aplication from pills - var/dissolvable = TRUE - -/obj/item/reagent_containers/pill/Initialize() - . = ..() - if(!icon_state) - icon_state = "pill[rand(1,20)]" - if(reagents.total_volume && rename_with_volume) - name += " ([reagents.total_volume]u)" - - -/obj/item/reagent_containers/pill/attack_self(mob/user) - return - - -/obj/item/reagent_containers/pill/attack(mob/M, mob/user, def_zone) - if(!canconsume(M, user)) - return FALSE - - if(M == user) - M.visible_message("[user] attempts to [apply_method] [src].") - if(self_delay) - if(!do_mob(user, M, self_delay)) - return FALSE - to_chat(M, "You [apply_method] [src].") - - else - M.visible_message("[user] attempts to force [M] to [apply_method] [src].", \ - "[user] attempts to force [M] to [apply_method] [src].") - if(!do_mob(user, M)) - return FALSE - M.visible_message("[user] forces [M] to [apply_method] [src].", \ - "[user] forces [M] to [apply_method] [src].") - - var/makes_me_think = pick(strings(REDPILL_FILE, "redpill_questions")) - if(icon_state == "pill4" && prob(5)) //you take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes - sleep(50) - to_chat(M, "[makes_me_think]") - - if(reagents.total_volume) - reagents.reaction(M, apply_type) - reagents.trans_to(M, reagents.total_volume, transfered_by = user) - qdel(src) - return TRUE - - -/obj/item/reagent_containers/pill/afterattack(obj/target, mob/user , proximity) - . = ..() - if(!proximity) - return - if(!dissolvable || !target.is_refillable()) - return - if(target.is_drainable() && !target.reagents.total_volume) - to_chat(user, "[target] is empty! There's nothing to dissolve [src] in.") - return - - if(target.reagents.holder_full()) - to_chat(user, "[target] is full.") - return - - user.visible_message("[user] slips something into [target]!", "You dissolve [src] in [target].", null, 2) - reagents.trans_to(target, reagents.total_volume, transfered_by = user) - qdel(src) - -/obj/item/reagent_containers/pill/tox - name = "toxins pill" - desc = "Highly toxic." - icon_state = "pill5" - list_reagents = list(/datum/reagent/toxin = 50) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/cyanide - name = "cyanide pill" - desc = "Don't swallow this." - icon_state = "pill5" - list_reagents = list(/datum/reagent/toxin/cyanide = 50) - -/obj/item/reagent_containers/pill/adminordrazine - name = "adminordrazine pill" - desc = "It's magic. We don't have to explain it." - icon_state = "pill16" - list_reagents = list(/datum/reagent/medicine/adminordrazine = 50) - -/obj/item/reagent_containers/pill/morphine - name = "morphine pill" - desc = "Commonly used to treat insomnia." - icon_state = "pill8" - list_reagents = list(/datum/reagent/medicine/morphine = 30) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/stimulant - name = "stimulant pill" - desc = "Often taken by overworked employees, athletes, and the inebriated. You'll snap to attention immediately!" - icon_state = "pill19" - list_reagents = list(/datum/reagent/medicine/ephedrine = 10, /datum/reagent/medicine/antihol = 10, /datum/reagent/consumable/coffee = 30) - -/obj/item/reagent_containers/pill/salbutamol - name = "salbutamol pill" - desc = "Used to treat oxygen deprivation." - icon_state = "pill16" - list_reagents = list(/datum/reagent/medicine/salbutamol = 30) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/charcoal - name = "charcoal pill" - desc = "Neutralizes many common toxins." - icon_state = "pill17" - list_reagents = list(/datum/reagent/medicine/charcoal = 10) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/epinephrine - name = "epinephrine pill" - desc = "Used to stabilize patients." - icon_state = "pill5" - list_reagents = list(/datum/reagent/medicine/epinephrine = 15) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/mannitol - name = "mannitol pill" - desc = "Used to treat brain damage." - icon_state = "pill17" - list_reagents = list(/datum/reagent/medicine/mannitol = 50) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/mutadone - name = "mutadone pill" - desc = "Used to treat genetic damage." - icon_state = "pill20" - list_reagents = list(/datum/reagent/medicine/mutadone = 50) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/salicyclic - name = "salicylic acid pill" - desc = "Used to dull pain." - icon_state = "pill9" - list_reagents = list(/datum/reagent/medicine/sal_acid = 24) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/oxandrolone - name = "oxandrolone pill" - desc = "Used to stimulate burn healing." - icon_state = "pill11" - list_reagents = list(/datum/reagent/medicine/oxandrolone = 24) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/insulin - name = "insulin pill" - desc = "Handles hyperglycaemic coma." - icon_state = "pill18" - list_reagents = list(/datum/reagent/medicine/insulin = 50) - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/psicodine - name = "psicodine pill" - desc = "Used to treat mental instability and phobias." - list_reagents = list(/datum/reagent/medicine/psicodine = 10) - icon_state = "pill22" - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/penacid - name = "pentetic acid pill" - desc = "Used to expunge radiation and toxins." - list_reagents = list(/datum/reagent/medicine/pen_acid = 10) - icon_state = "pill22" - rename_with_volume = TRUE - -/obj/item/reagent_containers/pill/neurine - name = "neurine pill" - desc = "Used to treat non-severe mental traumas." - list_reagents = list(/datum/reagent/medicine/neurine = 10) - icon_state = "pill22" - rename_with_volume = TRUE - -///////////////////////////////////////// this pill is used only in a legion mob drop -/obj/item/reagent_containers/pill/shadowtoxin - name = "black pill" - desc = "I wouldn't eat this if I were you." - icon_state = "pill9" - color = "#454545" - list_reagents = list(/datum/reagent/mutationtoxin/shadow = 1) - -//////////////////////////////////////// drugs -/obj/item/reagent_containers/pill/zoom - name = "yellow pill" - desc = "A poorly made canary-yellow pill; it is slightly crumbly." - list_reagents = list(/datum/reagent/medicine/synaptizine = 10, /datum/reagent/drug/nicotine = 10, /datum/reagent/drug/methamphetamine = 1) - icon_state = "pill7" - - -/obj/item/reagent_containers/pill/happy - name = "happy pill" - desc = "They have little happy faces on them, and they smell like marker pens." - list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/drug/space_drugs = 10) - icon_state = "pill_happy" - - -/obj/item/reagent_containers/pill/lsd - name = "sunshine pill" - desc = "Engraved on this split-coloured pill is a half-sun, half-moon." - list_reagents = list(/datum/reagent/drug/mushroomhallucinogen = 15, /datum/reagent/toxin/mindbreaker = 15) - icon_state = "pill14" - - -/obj/item/reagent_containers/pill/aranesp - name = "smooth pill" - desc = "This blue pill is feels slightly moist." - list_reagents = list(/datum/reagent/drug/aranesp = 10) - icon_state = "pill3" - -/obj/item/reagent_containers/pill/happiness - name = "happiness pill" - desc = "It has a creepy smiling face on it." - icon_state = "pill_happy" - list_reagents = list(/datum/reagent/drug/happiness = 10) - -/obj/item/reagent_containers/pill/floorpill - name = "floorpill" - desc = "A strange pill found in the depths of maintenance" - icon_state = "pill21" - var/static/list/names = list("maintenance pill","floorpill","mystery pill","suspicious pill","strange pill") - var/static/list/descs = list("Your feeling is telling you no, but...","Drugs are expensive, you can't afford not to eat any pills that you find."\ - , "Surely, there's no way this could go bad.") - -/obj/item/reagent_containers/pill/floorpill/Initialize() - list_reagents = list(get_random_reagent_id() = rand(10,50)) - . = ..() - name = pick(names) - if(prob(20)) - desc = pick(descs) - +/obj/item/reagent_containers/pill + name = "pill" + desc = "A tablet or capsule." + icon = 'icons/obj/chemical.dmi' + icon_state = "pill" + item_state = "pill" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + possible_transfer_amounts = list() + volume = 50 + grind_results = list() + var/apply_type = INGEST + var/apply_method = "swallow" + var/rename_with_volume = FALSE + var/self_delay = 0 //pills are instant, this is because patches inheret their aplication from pills + var/dissolvable = TRUE + +/obj/item/reagent_containers/pill/Initialize() + . = ..() + if(!icon_state) + icon_state = "pill[rand(1,20)]" + if(reagents.total_volume && rename_with_volume) + name += " ([reagents.total_volume]u)" + + +/obj/item/reagent_containers/pill/attack_self(mob/user) + return + + +/obj/item/reagent_containers/pill/attack(mob/M, mob/user, def_zone) + if(!canconsume(M, user)) + return FALSE + + if(M == user) + M.visible_message("[user] attempts to [apply_method] [src].") + if(self_delay) + if(!do_mob(user, M, self_delay)) + return FALSE + to_chat(M, "You [apply_method] [src].") + + else + M.visible_message("[user] attempts to force [M] to [apply_method] [src].", \ + "[user] attempts to force [M] to [apply_method] [src].") + if(!do_mob(user, M)) + return FALSE + M.visible_message("[user] forces [M] to [apply_method] [src].", \ + "[user] forces [M] to [apply_method] [src].") + + var/makes_me_think = pick(strings(REDPILL_FILE, "redpill_questions")) + if(icon_state == "pill4" && prob(5)) //you take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes + sleep(50) + to_chat(M, "[makes_me_think]") + + if(reagents.total_volume) + reagents.reaction(M, apply_type) + reagents.trans_to(M, reagents.total_volume, transfered_by = user) + qdel(src) + return TRUE + + +/obj/item/reagent_containers/pill/afterattack(obj/target, mob/user , proximity) + . = ..() + if(!proximity) + return + if(!dissolvable || !target.is_refillable()) + return + if(target.is_drainable() && !target.reagents.total_volume) + to_chat(user, "[target] is empty! There's nothing to dissolve [src] in.") + return + + if(target.reagents.holder_full()) + to_chat(user, "[target] is full.") + return + + user.visible_message("[user] slips something into [target]!", "You dissolve [src] in [target].", null, 2) + reagents.trans_to(target, reagents.total_volume, transfered_by = user) + qdel(src) + +/obj/item/reagent_containers/pill/tox + name = "toxins pill" + desc = "Highly toxic." + icon_state = "pill5" + list_reagents = list(/datum/reagent/toxin = 50) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/cyanide + name = "cyanide pill" + desc = "Don't swallow this." + icon_state = "pill5" + list_reagents = list(/datum/reagent/toxin/cyanide = 50) + +/obj/item/reagent_containers/pill/adminordrazine + name = "adminordrazine pill" + desc = "It's magic. We don't have to explain it." + icon_state = "pill16" + list_reagents = list(/datum/reagent/medicine/adminordrazine = 50) + +/obj/item/reagent_containers/pill/morphine + name = "morphine pill" + desc = "Commonly used to treat insomnia." + icon_state = "pill8" + list_reagents = list(/datum/reagent/medicine/morphine = 30) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/stimulant + name = "stimulant pill" + desc = "Often taken by overworked employees, athletes, and the inebriated. You'll snap to attention immediately!" + icon_state = "pill19" + list_reagents = list(/datum/reagent/medicine/ephedrine = 10, /datum/reagent/medicine/antihol = 10, /datum/reagent/consumable/coffee = 30) + +/obj/item/reagent_containers/pill/salbutamol + name = "salbutamol pill" + desc = "Used to treat oxygen deprivation." + icon_state = "pill16" + list_reagents = list(/datum/reagent/medicine/salbutamol = 30) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/charcoal + name = "charcoal pill" + desc = "Neutralizes many common toxins." + icon_state = "pill17" + list_reagents = list(/datum/reagent/medicine/charcoal = 10) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/epinephrine + name = "epinephrine pill" + desc = "Used to stabilize patients." + icon_state = "pill5" + list_reagents = list(/datum/reagent/medicine/epinephrine = 15) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/mannitol + name = "mannitol pill" + desc = "Used to treat brain damage." + icon_state = "pill17" + list_reagents = list(/datum/reagent/medicine/mannitol = 50) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/mutadone + name = "mutadone pill" + desc = "Used to treat genetic damage." + icon_state = "pill20" + list_reagents = list(/datum/reagent/medicine/mutadone = 50) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/salicyclic + name = "salicylic acid pill" + desc = "Used to dull pain." + icon_state = "pill9" + list_reagents = list(/datum/reagent/medicine/sal_acid = 24) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/oxandrolone + name = "oxandrolone pill" + desc = "Used to stimulate burn healing." + icon_state = "pill11" + list_reagents = list(/datum/reagent/medicine/oxandrolone = 24) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/insulin + name = "insulin pill" + desc = "Handles hyperglycaemic coma." + icon_state = "pill18" + list_reagents = list(/datum/reagent/medicine/insulin = 50) + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/psicodine + name = "psicodine pill" + desc = "Used to treat mental instability and phobias." + list_reagents = list(/datum/reagent/medicine/psicodine = 10) + icon_state = "pill22" + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/penacid + name = "pentetic acid pill" + desc = "Used to expunge radiation and toxins." + list_reagents = list(/datum/reagent/medicine/pen_acid = 10) + icon_state = "pill22" + rename_with_volume = TRUE + +/obj/item/reagent_containers/pill/neurine + name = "neurine pill" + desc = "Used to treat non-severe mental traumas." + list_reagents = list(/datum/reagent/medicine/neurine = 10) + icon_state = "pill22" + rename_with_volume = TRUE + +///////////////////////////////////////// this pill is used only in a legion mob drop +/obj/item/reagent_containers/pill/shadowtoxin + name = "black pill" + desc = "I wouldn't eat this if I were you." + icon_state = "pill9" + color = "#454545" + list_reagents = list(/datum/reagent/mutationtoxin/shadow = 1) + +//////////////////////////////////////// drugs +/obj/item/reagent_containers/pill/zoom + name = "yellow pill" + desc = "A poorly made canary-yellow pill; it is slightly crumbly." + list_reagents = list(/datum/reagent/medicine/synaptizine = 10, /datum/reagent/drug/nicotine = 10, /datum/reagent/drug/methamphetamine = 1) + icon_state = "pill7" + + +/obj/item/reagent_containers/pill/happy + name = "happy pill" + desc = "They have little happy faces on them, and they smell like marker pens." + list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/drug/space_drugs = 10) + icon_state = "pill_happy" + + +/obj/item/reagent_containers/pill/lsd + name = "sunshine pill" + desc = "Engraved on this split-coloured pill is a half-sun, half-moon." + list_reagents = list(/datum/reagent/drug/mushroomhallucinogen = 15, /datum/reagent/toxin/mindbreaker = 15) + icon_state = "pill14" + + +/obj/item/reagent_containers/pill/aranesp + name = "smooth pill" + desc = "This blue pill is feels slightly moist." + list_reagents = list(/datum/reagent/drug/aranesp = 10) + icon_state = "pill3" + +/obj/item/reagent_containers/pill/happiness + name = "happiness pill" + desc = "It has a creepy smiling face on it." + icon_state = "pill_happy" + list_reagents = list(/datum/reagent/drug/happiness = 10) + +/obj/item/reagent_containers/pill/floorpill + name = "floorpill" + desc = "A strange pill found in the depths of maintenance" + icon_state = "pill21" + var/static/list/names = list("maintenance pill","floorpill","mystery pill","suspicious pill","strange pill") + var/static/list/descs = list("Your feeling is telling you no, but...","Drugs are expensive, you can't afford not to eat any pills that you find."\ + , "Surely, there's no way this could go bad.") + +/obj/item/reagent_containers/pill/floorpill/Initialize() + list_reagents = list(get_random_reagent_id() = rand(10,50)) + . = ..() + name = pick(names) + if(prob(20)) + desc = pick(descs) + diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index 5ebe29144c11..b429a1e19b6b 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -1,373 +1,373 @@ -/obj/item/reagent_containers/spray - name = "spray bottle" - desc = "A spray bottle, with an unscrewable top." - icon = 'yogstation/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/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, transfered_by = user) //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, "Not enough left!") - return - - spray(A, user) - - 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) - var/contained = reagents.log_list() - - log_combat(user, T, "sprayed", src, addition="which had [contained]") - log_game("[key_name(user)] fired [contained] from \a [src] at [AREACOORD(T)].") //copypasta falling out of my pockets - return - - -/obj/item/reagent_containers/spray/proc/spray(atom/A, mob/living/user, log = 1) // yogs - makes log activate if a living mob is sprayed - var/range = max(min(current_range, get_dist(src, A)), 1) - 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) -// yogs start - viruslist stuff - if(log && user) - var/list/sprayed = list() - var/viruslist = "" - for(var/datum/reagent/R in reagents.reagent_list) - sprayed += R.name - if(istype(R, /datum/reagent/blood)) - var/datum/reagent/blood/RR = R - for(var/datum/disease/Disease in RR.data["viruses"]) - if(viruslist) - viruslist += " and " - viruslist += "[Disease.name]" - if(istype(Disease, /datum/disease/advance)) - var/datum/disease/advance/DD = Disease - viruslist += " \[ symptoms: " - for(var/datum/symptom/S in DD.symptoms) - viruslist += "[S.name] " - viruslist += "\]" - if(viruslist) - investigate_log("[user.real_name] ([user.ckey]) sprayed \a [src] containing [viruslist]", INVESTIGATE_VIROLOGY) - log_game("[user.real_name] ([user.ckey]) sprayed \a [src] containing [viruslist]") -// yogs end - D.color = mix_color_from_reagents(D.reagents.reagent_list) - var/wait_step = max(round(2+3/range), 2) - do_spray(A, wait_step, D, range, puff_reagent_left, user) - -/obj/item/reagent_containers/spray/proc/do_spray(atom/A, wait_step, obj/effect/decal/chempuff/D, range, puff_reagent_left, mob/user) - 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.is_hot() - 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() - -/obj/item/reagent_containers/spray/on_reagent_change(changetype) - var/total_reagent_weight - var/amount_of_reagents - for (var/datum/reagent/R in reagents.reagent_list) - total_reagent_weight = total_reagent_weight + R.reagent_weight - amount_of_reagents++ - - if(total_reagent_weight && amount_of_reagents) //don't bother if the container is empty - DIV/0 - var/average_reagent_weight = total_reagent_weight / amount_of_reagents - spray_range = CLAMP(round((initial(spray_range) / average_reagent_weight) - ((amount_of_reagents - 1) * 1)), 3, 5) //spray distance between 3 and 5 tiles rounded down; extra reagents lose a tile - else - spray_range = initial(spray_range) - if(stream_mode == 0) - current_range = spray_range - -//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 - -//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 - amount_per_transfer_from_this = 5 - list_reagents = list(/datum/reagent/consumable/condensedcapsaicin = 40) - -/obj/item/reagent_containers/spray/pepper/empty //for protolathe printing - list_reagents = null - -/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 - -/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, mob/user) - 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], user) - -/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) - - -/obj/item/reagent_containers/spray/chemsprayer/janitor - name = "janitor chem sprayer" - desc = "A utility used to spray large amounts of cleaning reagents in a given area. It regenerates space cleaner by itself but it's unable to be fueled by normal means." - icon_state = "chemsprayer_janitor" - item_state = "chemsprayer_janitor" - lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' - reagent_flags = NONE - list_reagents = list(/datum/reagent/space_cleaner = 1000) - volume = 1000 - amount_per_transfer_from_this = 5 - var/generate_amount = 50 - var/generate_type = /datum/reagent/space_cleaner - var/last_generate = 0 - var/generate_delay = 10 //deciseconds - -/obj/item/reagent_containers/spray/chemsprayer/janitor/Initialize() - . = ..() - START_PROCESSING(SSfastprocess, src) - -/obj/item/reagent_containers/spray/chemsprayer/janitor/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/obj/item/reagent_containers/spray/chemsprayer/janitor/process() - if(world.time < last_generate + generate_delay) - return - last_generate = world.time - reagents.add_reagent(generate_type, generate_amount) - -// 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 = 'yogstation/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/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, transfered_by = user) //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, "Not enough left!") + return + + spray(A, user) + + 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) + var/contained = reagents.log_list() + + log_combat(user, T, "sprayed", src, addition="which had [contained]") + log_game("[key_name(user)] fired [contained] from \a [src] at [AREACOORD(T)].") //copypasta falling out of my pockets + return + + +/obj/item/reagent_containers/spray/proc/spray(atom/A, mob/living/user, log = 1) // yogs - makes log activate if a living mob is sprayed + var/range = max(min(current_range, get_dist(src, A)), 1) + 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) +// yogs start - viruslist stuff + if(log && user) + var/list/sprayed = list() + var/viruslist = "" + for(var/datum/reagent/R in reagents.reagent_list) + sprayed += R.name + if(istype(R, /datum/reagent/blood)) + var/datum/reagent/blood/RR = R + for(var/datum/disease/Disease in RR.data["viruses"]) + if(viruslist) + viruslist += " and " + viruslist += "[Disease.name]" + if(istype(Disease, /datum/disease/advance)) + var/datum/disease/advance/DD = Disease + viruslist += " \[ symptoms: " + for(var/datum/symptom/S in DD.symptoms) + viruslist += "[S.name] " + viruslist += "\]" + if(viruslist) + investigate_log("[user.real_name] ([user.ckey]) sprayed \a [src] containing [viruslist]", INVESTIGATE_VIROLOGY) + log_game("[user.real_name] ([user.ckey]) sprayed \a [src] containing [viruslist]") +// yogs end + D.color = mix_color_from_reagents(D.reagents.reagent_list) + var/wait_step = max(round(2+3/range), 2) + do_spray(A, wait_step, D, range, puff_reagent_left, user) + +/obj/item/reagent_containers/spray/proc/do_spray(atom/A, wait_step, obj/effect/decal/chempuff/D, range, puff_reagent_left, mob/user) + 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.is_hot() + 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() + +/obj/item/reagent_containers/spray/on_reagent_change(changetype) + var/total_reagent_weight + var/amount_of_reagents + for (var/datum/reagent/R in reagents.reagent_list) + total_reagent_weight = total_reagent_weight + R.reagent_weight + amount_of_reagents++ + + if(total_reagent_weight && amount_of_reagents) //don't bother if the container is empty - DIV/0 + var/average_reagent_weight = total_reagent_weight / amount_of_reagents + spray_range = CLAMP(round((initial(spray_range) / average_reagent_weight) - ((amount_of_reagents - 1) * 1)), 3, 5) //spray distance between 3 and 5 tiles rounded down; extra reagents lose a tile + else + spray_range = initial(spray_range) + if(stream_mode == 0) + current_range = spray_range + +//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 + +//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 + amount_per_transfer_from_this = 5 + list_reagents = list(/datum/reagent/consumable/condensedcapsaicin = 40) + +/obj/item/reagent_containers/spray/pepper/empty //for protolathe printing + list_reagents = null + +/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 + +/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, mob/user) + 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], user) + +/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) + + +/obj/item/reagent_containers/spray/chemsprayer/janitor + name = "janitor chem sprayer" + desc = "A utility used to spray large amounts of cleaning reagents in a given area. It regenerates space cleaner by itself but it's unable to be fueled by normal means." + icon_state = "chemsprayer_janitor" + item_state = "chemsprayer_janitor" + lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + reagent_flags = NONE + list_reagents = list(/datum/reagent/space_cleaner = 1000) + volume = 1000 + amount_per_transfer_from_this = 5 + var/generate_amount = 50 + var/generate_type = /datum/reagent/space_cleaner + var/last_generate = 0 + var/generate_delay = 10 //deciseconds + +/obj/item/reagent_containers/spray/chemsprayer/janitor/Initialize() + . = ..() + START_PROCESSING(SSfastprocess, src) + +/obj/item/reagent_containers/spray/chemsprayer/janitor/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/item/reagent_containers/spray/chemsprayer/janitor/process() + if(world.time < last_generate + generate_delay) + return + last_generate = world.time + reagents.add_reagent(generate_type, generate_amount) + +// 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_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index cc2ab2a7237d..8dbd585fde9c 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -1,217 +1,217 @@ -/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) - if(reagent_id) - 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) - -/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/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/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))) - log_bomber(P.firer, "detonated a", src, "via projectile") - boom() - -/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/I, mob/living/user, params) - if(I.tool_behaviour == TOOL_WELDER) - 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, transfered_by = user) - 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 - user.visible_message("[user] catastrophically fails at refilling [user.p_their()] [W.name]!", "That was stupid of you.") - - log_bomber(user, "detonated a", src, "via welding tool") - - boom() - return - return ..() - - -/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/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-- - -/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/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 - -/obj/structure/reagent_dispensers/plumbed - name = "stationairy water tank" - anchored = TRUE - icon_state = "water_stationairy" - desc = "A stationairy, plumbed, water tank." - -/obj/structure/reagent_dispensers/plumbed/wrench_act(mob/living/user, obj/item/I) - default_unfasten_wrench(user, I) - return TRUE - -/obj/structure/reagent_dispensers/plumbed/default_unfasten_wrench(mob/user, obj/item/I, time = 20) - . = ..() - if(. == SUCCESSFUL_UNFASTEN) - user.visible_message("[user.name] [anchored ? "fasten" : "unfasten"] [src]", \ - "You [anchored ? "fasten" : "unfasten"] [src]") - var/datum/component/plumbing/CP = GetComponent(/datum/component/plumbing) - if(anchored) - CP.start() - else - CP.disable() - -/obj/structure/reagent_dispensers/plumbed/ComponentInitialize() - AddComponent(/datum/component/plumbing/simple_supply) - -/obj/structure/reagent_dispensers/plumbed/storage - name = "stationairy storage tank" - icon_state = "tank_stationairy" - reagent_id = null //start empty - -/obj/structure/reagent_dispensers/plumbed/storage/ComponentInitialize() - AddComponent(/datum/component/plumbing/tank) +/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) + if(reagent_id) + 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) + +/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/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/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))) + log_bomber(P.firer, "detonated a", src, "via projectile") + boom() + +/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/I, mob/living/user, params) + if(I.tool_behaviour == TOOL_WELDER) + 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, transfered_by = user) + 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 + user.visible_message("[user] catastrophically fails at refilling [user.p_their()] [W.name]!", "That was stupid of you.") + + log_bomber(user, "detonated a", src, "via welding tool") + + boom() + return + return ..() + + +/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/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-- + +/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/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 + +/obj/structure/reagent_dispensers/plumbed + name = "stationairy water tank" + anchored = TRUE + icon_state = "water_stationairy" + desc = "A stationairy, plumbed, water tank." + +/obj/structure/reagent_dispensers/plumbed/wrench_act(mob/living/user, obj/item/I) + default_unfasten_wrench(user, I) + return TRUE + +/obj/structure/reagent_dispensers/plumbed/default_unfasten_wrench(mob/user, obj/item/I, time = 20) + . = ..() + if(. == SUCCESSFUL_UNFASTEN) + user.visible_message("[user.name] [anchored ? "fasten" : "unfasten"] [src]", \ + "You [anchored ? "fasten" : "unfasten"] [src]") + var/datum/component/plumbing/CP = GetComponent(/datum/component/plumbing) + if(anchored) + CP.start() + else + CP.disable() + +/obj/structure/reagent_dispensers/plumbed/ComponentInitialize() + AddComponent(/datum/component/plumbing/simple_supply) + +/obj/structure/reagent_dispensers/plumbed/storage + name = "stationairy storage tank" + icon_state = "tank_stationairy" + reagent_id = null //start empty + +/obj/structure/reagent_dispensers/plumbed/storage/ComponentInitialize() + AddComponent(/datum/component/plumbing/tank) diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm index 44bf5248d52b..d5094f1d97da 100644 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -1,197 +1,197 @@ -/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_high.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" - item_state = "deliverypackage" - 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_high.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_high.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_high.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" + item_state = "deliverypackage" + 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_high.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_high.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 5cbe9a6f163f..312136a366c5 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: "id" = 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: "id" = 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 Comamnd 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: "id" = 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: "id" = 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 Comamnd 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 b58f8c4dc7fd..3fde9049b239 100644 --- a/code/modules/research/designs/bluespace_designs.dm +++ b/code/modules/research/designs/bluespace_designs.dm @@ -1,65 +1,65 @@ - -///////////////////////////////////////// -///////////////Bluespace///////////////// -///////////////////////////////////////// - -/datum/design/beacon - name = "Tracking Beacon" - desc = "A bluespace 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/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 | DEPARTMENTAL_FLAG_ENGINEERING - -/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 + +///////////////////////////////////////// +///////////////Bluespace///////////////// +///////////////////////////////////////// + +/datum/design/beacon + name = "Tracking Beacon" + desc = "A bluespace 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/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 | DEPARTMENTAL_FLAG_ENGINEERING + +/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/electronics_designs.dm b/code/modules/research/designs/electronics_designs.dm index 96d797650d1d..94766529208f 100644 --- a/code/modules/research/designs/electronics_designs.dm +++ b/code/modules/research/designs/electronics_designs.dm @@ -1,111 +1,111 @@ - -/////////////////////////////////// -/////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 - -/datum/design/ai_cam_upgrade - name = "AI Surveillance Software Update" - desc = "A software package that will allow an artificial intelligence to 'hear' from its cameras via lip reading." - id = "ai_cam_upgrade" - build_type = PROTOLATHE - materials = list(MAT_METAL = 5000, MAT_GLASS = 5000, MAT_GOLD = 15000, MAT_SILVER = 15000, MAT_DIAMOND = 20000, MAT_PLASMA = 10000) - build_path = /obj/item/surveillance_upgrade - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/////////////////////////////////// -//////////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_comm_remote - name = "Nanite Communication Remote" - desc = "Allows for the construction of a nanite communication remote." - id = "nanite_comm_remote" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 500, MAT_METAL = 500) - build_path = /obj/item/nanite_remote/comm - 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 + +/////////////////////////////////// +/////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 + +/datum/design/ai_cam_upgrade + name = "AI Surveillance Software Update" + desc = "A software package that will allow an artificial intelligence to 'hear' from its cameras via lip reading." + id = "ai_cam_upgrade" + build_type = PROTOLATHE + materials = list(MAT_METAL = 5000, MAT_GLASS = 5000, MAT_GOLD = 15000, MAT_SILVER = 15000, MAT_DIAMOND = 20000, MAT_PLASMA = 10000) + build_path = /obj/item/surveillance_upgrade + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/////////////////////////////////// +//////////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_comm_remote + name = "Nanite Communication Remote" + desc = "Allows for the construction of a nanite communication remote." + id = "nanite_comm_remote" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 500, MAT_METAL = 500) + build_path = /obj/item/nanite_remote/comm + 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 diff --git a/code/modules/research/designs/mining_designs.dm b/code/modules/research/designs/mining_designs.dm index 18934d021cfe..b4ac5f109d10 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/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index 8495383952a2..09b2d0f957c3 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -1,456 +1,456 @@ - -///////////////////////////////////////// -/////////////////HUDs//////////////////// -///////////////////////////////////////// - -/datum/design/health_hud - name = "Health Scanner HUD" - desc = "A heads-up display that scans the humans in view and provides accurate data about their health status." - id = "health_hud" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 500) - build_path = /obj/item/clothing/glasses/hud/health - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_MEDICAL - -/datum/design/health_hud_night - name = "Night Vision Health Scanner HUD" - desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness." - id = "health_hud_night" - build_type = PROTOLATHE - materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_URANIUM = 1000, MAT_SILVER = 350) - build_path = /obj/item/clothing/glasses/hud/health/night - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_MEDICAL - -/datum/design/security_hud - name = "Security HUD" - desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status." - id = "security_hud" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 500) - build_path = /obj/item/clothing/glasses/hud/security - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/security_hud_night - name = "Night Vision Security HUD" - desc = "A heads-up display which provides id data and vision in complete darkness." - id = "security_hud_night" - build_type = PROTOLATHE - materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_URANIUM = 1000, MAT_GOLD = 350) - build_path = /obj/item/clothing/glasses/hud/security/night - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/diagnostic_hud - name = "Diagnostic HUD" - desc = "A HUD used to analyze and determine faults within robotic machinery." - id = "diagnostic_hud" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 500) - build_path = /obj/item/clothing/glasses/hud/diagnostic - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/diagnostic_hud_night - name = "Night Vision Diagnostic HUD" - desc = "Upgraded version of the diagnostic HUD designed to function during a power failure." - id = "diagnostic_hud_night" - build_type = PROTOLATHE - materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_URANIUM = 1000, MAT_PLASMA = 300) - build_path = /obj/item/clothing/glasses/hud/diagnostic/night - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -///////////////////////////////////////// -//////////////////Misc/////////////////// -///////////////////////////////////////// - -/datum/design/welding_mask - name = "Welding Gas Mask" - desc = "A gas mask with built in welding goggles and face shield. Looks like a skull, clearly designed by a nerd." - id = "weldingmask" - build_type = PROTOLATHE - materials = list(MAT_METAL = 3000, MAT_GLASS = 1000) - build_path = /obj/item/clothing/mask/gas/welding - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/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." - id = "portaseeder" - build_type = PROTOLATHE - materials = list(MAT_METAL = 1000, MAT_GLASS = 400) - build_path = /obj/item/storage/bag/plants/portaseeder - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -/datum/design/air_horn - name = "Air Horn" - desc = "Damn son, where'd you find this?" - id = "air_horn" - build_type = PROTOLATHE - materials = list(MAT_METAL = 4000, MAT_BANANIUM = 1000) - build_path = /obj/item/bikehorn/airhorn - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ALL //HONK! - -/datum/design/mesons - name = "Optical Meson Scanners" - desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting condition." - id = "mesons" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 500) - build_path = /obj/item/clothing/glasses/meson - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/engine_goggles - name = "Engineering Scanner Goggles" - desc = "Goggles used by engineers. The Meson Scanner mode lets you see basic structural and terrain layouts through walls, regardless of lighting condition. The T-ray Scanner mode lets you see underfloor objects such as cables and pipes." - id = "engine_goggles" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 500, MAT_PLASMA = 100) - build_path = /obj/item/clothing/glasses/meson/engine - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/tray_goggles - name = "Optical T-Ray Scanners" - desc = "Used by engineering staff to see underfloor objects such as cables and pipes." - id = "tray_goggles" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 500) - build_path = /obj/item/clothing/glasses/meson/engine/tray - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/nvgmesons - name = "Night Vision Optical Meson Scanners" - desc = "Prototype meson scanners fitted with an extra sensor which amplifies the visible light spectrum and overlays it to the UHD display." - id = "nvgmesons" - build_type = PROTOLATHE - materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_PLASMA = 350, MAT_URANIUM = 1000) - build_path = /obj/item/clothing/glasses/meson/night - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO - -/datum/design/night_vision_goggles - name = "Night Vision Goggles" - desc = "Goggles that let you see through darkness unhindered." - id = "night_visision_goggles" - build_type = PROTOLATHE - materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_PLASMA = 350, MAT_URANIUM = 1000) - build_path = /obj/item/clothing/glasses/night - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_SECURITY - -/datum/design/magboots - name = "Magnetic Boots" - desc = "Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle." - id = "magboots" - build_type = PROTOLATHE - materials = list(MAT_METAL = 4500, MAT_SILVER = 1500, MAT_GOLD = 2500) - build_path = /obj/item/clothing/shoes/magboots - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/forcefield_projector - name = "Forcefield Projector" - desc = "A device which can project temporary forcefields to seal off an area." - id = "forcefield_projector" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2500, MAT_GLASS = 1000) - build_path = /obj/item/forcefield_projector - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/sci_goggles - name = "Science Goggles" - desc = "Goggles fitted with a portable analyzer capable of determining the research worth of an item or components of a machine." - id = "scigoggles" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 500) - build_path = /obj/item/clothing/glasses/science - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/diskplantgene - name = "Plant Data Disk" - desc = "A disk for storing plant genetic data." - id = "diskplantgene" - build_type = PROTOLATHE - materials = list(MAT_METAL=200, MAT_GLASS=100) - build_path = /obj/item/disk/plantgene - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -/datum/design/roastingstick - name = "Advanced Roasting Stick" - desc = "A roasting stick for cooking sausages in exotic ovens." - id = "roastingstick" - build_type = PROTOLATHE - materials = list(MAT_METAL=1000, MAT_GLASS=500, MAT_BLUESPACE = 250) - build_path = /obj/item/melee/roastingstick - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -/datum/design/locator - name = "Bluespace Locator" - desc = "Used to track portable teleportation beacons and targets with embedded tracking implants." - id = "locator" - build_type = PROTOLATHE - materials = list(MAT_METAL=1000, MAT_GLASS=500, MAT_SILVER = 500) - build_path = /obj/item/locator - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/quantum_keycard - name = "Quantum Keycard" - desc = "Allows for the construction of a quantum keycard." - id = "quantum_keycard" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 500, MAT_METAL = 500, MAT_SILVER = 500, MAT_BLUESPACE = 1000) - build_path = /obj/item/quantum_keycard - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/anomaly_neutralizer - name = "Anomaly Neutralizer" - desc = "An advanced tool capable of instantly neutralizing anomalies, designed to capture the fleeting aberrations created by the engine." - id = "anomaly_neutralizer" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2000, MAT_GOLD = 2000, MAT_PLASMA = 5000, MAT_URANIUM = 2000) - build_path = /obj/item/anomaly_neutralizer - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/donksoft_refill - name = "Donksoft Toy Vendor Refill" - desc = "A refill canister for Donksoft Toy Vendors." - id = "donksoft_refill" - build_type = PROTOLATHE - materials = list(MAT_METAL = 25000, MAT_GLASS = 15000, MAT_PLASMA = 20000, MAT_GOLD = 10000, MAT_SILVER = 10000) - build_path = /obj/item/vending_refill/donksoft - category = list("Equipment") - -/datum/design/oxygen_tank - name = "Oxygen Tank" - desc = "An empty oxygen tank." - id = "oxygen_tank" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2000) - build_path = /obj/item/tank/internals/oxygen/empty - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/plasma_tank - name = "Plasma Tank" - desc = "An empty oxygen tank." - id = "plasma_tank" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2000) - build_path = /obj/item/tank/internals/plasma/empty - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE - -///////////////////////////////////////// -////////////Janitor Designs////////////// -///////////////////////////////////////// - -/datum/design/advmop - name = "Advanced Mop" - desc = "An upgraded mop with a large internal capacity for holding water or other cleaning chemicals." - id = "advmop" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2500, MAT_GLASS = 200) - build_path = /obj/item/mop/advanced - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -/datum/design/blutrash - name = "Trashbag of Holding" - desc = "An advanced trash bag with bluespace properties; capable of holding a plethora of garbage." - id = "blutrash" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 1500, MAT_URANIUM = 250, MAT_PLASMA = 1500) - build_path = /obj/item/storage/bag/trash/bluespace - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -/datum/design/buffer - name = "Floor Buffer Upgrade" - desc = "A floor buffer that can be attached to vehicular janicarts." - id = "buffer" - build_type = PROTOLATHE - materials = list(MAT_METAL = 3000, MAT_GLASS = 200) - build_path = /obj/item/janiupgrade - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -/datum/design/spraybottle - name = "Spray Bottle" - desc = "A spray bottle, with an unscrewable top." - id = "spraybottle" - build_type = PROTOLATHE - materials = list(MAT_METAL = 3000, MAT_GLASS = 200) - build_path = /obj/item/reagent_containers/spray - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -/datum/design/beartrap - name = "Bear Trap" - desc = "A trap used to catch space bears and other legged creatures." - id = "beartrap" - build_type = PROTOLATHE - materials = list(MAT_METAL = 5000, MAT_TITANIUM = 1000) - build_path = /obj/item/restraints/legcuffs/beartrap - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -///////////////////////////////////////// -/////////////Holobarriers//////////////// -///////////////////////////////////////// - -/datum/design/holosign - name = "Holographic Sign Projector" - desc = "A holograpic projector used to project various warning signs." - id = "holosign" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2000, MAT_GLASS = 1000) - build_path = /obj/item/holosign_creator - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - -/datum/design/holobarrier_jani - name = "Custodial Holobarrier Projector" - desc = "A holograpic projector used to project hard light wet floor barriers." - id = "holobarrier_jani" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2000, MAT_GLASS = 1000, MAT_SILVER = 1000) - build_path = /obj/structure/holosign/barrier/wetsign - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SERVICE - - -/datum/design/holosignsec - name = "Security Holobarrier Projector" - desc = "A holographic projector that creates holographic security barriers." - id = "holosignsec" - build_type = PROTOLATHE - materials = list(MAT_METAL = 5000, MAT_GLASS = 1000, MAT_GOLD = 1000, MAT_SILVER = 1000) - build_path = /obj/item/holosign_creator/security - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/holosignengi - name = "Engineering Holobarrier Projector" - desc = "A holographic projector that creates holographic engineering barriers." - id = "holosignengi" - build_type = PROTOLATHE - materials = list(MAT_METAL = 5000, MAT_GLASS = 1000, MAT_GOLD = 1000, MAT_SILVER = 1000) - build_path = /obj/item/holosign_creator/engineering - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/holosignatmos - name = "ATMOS Holofan Projector" - desc = "A holographic projector that creates holographic barriers that prevent changes in atmospheric conditions." - id = "holosignatmos" - build_type = PROTOLATHE - materials = list(MAT_METAL = 5000, MAT_GLASS = 1000, MAT_GOLD = 1000, MAT_SILVER = 1000) - build_path = /obj/item/holosign_creator/atmos - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/holobarrier_med - name = "PENLITE Holobarrier Projector" - desc = "PENLITE holobarriers, a device that halts individuals with malicious diseases." - build_type = PROTOLATHE - build_path = /obj/item/holosign_creator/medical - materials = list(MAT_METAL = 500, MAT_GLASS = 500, MAT_SILVER = 100) //a hint of silver since it can troll 2 antags (bad viros and sentient disease) - id = "holobarrier_med" - category = list("Medical Designs") - departmental_flags = DEPARTMENTAL_FLAG_MEDICAL - -///////////////////////////////////////// -////////////////Armour/////////////////// -///////////////////////////////////////// - -/datum/design/reactive_armour - name = "Reactive Armour Shell" - desc = "An experimental suit of armour capable of utilizing an implanted anomaly core to protect the user." - id = "reactive_armour" - build_type = PROTOLATHE - materials = list(MAT_METAL = 10000, MAT_DIAMOND = 5000, MAT_URANIUM = 8000, MAT_SILVER = 4500, MAT_GOLD = 5000) - build_path = /obj/item/reactive_armour_shell - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING - -///////////////////////////////////////// -/////////////Security//////////////////// -///////////////////////////////////////// - -/datum/design/seclite - name = "Seclite" - desc = "A robust flashlight used by security." - id = "seclite" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2500) - build_path = /obj/item/flashlight/seclite - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/detective_scanner - name = "Forensic Scanner" - desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings." - id = "detective_scanner" - build_type = PROTOLATHE - materials = list(MAT_METAL = 5000, MAT_GLASS = 1000, MAT_GOLD = 2500, MAT_SILVER = 2000) - build_path = /obj/item/detective_scanner - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/pepperspray - name = "Pepper Spray" - desc = "Manufactured by UhangInc, used to blind and down an opponent quickly. Printed pepper sprays do not contain reagents." - id = "pepperspray" - build_type = PROTOLATHE - materials = list(MAT_METAL = 5000, MAT_GLASS = 1000) - build_path = /obj/item/reagent_containers/spray/pepper/empty - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/bola_energy - name = "Energy Bola" - desc = "A specialized hard-light bola designed to ensnare fleeing criminals and aid in arrests." - id = "bola_energy" - build_type = PROTOLATHE - materials = list(MAT_SILVER = 500, MAT_PLASMA = 500, MAT_TITANIUM = 500) - build_path = /obj/item/restraints/legcuffs/bola/energy - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/zipties - name = "Zipties" - desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." - id = "zipties" - build_type = PROTOLATHE - materials = list(MAT_PLASTIC = 250) - build_path = /obj/item/restraints/handcuffs/cable/zipties - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - -/datum/design/evidencebag - name = "Evidence Bag" - desc = "An empty evidence bag." - id = "evidencebag" - build_type = PROTOLATHE - materials = list(MAT_PLASTIC = 100) - build_path = /obj/item/evidencebag - category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_SECURITY - - + +///////////////////////////////////////// +/////////////////HUDs//////////////////// +///////////////////////////////////////// + +/datum/design/health_hud + name = "Health Scanner HUD" + desc = "A heads-up display that scans the humans in view and provides accurate data about their health status." + id = "health_hud" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 500) + build_path = /obj/item/clothing/glasses/hud/health + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + +/datum/design/health_hud_night + name = "Night Vision Health Scanner HUD" + desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness." + id = "health_hud_night" + build_type = PROTOLATHE + materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_URANIUM = 1000, MAT_SILVER = 350) + build_path = /obj/item/clothing/glasses/hud/health/night + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + +/datum/design/security_hud + name = "Security HUD" + desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status." + id = "security_hud" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 500) + build_path = /obj/item/clothing/glasses/hud/security + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/security_hud_night + name = "Night Vision Security HUD" + desc = "A heads-up display which provides id data and vision in complete darkness." + id = "security_hud_night" + build_type = PROTOLATHE + materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_URANIUM = 1000, MAT_GOLD = 350) + build_path = /obj/item/clothing/glasses/hud/security/night + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/diagnostic_hud + name = "Diagnostic HUD" + desc = "A HUD used to analyze and determine faults within robotic machinery." + id = "diagnostic_hud" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 500) + build_path = /obj/item/clothing/glasses/hud/diagnostic + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/diagnostic_hud_night + name = "Night Vision Diagnostic HUD" + desc = "Upgraded version of the diagnostic HUD designed to function during a power failure." + id = "diagnostic_hud_night" + build_type = PROTOLATHE + materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_URANIUM = 1000, MAT_PLASMA = 300) + build_path = /obj/item/clothing/glasses/hud/diagnostic/night + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +///////////////////////////////////////// +//////////////////Misc/////////////////// +///////////////////////////////////////// + +/datum/design/welding_mask + name = "Welding Gas Mask" + desc = "A gas mask with built in welding goggles and face shield. Looks like a skull, clearly designed by a nerd." + id = "weldingmask" + build_type = PROTOLATHE + materials = list(MAT_METAL = 3000, MAT_GLASS = 1000) + build_path = /obj/item/clothing/mask/gas/welding + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/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." + id = "portaseeder" + build_type = PROTOLATHE + materials = list(MAT_METAL = 1000, MAT_GLASS = 400) + build_path = /obj/item/storage/bag/plants/portaseeder + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/air_horn + name = "Air Horn" + desc = "Damn son, where'd you find this?" + id = "air_horn" + build_type = PROTOLATHE + materials = list(MAT_METAL = 4000, MAT_BANANIUM = 1000) + build_path = /obj/item/bikehorn/airhorn + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ALL //HONK! + +/datum/design/mesons + name = "Optical Meson Scanners" + desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting condition." + id = "mesons" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 500) + build_path = /obj/item/clothing/glasses/meson + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/engine_goggles + name = "Engineering Scanner Goggles" + desc = "Goggles used by engineers. The Meson Scanner mode lets you see basic structural and terrain layouts through walls, regardless of lighting condition. The T-ray Scanner mode lets you see underfloor objects such as cables and pipes." + id = "engine_goggles" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 500, MAT_PLASMA = 100) + build_path = /obj/item/clothing/glasses/meson/engine + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/tray_goggles + name = "Optical T-Ray Scanners" + desc = "Used by engineering staff to see underfloor objects such as cables and pipes." + id = "tray_goggles" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 500) + build_path = /obj/item/clothing/glasses/meson/engine/tray + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/nvgmesons + name = "Night Vision Optical Meson Scanners" + desc = "Prototype meson scanners fitted with an extra sensor which amplifies the visible light spectrum and overlays it to the UHD display." + id = "nvgmesons" + build_type = PROTOLATHE + materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_PLASMA = 350, MAT_URANIUM = 1000) + build_path = /obj/item/clothing/glasses/meson/night + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO + +/datum/design/night_vision_goggles + name = "Night Vision Goggles" + desc = "Goggles that let you see through darkness unhindered." + id = "night_visision_goggles" + build_type = PROTOLATHE + materials = list(MAT_METAL = 600, MAT_GLASS = 600, MAT_PLASMA = 350, MAT_URANIUM = 1000) + build_path = /obj/item/clothing/glasses/night + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_SECURITY + +/datum/design/magboots + name = "Magnetic Boots" + desc = "Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle." + id = "magboots" + build_type = PROTOLATHE + materials = list(MAT_METAL = 4500, MAT_SILVER = 1500, MAT_GOLD = 2500) + build_path = /obj/item/clothing/shoes/magboots + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/forcefield_projector + name = "Forcefield Projector" + desc = "A device which can project temporary forcefields to seal off an area." + id = "forcefield_projector" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2500, MAT_GLASS = 1000) + build_path = /obj/item/forcefield_projector + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/sci_goggles + name = "Science Goggles" + desc = "Goggles fitted with a portable analyzer capable of determining the research worth of an item or components of a machine." + id = "scigoggles" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 500) + build_path = /obj/item/clothing/glasses/science + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/diskplantgene + name = "Plant Data Disk" + desc = "A disk for storing plant genetic data." + id = "diskplantgene" + build_type = PROTOLATHE + materials = list(MAT_METAL=200, MAT_GLASS=100) + build_path = /obj/item/disk/plantgene + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/roastingstick + name = "Advanced Roasting Stick" + desc = "A roasting stick for cooking sausages in exotic ovens." + id = "roastingstick" + build_type = PROTOLATHE + materials = list(MAT_METAL=1000, MAT_GLASS=500, MAT_BLUESPACE = 250) + build_path = /obj/item/melee/roastingstick + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/locator + name = "Bluespace Locator" + desc = "Used to track portable teleportation beacons and targets with embedded tracking implants." + id = "locator" + build_type = PROTOLATHE + materials = list(MAT_METAL=1000, MAT_GLASS=500, MAT_SILVER = 500) + build_path = /obj/item/locator + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/quantum_keycard + name = "Quantum Keycard" + desc = "Allows for the construction of a quantum keycard." + id = "quantum_keycard" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 500, MAT_METAL = 500, MAT_SILVER = 500, MAT_BLUESPACE = 1000) + build_path = /obj/item/quantum_keycard + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/anomaly_neutralizer + name = "Anomaly Neutralizer" + desc = "An advanced tool capable of instantly neutralizing anomalies, designed to capture the fleeting aberrations created by the engine." + id = "anomaly_neutralizer" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2000, MAT_GOLD = 2000, MAT_PLASMA = 5000, MAT_URANIUM = 2000) + build_path = /obj/item/anomaly_neutralizer + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/donksoft_refill + name = "Donksoft Toy Vendor Refill" + desc = "A refill canister for Donksoft Toy Vendors." + id = "donksoft_refill" + build_type = PROTOLATHE + materials = list(MAT_METAL = 25000, MAT_GLASS = 15000, MAT_PLASMA = 20000, MAT_GOLD = 10000, MAT_SILVER = 10000) + build_path = /obj/item/vending_refill/donksoft + category = list("Equipment") + +/datum/design/oxygen_tank + name = "Oxygen Tank" + desc = "An empty oxygen tank." + id = "oxygen_tank" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2000) + build_path = /obj/item/tank/internals/oxygen/empty + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/plasma_tank + name = "Plasma Tank" + desc = "An empty oxygen tank." + id = "plasma_tank" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2000) + build_path = /obj/item/tank/internals/plasma/empty + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE + +///////////////////////////////////////// +////////////Janitor Designs////////////// +///////////////////////////////////////// + +/datum/design/advmop + name = "Advanced Mop" + desc = "An upgraded mop with a large internal capacity for holding water or other cleaning chemicals." + id = "advmop" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2500, MAT_GLASS = 200) + build_path = /obj/item/mop/advanced + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/blutrash + name = "Trashbag of Holding" + desc = "An advanced trash bag with bluespace properties; capable of holding a plethora of garbage." + id = "blutrash" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 1500, MAT_URANIUM = 250, MAT_PLASMA = 1500) + build_path = /obj/item/storage/bag/trash/bluespace + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/buffer + name = "Floor Buffer Upgrade" + desc = "A floor buffer that can be attached to vehicular janicarts." + id = "buffer" + build_type = PROTOLATHE + materials = list(MAT_METAL = 3000, MAT_GLASS = 200) + build_path = /obj/item/janiupgrade + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/spraybottle + name = "Spray Bottle" + desc = "A spray bottle, with an unscrewable top." + id = "spraybottle" + build_type = PROTOLATHE + materials = list(MAT_METAL = 3000, MAT_GLASS = 200) + build_path = /obj/item/reagent_containers/spray + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/beartrap + name = "Bear Trap" + desc = "A trap used to catch space bears and other legged creatures." + id = "beartrap" + build_type = PROTOLATHE + materials = list(MAT_METAL = 5000, MAT_TITANIUM = 1000) + build_path = /obj/item/restraints/legcuffs/beartrap + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +///////////////////////////////////////// +/////////////Holobarriers//////////////// +///////////////////////////////////////// + +/datum/design/holosign + name = "Holographic Sign Projector" + desc = "A holograpic projector used to project various warning signs." + id = "holosign" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2000, MAT_GLASS = 1000) + build_path = /obj/item/holosign_creator + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/holobarrier_jani + name = "Custodial Holobarrier Projector" + desc = "A holograpic projector used to project hard light wet floor barriers." + id = "holobarrier_jani" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2000, MAT_GLASS = 1000, MAT_SILVER = 1000) + build_path = /obj/structure/holosign/barrier/wetsign + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + + +/datum/design/holosignsec + name = "Security Holobarrier Projector" + desc = "A holographic projector that creates holographic security barriers." + id = "holosignsec" + build_type = PROTOLATHE + materials = list(MAT_METAL = 5000, MAT_GLASS = 1000, MAT_GOLD = 1000, MAT_SILVER = 1000) + build_path = /obj/item/holosign_creator/security + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/holosignengi + name = "Engineering Holobarrier Projector" + desc = "A holographic projector that creates holographic engineering barriers." + id = "holosignengi" + build_type = PROTOLATHE + materials = list(MAT_METAL = 5000, MAT_GLASS = 1000, MAT_GOLD = 1000, MAT_SILVER = 1000) + build_path = /obj/item/holosign_creator/engineering + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/holosignatmos + name = "ATMOS Holofan Projector" + desc = "A holographic projector that creates holographic barriers that prevent changes in atmospheric conditions." + id = "holosignatmos" + build_type = PROTOLATHE + materials = list(MAT_METAL = 5000, MAT_GLASS = 1000, MAT_GOLD = 1000, MAT_SILVER = 1000) + build_path = /obj/item/holosign_creator/atmos + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/holobarrier_med + name = "PENLITE Holobarrier Projector" + desc = "PENLITE holobarriers, a device that halts individuals with malicious diseases." + build_type = PROTOLATHE + build_path = /obj/item/holosign_creator/medical + materials = list(MAT_METAL = 500, MAT_GLASS = 500, MAT_SILVER = 100) //a hint of silver since it can troll 2 antags (bad viros and sentient disease) + id = "holobarrier_med" + category = list("Medical Designs") + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + +///////////////////////////////////////// +////////////////Armour/////////////////// +///////////////////////////////////////// + +/datum/design/reactive_armour + name = "Reactive Armour Shell" + desc = "An experimental suit of armour capable of utilizing an implanted anomaly core to protect the user." + id = "reactive_armour" + build_type = PROTOLATHE + materials = list(MAT_METAL = 10000, MAT_DIAMOND = 5000, MAT_URANIUM = 8000, MAT_SILVER = 4500, MAT_GOLD = 5000) + build_path = /obj/item/reactive_armour_shell + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + +///////////////////////////////////////// +/////////////Security//////////////////// +///////////////////////////////////////// + +/datum/design/seclite + name = "Seclite" + desc = "A robust flashlight used by security." + id = "seclite" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2500) + build_path = /obj/item/flashlight/seclite + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/detective_scanner + name = "Forensic Scanner" + desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings." + id = "detective_scanner" + build_type = PROTOLATHE + materials = list(MAT_METAL = 5000, MAT_GLASS = 1000, MAT_GOLD = 2500, MAT_SILVER = 2000) + build_path = /obj/item/detective_scanner + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/pepperspray + name = "Pepper Spray" + desc = "Manufactured by UhangInc, used to blind and down an opponent quickly. Printed pepper sprays do not contain reagents." + id = "pepperspray" + build_type = PROTOLATHE + materials = list(MAT_METAL = 5000, MAT_GLASS = 1000) + build_path = /obj/item/reagent_containers/spray/pepper/empty + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/bola_energy + name = "Energy Bola" + desc = "A specialized hard-light bola designed to ensnare fleeing criminals and aid in arrests." + id = "bola_energy" + build_type = PROTOLATHE + materials = list(MAT_SILVER = 500, MAT_PLASMA = 500, MAT_TITANIUM = 500) + build_path = /obj/item/restraints/legcuffs/bola/energy + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/zipties + name = "Zipties" + desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." + id = "zipties" + build_type = PROTOLATHE + materials = list(MAT_PLASTIC = 250) + build_path = /obj/item/restraints/handcuffs/cable/zipties + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/evidencebag + name = "Evidence Bag" + desc = "An empty evidence bag." + id = "evidencebag" + build_type = PROTOLATHE + materials = list(MAT_PLASTIC = 100) + build_path = /obj/item/evidencebag + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + + diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm index ac76e8b9fea2..7dd7b76007b8 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 c7db20cce568..05a6862dd034 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -1,684 +1,684 @@ -//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_typecache //items that can cause critical reactions - var/banned_typecache // items that won't be produced - -/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() - // Don't need to keep this typecache around, only used in this proc once. - var/list/banned_typecache = typecacheof(list( - /obj/item/stock_parts/cell/infinite, - /obj/item/grenade/chem_grenade/tuberculosis - )) - - 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(is_type_in_typecache(I, banned_typecache)) - continue - - 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 - 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) - -/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() - - critical_items_typecache = typecacheof(list( - /obj/item/construction/rcd, - /obj/item/grenade, - /obj/item/aicard, - /obj/item/storage/backpack/holding, - /obj/item/slime_extract, - /obj/item/onetankbomb, - /obj/item/transfer_valve)) - -/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/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Malfunction probability reduced by [badThingCoeff]%.
                Cooldown interval between experiments at [resetTime*0.1] seconds." - -/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() - - -/* yogs start - moved to yogstation/ folder -/obj/machinery/rnd/experimentor/proc/experiment(exp,obj/item/exp_on) - recentlyExperimented = 1 - icon_state = "h_lathe_wloop" - var/chosenchem - var/criticalReaction = is_type_in_typecache(exp_on, critical_items_typecache) - //////////////////////////////////////////////////////////////////////////////////////////////// - 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/uranium/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/uranium/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/classic,/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) -*/ // yogs end - -/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","infra-igniter-tank0","infra-igniter-tank1","infrared-radio0","infrared-radio1","prox-igniter0","prox-igniter1","prox-igniter2","prox-multitool0","prox-multitool1","prox-multitool2","prox-radio0","prox-radio1","prox-radio2","prox-igniter-tank0","prox-igniter-tank1","prox-igniter-tank2","radio-igniter","timer-igniter0","timer-igniter1","timer-igniter2","timer-igniter-tank0","timer-igniter-tank1","timer-igniter-tank2","timer-multitool1","timer-multitool2","timer-radio0","timer-radio1","timer-radio2") - 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_typecache //items that can cause critical reactions + var/banned_typecache // items that won't be produced + +/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() + // Don't need to keep this typecache around, only used in this proc once. + var/list/banned_typecache = typecacheof(list( + /obj/item/stock_parts/cell/infinite, + /obj/item/grenade/chem_grenade/tuberculosis + )) + + 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(is_type_in_typecache(I, banned_typecache)) + continue + + 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 + 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) + +/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() + + critical_items_typecache = typecacheof(list( + /obj/item/construction/rcd, + /obj/item/grenade, + /obj/item/aicard, + /obj/item/storage/backpack/holding, + /obj/item/slime_extract, + /obj/item/onetankbomb, + /obj/item/transfer_valve)) + +/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/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Malfunction probability reduced by [badThingCoeff]%.
                Cooldown interval between experiments at [resetTime*0.1] seconds." + +/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() + + +/* yogs start - moved to yogstation/ folder +/obj/machinery/rnd/experimentor/proc/experiment(exp,obj/item/exp_on) + recentlyExperimented = 1 + icon_state = "h_lathe_wloop" + var/chosenchem + var/criticalReaction = is_type_in_typecache(exp_on, critical_items_typecache) + //////////////////////////////////////////////////////////////////////////////////////////////// + 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/uranium/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/uranium/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/classic,/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) +*/ // yogs end + +/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","infra-igniter-tank0","infra-igniter-tank1","infrared-radio0","infrared-radio1","prox-igniter0","prox-igniter1","prox-igniter2","prox-multitool0","prox-multitool1","prox-multitool2","prox-radio0","prox-radio1","prox-radio2","prox-igniter-tank0","prox-igniter-tank1","prox-igniter-tank2","radio-igniter","timer-igniter0","timer-igniter1","timer-igniter2","timer-igniter-tank0","timer-igniter-tank1","timer-igniter-tank2","timer-multitool1","timer-multitool2","timer-radio0","timer-radio1","timer-radio2") + 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 9ad5a92921ee..5a8675c4231b 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -1,366 +1,366 @@ -/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 = 1.2 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - total_rating = CLAMP(total_rating - (M.rating * 0.1), 0, 1) - if(total_rating == 0) - efficiency_coeff = INFINITY - else - efficiency_coeff = 1/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) - if(notify_admins) - investigate_log("[key_name(usr)] built [amount] of [path] at [src]([type]).", INVESTIGATE_RESEARCH) - message_admins("[ADMIN_LOOKUPFLW(usr)] has built [amount] of [path] at \a [src]([type]).") - for(var/i in 1 to amount) - var/obj/item/I = new path(get_turf(src)) - if(efficient_with(I.type)) - I.materials = matlist.Copy() - SSblackbox.record_feedback("nested tally", "item_printed", amount, list("[type]", "[path]")) - -/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), (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 += "* [num2text(M.amount, 8)] of [M.name]: " // yogs - num2text - 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 = 1.2 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + total_rating = CLAMP(total_rating - (M.rating * 0.1), 0, 1) + if(total_rating == 0) + efficiency_coeff = INFINITY + else + efficiency_coeff = 1/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) + if(notify_admins) + investigate_log("[key_name(usr)] built [amount] of [path] at [src]([type]).", INVESTIGATE_RESEARCH) + message_admins("[ADMIN_LOOKUPFLW(usr)] has built [amount] of [path] at \a [src]([type]).") + for(var/i in 1 to amount) + var/obj/item/I = new path(get_turf(src)) + if(efficient_with(I.type)) + I.materials = matlist.Copy() + SSblackbox.record_feedback("nested tally", "item_printed", amount, list("[type]", "[path]")) + +/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), (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 += "* [num2text(M.amount, 8)] of [M.name]: " // yogs - num2text + 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 09cf9cda8718..948dad61db94 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 4c455ad9d058..d5847a4420a5 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 - department_tag = "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" diff --git a/code/modules/research/machinery/departmental_protolathe.dm b/code/modules/research/machinery/departmental_protolathe.dm index c5956ce2a39a..25b811f5a826 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" - circuit = /obj/item/circuitboard/machine/protolathe/department/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 diff --git a/code/modules/research/machinery/departmental_techfab.dm b/code/modules/research/machinery/departmental_techfab.dm index 9e598376e5c0..8adae9b1cc1a 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" - circuit = /obj/item/circuitboard/machine/techfab/department/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 diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm index ee9a1960fa5d..f90045ffd42e 100644 --- a/code/modules/research/machinery/protolathe.dm +++ b/code/modules/research/machinery/protolathe.dm @@ -1,26 +1,26 @@ -/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", - "Tool Designs", - "Mining Designs", - "Electronics", - "Weapons", - "Ammo", - "Firing Pins", - "Computer Parts", - "Spacepod Designs" // yogs - ) - 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", + "Tool Designs", + "Mining Designs", + "Electronics", + "Weapons", + "Ammo", + "Firing Pins", + "Computer Parts", + "Spacepod Designs" // yogs + ) + production_animation = "protolathe_n" + allowed_buildtypes = PROTOLATHE + +/obj/machinery/rnd/production/protolathe/disconnect_console() + linked_console.linked_lathe = null + ..() diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm index 447ab66fed65..af1b79d4224a 100644 --- a/code/modules/research/machinery/techfab.dm +++ b/code/modules/research/machinery/techfab.dm @@ -1,36 +1,36 @@ -/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", - "Tool Designs", - "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", - "Spacepod Designs" // yogs - ) - console_link = FALSE - production_animation = "protolathe_n" - requires_console = FALSE - consoleless_interface = TRUE - allowed_buildtypes = PROTOLATHE | IMPRINTER +/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", + "Tool Designs", + "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", + "Spacepod Designs" // yogs + ) + console_link = FALSE + production_animation = "protolathe_n" + requires_console = FALSE + consoleless_interface = TRUE + allowed_buildtypes = PROTOLATHE | IMPRINTER diff --git a/code/modules/research/nanites/nanite_chamber_computer.dm b/code/modules/research/nanites/nanite_chamber_computer.dm index 419f886e2a62..c971fd76fcfa 100644 --- a/code/modules/research/nanites/nanite_chamber_computer.dm +++ b/code/modules/research/nanites/nanite_chamber_computer.dm @@ -1,159 +1,159 @@ -/obj/machinery/computer/nanite_chamber_control - name = "nanite chamber control console" - desc = "Controls a connected nanite chamber. Can inoculate nanites, load programs, and analyze existing nanite swarms." - var/obj/machinery/nanite_chamber/chamber - var/obj/item/disk/nanite_program/disk - circuit = /obj/item/circuitboard/computer/nanite_chamber_control - icon_screen = "nanite_chamber_control" - -/obj/machinery/computer/nanite_chamber_control/Initialize() - . = ..() - find_chamber() - -/obj/machinery/computer/nanite_chamber_control/attackby(obj/item/I, mob/user) - if(istype(I, /obj/item/disk/nanite_program)) - var/obj/item/disk/nanite_program/N = I - if(disk) - eject(user) - if(user.transferItemToLoc(N, src)) - to_chat(user, "You insert [N] into [src]") - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - disk = N - else - ..() - -/obj/machinery/computer/nanite_chamber_control/proc/eject(mob/living/user) - if(!disk) - return - if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk)) - disk.forceMove(drop_location()) - disk = null - -/obj/machinery/computer/nanite_chamber_control/proc/find_chamber() - for(var/direction in GLOB.cardinals) - var/C = locate(/obj/machinery/nanite_chamber, get_step(src, direction)) - if(C) - var/obj/machinery/nanite_chamber/NC = C - chamber = NC - NC.console = src - -/obj/machinery/computer/nanite_chamber_control/interact() - if(!chamber) - find_chamber() - ..() - -/obj/machinery/computer/nanite_chamber_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) - SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "nanite_chamber_control", name, 550, 800, master_ui, state) - ui.open() - -/obj/machinery/computer/nanite_chamber_control/ui_data() - var/list/data = list() - if(disk) - data["has_disk"] = TRUE - var/list/disk_data = list() - var/datum/nanite_program/P = disk.program - if(P) - data["has_program"] = TRUE - disk_data["name"] = P.name - disk_data["desc"] = P.desc - - disk_data["activated"] = P.activated - disk_data["activation_delay"] = P.activation_delay - disk_data["timer"] = P.timer - disk_data["activation_code"] = P.activation_code - disk_data["deactivation_code"] = P.deactivation_code - disk_data["kill_code"] = P.kill_code - disk_data["trigger_code"] = P.trigger_code - disk_data["timer_type"] = P.get_timer_type_text() - - var/list/extra_settings = list() - for(var/X in P.extra_settings) - var/list/setting = list() - setting["name"] = X - setting["value"] = P.get_extra_setting(X) - extra_settings += list(setting) - disk_data["extra_settings"] = extra_settings - if(LAZYLEN(extra_settings)) - disk_data["has_extra_settings"] = TRUE - data["disk"] = disk_data - - if(!chamber) - data["status_msg"] = "No chamber detected." - return data - - if(!chamber.occupant) - data["status_msg"] = "No occupant detected." - return data - - var/mob/living/L = chamber.occupant - - if(!(MOB_ORGANIC in L.mob_biotypes) && !(MOB_UNDEAD in L.mob_biotypes)) - data["status_msg"] = "Occupant not compatible with nanites." - return data - - if(chamber.busy) - data["status_msg"] = chamber.busy_message - return data - - data["scan_level"] = chamber.scan_level - data["locked"] = chamber.locked - data["occupant_name"] = chamber.occupant.name - - SEND_SIGNAL(L, COMSIG_NANITE_UI_DATA, data, chamber.scan_level) - - return data - -/obj/machinery/computer/nanite_chamber_control/ui_act(action, params) - if(..()) - return - switch(action) - if("toggle_lock") - chamber.locked = !chamber.locked - chamber.update_icon() - . = TRUE - if("eject") - eject(usr) - . = TRUE - if("set_safety") - var/threshold = input("Set safety threshold (0-500):", name, null) as null|num - if(!isnull(threshold)) - chamber.set_safety(CLAMP(round(threshold, 1),0,500)) - playsound(src, "terminal_type", 25, 0) - chamber.occupant.investigate_log("'s nanites' safety threshold was set to [threshold] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) - . = TRUE - if("set_cloud") - var/cloud_id = input("Set cloud ID (1-100, 0 to disable):", name, null) as null|num - if(!isnull(cloud_id)) - chamber.set_cloud(CLAMP(round(cloud_id, 1),0,100)) - playsound(src, "terminal_type", 25, 0) - chamber.occupant.investigate_log("'s nanites' cloud id was set to [cloud_id] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) - . = TRUE - if("connect_chamber") - find_chamber() - . = TRUE - if("nanite_injection") - playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) - chamber.inject_nanites() - log_combat(usr, chamber.occupant, "injected", null, "with nanites via [src]") - chamber.occupant.investigate_log("was injected with nanites by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) - . = TRUE - if("add_program") - if(!disk?.program || !chamber || !chamber.occupant) - return - playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) - chamber.install_program(disk.program) - chamber.occupant.investigate_log("had program of type [disk.program.type] installed by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) - . = TRUE - if("remove_program") - if(!chamber || !chamber.occupant) - return - playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) - var/list/nanite_programs = list() - SEND_SIGNAL(chamber.occupant, COMSIG_NANITE_GET_PROGRAMS, nanite_programs) - if(LAZYLEN(nanite_programs)) - var/datum/nanite_program/P = nanite_programs[text2num(params["program_id"])] - chamber.uninstall_program(P) - chamber.occupant.investigate_log("had program of type [P.type] uninstalled by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) - . = TRUE +/obj/machinery/computer/nanite_chamber_control + name = "nanite chamber control console" + desc = "Controls a connected nanite chamber. Can inoculate nanites, load programs, and analyze existing nanite swarms." + var/obj/machinery/nanite_chamber/chamber + var/obj/item/disk/nanite_program/disk + circuit = /obj/item/circuitboard/computer/nanite_chamber_control + icon_screen = "nanite_chamber_control" + +/obj/machinery/computer/nanite_chamber_control/Initialize() + . = ..() + find_chamber() + +/obj/machinery/computer/nanite_chamber_control/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/disk/nanite_program)) + var/obj/item/disk/nanite_program/N = I + if(disk) + eject(user) + if(user.transferItemToLoc(N, src)) + to_chat(user, "You insert [N] into [src]") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + disk = N + else + ..() + +/obj/machinery/computer/nanite_chamber_control/proc/eject(mob/living/user) + if(!disk) + return + if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk)) + disk.forceMove(drop_location()) + disk = null + +/obj/machinery/computer/nanite_chamber_control/proc/find_chamber() + for(var/direction in GLOB.cardinals) + var/C = locate(/obj/machinery/nanite_chamber, get_step(src, direction)) + if(C) + var/obj/machinery/nanite_chamber/NC = C + chamber = NC + NC.console = src + +/obj/machinery/computer/nanite_chamber_control/interact() + if(!chamber) + find_chamber() + ..() + +/obj/machinery/computer/nanite_chamber_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) + SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "nanite_chamber_control", name, 550, 800, master_ui, state) + ui.open() + +/obj/machinery/computer/nanite_chamber_control/ui_data() + var/list/data = list() + if(disk) + data["has_disk"] = TRUE + var/list/disk_data = list() + var/datum/nanite_program/P = disk.program + if(P) + data["has_program"] = TRUE + disk_data["name"] = P.name + disk_data["desc"] = P.desc + + disk_data["activated"] = P.activated + disk_data["activation_delay"] = P.activation_delay + disk_data["timer"] = P.timer + disk_data["activation_code"] = P.activation_code + disk_data["deactivation_code"] = P.deactivation_code + disk_data["kill_code"] = P.kill_code + disk_data["trigger_code"] = P.trigger_code + disk_data["timer_type"] = P.get_timer_type_text() + + var/list/extra_settings = list() + for(var/X in P.extra_settings) + var/list/setting = list() + setting["name"] = X + setting["value"] = P.get_extra_setting(X) + extra_settings += list(setting) + disk_data["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + disk_data["has_extra_settings"] = TRUE + data["disk"] = disk_data + + if(!chamber) + data["status_msg"] = "No chamber detected." + return data + + if(!chamber.occupant) + data["status_msg"] = "No occupant detected." + return data + + var/mob/living/L = chamber.occupant + + if(!(MOB_ORGANIC in L.mob_biotypes) && !(MOB_UNDEAD in L.mob_biotypes)) + data["status_msg"] = "Occupant not compatible with nanites." + return data + + if(chamber.busy) + data["status_msg"] = chamber.busy_message + return data + + data["scan_level"] = chamber.scan_level + data["locked"] = chamber.locked + data["occupant_name"] = chamber.occupant.name + + SEND_SIGNAL(L, COMSIG_NANITE_UI_DATA, data, chamber.scan_level) + + return data + +/obj/machinery/computer/nanite_chamber_control/ui_act(action, params) + if(..()) + return + switch(action) + if("toggle_lock") + chamber.locked = !chamber.locked + chamber.update_icon() + . = TRUE + if("eject") + eject(usr) + . = TRUE + if("set_safety") + var/threshold = input("Set safety threshold (0-500):", name, null) as null|num + if(!isnull(threshold)) + chamber.set_safety(CLAMP(round(threshold, 1),0,500)) + playsound(src, "terminal_type", 25, 0) + chamber.occupant.investigate_log("'s nanites' safety threshold was set to [threshold] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE + if("set_cloud") + var/cloud_id = input("Set cloud ID (1-100, 0 to disable):", name, null) as null|num + if(!isnull(cloud_id)) + chamber.set_cloud(CLAMP(round(cloud_id, 1),0,100)) + playsound(src, "terminal_type", 25, 0) + chamber.occupant.investigate_log("'s nanites' cloud id was set to [cloud_id] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE + if("connect_chamber") + find_chamber() + . = TRUE + if("nanite_injection") + playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) + chamber.inject_nanites() + log_combat(usr, chamber.occupant, "injected", null, "with nanites via [src]") + chamber.occupant.investigate_log("was injected with nanites by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE + if("add_program") + if(!disk?.program || !chamber || !chamber.occupant) + return + playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) + chamber.install_program(disk.program) + chamber.occupant.investigate_log("had program of type [disk.program.type] installed by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE + if("remove_program") + if(!chamber || !chamber.occupant) + return + playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) + var/list/nanite_programs = list() + SEND_SIGNAL(chamber.occupant, COMSIG_NANITE_GET_PROGRAMS, nanite_programs) + if(LAZYLEN(nanite_programs)) + var/datum/nanite_program/P = nanite_programs[text2num(params["program_id"])] + chamber.uninstall_program(P) + chamber.occupant.investigate_log("had program of type [P.type] uninstalled by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + . = TRUE diff --git a/code/modules/research/nanites/public_chamber.dm b/code/modules/research/nanites/public_chamber.dm index 4dd9e112c978..bbfb35180992 100644 --- a/code/modules/research/nanites/public_chamber.dm +++ b/code/modules/research/nanites/public_chamber.dm @@ -1,183 +1,183 @@ -/obj/machinery/public_nanite_chamber - name = "public nanite chamber" - desc = "A device that can rapidly implant cloud-synced nanites without an external operator." - circuit = /obj/item/circuitboard/machine/public_nanite_chamber - icon = 'icons/obj/machines/nanite_chamber.dmi' - icon_state = "nanite_chamber" - layer = ABOVE_WINDOW_LAYER - use_power = IDLE_POWER_USE - anchored = TRUE - density = TRUE - idle_power_usage = 50 - active_power_usage = 300 - - var/cloud_id = 1 - var/locked = FALSE - var/breakout_time = 1200 - var/busy = FALSE - var/busy_icon_state - var/message_cooldown = 0 - -/obj/machinery/public_nanite_chamber/Initialize() - . = ..() - occupant_typecache = GLOB.typecache_living - -/obj/machinery/public_nanite_chamber/RefreshParts() - var/obj/item/circuitboard/machine/public_nanite_chamber/board = circuit - if(board) - cloud_id = board.cloud_id - -/obj/machinery/public_nanite_chamber/proc/set_busy(status, working_icon) - busy = status - busy_icon_state = working_icon - update_icon() - -/obj/machinery/public_nanite_chamber/proc/inject_nanites(mob/living/attacker) - if(stat & (NOPOWER|BROKEN)) - return - if((stat & MAINT) || panel_open) - return - if(!occupant || busy) - return - - var/locked_state = locked - locked = TRUE - - //TODO OMINOUS MACHINE SOUNDS - set_busy(TRUE, "[initial(icon_state)]_raising") - addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"),20) - addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"),60) - addtimer(CALLBACK(src, .proc/complete_injection, locked_state, attacker),80) - -/obj/machinery/public_nanite_chamber/proc/complete_injection(locked_state, mob/living/attacker) - //TODO MACHINE DING - locked = locked_state - set_busy(FALSE) - if(!occupant) - return - if(attacker) - occupant.investigate_log("was injected with nanites by [key_name(attacker)] using [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) - log_combat(attacker, occupant, "injected", null, "with nanites via [src]") - occupant.AddComponent(/datum/component/nanites, 75, cloud_id) - -/obj/machinery/public_nanite_chamber/update_icon() - cut_overlays() - - if((stat & MAINT) || panel_open) - add_overlay("maint") - - else if(!(stat & (NOPOWER|BROKEN))) - if(busy || locked) - add_overlay("red") - if(locked) - add_overlay("bolted") - else - add_overlay("green") - - - - //running and someone in there - if(occupant) - if(busy) - icon_state = busy_icon_state - else - icon_state = initial(icon_state)+ "_occupied" - return - - //running - icon_state = initial(icon_state)+ (state_open ? "_open" : "") - -/obj/machinery/public_nanite_chamber/power_change() - . = ..() - update_icon() - -/obj/machinery/public_nanite_chamber/proc/toggle_open(mob/user) - if(panel_open) - to_chat(user, "Close the maintenance panel first.") - return - - if(state_open) - close_machine(null, user) - return - - else if(locked) - to_chat(user, "The bolts are locked down, securing the door shut.") - return - - open_machine() - -/obj/machinery/public_nanite_chamber/container_resist(mob/living/user) - if(!locked) - open_machine() - return - if(busy) - return - 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 || !locked || busy) - return - locked = FALSE - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open_machine() - -/obj/machinery/public_nanite_chamber/close_machine(mob/living/carbon/user, mob/living/attacker) - if(!state_open) - return FALSE - - ..() - - . = TRUE - - addtimer(CALLBACK(src, .proc/try_inject_nanites, attacker), 30) //If someone is shoved in give them a chance to get out before the injection starts - -/obj/machinery/public_nanite_chamber/proc/try_inject_nanites(mob/living/attacker) - if(occupant) - var/mob/living/L = occupant - if(SEND_SIGNAL(L, COMSIG_HAS_NANITES)) - return - if((MOB_ORGANIC in L.mob_biotypes) || (MOB_UNDEAD in L.mob_biotypes)) - inject_nanites(attacker) - -/obj/machinery/public_nanite_chamber/open_machine() - if(state_open) - return FALSE - - ..() - - return TRUE - -/obj/machinery/public_nanite_chamber/relaymove(mob/user as mob) - if(user.stat || locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - open_machine() - -/obj/machinery/public_nanite_chamber/attackby(obj/item/I, mob/user, params) - if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))//sent icon_state is irrelevant... - update_icon()//..since we're updating the icon here, since the scanner can be unpowered when opened/closed - return - - if(default_pry_open(I)) - return - - if(default_deconstruction_crowbar(I)) - return - - return ..() - -/obj/machinery/public_nanite_chamber/interact(mob/user) - toggle_open(user) - -/obj/machinery/public_nanite_chamber/MouseDrop_T(mob/target, mob/user) - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) || !Adjacent(target) || !user.Adjacent(target) || !iscarbon(target)) - return - if(close_machine(target, user)) - log_combat(user, target, "inserted", null, "into [src].") - add_fingerprint(user) +/obj/machinery/public_nanite_chamber + name = "public nanite chamber" + desc = "A device that can rapidly implant cloud-synced nanites without an external operator." + circuit = /obj/item/circuitboard/machine/public_nanite_chamber + icon = 'icons/obj/machines/nanite_chamber.dmi' + icon_state = "nanite_chamber" + layer = ABOVE_WINDOW_LAYER + use_power = IDLE_POWER_USE + anchored = TRUE + density = TRUE + idle_power_usage = 50 + active_power_usage = 300 + + var/cloud_id = 1 + var/locked = FALSE + var/breakout_time = 1200 + var/busy = FALSE + var/busy_icon_state + var/message_cooldown = 0 + +/obj/machinery/public_nanite_chamber/Initialize() + . = ..() + occupant_typecache = GLOB.typecache_living + +/obj/machinery/public_nanite_chamber/RefreshParts() + var/obj/item/circuitboard/machine/public_nanite_chamber/board = circuit + if(board) + cloud_id = board.cloud_id + +/obj/machinery/public_nanite_chamber/proc/set_busy(status, working_icon) + busy = status + busy_icon_state = working_icon + update_icon() + +/obj/machinery/public_nanite_chamber/proc/inject_nanites(mob/living/attacker) + if(stat & (NOPOWER|BROKEN)) + return + if((stat & MAINT) || panel_open) + return + if(!occupant || busy) + return + + var/locked_state = locked + locked = TRUE + + //TODO OMINOUS MACHINE SOUNDS + set_busy(TRUE, "[initial(icon_state)]_raising") + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"),20) + addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"),60) + addtimer(CALLBACK(src, .proc/complete_injection, locked_state, attacker),80) + +/obj/machinery/public_nanite_chamber/proc/complete_injection(locked_state, mob/living/attacker) + //TODO MACHINE DING + locked = locked_state + set_busy(FALSE) + if(!occupant) + return + if(attacker) + occupant.investigate_log("was injected with nanites by [key_name(attacker)] using [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) + log_combat(attacker, occupant, "injected", null, "with nanites via [src]") + occupant.AddComponent(/datum/component/nanites, 75, cloud_id) + +/obj/machinery/public_nanite_chamber/update_icon() + cut_overlays() + + if((stat & MAINT) || panel_open) + add_overlay("maint") + + else if(!(stat & (NOPOWER|BROKEN))) + if(busy || locked) + add_overlay("red") + if(locked) + add_overlay("bolted") + else + add_overlay("green") + + + + //running and someone in there + if(occupant) + if(busy) + icon_state = busy_icon_state + else + icon_state = initial(icon_state)+ "_occupied" + return + + //running + icon_state = initial(icon_state)+ (state_open ? "_open" : "") + +/obj/machinery/public_nanite_chamber/power_change() + . = ..() + update_icon() + +/obj/machinery/public_nanite_chamber/proc/toggle_open(mob/user) + if(panel_open) + to_chat(user, "Close the maintenance panel first.") + return + + if(state_open) + close_machine(null, user) + return + + else if(locked) + to_chat(user, "The bolts are locked down, securing the door shut.") + return + + open_machine() + +/obj/machinery/public_nanite_chamber/container_resist(mob/living/user) + if(!locked) + open_machine() + return + if(busy) + return + 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 || !locked || busy) + return + locked = FALSE + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open_machine() + +/obj/machinery/public_nanite_chamber/close_machine(mob/living/carbon/user, mob/living/attacker) + if(!state_open) + return FALSE + + ..() + + . = TRUE + + addtimer(CALLBACK(src, .proc/try_inject_nanites, attacker), 30) //If someone is shoved in give them a chance to get out before the injection starts + +/obj/machinery/public_nanite_chamber/proc/try_inject_nanites(mob/living/attacker) + if(occupant) + var/mob/living/L = occupant + if(SEND_SIGNAL(L, COMSIG_HAS_NANITES)) + return + if((MOB_ORGANIC in L.mob_biotypes) || (MOB_UNDEAD in L.mob_biotypes)) + inject_nanites(attacker) + +/obj/machinery/public_nanite_chamber/open_machine() + if(state_open) + return FALSE + + ..() + + return TRUE + +/obj/machinery/public_nanite_chamber/relaymove(mob/user as mob) + if(user.stat || locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + open_machine() + +/obj/machinery/public_nanite_chamber/attackby(obj/item/I, mob/user, params) + if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))//sent icon_state is irrelevant... + update_icon()//..since we're updating the icon here, since the scanner can be unpowered when opened/closed + return + + if(default_pry_open(I)) + return + + if(default_deconstruction_crowbar(I)) + return + + return ..() + +/obj/machinery/public_nanite_chamber/interact(mob/user) + toggle_open(user) + +/obj/machinery/public_nanite_chamber/MouseDrop_T(mob/target, mob/user) + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) || !Adjacent(target) || !user.Adjacent(target) || !iscarbon(target)) + return + if(close_machine(target, user)) + log_combat(user, target, "inserted", null, "into [src].") + add_fingerprint(user) diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index 32c545af6420..7b313471bf7c 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -1,1139 +1,1139 @@ - -/* -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]" - if(iscarbon(user)) - var/obj/item/card/id/idcard = user.get_active_held_item() - if(istype(idcard)) - logname = "User: [idcard.registered_name]" - if(ishuman(user)) - var/mob/living/carbon/human/H = user - var/obj/item/I = H.wear_id - if(istype(I)) - var/obj/item/card/id/ID = I.GetID() - if(istype(ID)) - logname = "User: [ID.registered_name]" - var/i = stored_research.research_logs.len - stored_research.research_logs += null - stored_research.research_logs[++i] = list(TN.display_name, price["General Research"], logname, "[get_area(src)] ([src.x],[src.y],[src.z])") - 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)) - to_chat(user, "You disable the security protocols[locked? " and unlock the console":""].") - playsound(src, "sparks", 75, 1) - obj_flags |= EMAGGED - locked = FALSE - return ..() - -/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 += "* [num2text(M.amount, 8)] of [M.name]: " // yogs - num2text - 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(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 la 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]" + if(iscarbon(user)) + var/obj/item/card/id/idcard = user.get_active_held_item() + if(istype(idcard)) + logname = "User: [idcard.registered_name]" + if(ishuman(user)) + var/mob/living/carbon/human/H = user + var/obj/item/I = H.wear_id + if(istype(I)) + var/obj/item/card/id/ID = I.GetID() + if(istype(ID)) + logname = "User: [ID.registered_name]" + var/i = stored_research.research_logs.len + stored_research.research_logs += null + stored_research.research_logs[++i] = list(TN.display_name, price["General Research"], logname, "[get_area(src)] ([src.x],[src.y],[src.z])") + 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)) + to_chat(user, "You disable the security protocols[locked? " and unlock the console":""].") + playsound(src, "sparks", 75, 1) + obj_flags |= EMAGGED + locked = FALSE + return ..() + +/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 += "* [num2text(M.amount, 8)] of [M.name]: " // yogs - num2text + 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(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 la 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 87f8cefe783b..666d4916e9e8 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -1,102 +1,102 @@ - -//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(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) - 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(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) + 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 268e6a1be998..dd9d124a2e1a 100644 --- a/code/modules/research/research_disk.dm +++ b/code/modules/research/research_disk.dm @@ -1,22 +1,22 @@ - -/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 + 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 diff --git a/code/modules/research/techweb/__techweb_helpers.dm b/code/modules/research/techweb/__techweb_helpers.dm index 6c967c09245b..2fd89378a554 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 7a8e74b7ead3..0c1a0ce2515b 100644 --- a/code/modules/research/techweb/_techweb.dm +++ b/code/modules/research/techweb/_techweb.dm @@ -1,374 +1,374 @@ - -//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/discovered_mutations = list() //Mutations discovered by genetics, this way they are shared and cant be destroyed by destroying a single console - var/list/tiers = list() //Assoc list, id = number, 1 is available, 2 is all reqs are 1, so on - -/datum/techweb/New() - SSresearch.techwebs += src - 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/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] = 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/get_point_total(list/pointlist) - for(var/i in pointlist) - . += pointlist[i] - -/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) - if(!istype(src, /datum/techweb/admin)) - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) - if(D) - D.adjust_money(SSeconomy.techweb_bounty) - 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 + +//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/discovered_mutations = list() //Mutations discovered by genetics, this way they are shared and cant be destroyed by destroying a single console + var/list/tiers = list() //Assoc list, id = number, 1 is available, 2 is all reqs are 1, so on + +/datum/techweb/New() + SSresearch.techwebs += src + 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/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] = 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/get_point_total(list/pointlist) + for(var/i in pointlist) + . += pointlist[i] + +/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) + if(!istype(src, /datum/techweb/admin)) + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) + if(D) + D.adjust_money(SSeconomy.techweb_bounty) + 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 diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm index bd6aea0a3301..3d6e2d867642 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 1739fcf14143..92f043f1ac23 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("[ADMIN_LOOKUPFLW(triggerer)] triggered and [ADMIN_LOOKUPFLW(confirmer)] confirmed event [event]") - - var/area/A1 = get_area(triggerer) - deadchat_broadcast(" triggered [event] at [A1.name].", "[triggerer]", triggerer) - - var/area/A2 = get_area(confirmer) - deadchat_broadcast(" confirmed [event] at [A2.name].", "[confirmer]", 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("[ADMIN_LOOKUPFLW(triggerer)] triggered and [ADMIN_LOOKUPFLW(confirmer)] confirmed event [event]") + + var/area/A1 = get_area(triggerer) + deadchat_broadcast(" triggered [event] at [A1.name].", "[triggerer]", triggerer) + + var/area/A2 = get_area(confirmer) + deadchat_broadcast(" confirmed [event] at [A2.name].", "[confirmer]", 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 282cfada7f1e..0a8e4073d82a 100644 --- a/code/modules/security_levels/security_levels.dm +++ b/code/modules/security_levels/security_levels.dm @@ -1,118 +1,118 @@ -GLOBAL_VAR_INIT(security_level, SEC_LEVEL_GREEN) -//SEC_LEVEL_GREEN = code green -//SEC_LEVEL_BLUE = code blue -//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("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 - SSshuttle.emergency.modTimer(2) - 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.5) - 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) - SSshuttle.emergency.modTimer(2) - GLOB.security_level = SEC_LEVEL_BLUE - 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 - SSshuttle.emergency.modTimer(0.5) - else - minor_announce(CONFIG_GET(string/alert_red_downto), "Attention! Code red!") - GLOB.security_level = SEC_LEVEL_RED - - 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_GREEN) - SSshuttle.emergency.modTimer(0.25) - else if(GLOB.security_level == SEC_LEVEL_BLUE) - SSshuttle.emergency.modTimer(0.5) - GLOB.security_level = SEC_LEVEL_DELTA - 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_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_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("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_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("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 + SSshuttle.emergency.modTimer(2) + 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.5) + 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) + SSshuttle.emergency.modTimer(2) + GLOB.security_level = SEC_LEVEL_BLUE + 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 + SSshuttle.emergency.modTimer(0.5) + else + minor_announce(CONFIG_GET(string/alert_red_downto), "Attention! Code red!") + GLOB.security_level = SEC_LEVEL_RED + + 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_GREEN) + SSshuttle.emergency.modTimer(0.25) + else if(GLOB.security_level == SEC_LEVEL_BLUE) + SSshuttle.emergency.modTimer(0.5) + GLOB.security_level = SEC_LEVEL_DELTA + 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_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_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("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 4b05cfad8cbe..8c163a65f33e 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 detected on board" // yogs - typo fix - 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 detected on board" // yogs - typo fix + 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 7ee5a9eab753..81b57dee9a20 100644 --- a/code/modules/shuttle/computer.dm +++ b/code/modules/shuttle/computer.dm @@ -1,77 +1,77 @@ -/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_RECHARGING) - to_chat(usr, "Shuttle engines are not ready for use.") - return - 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.") - -/obj/machinery/computer/shuttle/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_RECHARGING) + to_chat(usr, "Shuttle engines are not ready for use.") + return + 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.") + +/obj/machinery/computer/shuttle/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 06e38bb3ebb6..f7e3bda44d11 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 FALSE - 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 FALSE + 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/monastery.dm b/code/modules/shuttle/monastery.dm index 04c6dd336b0c..b04c202dca3f 100644 --- a/code/modules/shuttle/monastery.dm +++ b/code/modules/shuttle/monastery.dm @@ -1,7 +1,7 @@ -/obj/machinery/computer/shuttle/monastery_shuttle - name = "monastery shuttle console" - desc = "Used to control the monastery shuttle." - circuit = /obj/item/circuitboard/computer/monastery_shuttle - shuttleId = "pod1" - possible_destinations = "monastery_shuttle_asteroid;monastery_shuttle_station" - no_destination_swap = TRUE +/obj/machinery/computer/shuttle/monastery_shuttle + name = "monastery shuttle console" + desc = "Used to control the monastery shuttle." + circuit = /obj/item/circuitboard/computer/monastery_shuttle + shuttleId = "pod1" + possible_destinations = "monastery_shuttle_asteroid;monastery_shuttle_station" + no_destination_swap = TRUE diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/syndicate.dm index bffce9e7abe4..4ebefc7e3cac 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 537872ed0cba..f25fe30f3658 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 55ca838c02bb..9696e1a39ec1 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 = TRUE - - var/randomise_selection = FALSE //if it lets the usr choose the teleport loc or picks it from the list - var/invocation_area = TRUE //if the invocation appends the selected area - var/sound1 = 'sound/weapons/zapbang.ogg' - var/sound2 = 'sound/weapons/zapbang.ogg' - - var/say_destination = TRUE - -/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/A = null - - 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 = TRUE - for(var/obj/O in T) - if(O.density) - clear = FALSE - 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/list/tempL = L - var/attempt = null - var/success = FALSE - while(tempL.len) - attempt = pick(tempL) - do_teleport(target, attempt, channel = TELEPORT_CHANNEL_MAGIC) - if(get_turf(target) == attempt) - success = TRUE - break - else - tempL.Remove(attempt) - - if(!success) - do_teleport(target, L, forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC) - playsound(get_turf(user), sound2, 50,1) - -/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null,mob/living/user = usr) - if(!invocation_area || !chosenarea) - ..() - else - var/words - if(say_destination) - words = "[invocation] [uppertext(chosenarea.name)]" - else - words = "[invocation]" - - switch(invocation_type) - if("shout") - user.say(words, 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(words, forced = "spell") +/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 = TRUE + + var/randomise_selection = FALSE //if it lets the usr choose the teleport loc or picks it from the list + var/invocation_area = TRUE //if the invocation appends the selected area + var/sound1 = 'sound/weapons/zapbang.ogg' + var/sound2 = 'sound/weapons/zapbang.ogg' + + var/say_destination = TRUE + +/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/A = null + + 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 = TRUE + for(var/obj/O in T) + if(O.density) + clear = FALSE + 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/list/tempL = L + var/attempt = null + var/success = FALSE + while(tempL.len) + attempt = pick(tempL) + do_teleport(target, attempt, channel = TELEPORT_CHANNEL_MAGIC) + if(get_turf(target) == attempt) + success = TRUE + break + else + tempL.Remove(attempt) + + if(!success) + do_teleport(target, L, forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC) + playsound(get_turf(user), sound2, 50,1) + +/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null,mob/living/user = usr) + if(!invocation_area || !chosenarea) + ..() + else + var/words + if(say_destination) + words = "[invocation] [uppertext(chosenarea.name)]" + else + words = "[invocation]" + + switch(invocation_type) + if("shout") + user.say(words, 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(words, forced = "spell") diff --git a/code/modules/spells/spell_types/conjure.dm b/code/modules/spells/spell_types/conjure.dm index 0c5eb3ff4d8b..ea46105f6474 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 TRUE, adds dense tiles to possible spawn places - var/summon_ignore_prev_spawn_points = TRUE //if set to TRUE, 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) - return BULLET_ACT_FORCE_PIERCE +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt + name = "Ethereal Jaunt" + desc = "This spell turns your form ethereal, temporarily making you invisible and able to pass through walls." + + school = "transmutation" + charge_max = 300 + clothes_req = TRUE + invocation = "none" + invocation_type = "none" + range = -1 + cooldown_min = 100 //50 deciseconds reduction per rank + include_user = TRUE + nonabstract_req = TRUE + 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.mobility_flags &= ~MOBILITY_MOVE + 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.mobility_flags |= MOBILITY_MOVE + +/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 = FALSE + 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 BULLET_ACT_FORCE_PIERCE diff --git a/code/modules/spells/spell_types/explosion.dm b/code/modules/spells/spell_types/explosion.dm index bd9d5d005ac2..0ef21ab7869f 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 812dc3a243fd..c3bdc74eb72c 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 defines - 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 defines + 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 f5ce493b4a27..2ad9a5ed169b 100644 --- a/code/modules/spells/spell_types/inflict_handler.dm +++ b/code/modules/spells/spell_types/inflict_handler.dm @@ -1,60 +1,60 @@ -/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_paralyze = 0 - var/amt_unconscious = 0 - var/amt_stun = 0 - - var/inflict_status - var/list/status_params = list() - - //set to negatives for healing - 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 - target.Paralyze(amt_paralyze) - 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) - - if(inflict_status) - var/list/stat_args = status_params.Copy() - stat_args.Insert(1,inflict_status) - target.apply_status_effect(arglist(stat_args)) +/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_paralyze = 0 + var/amt_unconscious = 0 + var/amt_stun = 0 + + var/inflict_status + var/list/status_params = list() + + //set to negatives for healing + 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 + target.Paralyze(amt_paralyze) + 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) + + if(inflict_status) + var/list/stat_args = status_params.Copy() + stat_args.Insert(1,inflict_status) + target.apply_status_effect(arglist(stat_args)) diff --git a/code/modules/spells/spell_types/knock.dm b/code/modules/spells/spell_types/knock.dm index 3e75989308d8..63e010ba13da 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 closets." - - school = "transmutation" - charge_max = 100 - clothes_req = FALSE - 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 closets." + + school = "transmutation" + charge_max = 100 + clothes_req = FALSE + 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 1791b23ea139..dff58c4a54b8 100644 --- a/code/modules/spells/spell_types/mime.dm +++ b/code/modules/spells/spell_types/mime.dm @@ -1,238 +1,238 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall - name = "Invisible Wall" - desc = "The mime's performance transmutates a wall 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 = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - 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/aoe_turf/conjure/mime_chair - name = "Invisible Chair" - desc = "The mime's performance transmutates a chair into physical reality." - school = "mime" - panel = "Mime" - summon_type = list(/obj/structure/chair/mime) - invocation_type = "emote" - invocation_emote_self = "You conjure an invisible chair and sit down." - summon_lifespan = 250 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] pulls out an invisible chair and sits down." - else - invocation_type ="none" - ..() - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/cast(list/targets,mob/user = usr) - ..() - var/turf/T = user.loc - for (var/obj/structure/chair/A in T) - if (is_type_in_list(A, summon_type)) - A.setDir(user.dir) - A.buckle_mob(user) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box - name = "Invisible Box" - desc = "The mime's performance transmutates a box into physical reality." - school = "mime" - panel = "Mime" - summon_type = list(/obj/item/storage/box/mime) - invocation_type = "emote" - invocation_emote_self = "You conjure up an invisible box, large enough to store a few things." - summon_lifespan = 250 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box/cast(list/targets,mob/user = usr) - ..() - var/turf/T = user.loc - for (var/obj/item/storage/box/mime/B in T) - user.put_in_hands(B) - B.alpha = 255 - addtimer(CALLBACK(B, /obj/item/storage/box/mime/.proc/emptyStorage, FALSE), (summon_lifespan - 1)) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] moves [usr.p_their()] hands in the shape of a cube, pressing a box out of the air." - 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 = FALSE - human_req = TRUE - antimagic_allowed = TRUE - charge_max = 3000 - range = -1 - include_user = TRUE - - 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 = FALSE - antimagic_allowed = TRUE - range = -1 - include_user = TRUE - - 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 = FALSE - antimagic_allowed = TRUE - 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(!.) - return - 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(!.) - return - 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/effect/proc_holder/spell/aoe_turf/conjure/mime_wall + name = "Invisible Wall" + desc = "The mime's performance transmutates a wall 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 = FALSE + antimagic_allowed = TRUE + range = 0 + cast_sound = null + human_req = TRUE + + 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/aoe_turf/conjure/mime_chair + name = "Invisible Chair" + desc = "The mime's performance transmutates a chair into physical reality." + school = "mime" + panel = "Mime" + summon_type = list(/obj/structure/chair/mime) + invocation_type = "emote" + invocation_emote_self = "You conjure an invisible chair and sit down." + summon_lifespan = 250 + charge_max = 300 + clothes_req = FALSE + antimagic_allowed = TRUE + range = 0 + cast_sound = null + human_req = TRUE + + action_icon_state = "mime" + action_background_icon_state = "bg_mime" + +/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/Click() + if(usr && usr.mind) + if(!usr.mind.miming) + to_chat(usr, "You must dedicate yourself to silence first.") + return + invocation = "[usr.real_name] pulls out an invisible chair and sits down." + else + invocation_type ="none" + ..() + +/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/cast(list/targets,mob/user = usr) + ..() + var/turf/T = user.loc + for (var/obj/structure/chair/A in T) + if (is_type_in_list(A, summon_type)) + A.setDir(user.dir) + A.buckle_mob(user) + +/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box + name = "Invisible Box" + desc = "The mime's performance transmutates a box into physical reality." + school = "mime" + panel = "Mime" + summon_type = list(/obj/item/storage/box/mime) + invocation_type = "emote" + invocation_emote_self = "You conjure up an invisible box, large enough to store a few things." + summon_lifespan = 250 + charge_max = 300 + clothes_req = FALSE + antimagic_allowed = TRUE + range = 0 + cast_sound = null + human_req = TRUE + + action_icon_state = "mime" + action_background_icon_state = "bg_mime" + +/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box/cast(list/targets,mob/user = usr) + ..() + var/turf/T = user.loc + for (var/obj/item/storage/box/mime/B in T) + user.put_in_hands(B) + B.alpha = 255 + addtimer(CALLBACK(B, /obj/item/storage/box/mime/.proc/emptyStorage, FALSE), (summon_lifespan - 1)) + +/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box/Click() + if(usr && usr.mind) + if(!usr.mind.miming) + to_chat(usr, "You must dedicate yourself to silence first.") + return + invocation = "[usr.real_name] moves [usr.p_their()] hands in the shape of a cube, pressing a box out of the air." + 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 = FALSE + human_req = TRUE + antimagic_allowed = TRUE + charge_max = 3000 + range = -1 + include_user = TRUE + + 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 = FALSE + antimagic_allowed = TRUE + range = -1 + include_user = TRUE + + 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 = FALSE + antimagic_allowed = TRUE + 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(!.) + return + 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(!.) + return + 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) diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm index 7f71a410a6c0..ca6eb91297b2 100644 --- a/code/modules/spells/spell_types/mind_transfer.dm +++ b/code/modules/spells/spell_types/mind_transfer.dm @@ -1,98 +1,98 @@ -/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 = FALSE - 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 - - if(istype(target, /mob/living/simple_animal/hostile/guardian)) - var/mob/living/simple_animal/hostile/guardian/stand = target - if(stand.summoner) - if(stand.summoner == user) - if(!silent) - to_chat(user, "Swapping minds with your own guardian would just put you back into your own head!") - return - else - target = stand.summoner - - 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(0) - caster.mind.transfer_to(victim) - - ghost.mind.transfer_to(caster) - if(ghost.key) - caster.key = ghost.key //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 = FALSE + 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 + + if(istype(target, /mob/living/simple_animal/hostile/guardian)) + var/mob/living/simple_animal/hostile/guardian/stand = target + if(stand.summoner) + if(stand.summoner == user) + if(!silent) + to_chat(user, "Swapping minds with your own guardian would just put you back into your own head!") + return + else + target = stand.summoner + + 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(0) + caster.mind.transfer_to(victim) + + ghost.mind.transfer_to(caster) + if(ghost.key) + caster.key = ghost.key //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 31c416cb42fe..adb52889c1fe 100644 --- a/code/modules/spells/spell_types/projectile.dm +++ b/code/modules/spells/spell_types/projectile.dm @@ -1,130 +1,130 @@ - - -/obj/item/projectile/magic/spell - name = "custom spell projectile" - var/list/ignored_factions //Do not hit these - var/check_holy = FALSE - var/check_antimagic = FALSE - var/trigger_range = 0 //How far we do we need to be to hit - var/linger = FALSE //Can't hit anything but the intended target - - var/trail = FALSE //if it leaves a trail - var/trail_lifespan = 0 //deciseconds - var/trail_icon = 'icons/obj/wizard.dmi' - var/trail_icon_state = "trail" - -//todo unify this and magic/aoe under common path -/obj/item/projectile/magic/spell/Range() - if(trigger_range > 1) - for(var/mob/living/L in range(trigger_range, get_turf(src))) - if(can_hit_target(L, ignore_loc = TRUE)) - return Bump(L) - . = ..() - -/obj/item/projectile/magic/spell/Moved(atom/OldLoc, Dir) - . = ..() - if(trail) - create_trail() - -/obj/item/projectile/magic/spell/proc/create_trail() - if(!trajectory) - return - var/datum/point/vector/previous = trajectory.return_vector_after_increments(1,-1) - var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf()) - trail.pixel_x = previous.return_px() - trail.pixel_y = previous.return_py() - trail.icon = trail_icon - trail.icon_state = trail_icon_state - //might be changed to temp overlay - trail.density = FALSE - trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - QDEL_IN(trail, trail_lifespan) - -/obj/item/projectile/magic/spell/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) - . = ..() - if(linger && target != original) - return FALSE - if(ismob(target) && !direct_target) //Unsure about the direct target, i guess it could always skip these. - var/mob/M = target - if(M.anti_magic_check(check_antimagic, check_holy)) - return FALSE - if(ignored_factions && ignored_factions.len && faction_check(M.faction,ignored_factions)) - return FALSE - - -//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_type = /obj/item/projectile/magic/spell //IMPORTANT use only subtypes of this - - - var/update_projectile = FALSE //So you want to admin abuse magic bullets ? This is for you - //Below only apply if update_projectile is true - var/proj_icon = 'icons/obj/projectiles.dmi' - var/proj_icon_state = "spell" - var/proj_name = "a spell projectile" - var/proj_trail = FALSE //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_lingering = FALSE //if it lingers or disappears upon hitting an obstacle - var/proj_homing = TRUE //if it follows the target - var/proj_insubstantial = FALSE //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 - var/list/ignore_factions = list() //Faction types that will be ignored - var/check_antimagic = TRUE - var/check_holy = FALSE - -/obj/effect/proc_holder/spell/targeted/projectile/proc/fire_projectile(atom/target, mob/user) - var/obj/item/projectile/magic/spell/projectile = new proj_type() - - if(update_projectile) - //Generally these should already be set on the projectile, this is mostly here for varedited spells. - projectile.icon = proj_icon - projectile.icon_state = proj_icon_state - projectile.name = proj_name - if(proj_insubstantial) - projectile.movement_type |= UNSTOPPABLE - if(proj_homing) - projectile.homing = TRUE - projectile.homing_turn_speed = 360 //Perfect tracking - if(proj_lingering) - projectile.linger = TRUE - projectile.trigger_range = proj_trigger_range - projectile.ignored_factions = ignore_factions - projectile.range = proj_lifespan - projectile.speed = proj_step_delay - projectile.trail = proj_trail - projectile.trail_lifespan = proj_trail_lifespan - projectile.trail_icon = proj_trail_icon - projectile.trail_icon_state = proj_trail_icon_state - - projectile.preparePixelProjectile(target,user) - if(projectile.homing) - projectile.set_homing_target(target) - projectile.fire() - -/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr) - playMagSound() - for(var/atom/target in targets) - fire_projectile(target, user) - -//This one just pops one projectile in direction user is facing, irrelevant of max_targets etc -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire - name = "Dumbfire projectile" - -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/choose_targets(mob/user = usr) - var/turf/T = get_turf(user) - for(var/i = 1; i < range; i++) - var/turf/new_turf = get_step(T, user.dir) - if(new_turf.density) - break - T = new_turf + + +/obj/item/projectile/magic/spell + name = "custom spell projectile" + var/list/ignored_factions //Do not hit these + var/check_holy = FALSE + var/check_antimagic = FALSE + var/trigger_range = 0 //How far we do we need to be to hit + var/linger = FALSE //Can't hit anything but the intended target + + var/trail = FALSE //if it leaves a trail + var/trail_lifespan = 0 //deciseconds + var/trail_icon = 'icons/obj/wizard.dmi' + var/trail_icon_state = "trail" + +//todo unify this and magic/aoe under common path +/obj/item/projectile/magic/spell/Range() + if(trigger_range > 1) + for(var/mob/living/L in range(trigger_range, get_turf(src))) + if(can_hit_target(L, ignore_loc = TRUE)) + return Bump(L) + . = ..() + +/obj/item/projectile/magic/spell/Moved(atom/OldLoc, Dir) + . = ..() + if(trail) + create_trail() + +/obj/item/projectile/magic/spell/proc/create_trail() + if(!trajectory) + return + var/datum/point/vector/previous = trajectory.return_vector_after_increments(1,-1) + var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf()) + trail.pixel_x = previous.return_px() + trail.pixel_y = previous.return_py() + trail.icon = trail_icon + trail.icon_state = trail_icon_state + //might be changed to temp overlay + trail.density = FALSE + trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + QDEL_IN(trail, trail_lifespan) + +/obj/item/projectile/magic/spell/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) + . = ..() + if(linger && target != original) + return FALSE + if(ismob(target) && !direct_target) //Unsure about the direct target, i guess it could always skip these. + var/mob/M = target + if(M.anti_magic_check(check_antimagic, check_holy)) + return FALSE + if(ignored_factions && ignored_factions.len && faction_check(M.faction,ignored_factions)) + return FALSE + + +//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_type = /obj/item/projectile/magic/spell //IMPORTANT use only subtypes of this + + + var/update_projectile = FALSE //So you want to admin abuse magic bullets ? This is for you + //Below only apply if update_projectile is true + var/proj_icon = 'icons/obj/projectiles.dmi' + var/proj_icon_state = "spell" + var/proj_name = "a spell projectile" + var/proj_trail = FALSE //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_lingering = FALSE //if it lingers or disappears upon hitting an obstacle + var/proj_homing = TRUE //if it follows the target + var/proj_insubstantial = FALSE //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 + var/list/ignore_factions = list() //Faction types that will be ignored + var/check_antimagic = TRUE + var/check_holy = FALSE + +/obj/effect/proc_holder/spell/targeted/projectile/proc/fire_projectile(atom/target, mob/user) + var/obj/item/projectile/magic/spell/projectile = new proj_type() + + if(update_projectile) + //Generally these should already be set on the projectile, this is mostly here for varedited spells. + projectile.icon = proj_icon + projectile.icon_state = proj_icon_state + projectile.name = proj_name + if(proj_insubstantial) + projectile.movement_type |= UNSTOPPABLE + if(proj_homing) + projectile.homing = TRUE + projectile.homing_turn_speed = 360 //Perfect tracking + if(proj_lingering) + projectile.linger = TRUE + projectile.trigger_range = proj_trigger_range + projectile.ignored_factions = ignore_factions + projectile.range = proj_lifespan + projectile.speed = proj_step_delay + projectile.trail = proj_trail + projectile.trail_lifespan = proj_trail_lifespan + projectile.trail_icon = proj_trail_icon + projectile.trail_icon_state = proj_trail_icon_state + + projectile.preparePixelProjectile(target,user) + if(projectile.homing) + projectile.set_homing_target(target) + projectile.fire() + +/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr) + playMagSound() + for(var/atom/target in targets) + fire_projectile(target, user) + +//This one just pops one projectile in direction user is facing, irrelevant of max_targets etc +/obj/effect/proc_holder/spell/targeted/projectile/dumbfire + name = "Dumbfire projectile" + +/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/choose_targets(mob/user = usr) + var/turf/T = get_turf(user) + for(var/i = 1; i < range; i++) + var/turf/new_turf = get_step(T, user.dir) + if(new_turf.density) + break + T = new_turf perform(list(T),user = user) \ No newline at end of file diff --git a/code/modules/spells/spell_types/rightandwrong.dm b/code/modules/spells/spell_types/rightandwrong.dm index 25848a5d5909..bb7fa7a1678b 100644 --- a/code/modules/spells/spell_types/rightandwrong.dm +++ b/code/modules/spells/spell_types/rightandwrong.dm @@ -1,193 +1,193 @@ -//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/disabler, - /obj/item/gun/energy/e_gun, - /obj/item/gun/energy/e_gun/advtaser, - /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/shotgun/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/rifle/boltaction, - /obj/item/pneumatic_cannon/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/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)) - -//if you add anything that isn't covered by the typepaths below, add it to summon_magic_objective_types -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)) - -//everything above except for single use spellbooks, because they are counted separately (and are for basic bitches anyways) -GLOBAL_LIST_INIT(summoned_magic_objectives, list( - /obj/item/antag_spawner/contract, - /obj/item/blood_contract, - /obj/item/clothing/suit/space/hardsuit/shielded/wizard, - /obj/item/gun/magic, - /obj/item/immortality_talisman, - /obj/item/melee/ghost_sword, - /obj/item/necromantic_stone, - /obj/item/scrying, - /obj/item/spellbook, - /obj/item/storage/belt/wands/full, - /obj/item/voodoo, - /obj/item/warpwhistle)) - -// 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("[ADMIN_LOOKUPFLW(user)] 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/disabler, + /obj/item/gun/energy/e_gun, + /obj/item/gun/energy/e_gun/advtaser, + /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/shotgun/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/rifle/boltaction, + /obj/item/pneumatic_cannon/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/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)) + +//if you add anything that isn't covered by the typepaths below, add it to summon_magic_objective_types +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)) + +//everything above except for single use spellbooks, because they are counted separately (and are for basic bitches anyways) +GLOBAL_LIST_INIT(summoned_magic_objectives, list( + /obj/item/antag_spawner/contract, + /obj/item/blood_contract, + /obj/item/clothing/suit/space/hardsuit/shielded/wizard, + /obj/item/gun/magic, + /obj/item/immortality_talisman, + /obj/item/melee/ghost_sword, + /obj/item/necromantic_stone, + /obj/item/scrying, + /obj/item/spellbook, + /obj/item/storage/belt/wands/full, + /obj/item/voodoo, + /obj/item/warpwhistle)) + +// 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("[ADMIN_LOOKUPFLW(user)] 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 7dfda2573090..39cff63d98f6 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/surgery/core_removal.dm b/code/modules/surgery/core_removal.dm index 2026e5c36179..7b92d162973c 100644 --- a/code/modules/surgery/core_removal.dm +++ b/code/modules/surgery/core_removal.dm @@ -1,42 +1,42 @@ -/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(/obj/item/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(/obj/item/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 f75960cd4214..6bdb5ab51b2e 100644 --- a/code/modules/surgery/eye_surgery.dm +++ b/code/modules/surgery/eye_surgery.dm @@ -1,49 +1,49 @@ -/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(/obj/item/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) - user.visible_message("[user] successfully fixes [target]'s eyes!", "You succeed in fixing [target]'s 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(/obj/item/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) + user.visible_message("[user] successfully fixes [target]'s eyes!", "You succeed in fixing [target]'s 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 ee7688431d15..8e863ffc4032 100644 --- a/code/modules/surgery/helpers.dm +++ b/code/modules/surgery/helpers.dm @@ -1,179 +1,179 @@ -/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.mobility_flags & MOBILITY_STAND)) - 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.mobility_flags & MOBILITY_STAND)) - 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)) - if (ishuman(M)) - var/mob/living/carbon/human/H = M - H.bleed_rate = max( (H.bleed_rate - 3), 0) - 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.mobility_flags & MOBILITY_STAND)) + 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.mobility_flags & MOBILITY_STAND)) + 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)) + if (ishuman(M)) + var/mob/living/carbon/human/H = M + H.bleed_rate = max( (H.bleed_rate - 3), 0) + 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 73f85832b386..b0734793fbf3 100644 --- a/code/modules/surgery/implant_removal.dm +++ b/code/modules/surgery/implant_removal.dm @@ -1,64 +1,64 @@ -/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(/obj/item/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/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(/obj/item/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) \ No newline at end of file diff --git a/code/modules/surgery/limb_augmentation.dm b/code/modules/surgery/limb_augmentation.dm index 91b0ce73b4c9..0fce217681a5 100644 --- a/code/modules/surgery/limb_augmentation.dm +++ b/code/modules/surgery/limb_augmentation.dm @@ -1,70 +1,70 @@ - -/////AUGMENTATION SURGERIES////// - - -//SURGERY STEPS - -/datum/surgery_step/replace - name = "sever muscles" - implements = list(/obj/item/scalpel = 100, TOOL_WIRECUTTER = 55) - time = 32 - - -/datum/surgery_step/replace/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You begin to sever the muscles on [target]'s [parse_zone(user.zone_selected)]...", - "[user] begins to sever the muscles on [target]'s [parse_zone(user.zone_selected)].", - "[user] begins an incision on [target]'s [parse_zone(user.zone_selected)].") - -/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, /datum/surgery_step/saw, /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 + name = "sever muscles" + implements = list(/obj/item/scalpel = 100, TOOL_WIRECUTTER = 55) + time = 32 + + +/datum/surgery_step/replace/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You begin to sever the muscles on [target]'s [parse_zone(user.zone_selected)]...", + "[user] begins to sever the muscles on [target]'s [parse_zone(user.zone_selected)].", + "[user] begins an incision on [target]'s [parse_zone(user.zone_selected)].") + +/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, /datum/surgery_step/saw, /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 b30111a76581..323abd340f85 100644 --- a/code/modules/surgery/lipoplasty.dm +++ b/code/modules/surgery/lipoplasty.dm @@ -1,62 +1,62 @@ -/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(/obj/item/circular_saw = 100, /obj/item/melee/transforming/energy/sword/cyborg/saw = 100, /obj/item/hatchet = 35, /obj/item/kitchen/knife/butcher = 25) - time = 64 - -/datum/surgery_step/cut_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - user.visible_message("[user] begins to cut away [target]'s excess fat.", "You begin to cut away [target]'s excess fat...") - 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(/obj/item/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.set_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(/obj/item/circular_saw = 100, /obj/item/melee/transforming/energy/sword/cyborg/saw = 100, /obj/item/hatchet = 35, /obj/item/kitchen/knife/butcher = 25) + time = 64 + +/datum/surgery_step/cut_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + user.visible_message("[user] begins to cut away [target]'s excess fat.", "You begin to cut away [target]'s excess fat...") + 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(/obj/item/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.set_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 06ec88557fb1..964349b7694f 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 ef3854444a32..4b3ab264e645 100755 --- a/code/modules/surgery/organs/liver.dm +++ b/code/modules/surgery/organs/liver.dm @@ -1,111 +1,111 @@ -#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 - -/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/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 - -#define HAS_SILENT_TOXIN 0 //don't provide a feedback message if this is the only toxin present -#define HAS_NO_TOXIN 1 -#define HAS_PAINFUL_TOXIN 2 - -/obj/item/organ/liver/on_life() - var/mob/living/carbon/C = owner - ..() //perform general on_life() - if(istype(C)) - if(!(organ_flags & ORGAN_FAILING))//can't process reagents with a failing liver - - var/provide_pain_message = HAS_NO_TOXIN - 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) - if(provide_pain_message != HAS_PAINFUL_TOXIN) - provide_pain_message = T.silent_toxin ? HAS_SILENT_TOXIN : HAS_PAINFUL_TOXIN - - //metabolize reagents - C.reagents.metabolize(C, can_overdose=TRUE) - - if(provide_pain_message && damage > 10 && prob(damage/3))//the higher the damage the higher the probability - to_chat(C, "You feel a dull pain in your abdomen.") - else //for when our liver's failing - reagents.end_metabolization(C, keep_liverless = TRUE) //Stops trait-based effects on reagents, to prevent permanent buffs - reagents.metabolize(C, can_overdose=FALSE, liverless = TRUE) - if(HAS_TRAIT(C, TRAIT_STABLELIVER)) - return - C.adjustToxLoss(4, TRUE, TRUE) - if(prob(30)) - to_chat(C, "You feel a stabbing pain in your abdomen!") - - if(damage > maxHealth)//cap liver damage - damage = maxHealth - -#undef HAS_SILENT_TOXIN -#undef HAS_NO_TOXIN -#undef HAS_PAINFUL_TOXIN - -/obj/item/organ/liver/prepare_eat() - var/obj/S = ..() - S.reagents.add_reagent(/datum/reagent/iron, 5) - return S - -/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/alien - name = "alien liver" // doesnt matter for actual aliens because they dont take toxin damage - icon_state = "liver-x" // Same sprite as fly-person liver. - desc = "A liver that used to belong to a killer alien, who knows what it used to eat." - toxLethality = LIVER_DEFAULT_TOX_LETHALITY * 2.5 // rejects its owner early after too much punishment - toxTolerance = 15 // complete toxin immunity like xenos have would be too powerful - -/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. Handles toxins slightly better than an organic liver." - organ_flags = ORGAN_SYNTHETIC - maxHealth = 1.1 * STANDARD_ORGAN_THRESHOLD - toxTolerance = 3.3 - toxLethality = 0.009 - -/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 further 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 //double the health of a normal liver - 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_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 + +/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/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 + +#define HAS_SILENT_TOXIN 0 //don't provide a feedback message if this is the only toxin present +#define HAS_NO_TOXIN 1 +#define HAS_PAINFUL_TOXIN 2 + +/obj/item/organ/liver/on_life() + var/mob/living/carbon/C = owner + ..() //perform general on_life() + if(istype(C)) + if(!(organ_flags & ORGAN_FAILING))//can't process reagents with a failing liver + + var/provide_pain_message = HAS_NO_TOXIN + 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) + if(provide_pain_message != HAS_PAINFUL_TOXIN) + provide_pain_message = T.silent_toxin ? HAS_SILENT_TOXIN : HAS_PAINFUL_TOXIN + + //metabolize reagents + C.reagents.metabolize(C, can_overdose=TRUE) + + if(provide_pain_message && damage > 10 && prob(damage/3))//the higher the damage the higher the probability + to_chat(C, "You feel a dull pain in your abdomen.") + else //for when our liver's failing + reagents.end_metabolization(C, keep_liverless = TRUE) //Stops trait-based effects on reagents, to prevent permanent buffs + reagents.metabolize(C, can_overdose=FALSE, liverless = TRUE) + if(HAS_TRAIT(C, TRAIT_STABLELIVER)) + return + C.adjustToxLoss(4, TRUE, TRUE) + if(prob(30)) + to_chat(C, "You feel a stabbing pain in your abdomen!") + + if(damage > maxHealth)//cap liver damage + damage = maxHealth + +#undef HAS_SILENT_TOXIN +#undef HAS_NO_TOXIN +#undef HAS_PAINFUL_TOXIN + +/obj/item/organ/liver/prepare_eat() + var/obj/S = ..() + S.reagents.add_reagent(/datum/reagent/iron, 5) + return S + +/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/alien + name = "alien liver" // doesnt matter for actual aliens because they dont take toxin damage + icon_state = "liver-x" // Same sprite as fly-person liver. + desc = "A liver that used to belong to a killer alien, who knows what it used to eat." + toxLethality = LIVER_DEFAULT_TOX_LETHALITY * 2.5 // rejects its owner early after too much punishment + toxTolerance = 15 // complete toxin immunity like xenos have would be too powerful + +/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. Handles toxins slightly better than an organic liver." + organ_flags = ORGAN_SYNTHETIC + maxHealth = 1.1 * STANDARD_ORGAN_THRESHOLD + toxTolerance = 3.3 + toxLethality = 0.009 + +/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 further 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 //double the health of a normal liver + 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 10aca4c58417..d583ac027b78 100644 --- a/code/modules/surgery/organs/stomach.dm +++ b/code/modules/surgery/organs/stomach.dm @@ -1,127 +1,127 @@ -/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." - - 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." - - var/disgust_metabolism = 1 - -/obj/item/organ/stomach/on_life() - var/mob/living/carbon/human/H = owner - var/datum/reagent/Nutri - - ..() - if(istype(H)) - if(!(organ_flags & ORGAN_FAILING)) - H.dna.species.handle_digestion(H) - handle_disgust(H) - - if(damage < low_threshold) - return - - 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!") - -/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/ethereal - name = "biological battery" - icon_state = "stomach-p" //Welp. At least it's more unique in functionaliy. - desc = "A crystal-like organ that stores the electric charge of ethereals." - var/crystal_charge = ETHEREAL_CHARGE_FULL - -/obj/item/organ/stomach/ethereal/on_life() - ..() - adjust_charge(-ETHEREAL_CHARGE_FACTOR) - -/obj/item/organ/stomach/ethereal/Insert(mob/living/carbon/M, special = 0) - ..() - RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge) - RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_electrocute) - -/obj/item/organ/stomach/ethereal/Remove(mob/living/carbon/M, special = 0) - UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT) - UnregisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT) - ..() - -/obj/item/organ/stomach/ethereal/proc/charge(datum/source, amount, repairs) - adjust_charge(amount / 70) - -/obj/item/organ/stomach/ethereal/proc/on_electrocute(datum/source, shock_damage, siemens_coeff = 1, illusion = FALSE) - if(illusion) - return - adjust_charge(shock_damage * siemens_coeff * 2) - to_chat(owner, "You absorb some of the shock into your body!") - -/obj/item/organ/stomach/ethereal/proc/adjust_charge(amount) - crystal_charge = CLAMP(crystal_charge + amount, ETHEREAL_CHARGE_NONE, ETHEREAL_CHARGE_FULL) +/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." + + 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." + + var/disgust_metabolism = 1 + +/obj/item/organ/stomach/on_life() + var/mob/living/carbon/human/H = owner + var/datum/reagent/Nutri + + ..() + if(istype(H)) + if(!(organ_flags & ORGAN_FAILING)) + H.dna.species.handle_digestion(H) + handle_disgust(H) + + if(damage < low_threshold) + return + + 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!") + +/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/ethereal + name = "biological battery" + icon_state = "stomach-p" //Welp. At least it's more unique in functionaliy. + desc = "A crystal-like organ that stores the electric charge of ethereals." + var/crystal_charge = ETHEREAL_CHARGE_FULL + +/obj/item/organ/stomach/ethereal/on_life() + ..() + adjust_charge(-ETHEREAL_CHARGE_FACTOR) + +/obj/item/organ/stomach/ethereal/Insert(mob/living/carbon/M, special = 0) + ..() + RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge) + RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_electrocute) + +/obj/item/organ/stomach/ethereal/Remove(mob/living/carbon/M, special = 0) + UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT) + UnregisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT) + ..() + +/obj/item/organ/stomach/ethereal/proc/charge(datum/source, amount, repairs) + adjust_charge(amount / 70) + +/obj/item/organ/stomach/ethereal/proc/on_electrocute(datum/source, shock_damage, siemens_coeff = 1, illusion = FALSE) + if(illusion) + return + adjust_charge(shock_damage * siemens_coeff * 2) + to_chat(owner, "You absorb some of the shock into your body!") + +/obj/item/organ/stomach/ethereal/proc/adjust_charge(amount) + crystal_charge = CLAMP(crystal_charge + amount, ETHEREAL_CHARGE_NONE, ETHEREAL_CHARGE_FULL) diff --git a/code/modules/surgery/plastic_surgery.dm b/code/modules/surgery/plastic_surgery.dm index 49002d950bc8..be40726b0ba2 100644 --- a/code/modules/surgery/plastic_surgery.dm +++ b/code/modules/surgery/plastic_surgery.dm @@ -1,52 +1,52 @@ -/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(/obj/item/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) - user.visible_message("[user] begins to alter [target]'s appearance.", "You begin to alter [target]'s appearance...") - 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 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) +/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(/obj/item/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) + user.visible_message("[user] begins to alter [target]'s appearance.", "You begin to alter [target]'s appearance...") + 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 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 \ No newline at end of file diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index b19d811a6728..520d25c8cae3 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -1,157 +1,157 @@ -/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 = 0 //does the surgery step require an open hand? If true, ignores implements. Compatible with accept_any_item. - var/accept_any_item = 0 //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 = 0 //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? - -/datum/surgery_step/proc/try_op(mob/user, mob/living/carbon/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 - -// yogs start - tool switcher - if(istype(tool, /obj/item/storage/belt/tool_switcher)) - var/obj/item/storage/belt/tool_switcher/S = tool - tool = S.find_current_tool() - if(!tool) - tool = S -// yogs end - - 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) - 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 1 - else - surgery.status-- - - if(iscyborg(user) && user.a_intent != INTENT_HARM) //to save asimov borgs a LOT of heartache - return 1 - - return 0 - - -/datum/surgery_step/proc/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) - surgery.step_in_progress = 1 - - var/speed_mod = 1 - - if(preop(user, target, target_zone, tool, surgery) == -1) - surgery.step_in_progress = 0 - return - - if(tool) - speed_mod = tool.toolspeed - - if(do_after(user, time * speed_mod, target = target)) - var/advance = 0 - 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)) && chem_check(target) && !try_to_fail) - if(success(user, target, target_zone, tool, surgery)) - advance = 1 - else - if(failure(user, target, target_zone, tool, surgery)) - advance = 1 - - if(advance && !repeatable) - surgery.status++ - if(surgery.status > surgery.steps.len) - surgery.complete() - - surgery.step_in_progress = 0 - - -/datum/surgery_step/proc/preop(mob/user, mob/living/carbon/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/carbon/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/carbon/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 1 - -/datum/surgery_step/proc/chem_check(mob/living/carbon/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 = 0 //does the surgery step require an open hand? If true, ignores implements. Compatible with accept_any_item. + var/accept_any_item = 0 //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 = 0 //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? + +/datum/surgery_step/proc/try_op(mob/user, mob/living/carbon/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 + +// yogs start - tool switcher + if(istype(tool, /obj/item/storage/belt/tool_switcher)) + var/obj/item/storage/belt/tool_switcher/S = tool + tool = S.find_current_tool() + if(!tool) + tool = S +// yogs end + + 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) + 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 1 + else + surgery.status-- + + if(iscyborg(user) && user.a_intent != INTENT_HARM) //to save asimov borgs a LOT of heartache + return 1 + + return 0 + + +/datum/surgery_step/proc/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) + surgery.step_in_progress = 1 + + var/speed_mod = 1 + + if(preop(user, target, target_zone, tool, surgery) == -1) + surgery.step_in_progress = 0 + return + + if(tool) + speed_mod = tool.toolspeed + + if(do_after(user, time * speed_mod, target = target)) + var/advance = 0 + 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)) && chem_check(target) && !try_to_fail) + if(success(user, target, target_zone, tool, surgery)) + advance = 1 + else + if(failure(user, target, target_zone, tool, surgery)) + advance = 1 + + if(advance && !repeatable) + surgery.status++ + if(surgery.status > surgery.steps.len) + surgery.complete() + + surgery.step_in_progress = 0 + + +/datum/surgery_step/proc/preop(mob/user, mob/living/carbon/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/carbon/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/carbon/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 1 + +/datum/surgery_step/proc/chem_check(mob/living/carbon/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 91de8ccf5e13..5e0ad009c7a4 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/tools.dm @@ -1,377 +1,377 @@ -/obj/item/retractor - name = "retractor" - desc = "Retracts stuff." - icon = 'icons/obj/surgery.dmi' - icon_state = "retractor" - materials = list(MAT_METAL=6000, MAT_GLASS=3000) - flags_1 = CONDUCT_1 - item_flags = SURGICAL_TOOL - w_class = WEIGHT_CLASS_TINY - - -/obj/item/retractor/augment - name = "retractor" - desc = "Micro-mechanical manipulator for retracting stuff." - icon = 'icons/obj/surgery.dmi' - icon_state = "retractor" - materials = list(MAT_METAL=6000, MAT_GLASS=3000) - w_class = WEIGHT_CLASS_TINY - toolspeed = 0.5 - - -/obj/item/hemostat - name = "hemostat" - desc = "You think you have seen this before." - icon = 'icons/obj/surgery.dmi' - icon_state = "hemostat" - materials = list(MAT_METAL=5000, MAT_GLASS=2500) - flags_1 = CONDUCT_1 - item_flags = SURGICAL_TOOL - w_class = WEIGHT_CLASS_TINY - attack_verb = list("attacked", "pinched") - - -/obj/item/hemostat/augment - name = "hemostat" - desc = "Tiny servos power a pair of pincers to stop bleeding." - icon = 'icons/obj/surgery.dmi' - icon_state = "hemostat" - materials = list(MAT_METAL=5000, MAT_GLASS=2500) - w_class = WEIGHT_CLASS_TINY - toolspeed = 0.5 - attack_verb = list("attacked", "pinched") - - -/obj/item/cautery - name = "cautery" - desc = "This stops bleeding." - icon = 'icons/obj/surgery.dmi' - icon_state = "cautery" - materials = list(MAT_METAL=2500, MAT_GLASS=750) - flags_1 = CONDUCT_1 - item_flags = SURGICAL_TOOL - w_class = WEIGHT_CLASS_TINY - attack_verb = list("burnt") - - -/obj/item/cautery/augment - name = "cautery" - desc = "A heated element that cauterizes wounds." - icon = 'icons/obj/surgery.dmi' - icon_state = "cautery" - materials = list(MAT_METAL=2500, MAT_GLASS=750) - w_class = WEIGHT_CLASS_TINY - toolspeed = 0.5 - attack_verb = list("burnt") - - -/obj/item/surgicaldrill - name = "surgical drill" - desc = "You can drill using this item. You dig?" - icon = 'icons/obj/surgery.dmi' - icon_state = "drill" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - hitsound = 'sound/weapons/circsawhit.ogg' - materials = list(MAT_METAL=10000, MAT_GLASS=6000) - flags_1 = CONDUCT_1 - item_flags = SURGICAL_TOOL - force = 15 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("drilled") - - -/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) - 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 - item_flags = SURGICAL_TOOL - - 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") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP_ACCURATE - -/obj/item/scalpel/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 80 * toolspeed, 100, 0) - -/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" - 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' - flags_1 = CONDUCT_1 - item_flags = SURGICAL_TOOL - 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 - -/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' - 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/scalpel/advanced - name = "laser scalpel" - desc = "An advanced scalpel which uses laser technology to cut. It's set to scalpel mode." - icon = 'icons/obj/surgery.dmi' - icon_state = "scalpel_a" - hitsound = 'sound/weapons/blade1.ogg' - force = 16 - toolspeed = 0.7 - light_color = LIGHT_COLOR_GREEN - -/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,1) - var/obj/item/circular_saw/advanced/saw = new /obj/item/circular_saw/advanced(drop_location()) - to_chat(user, "You incease the power, now it can cut bones.") - qdel(src) - user.put_in_active_hand(saw) - -/obj/item/circular_saw/advanced - name = "laser scalpel" - desc = "An advanced scalpel which uses laser technology to cut. It's set to saw mode." - icon = 'icons/obj/surgery.dmi' - icon_state = "saw_a" - hitsound = 'sound/weapons/blade1.ogg' - force = 17 - toolspeed = 0.7 - sharpness = IS_SHARP_ACCURATE - light_color = LIGHT_COLOR_GREEN - -/obj/item/circular_saw/advanced/Initialize() - . = ..() - set_light(2) - -/obj/item/circular_saw/advanced/attack_self(mob/user) - playsound(get_turf(user),'sound/machines/click.ogg',50,1) - var/obj/item/scalpel/advanced/scalpel = new /obj/item/scalpel/advanced(drop_location()) - to_chat(user, "You lower the power.") - qdel(src) - user.put_in_active_hand(scalpel) - -/obj/item/retractor/advanced - name = "mechanical pinches" - desc = "An agglomerate of rods and gears. It resembles a retractor." - 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,1) - var/obj/item/hemostat/advanced/hemostat = new /obj/item/hemostat/advanced(drop_location()) - to_chat(user, "You set the [src] to hemostat mode.") - qdel(src) - user.put_in_active_hand(hemostat) - -/obj/item/hemostat/advanced - name = "mechanical pinches" - desc = "An agglomerate of rods and gears. It resembles an hemostat." - icon = 'icons/obj/surgery.dmi' - icon_state = "hemostat_a" - toolspeed = 0.7 - -/obj/item/hemostat/advanced/attack_self(mob/user) - playsound(get_turf(user),'sound/items/change_drill.ogg',50,1) - var/obj/item/retractor/advanced/retractor = new /obj/item/retractor/advanced(drop_location()) - to_chat(user, "You set the [src] to retractor mode.") - qdel(src) - user.put_in_active_hand(retractor) - -/obj/item/surgicaldrill/advanced - name = "searing tool" - desc = "It projects a high power laser used for medical application. It's set to drilling mode." - icon = 'icons/obj/surgery.dmi' - icon_state = "surgicaldrill_a" - hitsound = 'sound/items/welder.ogg' - toolspeed = 0.7 - light_color = LIGHT_COLOR_RED - -/obj/item/surgicaldrill/advanced/Initialize() - . = ..() - set_light(1) - -/obj/item/surgicaldrill/advanced/attack_self(mob/user) - playsound(get_turf(user),'sound/weapons/tap.ogg',50,1) - var/obj/item/cautery/advanced/cautery = new /obj/item/cautery/advanced(drop_location()) - to_chat(user, "You dilate the lenses, setting it to mending mode.") - qdel(src) - user.put_in_active_hand(cautery) - -/obj/item/cautery/advanced - name = "searing tool" - desc = "It projects a high power laser used for medical application. It's set to mending mode." - icon = 'icons/obj/surgery.dmi' - icon_state = "cautery_a" - hitsound = 'sound/items/welder2.ogg' - force = 15 - toolspeed = 0.7 - light_color = LIGHT_COLOR_RED - -/obj/item/cautery/advanced/Initialize() - . = ..() - set_light(1) - -/obj/item/cautery/advanced/attack_self(mob/user) - playsound(get_turf(user),'sound/items/welderdeactivate.ogg',50,1) - var/obj/item/surgicaldrill/advanced/surgicaldrill = new /obj/item/surgicaldrill/advanced(drop_location()) - to_chat(user, "You focus the lensess, it is now set to drilling mode.") - qdel(src) - user.put_in_active_hand(surgicaldrill) +/obj/item/retractor + name = "retractor" + desc = "Retracts stuff." + icon = 'icons/obj/surgery.dmi' + icon_state = "retractor" + materials = list(MAT_METAL=6000, MAT_GLASS=3000) + flags_1 = CONDUCT_1 + item_flags = SURGICAL_TOOL + w_class = WEIGHT_CLASS_TINY + + +/obj/item/retractor/augment + name = "retractor" + desc = "Micro-mechanical manipulator for retracting stuff." + icon = 'icons/obj/surgery.dmi' + icon_state = "retractor" + materials = list(MAT_METAL=6000, MAT_GLASS=3000) + w_class = WEIGHT_CLASS_TINY + toolspeed = 0.5 + + +/obj/item/hemostat + name = "hemostat" + desc = "You think you have seen this before." + icon = 'icons/obj/surgery.dmi' + icon_state = "hemostat" + materials = list(MAT_METAL=5000, MAT_GLASS=2500) + flags_1 = CONDUCT_1 + item_flags = SURGICAL_TOOL + w_class = WEIGHT_CLASS_TINY + attack_verb = list("attacked", "pinched") + + +/obj/item/hemostat/augment + name = "hemostat" + desc = "Tiny servos power a pair of pincers to stop bleeding." + icon = 'icons/obj/surgery.dmi' + icon_state = "hemostat" + materials = list(MAT_METAL=5000, MAT_GLASS=2500) + w_class = WEIGHT_CLASS_TINY + toolspeed = 0.5 + attack_verb = list("attacked", "pinched") + + +/obj/item/cautery + name = "cautery" + desc = "This stops bleeding." + icon = 'icons/obj/surgery.dmi' + icon_state = "cautery" + materials = list(MAT_METAL=2500, MAT_GLASS=750) + flags_1 = CONDUCT_1 + item_flags = SURGICAL_TOOL + w_class = WEIGHT_CLASS_TINY + attack_verb = list("burnt") + + +/obj/item/cautery/augment + name = "cautery" + desc = "A heated element that cauterizes wounds." + icon = 'icons/obj/surgery.dmi' + icon_state = "cautery" + materials = list(MAT_METAL=2500, MAT_GLASS=750) + w_class = WEIGHT_CLASS_TINY + toolspeed = 0.5 + attack_verb = list("burnt") + + +/obj/item/surgicaldrill + name = "surgical drill" + desc = "You can drill using this item. You dig?" + icon = 'icons/obj/surgery.dmi' + icon_state = "drill" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + hitsound = 'sound/weapons/circsawhit.ogg' + materials = list(MAT_METAL=10000, MAT_GLASS=6000) + flags_1 = CONDUCT_1 + item_flags = SURGICAL_TOOL + force = 15 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("drilled") + + +/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) + 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 + item_flags = SURGICAL_TOOL + + 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") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP_ACCURATE + +/obj/item/scalpel/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 80 * toolspeed, 100, 0) + +/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" + 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' + flags_1 = CONDUCT_1 + item_flags = SURGICAL_TOOL + 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 + +/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' + 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/scalpel/advanced + name = "laser scalpel" + desc = "An advanced scalpel which uses laser technology to cut. It's set to scalpel mode." + icon = 'icons/obj/surgery.dmi' + icon_state = "scalpel_a" + hitsound = 'sound/weapons/blade1.ogg' + force = 16 + toolspeed = 0.7 + light_color = LIGHT_COLOR_GREEN + +/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,1) + var/obj/item/circular_saw/advanced/saw = new /obj/item/circular_saw/advanced(drop_location()) + to_chat(user, "You incease the power, now it can cut bones.") + qdel(src) + user.put_in_active_hand(saw) + +/obj/item/circular_saw/advanced + name = "laser scalpel" + desc = "An advanced scalpel which uses laser technology to cut. It's set to saw mode." + icon = 'icons/obj/surgery.dmi' + icon_state = "saw_a" + hitsound = 'sound/weapons/blade1.ogg' + force = 17 + toolspeed = 0.7 + sharpness = IS_SHARP_ACCURATE + light_color = LIGHT_COLOR_GREEN + +/obj/item/circular_saw/advanced/Initialize() + . = ..() + set_light(2) + +/obj/item/circular_saw/advanced/attack_self(mob/user) + playsound(get_turf(user),'sound/machines/click.ogg',50,1) + var/obj/item/scalpel/advanced/scalpel = new /obj/item/scalpel/advanced(drop_location()) + to_chat(user, "You lower the power.") + qdel(src) + user.put_in_active_hand(scalpel) + +/obj/item/retractor/advanced + name = "mechanical pinches" + desc = "An agglomerate of rods and gears. It resembles a retractor." + 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,1) + var/obj/item/hemostat/advanced/hemostat = new /obj/item/hemostat/advanced(drop_location()) + to_chat(user, "You set the [src] to hemostat mode.") + qdel(src) + user.put_in_active_hand(hemostat) + +/obj/item/hemostat/advanced + name = "mechanical pinches" + desc = "An agglomerate of rods and gears. It resembles an hemostat." + icon = 'icons/obj/surgery.dmi' + icon_state = "hemostat_a" + toolspeed = 0.7 + +/obj/item/hemostat/advanced/attack_self(mob/user) + playsound(get_turf(user),'sound/items/change_drill.ogg',50,1) + var/obj/item/retractor/advanced/retractor = new /obj/item/retractor/advanced(drop_location()) + to_chat(user, "You set the [src] to retractor mode.") + qdel(src) + user.put_in_active_hand(retractor) + +/obj/item/surgicaldrill/advanced + name = "searing tool" + desc = "It projects a high power laser used for medical application. It's set to drilling mode." + icon = 'icons/obj/surgery.dmi' + icon_state = "surgicaldrill_a" + hitsound = 'sound/items/welder.ogg' + toolspeed = 0.7 + light_color = LIGHT_COLOR_RED + +/obj/item/surgicaldrill/advanced/Initialize() + . = ..() + set_light(1) + +/obj/item/surgicaldrill/advanced/attack_self(mob/user) + playsound(get_turf(user),'sound/weapons/tap.ogg',50,1) + var/obj/item/cautery/advanced/cautery = new /obj/item/cautery/advanced(drop_location()) + to_chat(user, "You dilate the lenses, setting it to mending mode.") + qdel(src) + user.put_in_active_hand(cautery) + +/obj/item/cautery/advanced + name = "searing tool" + desc = "It projects a high power laser used for medical application. It's set to mending mode." + icon = 'icons/obj/surgery.dmi' + icon_state = "cautery_a" + hitsound = 'sound/items/welder2.ogg' + force = 15 + toolspeed = 0.7 + light_color = LIGHT_COLOR_RED + +/obj/item/cautery/advanced/Initialize() + . = ..() + set_light(1) + +/obj/item/cautery/advanced/attack_self(mob/user) + playsound(get_turf(user),'sound/items/welderdeactivate.ogg',50,1) + var/obj/item/surgicaldrill/advanced/surgicaldrill = new /obj/item/surgicaldrill/advanced(drop_location()) + to_chat(user, "You focus the lensess, it is now set to drilling mode.") + qdel(src) + user.put_in_active_hand(surgicaldrill) diff --git a/code/modules/tgs/core/_definitions.dm b/code/modules/tgs/core/_definitions.dm index d5e1a0075ba2..ebf6d17c2a07 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 ed3949f568c4..70252cfb49a4 100644 --- a/code/modules/tgs/core/core.dm +++ b/code/modules/tgs/core/core.dm @@ -1,164 +1,164 @@ -/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) - var/current_api = TGS_READ_GLOBAL(tgs) - if(current_api) - TGS_ERROR_LOG("TgsNew(): TGS API datum already set ([current_api])! Was TgsNew() called more than once?") - return - -#ifdef TGS_V3_API - minimum_required_security_level = TGS_SECURITY_TRUSTED -#endif - var/raw_parameter = world.params[TGS_VERSION_PARAMETER] - if(!raw_parameter) - return - - var/datum/tgs_version/version = new(raw_parameter) - if(!version.Valid(FALSE)) - TGS_ERROR_LOG("Failed to validate TGS version parameter: [raw_parameter]!") - return - - var/api_datum - switch(version.suite) - if(3) -#ifndef TGS_V3_API - TGS_ERROR_LOG("Detected V3 API but TGS_V3_API isn't defined!") -#else - switch(version.major) - if(2) - api_datum = /datum/tgs_api/v3210 -#endif - if(4) - switch(version.major) - if(0) - api_datum = /datum/tgs_api/v4 - - var/datum/tgs_version/max_api_version = TgsMaximumAPIVersion(); - if(version.suite != null && version.major != null && version.minor != null && version.patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter) - TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") - api_datum = /datum/tgs_api/latest - - if(!api_datum) - TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.") - return - - TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]") - var/datum/tgs_api/new_api = new api_datum(version) - - TGS_WRITE_GLOBAL(tgs, new_api) - - var/result = new_api.OnWorldNew(event_handler, minimum_required_security_level) - if(!result || result == TGS_UNIMPLEMENTED) - TGS_WRITE_GLOBAL(tgs, null) - TGS_ERROR_LOG("Failed to activate API!") - -/world/TgsMaximumAPIVersion() - return new /datum/tgs_version("4.0.x.x") - -/world/TgsMinimumAPIVersion() - return new /datum/tgs_version("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() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - return api.version - -/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) - -/world/TgsSecurityLevel() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - api.SecurityLevel() - -/* -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, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) + var/current_api = TGS_READ_GLOBAL(tgs) + if(current_api) + TGS_ERROR_LOG("TgsNew(): TGS API datum already set ([current_api])! Was TgsNew() called more than once?") + return + +#ifdef TGS_V3_API + minimum_required_security_level = TGS_SECURITY_TRUSTED +#endif + var/raw_parameter = world.params[TGS_VERSION_PARAMETER] + if(!raw_parameter) + return + + var/datum/tgs_version/version = new(raw_parameter) + if(!version.Valid(FALSE)) + TGS_ERROR_LOG("Failed to validate TGS version parameter: [raw_parameter]!") + return + + var/api_datum + switch(version.suite) + if(3) +#ifndef TGS_V3_API + TGS_ERROR_LOG("Detected V3 API but TGS_V3_API isn't defined!") +#else + switch(version.major) + if(2) + api_datum = /datum/tgs_api/v3210 +#endif + if(4) + switch(version.major) + if(0) + api_datum = /datum/tgs_api/v4 + + var/datum/tgs_version/max_api_version = TgsMaximumAPIVersion(); + if(version.suite != null && version.major != null && version.minor != null && version.patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter) + TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") + api_datum = /datum/tgs_api/latest + + if(!api_datum) + TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.") + return + + TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]") + var/datum/tgs_api/new_api = new api_datum(version) + + TGS_WRITE_GLOBAL(tgs, new_api) + + var/result = new_api.OnWorldNew(event_handler, minimum_required_security_level) + if(!result || result == TGS_UNIMPLEMENTED) + TGS_WRITE_GLOBAL(tgs, null) + TGS_ERROR_LOG("Failed to activate API!") + +/world/TgsMaximumAPIVersion() + return new /datum/tgs_version("4.0.x.x") + +/world/TgsMinimumAPIVersion() + return new /datum/tgs_version("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() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + return api.version + +/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) + +/world/TgsSecurityLevel() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.SecurityLevel() + +/* +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 dc46ac6aac90..fb2508059a6d 100644 --- a/code/modules/tgs/core/datum.dm +++ b/code/modules/tgs/core/datum.dm @@ -1,82 +1,82 @@ -TGS_DEFINE_AND_SET_GLOBAL(tgs, null) - -/datum/tgs_api - var/datum/tgs_version/version - -/datum/tgs_api/New(datum/tgs_version/version) - . = ..() - src.version = version - -/datum/tgs_api/latest - parent_type = /datum/tgs_api/v4 - -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 - -/datum/tgs_api/proc/SecurityLevel() - 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 + var/datum/tgs_version/version + +/datum/tgs_api/New(datum/tgs_version/version) + . = ..() + src.version = version + +/datum/tgs_api/latest + parent_type = /datum/tgs_api/v4 + +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 + +/datum/tgs_api/proc/SecurityLevel() + 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/includes.dm b/code/modules/tgs/includes.dm index b67ecc78de72..247f1fda5deb 100644 --- a/code/modules/tgs/includes.dm +++ b/code/modules/tgs/includes.dm @@ -1,10 +1,10 @@ -#include "core\_definitions.dm" -#include "core\core.dm" -#include "core\datum.dm" -#include "core\tgs_version.dm" -#ifdef TGS_V3_API -#include "v3210\api.dm" -#include "v3210\commands.dm" -#endif -#include "v4\api.dm" -#include "v4\commands.dm" +#include "core\_definitions.dm" +#include "core\core.dm" +#include "core\datum.dm" +#include "core\tgs_version.dm" +#ifdef TGS_V3_API +#include "v3210\api.dm" +#include "v3210\commands.dm" +#endif +#include "v4\api.dm" +#include "v4\commands.dm" diff --git a/code/modules/tgs/v3210/api.dm b/code/modules/tgs/v3210/api.dm index 583c9d5a0b14..c536995b04ba 100644 --- a/code/modules/tgs/v3210/api.dm +++ b/code/modules/tgs/v3210/api.dm @@ -1,253 +1,253 @@ -#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, minimum_required_security_level) //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 - return ri - -/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, datum/tgs_chat_user/user) - return TGS_UNIMPLEMENTED - -/datum/tgs_api/v3210/SecurityLevel() - return TGS_SECURITY_TRUSTED - -#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, minimum_required_security_level) //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 + return ri + +/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, datum/tgs_chat_user/user) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/v3210/SecurityLevel() + return TGS_SECURITY_TRUSTED + +#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 71d7e323661a..50466319813d 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/tgs/v4/api.dm b/code/modules/tgs/v4/api.dm index b3d5407fe0d6..d40325cb5a13 100644 --- a/code/modules/tgs/v4/api.dm +++ b/code/modules/tgs/v4/api.dm @@ -1,342 +1,342 @@ -#define TGS4_PARAM_INFO_JSON "tgs_json" - -#define TGS4_INTEROP_ACCESS_IDENTIFIER "tgs_tok" - -#define TGS4_RESPONSE_SUCCESS "tgs_succ" - -#define TGS4_TOPIC_CHANGE_PORT "tgs_port" -#define TGS4_TOPIC_CHANGE_REBOOT_MODE "tgs_rmode" -#define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm" -#define TGS4_TOPIC_EVENT "tgs_event" -#define TGS4_TOPIC_INTEROP_RESPONSE "tgs_interop" - -#define TGS4_COMM_NEW_PORT "tgs_new_port" -#define TGS4_COMM_VALIDATE "tgs_validate" -#define TGS4_COMM_SERVER_PRIMED "tgs_prime" -#define TGS4_COMM_WORLD_REBOOT "tgs_reboot" -#define TGS4_COMM_END_PROCESS "tgs_kill" -#define TGS4_COMM_CHAT "tgs_chat_send" - -#define TGS4_PARAMETER_COMMAND "tgs_com" -#define TGS4_PARAMETER_DATA "tgs_data" - -#define TGS4_PORT_CRITFAIL_MESSAGE " Must exit to let watchdog reboot..." - -#define EXPORT_TIMEOUT_DS 200 - -/datum/tgs_api/v4 - var/access_identifier - var/instance_name - var/json_path - var/chat_channels_json_path - var/chat_commands_json_path - var/server_commands_json_path - var/reboot_mode = TGS_REBOOT_MODE_NORMAL - var/security_level - - var/requesting_new_port = FALSE - - var/list/intercepted_message_queue - - var/list/custom_commands - - var/list/cached_test_merges - var/datum/tgs_revision_information/cached_revision - - var/datum/tgs_event_handler/event_handler - - var/export_lock = FALSE - var/list/last_interop_response - -/datum/tgs_api/v4/ApiVersion() - return "4.0.0.0" - -/datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler, minimum_required_security_level) - json_path = world.params[TGS4_PARAM_INFO_JSON] - if(!json_path) - TGS_ERROR_LOG("Missing [TGS4_PARAM_INFO_JSON] world parameter!") - return - var/json_file = file2text(json_path) - if(!json_file) - TGS_ERROR_LOG("Missing specified json file: [json_path]") - return - var/cached_json = json_decode(json_file) - if(!cached_json) - TGS_ERROR_LOG("Failed to decode info json: [json_file]") - return - - access_identifier = cached_json["accessIdentifier"] - server_commands_json_path = cached_json["serverCommandsJson"] - - if(cached_json["apiValidateOnly"]) - TGS_INFO_LOG("Validating API and exiting...") - Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]")) - del(world) - - security_level = cached_json["securityLevel"] - chat_channels_json_path = cached_json["chatChannelsJson"] - chat_commands_json_path = cached_json["chatCommandsJson"] - src.event_handler = event_handler - instance_name = cached_json["instanceName"] - - ListCustomCommands() - - var/list/revisionData = cached_json["revision"] - if(revisionData) - cached_revision = new - cached_revision.commit = revisionData["commitSha"] - cached_revision.origin_commit = revisionData["originCommitSha"] - - cached_test_merges = list() - var/list/json = cached_json["testMerges"] - for(var/entry in json) - var/datum/tgs_revision_information/test_merge/tm = new - tm.time_merged = text2num(entry["timeMerged"]) - - var/list/revInfo = entry["revision"] - if(revInfo) - tm.commit = revInfo["commitSha"] - tm.origin_commit = revInfo["originCommitSha"] - - tm.title = entry["titleAtMerge"] - tm.body = entry["bodyAtMerge"] - tm.url = entry["url"] - tm.author = entry["author"] - tm.number = entry["number"] - tm.pull_request_commit = entry["pullRequestRevision"] - tm.comment = entry["comment"] - - cached_test_merges += tm - - return TRUE - -/datum/tgs_api/v4/OnInitializationComplete() - Export(TGS4_COMM_SERVER_PRIMED) - - var/tgs4_secret_sleep_offline_sauce = 24051994 - var/old_sleep_offline = world.sleep_offline - world.sleep_offline = tgs4_secret_sleep_offline_sauce - sleep(1) - if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it - world.sleep_offline = old_sleep_offline - -/datum/tgs_api/v4/OnTopic(T) - var/list/params = params2list(T) - var/their_sCK = params[TGS4_INTEROP_ACCESS_IDENTIFIER] - if(!their_sCK) - return FALSE //continue world/Topic - - if(their_sCK != access_identifier) - return "Invalid comms key!"; - - var/command = params[TGS4_PARAMETER_COMMAND] - if(!command) - return "No command!" - - . = TGS4_RESPONSE_SUCCESS - - switch(command) - if(TGS4_TOPIC_CHAT_COMMAND) - var/result = HandleCustomCommand(params[TGS4_PARAMETER_DATA]) - if(result == null) - result = "Error running chat command!" - return result - if(TGS4_TOPIC_EVENT) - intercepted_message_queue = list() - var/list/event_notification = json_decode(params[TGS4_PARAMETER_DATA]) - var/list/event_parameters = event_notification["Parameters"] - - var/list/event_call = list(event_notification["Type"]) - if(event_parameters) - event_call += event_parameters - - if(event_handler != null) - event_handler.HandleEvent(arglist(event_call)) - - . = json_encode(intercepted_message_queue) - intercepted_message_queue = null - return - if(TGS4_TOPIC_INTEROP_RESPONSE) - last_interop_response = json_decode(params[TGS4_PARAMETER_DATA]) - return - if(TGS4_TOPIC_CHANGE_PORT) - var/new_port = text2num(params[TGS4_PARAMETER_DATA]) - if (!(new_port > 0)) - return "Invalid port: [new_port]" - - //the topic still completes, miraculously - //I honestly didn't believe byond could do it - if(event_handler != null) - event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) - if(!world.OpenPort(new_port)) - return "Port change failed!" - return - if(TGS4_TOPIC_CHANGE_REBOOT_MODE) - var/new_reboot_mode = text2num(params[TGS4_PARAMETER_DATA]) - if(event_handler != null) - event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode) - reboot_mode = new_reboot_mode - return - - return "Unknown command: [command]" - -/datum/tgs_api/v4/proc/Export(command, list/data, override_requesting_new_port = FALSE) - if(!data) - data = list() - data[TGS4_PARAMETER_COMMAND] = command - var/json = json_encode(data) - - while(requesting_new_port && !override_requesting_new_port) - sleep(1) - - //we need some port open at this point to facilitate return communication - if(!world.port) - requesting_new_port = TRUE - if(!world.OpenPort(0)) //open any port - TGS_ERROR_LOG("Unable to open random port to retrieve new port![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) - - //request a new port - export_lock = FALSE - var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose - - if(!new_port_json) - TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) - - var/new_port = new_port_json[TGS4_PARAMETER_DATA] - if(!isnum(new_port) || new_port <= 0) - TGS_ERROR_LOG("Malformed new port json ([json_encode(new_port_json)])![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) - - if(new_port != world.port && !world.OpenPort(new_port)) - TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) - requesting_new_port = FALSE - - while(export_lock) - sleep(1) - export_lock = TRUE - - last_interop_response = null - fdel(server_commands_json_path) - text2file(json, server_commands_json_path) - - for(var/I = 0; I < EXPORT_TIMEOUT_DS && !last_interop_response; ++I) - sleep(1) - - if(!last_interop_response) - TGS_ERROR_LOG("Failed to get export result for: [json]") - else - . = last_interop_response - - export_lock = FALSE - -/datum/tgs_api/v4/OnReboot() - var/list/result = Export(TGS4_COMM_WORLD_REBOOT) - if(!result) - return - - //okay so the standard TGS4 proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter - - var/port = result[TGS4_PARAMETER_DATA] - if(!isnum(port)) - return //this is valid, server may just want use to reboot - - if(port == 0) - //to byond 0 means any port and "none" means close vOv - port = "none" - - if(!world.OpenPort(port)) - TGS_ERROR_LOG("Unable to set port to [port]!") - -/datum/tgs_api/v4/InstanceName() - return instance_name - -/datum/tgs_api/v4/TestMerges() - return cached_test_merges - -/datum/tgs_api/v4/EndProcess() - Export(TGS4_COMM_END_PROCESS) - -/datum/tgs_api/v4/Revision() - return cached_revision - -/datum/tgs_api/v4/ChatBroadcast(message, list/channels) - var/list/ids - if(length(channels)) - ids = list() - for(var/I in channels) - var/datum/tgs_chat_channel/channel = I - ids += channel.id - message = list("message" = message, "channelIds" = ids) - if(intercepted_message_queue) - intercepted_message_queue += list(message) - else - Export(TGS4_COMM_CHAT, message) - -/datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) - var/list/channels = list() - for(var/I in ChatChannelInfo()) - var/datum/tgs_chat_channel/channel = I - if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only))) - channels += channel.id - message = list("message" = message, "channelIds" = channels) - if(intercepted_message_queue) - intercepted_message_queue += list(message) - else - Export(TGS4_COMM_CHAT, message) - -/datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) - message = list("message" = message, "channelIds" = list(user.channel.id)) - if(intercepted_message_queue) - intercepted_message_queue += list(message) - else - Export(TGS4_COMM_CHAT, message) - -/datum/tgs_api/v4/ChatChannelInfo() - . = list() - //no caching cause tgs may change this - var/list/json = json_decode(file2text(chat_channels_json_path)) - for(var/I in json) - . += DecodeChannel(I) - -/datum/tgs_api/v4/proc/DecodeChannel(channel_json) - var/datum/tgs_chat_channel/channel = new - channel.id = channel_json["id"] - channel.friendly_name = channel_json["friendlyName"] - channel.connection_name = channel_json["connectionName"] - channel.is_admin_channel = channel_json["isAdminChannel"] - channel.is_private_channel = channel_json["isPrivateChannel"] - channel.custom_tag = channel_json["tag"] - return channel - -/datum/tgs_api/v4/SecurityLevel() - return security_level - -/* -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 TGS4_PARAM_INFO_JSON "tgs_json" + +#define TGS4_INTEROP_ACCESS_IDENTIFIER "tgs_tok" + +#define TGS4_RESPONSE_SUCCESS "tgs_succ" + +#define TGS4_TOPIC_CHANGE_PORT "tgs_port" +#define TGS4_TOPIC_CHANGE_REBOOT_MODE "tgs_rmode" +#define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm" +#define TGS4_TOPIC_EVENT "tgs_event" +#define TGS4_TOPIC_INTEROP_RESPONSE "tgs_interop" + +#define TGS4_COMM_NEW_PORT "tgs_new_port" +#define TGS4_COMM_VALIDATE "tgs_validate" +#define TGS4_COMM_SERVER_PRIMED "tgs_prime" +#define TGS4_COMM_WORLD_REBOOT "tgs_reboot" +#define TGS4_COMM_END_PROCESS "tgs_kill" +#define TGS4_COMM_CHAT "tgs_chat_send" + +#define TGS4_PARAMETER_COMMAND "tgs_com" +#define TGS4_PARAMETER_DATA "tgs_data" + +#define TGS4_PORT_CRITFAIL_MESSAGE " Must exit to let watchdog reboot..." + +#define EXPORT_TIMEOUT_DS 200 + +/datum/tgs_api/v4 + var/access_identifier + var/instance_name + var/json_path + var/chat_channels_json_path + var/chat_commands_json_path + var/server_commands_json_path + var/reboot_mode = TGS_REBOOT_MODE_NORMAL + var/security_level + + var/requesting_new_port = FALSE + + var/list/intercepted_message_queue + + var/list/custom_commands + + var/list/cached_test_merges + var/datum/tgs_revision_information/cached_revision + + var/datum/tgs_event_handler/event_handler + + var/export_lock = FALSE + var/list/last_interop_response + +/datum/tgs_api/v4/ApiVersion() + return "4.0.0.0" + +/datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler, minimum_required_security_level) + json_path = world.params[TGS4_PARAM_INFO_JSON] + if(!json_path) + TGS_ERROR_LOG("Missing [TGS4_PARAM_INFO_JSON] world parameter!") + return + var/json_file = file2text(json_path) + if(!json_file) + TGS_ERROR_LOG("Missing specified json file: [json_path]") + return + var/cached_json = json_decode(json_file) + if(!cached_json) + TGS_ERROR_LOG("Failed to decode info json: [json_file]") + return + + access_identifier = cached_json["accessIdentifier"] + server_commands_json_path = cached_json["serverCommandsJson"] + + if(cached_json["apiValidateOnly"]) + TGS_INFO_LOG("Validating API and exiting...") + Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]")) + del(world) + + security_level = cached_json["securityLevel"] + chat_channels_json_path = cached_json["chatChannelsJson"] + chat_commands_json_path = cached_json["chatCommandsJson"] + src.event_handler = event_handler + instance_name = cached_json["instanceName"] + + ListCustomCommands() + + var/list/revisionData = cached_json["revision"] + if(revisionData) + cached_revision = new + cached_revision.commit = revisionData["commitSha"] + cached_revision.origin_commit = revisionData["originCommitSha"] + + cached_test_merges = list() + var/list/json = cached_json["testMerges"] + for(var/entry in json) + var/datum/tgs_revision_information/test_merge/tm = new + tm.time_merged = text2num(entry["timeMerged"]) + + var/list/revInfo = entry["revision"] + if(revInfo) + tm.commit = revInfo["commitSha"] + tm.origin_commit = revInfo["originCommitSha"] + + tm.title = entry["titleAtMerge"] + tm.body = entry["bodyAtMerge"] + tm.url = entry["url"] + tm.author = entry["author"] + tm.number = entry["number"] + tm.pull_request_commit = entry["pullRequestRevision"] + tm.comment = entry["comment"] + + cached_test_merges += tm + + return TRUE + +/datum/tgs_api/v4/OnInitializationComplete() + Export(TGS4_COMM_SERVER_PRIMED) + + var/tgs4_secret_sleep_offline_sauce = 24051994 + var/old_sleep_offline = world.sleep_offline + world.sleep_offline = tgs4_secret_sleep_offline_sauce + sleep(1) + if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it + world.sleep_offline = old_sleep_offline + +/datum/tgs_api/v4/OnTopic(T) + var/list/params = params2list(T) + var/their_sCK = params[TGS4_INTEROP_ACCESS_IDENTIFIER] + if(!their_sCK) + return FALSE //continue world/Topic + + if(their_sCK != access_identifier) + return "Invalid comms key!"; + + var/command = params[TGS4_PARAMETER_COMMAND] + if(!command) + return "No command!" + + . = TGS4_RESPONSE_SUCCESS + + switch(command) + if(TGS4_TOPIC_CHAT_COMMAND) + var/result = HandleCustomCommand(params[TGS4_PARAMETER_DATA]) + if(result == null) + result = "Error running chat command!" + return result + if(TGS4_TOPIC_EVENT) + intercepted_message_queue = list() + var/list/event_notification = json_decode(params[TGS4_PARAMETER_DATA]) + var/list/event_parameters = event_notification["Parameters"] + + var/list/event_call = list(event_notification["Type"]) + if(event_parameters) + event_call += event_parameters + + if(event_handler != null) + event_handler.HandleEvent(arglist(event_call)) + + . = json_encode(intercepted_message_queue) + intercepted_message_queue = null + return + if(TGS4_TOPIC_INTEROP_RESPONSE) + last_interop_response = json_decode(params[TGS4_PARAMETER_DATA]) + return + if(TGS4_TOPIC_CHANGE_PORT) + var/new_port = text2num(params[TGS4_PARAMETER_DATA]) + if (!(new_port > 0)) + return "Invalid port: [new_port]" + + //the topic still completes, miraculously + //I honestly didn't believe byond could do it + if(event_handler != null) + event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) + if(!world.OpenPort(new_port)) + return "Port change failed!" + return + if(TGS4_TOPIC_CHANGE_REBOOT_MODE) + var/new_reboot_mode = text2num(params[TGS4_PARAMETER_DATA]) + if(event_handler != null) + event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode) + reboot_mode = new_reboot_mode + return + + return "Unknown command: [command]" + +/datum/tgs_api/v4/proc/Export(command, list/data, override_requesting_new_port = FALSE) + if(!data) + data = list() + data[TGS4_PARAMETER_COMMAND] = command + var/json = json_encode(data) + + while(requesting_new_port && !override_requesting_new_port) + sleep(1) + + //we need some port open at this point to facilitate return communication + if(!world.port) + requesting_new_port = TRUE + if(!world.OpenPort(0)) //open any port + TGS_ERROR_LOG("Unable to open random port to retrieve new port![TGS4_PORT_CRITFAIL_MESSAGE]") + del(world) + + //request a new port + export_lock = FALSE + var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose + + if(!new_port_json) + TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]") + del(world) + + var/new_port = new_port_json[TGS4_PARAMETER_DATA] + if(!isnum(new_port) || new_port <= 0) + TGS_ERROR_LOG("Malformed new port json ([json_encode(new_port_json)])![TGS4_PORT_CRITFAIL_MESSAGE]") + del(world) + + if(new_port != world.port && !world.OpenPort(new_port)) + TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]") + del(world) + requesting_new_port = FALSE + + while(export_lock) + sleep(1) + export_lock = TRUE + + last_interop_response = null + fdel(server_commands_json_path) + text2file(json, server_commands_json_path) + + for(var/I = 0; I < EXPORT_TIMEOUT_DS && !last_interop_response; ++I) + sleep(1) + + if(!last_interop_response) + TGS_ERROR_LOG("Failed to get export result for: [json]") + else + . = last_interop_response + + export_lock = FALSE + +/datum/tgs_api/v4/OnReboot() + var/list/result = Export(TGS4_COMM_WORLD_REBOOT) + if(!result) + return + + //okay so the standard TGS4 proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter + + var/port = result[TGS4_PARAMETER_DATA] + if(!isnum(port)) + return //this is valid, server may just want use to reboot + + if(port == 0) + //to byond 0 means any port and "none" means close vOv + port = "none" + + if(!world.OpenPort(port)) + TGS_ERROR_LOG("Unable to set port to [port]!") + +/datum/tgs_api/v4/InstanceName() + return instance_name + +/datum/tgs_api/v4/TestMerges() + return cached_test_merges + +/datum/tgs_api/v4/EndProcess() + Export(TGS4_COMM_END_PROCESS) + +/datum/tgs_api/v4/Revision() + return cached_revision + +/datum/tgs_api/v4/ChatBroadcast(message, list/channels) + var/list/ids + if(length(channels)) + ids = list() + for(var/I in channels) + var/datum/tgs_chat_channel/channel = I + ids += channel.id + message = list("message" = message, "channelIds" = ids) + if(intercepted_message_queue) + intercepted_message_queue += list(message) + else + Export(TGS4_COMM_CHAT, message) + +/datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) + var/list/channels = list() + for(var/I in ChatChannelInfo()) + var/datum/tgs_chat_channel/channel = I + if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only))) + channels += channel.id + message = list("message" = message, "channelIds" = channels) + if(intercepted_message_queue) + intercepted_message_queue += list(message) + else + Export(TGS4_COMM_CHAT, message) + +/datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) + message = list("message" = message, "channelIds" = list(user.channel.id)) + if(intercepted_message_queue) + intercepted_message_queue += list(message) + else + Export(TGS4_COMM_CHAT, message) + +/datum/tgs_api/v4/ChatChannelInfo() + . = list() + //no caching cause tgs may change this + var/list/json = json_decode(file2text(chat_channels_json_path)) + for(var/I in json) + . += DecodeChannel(I) + +/datum/tgs_api/v4/proc/DecodeChannel(channel_json) + var/datum/tgs_chat_channel/channel = new + channel.id = channel_json["id"] + channel.friendly_name = channel_json["friendlyName"] + channel.connection_name = channel_json["connectionName"] + channel.is_admin_channel = channel_json["isAdminChannel"] + channel.is_private_channel = channel_json["isPrivateChannel"] + channel.custom_tag = channel_json["tag"] + return channel + +/datum/tgs_api/v4/SecurityLevel() + return security_level + +/* +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 9b942954149c..77ee3697ba68 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 2e920c7e2f5d..a0352db0dddc 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -1,14 +1,14 @@ -//include unit test files in this module in this ifdef -//Keep this sorted alphabetically - -#ifdef UNIT_TESTS -#include "anchored_mobs.dm" -#include "component_tests.dm" -#include "reagent_id_typos.dm" -#include "reagent_recipe_collisions.dm" -#include "spawn_humans.dm" -#include "species_whitelists.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 "anchored_mobs.dm" +#include "component_tests.dm" +#include "reagent_id_typos.dm" +#include "reagent_recipe_collisions.dm" +#include "spawn_humans.dm" +#include "species_whitelists.dm" +#include "subsystem_init.dm" +#include "timer_sanity.dm" +#include "unit_test.dm" +#endif diff --git a/code/modules/unit_tests/anchored_mobs.dm b/code/modules/unit_tests/anchored_mobs.dm index d68b02b5dace..103b97e7a993 100644 --- a/code/modules/unit_tests/anchored_mobs.dm +++ b/code/modules/unit_tests/anchored_mobs.dm @@ -1,9 +1,9 @@ -/datum/unit_test/anchored_mobs/Run() - var/list/L = list() - for(var/i in typesof(/mob)) - var/mob/M = i - if(initial(M.anchored)) - L += "[i]" - if(!L.len) - return //passed! - Fail("The following mobs are defined as anchored. This is incompatible with the new move force/resist system and needs to be revised.: [L.Join(" ")]") +/datum/unit_test/anchored_mobs/Run() + var/list/L = list() + for(var/i in typesof(/mob)) + var/mob/M = i + if(initial(M.anchored)) + L += "[i]" + if(!L.len) + return //passed! + Fail("The following mobs are defined as anchored. This is incompatible with the new move force/resist system and needs to be revised.: [L.Join(" ")]") diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm index 7cf465a33140..409d7f432224 100644 --- a/code/modules/unit_tests/component_tests.dm +++ b/code/modules/unit_tests/component_tests.dm @@ -1,12 +1,12 @@ -/datum/unit_test/component_duping/Run() - var/list/bad_dms = list() - var/list/bad_dts = list() - for(var/t in typesof(/datum/component)) - var/datum/component/comp = t - if(!isnum(initial(comp.dupe_mode))) - bad_dms += t - var/dupe_type = initial(comp.dupe_type) - if(dupe_type && !ispath(dupe_type)) - bad_dts += t - if(length(bad_dms) || length(bad_dts)) +/datum/unit_test/component_duping/Run() + var/list/bad_dms = list() + var/list/bad_dts = list() + for(var/t in typesof(/datum/component)) + var/datum/component/comp = t + if(!isnum(initial(comp.dupe_mode))) + bad_dms += t + var/dupe_type = initial(comp.dupe_type) + if(dupe_type && !ispath(dupe_type)) + bad_dts += t + if(length(bad_dms) || length(bad_dts)) Fail("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])") \ No newline at end of file diff --git a/code/modules/unit_tests/spawn_humans.dm b/code/modules/unit_tests/spawn_humans.dm index f66b3628dbdc..7189e87277de 100644 --- a/code/modules/unit_tests/spawn_humans.dm +++ b/code/modules/unit_tests/spawn_humans.dm @@ -1,7 +1,7 @@ -/datum/unit_test/spawn_humans/Run() - var/locs = block(run_loc_bottom_left, run_loc_top_right) - - for(var/I in 1 to 5) - new /mob/living/carbon/human(pick(locs)) - - sleep(50) +/datum/unit_test/spawn_humans/Run() + var/locs = block(run_loc_bottom_left, run_loc_top_right) + + for(var/I in 1 to 5) + new /mob/living/carbon/human(pick(locs)) + + sleep(50) diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 492bfecf4d9a..c85b64cd434b 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -1,77 +1,77 @@ -/* - -Usage: -Override /Run() to run your test code - -Call Fail() to fail the test (You should specify a reason) - -You may use /New() and /Destroy() for setup/teardown respectively - -You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing - -*/ - -GLOBAL_DATUM(current_test, /datum/unit_test) -GLOBAL_VAR_INIT(failed_any_test, FALSE) -GLOBAL_VAR(test_log) - -/datum/unit_test - //Bit of metadata for the future maybe - var/list/procs_tested - - //usable vars - var/turf/run_loc_bottom_left - var/turf/run_loc_top_right - - //internal shit - var/succeeded = TRUE - var/list/fail_reasons - -/datum/unit_test/New() - run_loc_bottom_left = locate(1, 1, 1) - run_loc_top_right = locate(5, 5, 1) - -/datum/unit_test/Destroy() - //clear the test area - for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right)) - qdel(AM) - return ..() - -/datum/unit_test/proc/Run() - Fail("Run() called parent or not implemented") - -/datum/unit_test/proc/Fail(reason = "No reason") - succeeded = FALSE - - if(!istext(reason)) - reason = "FORMATTED: [reason != null ? reason : "NULL"]" - - LAZYADD(fail_reasons, reason) - -/proc/RunUnitTests() - CHECK_TICK - - for(var/I in subtypesof(/datum/unit_test)) - var/datum/unit_test/test = new I - - GLOB.current_test = test - var/duration = REALTIMEOFDAY - - test.Run() - - duration = REALTIMEOFDAY - duration - GLOB.current_test = null - GLOB.failed_any_test |= !test.succeeded - - var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [I] [duration / 10]s") - var/list/fail_reasons = test.fail_reasons - - qdel(test) - - for(var/J in 1 to LAZYLEN(fail_reasons)) - log_entry += "\tREASON #[J]: [fail_reasons[J]]" - log_test(log_entry.Join("\n")) - - CHECK_TICK - - SSticker.force_ending = TRUE +/* + +Usage: +Override /Run() to run your test code + +Call Fail() to fail the test (You should specify a reason) + +You may use /New() and /Destroy() for setup/teardown respectively + +You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing + +*/ + +GLOBAL_DATUM(current_test, /datum/unit_test) +GLOBAL_VAR_INIT(failed_any_test, FALSE) +GLOBAL_VAR(test_log) + +/datum/unit_test + //Bit of metadata for the future maybe + var/list/procs_tested + + //usable vars + var/turf/run_loc_bottom_left + var/turf/run_loc_top_right + + //internal shit + var/succeeded = TRUE + var/list/fail_reasons + +/datum/unit_test/New() + run_loc_bottom_left = locate(1, 1, 1) + run_loc_top_right = locate(5, 5, 1) + +/datum/unit_test/Destroy() + //clear the test area + for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right)) + qdel(AM) + return ..() + +/datum/unit_test/proc/Run() + Fail("Run() called parent or not implemented") + +/datum/unit_test/proc/Fail(reason = "No reason") + succeeded = FALSE + + if(!istext(reason)) + reason = "FORMATTED: [reason != null ? reason : "NULL"]" + + LAZYADD(fail_reasons, reason) + +/proc/RunUnitTests() + CHECK_TICK + + for(var/I in subtypesof(/datum/unit_test)) + var/datum/unit_test/test = new I + + GLOB.current_test = test + var/duration = REALTIMEOFDAY + + test.Run() + + duration = REALTIMEOFDAY - duration + GLOB.current_test = null + GLOB.failed_any_test |= !test.succeeded + + var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [I] [duration / 10]s") + var/list/fail_reasons = test.fail_reasons + + qdel(test) + + for(var/J in 1 to LAZYLEN(fail_reasons)) + log_entry += "\tREASON #[J]: [fail_reasons[J]]" + log_test(log_entry.Join("\n")) + + CHECK_TICK + + SSticker.force_ending = TRUE diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm index 44f713a92ed9..add2cf4030f9 100644 --- a/code/modules/uplink/uplink_devices.dm +++ b/code/modules/uplink/uplink_devices.dm @@ -1,76 +1,76 @@ -// 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/debug - name = "debug uplink" - -/obj/item/uplink/debug/Initialize(mapload, owner, tc_amount = 9000) - . = ..() - var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) - hidden_uplink.name = "debug uplink" - -/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/debug - name = "debug nuclear uplink" - -/obj/item/uplink/nuclear/debug/Initialize(mapload, owner, tc_amount = 9000) - . = ..() - var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) - hidden_uplink.set_gamemode(/datum/game_mode/nuclear) - hidden_uplink.name = "debug nuclear uplink" - -/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/debug + name = "debug uplink" + +/obj/item/uplink/debug/Initialize(mapload, owner, tc_amount = 9000) + . = ..() + var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) + hidden_uplink.name = "debug uplink" + +/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/debug + name = "debug nuclear uplink" + +/obj/item/uplink/nuclear/debug/Initialize(mapload, owner, tc_amount = 9000) + . = ..() + var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) + hidden_uplink.set_gamemode(/datum/game_mode/nuclear) + hidden_uplink.name = "debug nuclear uplink" + +/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 2f199efe9428..8f63f70d865d 100644 --- a/code/modules/vehicles/lavaboat.dm +++ b/code/modules/vehicles/lavaboat.dm @@ -1,71 +1,71 @@ - -//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 - legs_required = 0 - arms_required = 0 - -/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 + legs_required = 0 + arms_required = 0 + +/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 caddcebe6d4b..719222add7b7 100644 --- a/code/modules/vehicles/ridden.dm +++ b/code/modules/vehicles/ridden.dm @@ -1,100 +1,100 @@ -/obj/vehicle/ridden - name = "ridden vehicle" - can_buckle = TRUE - max_buckled_mobs = 1 - buckle_lying = FALSE - default_driver_move = FALSE - var/legs_required = 2 - var/arms_required = 1 //why not? - var/fall_off_if_missing_arms = FALSE //heh... - var/message_cooldown = 0 - -/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) - 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 ..() - -/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 - if(legs_required) - var/how_many_legs = user.get_num_legs() - if(how_many_legs < legs_required) - if(message_cooldown < world.time) - to_chat(user, "You can't seem to manage that with[how_many_legs ? " your leg[how_many_legs > 1 ? "s" : null]" : "out legs"]...") - message_cooldown = world.time + 5 SECONDS - return FALSE - if(arms_required) - var/how_many_arms = user.get_num_arms() - if(how_many_arms < arms_required) - if(fall_off_if_missing_arms) - unbuckle_mob(user, TRUE) - user.visible_message("[user] falls off of \the [src].",\ - "You fall of \the [src] while trying to operate it without [arms_required ? "both arms":"an arm"]!") - if(isliving(user)) - var/mob/living/L = user - L.Stun(30) - return FALSE - - if(message_cooldown < world.time) - to_chat(user, "You can't seem to manage that with[how_many_arms ? " your arm[how_many_arms > 1 ? "s" : null]" : "out arms"]...") - message_cooldown = world.time + 5 SECONDS - 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 +/obj/vehicle/ridden + name = "ridden vehicle" + can_buckle = TRUE + max_buckled_mobs = 1 + buckle_lying = FALSE + default_driver_move = FALSE + var/legs_required = 2 + var/arms_required = 1 //why not? + var/fall_off_if_missing_arms = FALSE //heh... + var/message_cooldown = 0 + +/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) + 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 ..() + +/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 + if(legs_required) + var/how_many_legs = user.get_num_legs() + if(how_many_legs < legs_required) + if(message_cooldown < world.time) + to_chat(user, "You can't seem to manage that with[how_many_legs ? " your leg[how_many_legs > 1 ? "s" : null]" : "out legs"]...") + message_cooldown = world.time + 5 SECONDS + return FALSE + if(arms_required) + var/how_many_arms = user.get_num_arms() + if(how_many_arms < arms_required) + if(fall_off_if_missing_arms) + unbuckle_mob(user, TRUE) + user.visible_message("[user] falls off of \the [src].",\ + "You fall of \the [src] while trying to operate it without [arms_required ? "both arms":"an arm"]!") + if(isliving(user)) + var/mob/living/L = user + L.Stun(30) + return FALSE + + if(message_cooldown < world.time) + to_chat(user, "You can't seem to manage that with[how_many_arms ? " your arm[how_many_arms > 1 ? "s" : null]" : "out arms"]...") + message_cooldown = world.time + 5 SECONDS + 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 ..() \ No newline at end of file diff --git a/code/modules/vehicles/vehicle_actions.dm b/code/modules/vehicles/vehicle_actions.dm index 745c8df8f26e..134fde89e660 100644 --- a/code/modules/vehicles/vehicle_actions.dm +++ b/code/modules/vehicles/vehicle_actions.dm @@ -1,193 +1,193 @@ -//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) - -/datum/action/vehicle/sealed/Cannon - name = "Toggle siege mode" - desc = "Destroy them with their own fodder" - button_icon_state = "car_cannon" - -/datum/action/vehicle/sealed/Cannon/Trigger() - if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) - var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target - if(C.cannonbusy) - to_chat(owner, "Please wait for the vehicle to finish its current action first.") - C.ToggleCannon() - -/datum/action/vehicle/sealed/Thank - name = "Thank the Clown car Driver" - desc = "They're just doing their job." - button_icon_state = "car_thanktheclown" - var/last_thank_time - -/datum/action/vehicle/sealed/Thank/Trigger() - if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) - var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target - if(world.time >= last_thank_time + 60) - var/mob/living/carbon/human/clown = pick(C.return_drivers()) - owner.say("Thank you for the fun ride, [clown.name]!") - last_thank_time = world.time - C.ThanksCounter() +//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) + +/datum/action/vehicle/sealed/Cannon + name = "Toggle siege mode" + desc = "Destroy them with their own fodder" + button_icon_state = "car_cannon" + +/datum/action/vehicle/sealed/Cannon/Trigger() + if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) + var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target + if(C.cannonbusy) + to_chat(owner, "Please wait for the vehicle to finish its current action first.") + C.ToggleCannon() + +/datum/action/vehicle/sealed/Thank + name = "Thank the Clown car Driver" + desc = "They're just doing their job." + button_icon_state = "car_thanktheclown" + var/last_thank_time + +/datum/action/vehicle/sealed/Thank/Trigger() + if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) + var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target + if(world.time >= last_thank_time + 60) + var/mob/living/carbon/human/clown = pick(C.return_drivers()) + owner.say("Thank you for the fun ride, [clown.name]!") + last_thank_time = world.time + C.ThanksCounter() diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm index 204a10bd17e8..e34ad9253d5c 100644 --- a/code/modules/vehicles/vehicle_key.dm +++ b/code/modules/vehicles/vehicle_key.dm @@ -1,15 +1,15 @@ -/obj/item/key - name = "key" - desc = "A small grey key." - icon = 'icons/obj/vehicles.dmi' - icon_state = "key" - w_class = WEIGHT_CLASS_TINY - -/obj/item/key/security - desc = "A keyring with a small steel key, and a rubber stun baton accessory." - icon_state = "keysec" - -/obj/item/key/janitor - desc = "A keyring with a small steel key, and a pink fob reading \"Pussy Wagon\"." - icon_state = "keyjanitor" - +/obj/item/key + name = "key" + desc = "A small grey key." + icon = 'icons/obj/vehicles.dmi' + icon_state = "key" + w_class = WEIGHT_CLASS_TINY + +/obj/item/key/security + desc = "A keyring with a small steel key, and a rubber stun baton accessory." + icon_state = "keysec" + +/obj/item/key/janitor + desc = "A keyring with a small steel key, and a pink fob reading \"Pussy Wagon\"." + icon_state = "keyjanitor" + diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index 38144fb1ca74..c469061243c5 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -1,406 +1,406 @@ -/obj/item/vending_refill/wardrobe - icon_state = "refill_clothes" - -/obj/machinery/vending/wardrobe - default_price = 50 - extra_price = 75 - payment_department = NO_FREEBIES - input_display_header = "Returned Clothing" - -/obj/machinery/vending/wardrobe/canLoadItem(obj/item/I,mob/user) - return (I.type in products) - -/obj/machinery/vending/wardrobe/sec_wardrobe - name = "\improper SecDrobe" - desc = "A vending machine for security and security-related clothing!" - icon_state = "secdrobe" - product_ads = "Beat perps in style!;It's red so you can't see the blood!;You have the right to be fashionable!;Now you can be the fashion police you always wanted to be!" - vend_reply = "Thank you for using the SecDrobe!" - products = list(/obj/item/clothing/suit/hooded/wintercoat/security = 3, - /obj/item/storage/backpack/security = 3, - /obj/item/storage/backpack/satchel/sec = 3, - /obj/item/storage/backpack/duffelbag/sec = 3, - /obj/item/clothing/under/rank/security = 3, - /obj/item/clothing/shoes/jackboots = 3, - /obj/item/clothing/head/beret/sec = 3, - /obj/item/clothing/head/soft/sec = 3, - /obj/item/clothing/mask/bandana/red = 3, - /obj/item/clothing/under/rank/security/skirt = 3, - /obj/item/clothing/under/rank/security/grey = 3, - /obj/item/clothing/under/pants/khaki = 3, - /obj/item/clothing/under/rank/security/blueshirt = 3) - premium = list(/obj/item/clothing/under/rank/security/navyblue = 3, - /obj/item/clothing/suit/security/officer = 3, - /obj/item/clothing/head/beret/sec/navyofficer = 3) - refill_canister = /obj/item/vending_refill/wardrobe/sec_wardrobe - payment_department = ACCOUNT_SEC - -/obj/item/vending_refill/wardrobe/sec_wardrobe - machine_name = "SecDrobe" - -/obj/machinery/vending/wardrobe/medi_wardrobe - name = "\improper MediDrobe" - desc = "A vending machine rumoured to be capable of dispensing clothing for medical personnel." - icon_state = "medidrobe" - product_ads = "Make those blood stains look fashionable!!" - vend_reply = "Thank you for using the MediDrobe!" - products = list(/obj/item/clothing/accessory/pocketprotector = 4, - /obj/item/storage/backpack/duffelbag/med = 4, - /obj/item/storage/backpack/medic = 4, - /obj/item/storage/backpack/satchel/med = 4, - /obj/item/clothing/suit/hooded/wintercoat/medical = 4, - /obj/item/clothing/under/rank/nursesuit = 4, - /obj/item/clothing/head/nursehat = 4, - /obj/item/clothing/under/rank/medical/skirt= 4, - /obj/item/clothing/under/rank/medical/blue = 4, - /obj/item/clothing/under/rank/medical/green = 4, - /obj/item/clothing/under/rank/medical/purple = 4, - /obj/item/clothing/under/rank/medical = 4, - /obj/item/clothing/suit/toggle/labcoat = 4, - /obj/item/clothing/suit/toggle/labcoat/emt = 4, - /obj/item/clothing/shoes/sneakers/white = 4, - /obj/item/clothing/head/soft/emt = 4, - /obj/item/clothing/suit/apron/surgical = 4, - /obj/item/clothing/mask/surgical = 4) - refill_canister = /obj/item/vending_refill/wardrobe/medi_wardrobe - payment_department = ACCOUNT_MED -/obj/item/vending_refill/wardrobe/medi_wardrobe - machine_name = "MediDrobe" - -/obj/machinery/vending/wardrobe/engi_wardrobe - name = "EngiDrobe" - desc = "A vending machine renowned for vending industrial grade clothing." - icon_state = "engidrobe" - product_ads = "Guaranteed to protect your feet from industrial accidents!;Afraid of radiation? Then wear yellow!" - vend_reply = "Thank you for using the EngiDrobe!" - products = list(/obj/item/clothing/accessory/pocketprotector = 3, - /obj/item/storage/backpack/duffelbag/engineering = 3, - /obj/item/storage/backpack/industrial = 3, - /obj/item/storage/backpack/satchel/eng = 3, - /obj/item/clothing/suit/hooded/wintercoat/engineering = 3, - /obj/item/clothing/under/rank/engineer = 3, - /obj/item/clothing/under/rank/engineer/skirt = 3, - /obj/item/clothing/under/rank/engineer/hazard = 3, - /obj/item/clothing/suit/hazardvest = 3, - /obj/item/clothing/shoes/workboots = 3, - /obj/item/clothing/head/hardhat = 3, - /obj/item/clothing/head/hardhat/weldhat = 3) - refill_canister = /obj/item/vending_refill/wardrobe/engi_wardrobe - payment_department = ACCOUNT_ENG -/obj/item/vending_refill/wardrobe/engi_wardrobe - machine_name = "EngiDrobe" - -/obj/machinery/vending/wardrobe/atmos_wardrobe - name = "AtmosDrobe" - desc = "This relatively unknown vending machine delivers clothing for Atmospherics Technicians, an equally unknown job." - icon_state = "atmosdrobe" - product_ads = "Get your inflammable clothing right here!!!" - vend_reply = "Thank you for using the AtmosDrobe!" - products = list(/obj/item/clothing/accessory/pocketprotector = 2, - /obj/item/storage/backpack/duffelbag/engineering = 2, - /obj/item/storage/backpack/satchel/eng = 2, - /obj/item/storage/backpack/industrial = 2, - /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3, - /obj/item/clothing/under/rank/atmospheric_technician = 3, - /obj/item/clothing/under/rank/atmospheric_technician/skirt = 3, - /obj/item/clothing/shoes/sneakers/black = 3) - refill_canister = /obj/item/vending_refill/wardrobe/atmos_wardrobe - payment_department = ACCOUNT_ENG -/obj/item/vending_refill/wardrobe/atmos_wardrobe - machine_name = "AtmosDrobe" - -/obj/machinery/vending/wardrobe/cargo_wardrobe - name = "CargoDrobe" - desc = "A highly advanced vending machine for buying cargo related clothing for free." - icon_state = "cargodrobe" - product_ads = "Upgraded Assistant Style! Pick yours today!;These shorts are comfy and easy to wear, get yours now!" - vend_reply = "Thank you for using the CargoDrobe!" - products = list(/obj/item/clothing/suit/hooded/wintercoat/cargo = 3, - /obj/item/clothing/under/rank/cargotech = 3, - /obj/item/clothing/under/rank/cargotech/skirt = 3, - /obj/item/clothing/shoes/sneakers/black = 3, - /obj/item/clothing/gloves/fingerless = 3, - /obj/item/clothing/head/soft = 3, - /obj/item/radio/headset/headset_cargo = 3) - premium = list(/obj/item/clothing/under/rank/miner = 3) - refill_canister = /obj/item/vending_refill/wardrobe/cargo_wardrobe - payment_department = ACCOUNT_CAR -/obj/item/vending_refill/wardrobe/cargo_wardrobe - machine_name = "CargoDrobe" - -/obj/machinery/vending/wardrobe/robo_wardrobe - name = "RoboDrobe" - desc = "A vending machine designed to dispense clothing known only to roboticists." - icon_state = "robodrobe" - product_ads = "You turn me TRUE, use defines!;0110001101101100011011110111010001101000011001010111001101101000011001010111001001100101" - vend_reply = "Thank you for using the RoboDrobe!" - products = list(/obj/item/clothing/glasses/hud/diagnostic = 2, - /obj/item/clothing/under/rank/roboticist = 2, - /obj/item/clothing/under/rank/roboticist/skirt = 2, - /obj/item/clothing/suit/toggle/labcoat = 2, - /obj/item/clothing/shoes/sneakers/black = 2, - /obj/item/clothing/gloves/fingerless = 2, - /obj/item/clothing/head/soft/black = 2, - /obj/item/clothing/mask/bandana/skull = 2) - refill_canister = /obj/item/vending_refill/wardrobe/robo_wardrobe - payment_department = ACCOUNT_SCI -/obj/item/vending_refill/wardrobe/robo_wardrobe - machine_name = "RoboDrobe" - -/obj/machinery/vending/wardrobe/science_wardrobe - name = "SciDrobe" - desc = "A simple vending machine suitable to dispense well tailored science clothing. Endorsed by Space Cubans." - icon_state = "scidrobe" - product_ads = "Longing for the smell of plasma burnt flesh? Buy your science clothing now!;Made with 10% Auxetics, so you don't have to worry about losing your arm!" - vend_reply = "Thank you for using the SciDrobe!" - products = list(/obj/item/clothing/accessory/pocketprotector = 3, - /obj/item/storage/backpack/science = 3, - /obj/item/storage/backpack/satchel/tox = 3, - /obj/item/clothing/suit/hooded/wintercoat/science = 3, - /obj/item/clothing/under/rank/scientist = 3, - /obj/item/clothing/under/rank/scientist/skirt = 3, - /obj/item/clothing/suit/toggle/labcoat/science = 3, - /obj/item/clothing/shoes/sneakers/white = 3, - /obj/item/radio/headset/headset_sci = 3, - /obj/item/clothing/mask/gas = 3) - refill_canister = /obj/item/vending_refill/wardrobe/science_wardrobe - payment_department = ACCOUNT_SCI -/obj/item/vending_refill/wardrobe/science_wardrobe - machine_name = "SciDrobe" - -/obj/machinery/vending/wardrobe/hydro_wardrobe - name = "Hydrobe" - desc = "A machine with a catchy name. It dispenses botany related clothing and gear." - icon_state = "hydrobe" - product_ads = "Do you love soil? Then buy our clothes!;Get outfits to match your green thumb here!" - vend_reply = "Thank you for using the Hydrobe!" - products = list(/obj/item/storage/backpack/botany = 2, - /obj/item/storage/backpack/satchel/hyd = 2, - /obj/item/clothing/suit/hooded/wintercoat/hydro = 2, - /obj/item/clothing/suit/apron = 2, - /obj/item/clothing/suit/apron/overalls = 3, - /obj/item/clothing/under/rank/hydroponics = 3, - /obj/item/clothing/under/rank/hydroponics/skirt = 3, - /obj/item/clothing/mask/bandana = 3, - /obj/item/clothing/accessory/armband/hydro = 3) - refill_canister = /obj/item/vending_refill/wardrobe/hydro_wardrobe - payment_department = ACCOUNT_SRV -/obj/item/vending_refill/wardrobe/hydro_wardrobe - machine_name = "HyDrobe" - -/obj/machinery/vending/wardrobe/curator_wardrobe - name = "CuraDrobe" - desc = "A lowstock vendor only capable of vending clothing for curators and librarians." - icon_state = "curadrobe" - product_ads = "Glasses for your eyes and literature for your soul, Curadrobe has it all!; Impress & enthrall your library guests with Curadrobe's extended line of pens!" - vend_reply = "Thank you for using the CuraDrobe!" - products = list(/obj/item/pen = 4, - /obj/item/pen/red = 2, - /obj/item/pen/blue = 2, - /obj/item/pen/fourcolor = 1, - /obj/item/pen/fountain = 2, - /obj/item/clothing/accessory/pocketprotector = 2, - /obj/item/clothing/under/rank/curator/skirt = 2, - /obj/item/clothing/under/gimmick/rank/captain/suit/skirt = 2, - /obj/item/clothing/under/gimmick/rank/head_of_personnel/suit/skirt = 2, - /obj/item/storage/backpack/satchel/explorer = 1, - /obj/item/clothing/glasses/regular = 2, - /obj/item/clothing/glasses/regular/jamjar = 1, - /obj/item/storage/bag/books = 1) - refill_canister = /obj/item/vending_refill/wardrobe/curator_wardrobe - payment_department = ACCOUNT_CIV -/obj/item/vending_refill/wardrobe/curator_wardrobe - machine_name = "CuraDrobe" - -/obj/machinery/vending/wardrobe/bar_wardrobe - name = "BarDrobe" - desc = "A stylish vendor to dispense the most stylish bar clothing!" - icon_state = "bardrobe" - product_ads = "Guaranteed to prevent stains from spilled drinks!" - vend_reply = "Thank you for using the BarDrobe!" - products = list(/obj/item/clothing/head/that = 2, - /obj/item/radio/headset/headset_srv = 2, - /obj/item/clothing/under/sl_suit = 2, - /obj/item/clothing/under/rank/bartender = 2, - /obj/item/clothing/under/rank/bartender/purple = 2, - /obj/item/clothing/under/rank/bartender/skirt = 2, - /obj/item/clothing/accessory/waistcoat = 2, - /obj/item/clothing/suit/apron/purple_bartender = 2, - /obj/item/clothing/head/soft/black = 2, - /obj/item/clothing/shoes/sneakers/black = 2, - /obj/item/reagent_containers/glass/rag = 2, - /obj/item/storage/box/beanbag = 1, - /obj/item/clothing/suit/armor/vest/alt = 1, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/clothing/glasses/sunglasses/reagent = 1, - /obj/item/storage/belt/bandolier = 1) // yogs - removed pet collar - premium = list(/obj/item/storage/box/dishdrive = 1) - refill_canister = /obj/item/vending_refill/wardrobe/bar_wardrobe - payment_department = ACCOUNT_SRV -/obj/item/vending_refill/wardrobe/bar_wardrobe - machine_name = "BarDrobe" - -/obj/machinery/vending/wardrobe/chef_wardrobe - name = "ChefDrobe" - desc = "This vending machine might not dispense meat, but it certainly dispenses chef related clothing." - icon_state = "chefdrobe" - product_ads = "Our clothes are guaranteed to protect you from food splatters!" - vend_reply = "Thank you for using the ChefDrobe!" - products = list(/obj/item/clothing/under/waiter = 2, - /obj/item/radio/headset/headset_srv = 2, - /obj/item/clothing/accessory/waistcoat = 2, - /obj/item/clothing/suit/apron/chef = 3, - /obj/item/clothing/head/soft/mime = 2, - /obj/item/storage/box/mousetraps = 2, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/clothing/suit/toggle/chef = 1, - /obj/item/clothing/under/rank/chef = 1, - /obj/item/clothing/under/rank/chef/skirt = 2, - /obj/item/clothing/head/chefhat = 1, - /obj/item/reagent_containers/glass/rag = 1, - /obj/item/clothing/suit/hooded/wintercoat = 2) - refill_canister = /obj/item/vending_refill/wardrobe/chef_wardrobe - payment_department = ACCOUNT_SRV -/obj/item/vending_refill/wardrobe/chef_wardrobe - machine_name = "ChefDrobe" - -/obj/machinery/vending/wardrobe/jani_wardrobe - name = "JaniDrobe" - desc = "A self cleaning vending machine capable of dispensing clothing for janitors." - icon_state = "janidrobe" - product_ads = "Come and get your janitorial clothing, now endorsed by lizard janitors everywhere!" - vend_reply = "Thank you for using the JaniDrobe!" - products = list(/obj/item/clothing/under/rank/janitor = 1, - /obj/item/cartridge/janitor = 1, - /obj/item/clothing/under/rank/janitor/skirt = 2, - /obj/item/clothing/gloves/color/black = 1, - /obj/item/clothing/head/soft/purple = 1, - /obj/item/paint/paint_remover = 1, - /obj/item/melee/flyswatter = 1, - /obj/item/flashlight = 1, - /obj/item/clothing/suit/caution = 6, - /obj/item/holosign_creator/janibarrier = 1, - /obj/item/lightreplacer = 1, - /obj/item/soap/nanotrasen = 1, - /obj/item/storage/bag/trash = 1, - /obj/item/clothing/shoes/galoshes = 1, - /obj/item/watertank/janitor = 1, - /obj/item/storage/belt/janitor = 1) - refill_canister = /obj/item/vending_refill/wardrobe/jani_wardrobe - payment_department = ACCOUNT_SRV -/obj/item/vending_refill/wardrobe/jani_wardrobe - machine_name = "JaniDrobe" - -/obj/machinery/vending/wardrobe/law_wardrobe - name = "LawDrobe" - desc = "Objection! This wardrobe dispenses the rule of law... and lawyer clothing." - icon_state = "lawdrobe" - product_ads = "OBJECTION! Get the rule of law for yourself!" - vend_reply = "Thank you for using the LawDrobe!" - products = list(/obj/item/clothing/under/lawyer/female = 1, - /obj/item/clothing/under/lawyer/black = 1, - /obj/item/clothing/under/lawyer/black/skirt = 1, - /obj/item/clothing/under/lawyer/red = 1, - /obj/item/clothing/under/lawyer/red/skirt = 1, - /obj/item/clothing/under/lawyer/bluesuit = 1, - /obj/item/clothing/under/lawyer/bluesuit/skirt = 1, - /obj/item/clothing/under/lawyer/blue/skirt = 1, - /obj/item/clothing/suit/toggle/lawyer = 1, - /obj/item/clothing/under/lawyer/purpsuit = 1, - /obj/item/clothing/under/lawyer/purpsuit/skirt = 1, - /obj/item/clothing/suit/toggle/lawyer/purple = 1, - /obj/item/clothing/under/lawyer/blacksuit = 1, - /obj/item/clothing/under/lawyer/blacksuit/skirt = 1, - /obj/item/clothing/suit/toggle/lawyer/black = 1, - /obj/item/clothing/shoes/laceup = 2, - /obj/item/clothing/accessory/lawyers_badge = 2) - refill_canister = /obj/item/vending_refill/wardrobe/law_wardrobe - payment_department = ACCOUNT_CIV -/obj/item/vending_refill/wardrobe/law_wardrobe - machine_name = "LawDrobe" - -/obj/machinery/vending/wardrobe/chap_wardrobe - name = "ChapDrobe" - desc = "This most blessed and holy machine vends clothing only suitable for chaplains to gaze upon." - icon_state = "chapdrobe" - product_ads = "Are you being bothered by cultists or pesky revenants? Then come and dress like the holy man!;Clothes for men of the cloth!" - vend_reply = "Thank you for using the ChapDrobe!" - products = list(/obj/item/choice_beacon/holy = 1, - /obj/item/storage/backpack/cultpack = 1, - /obj/item/clothing/accessory/pocketprotector/cosmetology = 1, - /obj/item/clothing/under/rank/chaplain = 1, - /obj/item/clothing/under/rank/chaplain/skirt = 2, - /obj/item/clothing/shoes/sneakers/black = 1, - /obj/item/clothing/suit/chaplainsuit/nun = 1, - /obj/item/clothing/head/nun_hood = 1, - /obj/item/clothing/suit/chaplainsuit/holidaypriest = 1, - /obj/item/storage/fancy/candle_box = 2, - /obj/item/clothing/head/kippah = 3, - /obj/item/clothing/suit/chaplainsuit/whiterobe = 1, - /obj/item/clothing/head/taqiyahwhite = 1, - /obj/item/clothing/head/taqiyahred = 3, - /obj/item/clothing/head/beanie/rasta = 1) - contraband = list(/obj/item/toy/plush/plushvar = 1, - /obj/item/toy/plush/narplush = 1, - /obj/item/clothing/head/medievaljewhat = 3, - /obj/item/clothing/suit/chaplainsuit/clownpriest = 1, - /obj/item/clothing/head/clownmitre = 1) - premium = list(/obj/item/clothing/suit/chaplainsuit/bishoprobe = 1, - /obj/item/clothing/head/bishopmitre = 1) - refill_canister = /obj/item/vending_refill/wardrobe/chap_wardrobe - payment_department = ACCOUNT_CIV -/obj/item/vending_refill/wardrobe/chap_wardrobe - machine_name = "ChapDrobe" - -/obj/machinery/vending/wardrobe/chem_wardrobe - name = "ChemDrobe" - desc = "A vending machine for dispensing chemistry related clothing." - icon_state = "chemdrobe" - product_ads = "Our clothes are 0.5% more resistant to acid spills! Get yours now!" - vend_reply = "Thank you for using the ChemDrobe!" - products = list(/obj/item/clothing/under/rank/chemist = 2, - /obj/item/clothing/under/rank/chemist/skirt = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/chemist = 2, - /obj/item/storage/backpack/chemistry = 2, - /obj/item/storage/backpack/satchel/chem = 2, - /obj/item/storage/bag/chemistry = 2) - refill_canister = /obj/item/vending_refill/wardrobe/chem_wardrobe - payment_department = ACCOUNT_MED -/obj/item/vending_refill/wardrobe/chem_wardrobe - machine_name = "ChemDrobe" - -/obj/machinery/vending/wardrobe/gene_wardrobe - name = "GeneDrobe" - desc = "A machine for dispensing clothing related to genetics." - icon_state = "genedrobe" - product_ads = "Perfect for the mad scientist in you!" - vend_reply = "Thank you for using the GeneDrobe!" - products = list(/obj/item/clothing/under/rank/geneticist = 2, - /obj/item/clothing/under/rank/geneticist/skirt = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/genetics = 2, - /obj/item/storage/backpack/genetics = 2, - /obj/item/storage/backpack/satchel/gen = 2) - refill_canister = /obj/item/vending_refill/wardrobe/gene_wardrobe - payment_department = ACCOUNT_MED -/obj/item/vending_refill/wardrobe/gene_wardrobe - machine_name = "GeneDrobe" - -/obj/machinery/vending/wardrobe/viro_wardrobe - name = "ViroDrobe" - desc = "An unsterilized machine for dispending virology related clothing." - icon_state = "virodrobe" - product_ads = " Viruses getting you down? Then upgrade to sterilized clothing today!" - vend_reply = "Thank you for using the ViroDrobe" - products = list(/obj/item/clothing/under/rank/virologist = 2, - /obj/item/clothing/under/rank/virologist/skirt = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/virologist = 2, - /obj/item/clothing/mask/surgical = 2, - /obj/item/storage/backpack/virology = 2, - /obj/item/storage/backpack/satchel/vir = 2) - refill_canister = /obj/item/vending_refill/wardrobe/viro_wardrobe - payment_department = ACCOUNT_MED -/obj/item/vending_refill/wardrobe/viro_wardrobe - machine_name = "ViroDrobe" +/obj/item/vending_refill/wardrobe + icon_state = "refill_clothes" + +/obj/machinery/vending/wardrobe + default_price = 50 + extra_price = 75 + payment_department = NO_FREEBIES + input_display_header = "Returned Clothing" + +/obj/machinery/vending/wardrobe/canLoadItem(obj/item/I,mob/user) + return (I.type in products) + +/obj/machinery/vending/wardrobe/sec_wardrobe + name = "\improper SecDrobe" + desc = "A vending machine for security and security-related clothing!" + icon_state = "secdrobe" + product_ads = "Beat perps in style!;It's red so you can't see the blood!;You have the right to be fashionable!;Now you can be the fashion police you always wanted to be!" + vend_reply = "Thank you for using the SecDrobe!" + products = list(/obj/item/clothing/suit/hooded/wintercoat/security = 3, + /obj/item/storage/backpack/security = 3, + /obj/item/storage/backpack/satchel/sec = 3, + /obj/item/storage/backpack/duffelbag/sec = 3, + /obj/item/clothing/under/rank/security = 3, + /obj/item/clothing/shoes/jackboots = 3, + /obj/item/clothing/head/beret/sec = 3, + /obj/item/clothing/head/soft/sec = 3, + /obj/item/clothing/mask/bandana/red = 3, + /obj/item/clothing/under/rank/security/skirt = 3, + /obj/item/clothing/under/rank/security/grey = 3, + /obj/item/clothing/under/pants/khaki = 3, + /obj/item/clothing/under/rank/security/blueshirt = 3) + premium = list(/obj/item/clothing/under/rank/security/navyblue = 3, + /obj/item/clothing/suit/security/officer = 3, + /obj/item/clothing/head/beret/sec/navyofficer = 3) + refill_canister = /obj/item/vending_refill/wardrobe/sec_wardrobe + payment_department = ACCOUNT_SEC + +/obj/item/vending_refill/wardrobe/sec_wardrobe + machine_name = "SecDrobe" + +/obj/machinery/vending/wardrobe/medi_wardrobe + name = "\improper MediDrobe" + desc = "A vending machine rumoured to be capable of dispensing clothing for medical personnel." + icon_state = "medidrobe" + product_ads = "Make those blood stains look fashionable!!" + vend_reply = "Thank you for using the MediDrobe!" + products = list(/obj/item/clothing/accessory/pocketprotector = 4, + /obj/item/storage/backpack/duffelbag/med = 4, + /obj/item/storage/backpack/medic = 4, + /obj/item/storage/backpack/satchel/med = 4, + /obj/item/clothing/suit/hooded/wintercoat/medical = 4, + /obj/item/clothing/under/rank/nursesuit = 4, + /obj/item/clothing/head/nursehat = 4, + /obj/item/clothing/under/rank/medical/skirt= 4, + /obj/item/clothing/under/rank/medical/blue = 4, + /obj/item/clothing/under/rank/medical/green = 4, + /obj/item/clothing/under/rank/medical/purple = 4, + /obj/item/clothing/under/rank/medical = 4, + /obj/item/clothing/suit/toggle/labcoat = 4, + /obj/item/clothing/suit/toggle/labcoat/emt = 4, + /obj/item/clothing/shoes/sneakers/white = 4, + /obj/item/clothing/head/soft/emt = 4, + /obj/item/clothing/suit/apron/surgical = 4, + /obj/item/clothing/mask/surgical = 4) + refill_canister = /obj/item/vending_refill/wardrobe/medi_wardrobe + payment_department = ACCOUNT_MED +/obj/item/vending_refill/wardrobe/medi_wardrobe + machine_name = "MediDrobe" + +/obj/machinery/vending/wardrobe/engi_wardrobe + name = "EngiDrobe" + desc = "A vending machine renowned for vending industrial grade clothing." + icon_state = "engidrobe" + product_ads = "Guaranteed to protect your feet from industrial accidents!;Afraid of radiation? Then wear yellow!" + vend_reply = "Thank you for using the EngiDrobe!" + products = list(/obj/item/clothing/accessory/pocketprotector = 3, + /obj/item/storage/backpack/duffelbag/engineering = 3, + /obj/item/storage/backpack/industrial = 3, + /obj/item/storage/backpack/satchel/eng = 3, + /obj/item/clothing/suit/hooded/wintercoat/engineering = 3, + /obj/item/clothing/under/rank/engineer = 3, + /obj/item/clothing/under/rank/engineer/skirt = 3, + /obj/item/clothing/under/rank/engineer/hazard = 3, + /obj/item/clothing/suit/hazardvest = 3, + /obj/item/clothing/shoes/workboots = 3, + /obj/item/clothing/head/hardhat = 3, + /obj/item/clothing/head/hardhat/weldhat = 3) + refill_canister = /obj/item/vending_refill/wardrobe/engi_wardrobe + payment_department = ACCOUNT_ENG +/obj/item/vending_refill/wardrobe/engi_wardrobe + machine_name = "EngiDrobe" + +/obj/machinery/vending/wardrobe/atmos_wardrobe + name = "AtmosDrobe" + desc = "This relatively unknown vending machine delivers clothing for Atmospherics Technicians, an equally unknown job." + icon_state = "atmosdrobe" + product_ads = "Get your inflammable clothing right here!!!" + vend_reply = "Thank you for using the AtmosDrobe!" + products = list(/obj/item/clothing/accessory/pocketprotector = 2, + /obj/item/storage/backpack/duffelbag/engineering = 2, + /obj/item/storage/backpack/satchel/eng = 2, + /obj/item/storage/backpack/industrial = 2, + /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3, + /obj/item/clothing/under/rank/atmospheric_technician = 3, + /obj/item/clothing/under/rank/atmospheric_technician/skirt = 3, + /obj/item/clothing/shoes/sneakers/black = 3) + refill_canister = /obj/item/vending_refill/wardrobe/atmos_wardrobe + payment_department = ACCOUNT_ENG +/obj/item/vending_refill/wardrobe/atmos_wardrobe + machine_name = "AtmosDrobe" + +/obj/machinery/vending/wardrobe/cargo_wardrobe + name = "CargoDrobe" + desc = "A highly advanced vending machine for buying cargo related clothing for free." + icon_state = "cargodrobe" + product_ads = "Upgraded Assistant Style! Pick yours today!;These shorts are comfy and easy to wear, get yours now!" + vend_reply = "Thank you for using the CargoDrobe!" + products = list(/obj/item/clothing/suit/hooded/wintercoat/cargo = 3, + /obj/item/clothing/under/rank/cargotech = 3, + /obj/item/clothing/under/rank/cargotech/skirt = 3, + /obj/item/clothing/shoes/sneakers/black = 3, + /obj/item/clothing/gloves/fingerless = 3, + /obj/item/clothing/head/soft = 3, + /obj/item/radio/headset/headset_cargo = 3) + premium = list(/obj/item/clothing/under/rank/miner = 3) + refill_canister = /obj/item/vending_refill/wardrobe/cargo_wardrobe + payment_department = ACCOUNT_CAR +/obj/item/vending_refill/wardrobe/cargo_wardrobe + machine_name = "CargoDrobe" + +/obj/machinery/vending/wardrobe/robo_wardrobe + name = "RoboDrobe" + desc = "A vending machine designed to dispense clothing known only to roboticists." + icon_state = "robodrobe" + product_ads = "You turn me TRUE, use defines!;0110001101101100011011110111010001101000011001010111001101101000011001010111001001100101" + vend_reply = "Thank you for using the RoboDrobe!" + products = list(/obj/item/clothing/glasses/hud/diagnostic = 2, + /obj/item/clothing/under/rank/roboticist = 2, + /obj/item/clothing/under/rank/roboticist/skirt = 2, + /obj/item/clothing/suit/toggle/labcoat = 2, + /obj/item/clothing/shoes/sneakers/black = 2, + /obj/item/clothing/gloves/fingerless = 2, + /obj/item/clothing/head/soft/black = 2, + /obj/item/clothing/mask/bandana/skull = 2) + refill_canister = /obj/item/vending_refill/wardrobe/robo_wardrobe + payment_department = ACCOUNT_SCI +/obj/item/vending_refill/wardrobe/robo_wardrobe + machine_name = "RoboDrobe" + +/obj/machinery/vending/wardrobe/science_wardrobe + name = "SciDrobe" + desc = "A simple vending machine suitable to dispense well tailored science clothing. Endorsed by Space Cubans." + icon_state = "scidrobe" + product_ads = "Longing for the smell of plasma burnt flesh? Buy your science clothing now!;Made with 10% Auxetics, so you don't have to worry about losing your arm!" + vend_reply = "Thank you for using the SciDrobe!" + products = list(/obj/item/clothing/accessory/pocketprotector = 3, + /obj/item/storage/backpack/science = 3, + /obj/item/storage/backpack/satchel/tox = 3, + /obj/item/clothing/suit/hooded/wintercoat/science = 3, + /obj/item/clothing/under/rank/scientist = 3, + /obj/item/clothing/under/rank/scientist/skirt = 3, + /obj/item/clothing/suit/toggle/labcoat/science = 3, + /obj/item/clothing/shoes/sneakers/white = 3, + /obj/item/radio/headset/headset_sci = 3, + /obj/item/clothing/mask/gas = 3) + refill_canister = /obj/item/vending_refill/wardrobe/science_wardrobe + payment_department = ACCOUNT_SCI +/obj/item/vending_refill/wardrobe/science_wardrobe + machine_name = "SciDrobe" + +/obj/machinery/vending/wardrobe/hydro_wardrobe + name = "Hydrobe" + desc = "A machine with a catchy name. It dispenses botany related clothing and gear." + icon_state = "hydrobe" + product_ads = "Do you love soil? Then buy our clothes!;Get outfits to match your green thumb here!" + vend_reply = "Thank you for using the Hydrobe!" + products = list(/obj/item/storage/backpack/botany = 2, + /obj/item/storage/backpack/satchel/hyd = 2, + /obj/item/clothing/suit/hooded/wintercoat/hydro = 2, + /obj/item/clothing/suit/apron = 2, + /obj/item/clothing/suit/apron/overalls = 3, + /obj/item/clothing/under/rank/hydroponics = 3, + /obj/item/clothing/under/rank/hydroponics/skirt = 3, + /obj/item/clothing/mask/bandana = 3, + /obj/item/clothing/accessory/armband/hydro = 3) + refill_canister = /obj/item/vending_refill/wardrobe/hydro_wardrobe + payment_department = ACCOUNT_SRV +/obj/item/vending_refill/wardrobe/hydro_wardrobe + machine_name = "HyDrobe" + +/obj/machinery/vending/wardrobe/curator_wardrobe + name = "CuraDrobe" + desc = "A lowstock vendor only capable of vending clothing for curators and librarians." + icon_state = "curadrobe" + product_ads = "Glasses for your eyes and literature for your soul, Curadrobe has it all!; Impress & enthrall your library guests with Curadrobe's extended line of pens!" + vend_reply = "Thank you for using the CuraDrobe!" + products = list(/obj/item/pen = 4, + /obj/item/pen/red = 2, + /obj/item/pen/blue = 2, + /obj/item/pen/fourcolor = 1, + /obj/item/pen/fountain = 2, + /obj/item/clothing/accessory/pocketprotector = 2, + /obj/item/clothing/under/rank/curator/skirt = 2, + /obj/item/clothing/under/gimmick/rank/captain/suit/skirt = 2, + /obj/item/clothing/under/gimmick/rank/head_of_personnel/suit/skirt = 2, + /obj/item/storage/backpack/satchel/explorer = 1, + /obj/item/clothing/glasses/regular = 2, + /obj/item/clothing/glasses/regular/jamjar = 1, + /obj/item/storage/bag/books = 1) + refill_canister = /obj/item/vending_refill/wardrobe/curator_wardrobe + payment_department = ACCOUNT_CIV +/obj/item/vending_refill/wardrobe/curator_wardrobe + machine_name = "CuraDrobe" + +/obj/machinery/vending/wardrobe/bar_wardrobe + name = "BarDrobe" + desc = "A stylish vendor to dispense the most stylish bar clothing!" + icon_state = "bardrobe" + product_ads = "Guaranteed to prevent stains from spilled drinks!" + vend_reply = "Thank you for using the BarDrobe!" + products = list(/obj/item/clothing/head/that = 2, + /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/under/sl_suit = 2, + /obj/item/clothing/under/rank/bartender = 2, + /obj/item/clothing/under/rank/bartender/purple = 2, + /obj/item/clothing/under/rank/bartender/skirt = 2, + /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/suit/apron/purple_bartender = 2, + /obj/item/clothing/head/soft/black = 2, + /obj/item/clothing/shoes/sneakers/black = 2, + /obj/item/reagent_containers/glass/rag = 2, + /obj/item/storage/box/beanbag = 1, + /obj/item/clothing/suit/armor/vest/alt = 1, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/clothing/glasses/sunglasses/reagent = 1, + /obj/item/storage/belt/bandolier = 1) // yogs - removed pet collar + premium = list(/obj/item/storage/box/dishdrive = 1) + refill_canister = /obj/item/vending_refill/wardrobe/bar_wardrobe + payment_department = ACCOUNT_SRV +/obj/item/vending_refill/wardrobe/bar_wardrobe + machine_name = "BarDrobe" + +/obj/machinery/vending/wardrobe/chef_wardrobe + name = "ChefDrobe" + desc = "This vending machine might not dispense meat, but it certainly dispenses chef related clothing." + icon_state = "chefdrobe" + product_ads = "Our clothes are guaranteed to protect you from food splatters!" + vend_reply = "Thank you for using the ChefDrobe!" + products = list(/obj/item/clothing/under/waiter = 2, + /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/suit/apron/chef = 3, + /obj/item/clothing/head/soft/mime = 2, + /obj/item/storage/box/mousetraps = 2, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/clothing/suit/toggle/chef = 1, + /obj/item/clothing/under/rank/chef = 1, + /obj/item/clothing/under/rank/chef/skirt = 2, + /obj/item/clothing/head/chefhat = 1, + /obj/item/reagent_containers/glass/rag = 1, + /obj/item/clothing/suit/hooded/wintercoat = 2) + refill_canister = /obj/item/vending_refill/wardrobe/chef_wardrobe + payment_department = ACCOUNT_SRV +/obj/item/vending_refill/wardrobe/chef_wardrobe + machine_name = "ChefDrobe" + +/obj/machinery/vending/wardrobe/jani_wardrobe + name = "JaniDrobe" + desc = "A self cleaning vending machine capable of dispensing clothing for janitors." + icon_state = "janidrobe" + product_ads = "Come and get your janitorial clothing, now endorsed by lizard janitors everywhere!" + vend_reply = "Thank you for using the JaniDrobe!" + products = list(/obj/item/clothing/under/rank/janitor = 1, + /obj/item/cartridge/janitor = 1, + /obj/item/clothing/under/rank/janitor/skirt = 2, + /obj/item/clothing/gloves/color/black = 1, + /obj/item/clothing/head/soft/purple = 1, + /obj/item/paint/paint_remover = 1, + /obj/item/melee/flyswatter = 1, + /obj/item/flashlight = 1, + /obj/item/clothing/suit/caution = 6, + /obj/item/holosign_creator/janibarrier = 1, + /obj/item/lightreplacer = 1, + /obj/item/soap/nanotrasen = 1, + /obj/item/storage/bag/trash = 1, + /obj/item/clothing/shoes/galoshes = 1, + /obj/item/watertank/janitor = 1, + /obj/item/storage/belt/janitor = 1) + refill_canister = /obj/item/vending_refill/wardrobe/jani_wardrobe + payment_department = ACCOUNT_SRV +/obj/item/vending_refill/wardrobe/jani_wardrobe + machine_name = "JaniDrobe" + +/obj/machinery/vending/wardrobe/law_wardrobe + name = "LawDrobe" + desc = "Objection! This wardrobe dispenses the rule of law... and lawyer clothing." + icon_state = "lawdrobe" + product_ads = "OBJECTION! Get the rule of law for yourself!" + vend_reply = "Thank you for using the LawDrobe!" + products = list(/obj/item/clothing/under/lawyer/female = 1, + /obj/item/clothing/under/lawyer/black = 1, + /obj/item/clothing/under/lawyer/black/skirt = 1, + /obj/item/clothing/under/lawyer/red = 1, + /obj/item/clothing/under/lawyer/red/skirt = 1, + /obj/item/clothing/under/lawyer/bluesuit = 1, + /obj/item/clothing/under/lawyer/bluesuit/skirt = 1, + /obj/item/clothing/under/lawyer/blue/skirt = 1, + /obj/item/clothing/suit/toggle/lawyer = 1, + /obj/item/clothing/under/lawyer/purpsuit = 1, + /obj/item/clothing/under/lawyer/purpsuit/skirt = 1, + /obj/item/clothing/suit/toggle/lawyer/purple = 1, + /obj/item/clothing/under/lawyer/blacksuit = 1, + /obj/item/clothing/under/lawyer/blacksuit/skirt = 1, + /obj/item/clothing/suit/toggle/lawyer/black = 1, + /obj/item/clothing/shoes/laceup = 2, + /obj/item/clothing/accessory/lawyers_badge = 2) + refill_canister = /obj/item/vending_refill/wardrobe/law_wardrobe + payment_department = ACCOUNT_CIV +/obj/item/vending_refill/wardrobe/law_wardrobe + machine_name = "LawDrobe" + +/obj/machinery/vending/wardrobe/chap_wardrobe + name = "ChapDrobe" + desc = "This most blessed and holy machine vends clothing only suitable for chaplains to gaze upon." + icon_state = "chapdrobe" + product_ads = "Are you being bothered by cultists or pesky revenants? Then come and dress like the holy man!;Clothes for men of the cloth!" + vend_reply = "Thank you for using the ChapDrobe!" + products = list(/obj/item/choice_beacon/holy = 1, + /obj/item/storage/backpack/cultpack = 1, + /obj/item/clothing/accessory/pocketprotector/cosmetology = 1, + /obj/item/clothing/under/rank/chaplain = 1, + /obj/item/clothing/under/rank/chaplain/skirt = 2, + /obj/item/clothing/shoes/sneakers/black = 1, + /obj/item/clothing/suit/chaplainsuit/nun = 1, + /obj/item/clothing/head/nun_hood = 1, + /obj/item/clothing/suit/chaplainsuit/holidaypriest = 1, + /obj/item/storage/fancy/candle_box = 2, + /obj/item/clothing/head/kippah = 3, + /obj/item/clothing/suit/chaplainsuit/whiterobe = 1, + /obj/item/clothing/head/taqiyahwhite = 1, + /obj/item/clothing/head/taqiyahred = 3, + /obj/item/clothing/head/beanie/rasta = 1) + contraband = list(/obj/item/toy/plush/plushvar = 1, + /obj/item/toy/plush/narplush = 1, + /obj/item/clothing/head/medievaljewhat = 3, + /obj/item/clothing/suit/chaplainsuit/clownpriest = 1, + /obj/item/clothing/head/clownmitre = 1) + premium = list(/obj/item/clothing/suit/chaplainsuit/bishoprobe = 1, + /obj/item/clothing/head/bishopmitre = 1) + refill_canister = /obj/item/vending_refill/wardrobe/chap_wardrobe + payment_department = ACCOUNT_CIV +/obj/item/vending_refill/wardrobe/chap_wardrobe + machine_name = "ChapDrobe" + +/obj/machinery/vending/wardrobe/chem_wardrobe + name = "ChemDrobe" + desc = "A vending machine for dispensing chemistry related clothing." + icon_state = "chemdrobe" + product_ads = "Our clothes are 0.5% more resistant to acid spills! Get yours now!" + vend_reply = "Thank you for using the ChemDrobe!" + products = list(/obj/item/clothing/under/rank/chemist = 2, + /obj/item/clothing/under/rank/chemist/skirt = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/chemist = 2, + /obj/item/storage/backpack/chemistry = 2, + /obj/item/storage/backpack/satchel/chem = 2, + /obj/item/storage/bag/chemistry = 2) + refill_canister = /obj/item/vending_refill/wardrobe/chem_wardrobe + payment_department = ACCOUNT_MED +/obj/item/vending_refill/wardrobe/chem_wardrobe + machine_name = "ChemDrobe" + +/obj/machinery/vending/wardrobe/gene_wardrobe + name = "GeneDrobe" + desc = "A machine for dispensing clothing related to genetics." + icon_state = "genedrobe" + product_ads = "Perfect for the mad scientist in you!" + vend_reply = "Thank you for using the GeneDrobe!" + products = list(/obj/item/clothing/under/rank/geneticist = 2, + /obj/item/clothing/under/rank/geneticist/skirt = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/genetics = 2, + /obj/item/storage/backpack/genetics = 2, + /obj/item/storage/backpack/satchel/gen = 2) + refill_canister = /obj/item/vending_refill/wardrobe/gene_wardrobe + payment_department = ACCOUNT_MED +/obj/item/vending_refill/wardrobe/gene_wardrobe + machine_name = "GeneDrobe" + +/obj/machinery/vending/wardrobe/viro_wardrobe + name = "ViroDrobe" + desc = "An unsterilized machine for dispending virology related clothing." + icon_state = "virodrobe" + product_ads = " Viruses getting you down? Then upgrade to sterilized clothing today!" + vend_reply = "Thank you for using the ViroDrobe" + products = list(/obj/item/clothing/under/rank/virologist = 2, + /obj/item/clothing/under/rank/virologist/skirt = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/virologist = 2, + /obj/item/clothing/mask/surgical = 2, + /obj/item/storage/backpack/virology = 2, + /obj/item/storage/backpack/satchel/vir = 2) + refill_canister = /obj/item/vending_refill/wardrobe/viro_wardrobe + payment_department = ACCOUNT_MED +/obj/item/vending_refill/wardrobe/viro_wardrobe + machine_name = "ViroDrobe" diff --git a/config/admins.txt b/config/admins.txt index fff722ea88bd..66604b0d4260 100644 --- a/config/admins.txt +++ b/config/admins.txt @@ -1,142 +1,142 @@ -############################################################################################### -# Basically, ckey goes first. Rank goes after the "=" # -# Case is not important for ckey. # -# Case IS important for the rank. # -# All punctuation (spaces etc) EXCEPT '-', '_' and '@' will be stripped from rank names. # -# Ranks can be anything defined in admin_ranks.txt # -# NOTE: if the rank-name cannot be found in admin_ranks.txt, they will not be adminned! ~Carn # -# NOTE: syntax was changed to allow hyphenation of ranknames, since spaces are stripped. # -# If SQL-based admin loading is enabled, admins listed here will always be loaded first # -# and will override any duplicate entries in the database. # -############################################################################################### -Optimumtact = Host -CitrusGender = Game Master -NewSta = Game Master -Expletives = Game Master -kingofkosmos = Game Master -MrStonedOne = Lazy Master -microscopics = Game Master -Gun Hog = Game Master -KorPhaeron = Game Master -razharas = Game Master -Lordpidey = Game Master -Niknakflak = Game Master -rolan7 = Game Master -quarxink = Game Master -adrix89 = Game Master -tle = Game Master -xsi = Game Master -scaredofshadows = Game Master -neofite = Game Master -trubblebass = Game Master -mport2004 = Game Master -deuryn = Game Master -agouri = Game Master -errorage = Game Master -superxpdude = Game Master -petethegoat = Game Master -nodrak = Game Master -carnwennan = Game Master -ikarrus = Game Master -cheridan = Game Master -giacomand = Game Master -rockdtben = Game Master -sieve = Game Master -aranclanos = Game Master -intigracy = Game Master -dumpdavidson = Game Master -kazeespada = Game Master -malkevin = Game Master -incoming = Game Master -demas = Game Master -fleure = Game Master -ricotez = Game Master -misterperson = Game Master -crimsonvision = Game Master -iamgoofball = Game Master -zelacks = Game Master -androidsfv = Game Master -miggles = Game Master -jordie0608 = Game Master -s0ldi3rkr4s0 = Game Master -ergovisavi = Game Master -vistapowa = Game Master -miauw62 = Game Master -rumia29 = Game Master -bobylein = Game Master -sirbayer = Game Master -hornygranny = Game Master -yota = Game Master -firecage = Game Master -donkieyo = Game Master -argoneus = Game Master -paprka = Game Master -cookingboy3 = Game Master -limeliz = Game Master -steelpoint = Game Master -phil235 = Game Master -CorruptComputer = Game Master -xxnoob = Game Master -tkdrg = Game Master -Cuboos = Game Master -thunder12345 = Game Master -wjohnston = Game Master -mandurrh = Game Master -thurgatar = Game Master -xerux = Game Master -dannno = Game Master -lo6a4evskiy = Game Master -vekter = Game Master -Ahammer18 = Game Master -ACCount12 = Game Master -fayrik = Game Master -shadowlight213 = Game Master -drovidicorv = Game Master -Dunc = Game Master -MMMiracles = Game Master -bear1ake = Game Master -CoreOverload = Game Master -Jalleo = Game Master -ChangelingRain = Game Master -FoxPMcCloud = Game Master -Xhuis = Game Master -Astralenigma = Game Master -Tokiko1 = Game Master -SuperSayu = Game Master -Lzimann = Game Master -As334 = Game Master -neersighted = Game Master -Swankcookie = Game Master -Ressler = Game Master -Folix = Game Master -Bawhoppennn = Game Master -Anturke = Host -Lumipharon = Game Master -bgobandit = Game Master -coiax = Game Master -RandomMarine = Game Master -PKPenguin321 = Game Master -TechnoAlchemist = Game Master -Aloraydrel = Game Master -Quiltyquilty = Game Master -SnipeDragon = Game Master -Fjeld = Game Master -kevinz000 = Game Master -Tacolizard = Game Master -TrustyGun = Game Master -Cyberboss = Game Master -PJB3005 = Game Master -Sweaterkittens = Game Master -Feemjmeem = Game Master -JStheguy = Game Master -excessiveuseofcobby = Game Master -Plizzard = Game Master -octareenroon91 = Game Master -Serpentarium = Game Master -Averagejoe82 = Game Master -The Dreamweaver = Game Master -88Naoki = Game Master -Naksuasdf = Game Master -MrDoomBringer = Game Master -shizcalev = Game Master -NicBR = Game Master +############################################################################################### +# Basically, ckey goes first. Rank goes after the "=" # +# Case is not important for ckey. # +# Case IS important for the rank. # +# All punctuation (spaces etc) EXCEPT '-', '_' and '@' will be stripped from rank names. # +# Ranks can be anything defined in admin_ranks.txt # +# NOTE: if the rank-name cannot be found in admin_ranks.txt, they will not be adminned! ~Carn # +# NOTE: syntax was changed to allow hyphenation of ranknames, since spaces are stripped. # +# If SQL-based admin loading is enabled, admins listed here will always be loaded first # +# and will override any duplicate entries in the database. # +############################################################################################### +Optimumtact = Host +CitrusGender = Game Master +NewSta = Game Master +Expletives = Game Master +kingofkosmos = Game Master +MrStonedOne = Lazy Master +microscopics = Game Master +Gun Hog = Game Master +KorPhaeron = Game Master +razharas = Game Master +Lordpidey = Game Master +Niknakflak = Game Master +rolan7 = Game Master +quarxink = Game Master +adrix89 = Game Master +tle = Game Master +xsi = Game Master +scaredofshadows = Game Master +neofite = Game Master +trubblebass = Game Master +mport2004 = Game Master +deuryn = Game Master +agouri = Game Master +errorage = Game Master +superxpdude = Game Master +petethegoat = Game Master +nodrak = Game Master +carnwennan = Game Master +ikarrus = Game Master +cheridan = Game Master +giacomand = Game Master +rockdtben = Game Master +sieve = Game Master +aranclanos = Game Master +intigracy = Game Master +dumpdavidson = Game Master +kazeespada = Game Master +malkevin = Game Master +incoming = Game Master +demas = Game Master +fleure = Game Master +ricotez = Game Master +misterperson = Game Master +crimsonvision = Game Master +iamgoofball = Game Master +zelacks = Game Master +androidsfv = Game Master +miggles = Game Master +jordie0608 = Game Master +s0ldi3rkr4s0 = Game Master +ergovisavi = Game Master +vistapowa = Game Master +miauw62 = Game Master +rumia29 = Game Master +bobylein = Game Master +sirbayer = Game Master +hornygranny = Game Master +yota = Game Master +firecage = Game Master +donkieyo = Game Master +argoneus = Game Master +paprka = Game Master +cookingboy3 = Game Master +limeliz = Game Master +steelpoint = Game Master +phil235 = Game Master +CorruptComputer = Game Master +xxnoob = Game Master +tkdrg = Game Master +Cuboos = Game Master +thunder12345 = Game Master +wjohnston = Game Master +mandurrh = Game Master +thurgatar = Game Master +xerux = Game Master +dannno = Game Master +lo6a4evskiy = Game Master +vekter = Game Master +Ahammer18 = Game Master +ACCount12 = Game Master +fayrik = Game Master +shadowlight213 = Game Master +drovidicorv = Game Master +Dunc = Game Master +MMMiracles = Game Master +bear1ake = Game Master +CoreOverload = Game Master +Jalleo = Game Master +ChangelingRain = Game Master +FoxPMcCloud = Game Master +Xhuis = Game Master +Astralenigma = Game Master +Tokiko1 = Game Master +SuperSayu = Game Master +Lzimann = Game Master +As334 = Game Master +neersighted = Game Master +Swankcookie = Game Master +Ressler = Game Master +Folix = Game Master +Bawhoppennn = Game Master +Anturke = Host +Lumipharon = Game Master +bgobandit = Game Master +coiax = Game Master +RandomMarine = Game Master +PKPenguin321 = Game Master +TechnoAlchemist = Game Master +Aloraydrel = Game Master +Quiltyquilty = Game Master +SnipeDragon = Game Master +Fjeld = Game Master +kevinz000 = Game Master +Tacolizard = Game Master +TrustyGun = Game Master +Cyberboss = Game Master +PJB3005 = Game Master +Sweaterkittens = Game Master +Feemjmeem = Game Master +JStheguy = Game Master +excessiveuseofcobby = Game Master +Plizzard = Game Master +octareenroon91 = Game Master +Serpentarium = Game Master +Averagejoe82 = Game Master +The Dreamweaver = Game Master +88Naoki = Game Master +Naksuasdf = Game Master +MrDoomBringer = Game Master +shizcalev = Game Master +NicBR = Game Master diff --git a/config/antag_rep.txt b/config/antag_rep.txt index a26b157d5a8d..e8a7250686a4 100644 --- a/config/antag_rep.txt +++ b/config/antag_rep.txt @@ -1,5 +1,5 @@ -## Custom antag reputation values -## List of job titles followed by antag rep value, all prefixed with ANTAG_REP. See code/modules/jobs/job_types for titles -## e.g. -## ANTAG_REP Captain 10 -## ANTAG_REP Assistant 0 +## Custom antag reputation values +## List of job titles followed by antag rep value, all prefixed with ANTAG_REP. See code/modules/jobs/job_types for titles +## e.g. +## ANTAG_REP Captain 10 +## ANTAG_REP Assistant 0 diff --git a/config/comms.txt b/config/comms.txt index c5c628aede49..65ebb94acb17 100644 --- a/config/comms.txt +++ b/config/comms.txt @@ -1,17 +1,17 @@ -## Communication key for receiving data through world/Topic(), you don't want to give this out -#COMMS_KEY default_pwd - -## World address and port for server receiving cross server messages -## Use '+' to denote spaces in ServerName -## Repeat this entry to add more servers -#CROSS_SERVER ServerName byond:\\address:port - -## Name that the server calls itself in communications -#CROSS_COMMS_NAME - -## Hub address for tracking stats -## example: Hubmakerckey.Hubname -#MEDAL_HUB_ADDRESS - -## Password for the hub page +## Communication key for receiving data through world/Topic(), you don't want to give this out +#COMMS_KEY default_pwd + +## World address and port for server receiving cross server messages +## Use '+' to denote spaces in ServerName +## Repeat this entry to add more servers +#CROSS_SERVER ServerName byond:\\address:port + +## Name that the server calls itself in communications +#CROSS_COMMS_NAME + +## Hub address for tracking stats +## example: Hubmakerckey.Hubname +#MEDAL_HUB_ADDRESS + +## Password for the hub page #MEDAL_HUB_PASSWORD \ No newline at end of file diff --git a/html/archivedchangelog.html b/html/archivedchangelog.html index 033acbaca945..6b0fb6c6e694 100644 --- a/html/archivedchangelog.html +++ b/html/archivedchangelog.html @@ -1,9051 +1,9051 @@ - -

                01 April 2015

                -

                ACCount updated:

                -
                  -
                • You can now replace test range firing pins with any other pin.
                • -
                • The plasma cutter is now a gun that comes in the mining vending machine. Use it to mine fast but expensively.
                • -
                • It's also cheaper in RnD, and there's an advanced version of it there too. Yay!
                • -
                • Ripleys and Firefighters have been buffed significantly. Go wild.
                • -
                • Precious mesons are cheaper in RnD.
                • -
                • Now you can mine bluespace crystals. They are extremely rare.
                • -
                • You can make remote bombs out of gibtonite. Figure out how.
                • -
                • Gibtonite is a bit less stable. Do not hit it with anything heavy.
                • -
                -

                Fayrik updated:

                -
                  -
                • Added a bowman headset for death squads and ERTs.
                • -
                • Deathsquad radio channel is now called the Centcom radio channel.
                • -
                • Fixed the admin buttons not spawning ERT Medic gear properly.
                • -
                • Expanded Emergency Shuttle dock to allow for more round end griff.
                • -
                • All 7 ERT members now spawn geared, not just the first 4.
                • -
                • Spawning an ERT or Deathsquad no longer stops a second ERT or Deathsquad from being spawned.
                • -
                • Pulse rifles now have 80 shots, not 40.
                • -
                • Deathsquads now use loyalty pinned Pulse rifles.
                • -
                -

                Gun Hog updated:

                -
                  -
                • NTSL has been updated! It can now read and modify verbs (says, yells, asks)! You can set your own using $say, $yell, $ask, and $exclaim variables in your scripts.
                • -
                • NTSL can now also modify the font, italics, and bolding of radio messages in four ways! In a script, place $loud, $wacky, $emphasis, and/or $robot in a vector to $filters.
                • -
                -

                Jordie0608 updated:

                -
                  -
                • The admin's old player panel has been removed and the new one has taken it's name.
                • -
                -

                NikNakFlak updated:

                -
                  -
                • Adds the ability to shave corgis.
                • -
                -

                Sawu updated:

                -
                  -
                • Spraycans for Revheads: instant use crayons that can be used on walls.
                • -
                • Spraycans can be used as ghetto pepper spray and leave their target with colored faces that can be removed with space cleaner.
                • -
                -

                Xhuis updated:

                -
                  -
                • Area ambience and ship ambience are now separate toggles. If you want one type of ambience but not the other, you only need to switch off the type you don't want.
                • -
                • Added cigar cases. They come in three flavors and can be bought from cigarette machines.
                • -
                - -

                30 March 2015

                -

                AnturK updated:

                -
                  -
                • Added new wizard spell : Lightning Bolt
                • -
                -

                Dannno updated:

                -
                  -
                • Added a hammer and gavel. Court is now in session.
                • -
                -

                Iamgoofball updated:

                -
                  -
                • You can now *flip.
                • -
                • Objects now spin when thrown. Please report any oddities.
                • -
                -

                RemieRichards updated:

                -
                  -
                • Monkeys can now wear ANY mask, not just ones they had specific icons for.
                • -
                • Monkeys can now wear HATS, That's right, Monkey Hats!
                • -
                -

                Szunti updated:

                -
                  -
                • Mining mobs vision adapted to darkness of the asteroids.
                • -
                -

                phil235 updated:

                -
                  -
                • All slimes are now simple animals instead of carbon life forms.
                • -
                • Remove crit status from brain and slimes.
                • -
                • Simple animal aliens can now see in the dark like regular aliens.
                • -
                -

                pudl updated:

                -
                  -
                • The detective's forensic scanner has been resprited.
                • -
                • So has the cargo tagger.
                • -
                • And the RPED.
                • -
                -

                tedward1337 updated:

                -
                  -
                • Admins now have a button to use Bluespace Artillery at their will.
                • -
                -

                xxalpha updated:

                -
                  -
                • You will no longer be slowed down by anything if you use a jetpack in zero gravity.
                • -
                • Engineering hardsuits come with an inbuilt jetpack. Requires an internals tank in suit storage and is slower than a normal jetpack.
                • -
                - -

                25 March 2015

                -

                AnturK updated:

                -
                  -
                • Added an arrow graffiti, made arrow and body graffiti face the same way as the user
                • -
                • New changeling ability : Last Resort : Explode and infect corpses if the situation looks grim
                • -
                -

                Fayrik updated:

                -
                  -
                • Deleted the toy crossbow that wasn't a gun, but flung projectiles like a gun. Why was that even a thing?
                • -
                • Added an abstract new type of reusable ammo.
                • -
                • Added three new foam force guns and a foam force crossbow, all of which take the new foam dart ammo.
                • -
                • Added foam force dart boxes to the autolathe, and two crates orderable from cargo containing the new guns.
                • -
                • Added two new donksoft guns, the syndicate's own brand of Foam Force. Available in all good uplinks!
                • -
                • New crossbow can be won as an arcade prize.
                • -
                • All kinds of ammo containers can now be used to rapidly collect live ammunition on the ground.
                • -
                • Speedloaders and traditional ammo boxes can now be restocked.
                • -
                • Security bots now react to being shot.
                • -
                -

                MrPerson updated:

                -
                  -
                • Added a preference toggle for hearing instruments play as suggested by PKPenguin321.
                • -
                -

                Pennwick updated:

                -
                  -
                • Added a buildable Chem Master, board is in Circuit Imprinter.
                • -
                -

                Wjohnston updated:

                -
                  -
                • Finally fixed the gulag shuttle missing a redemption console.
                • -
                - -

                24 March 2015

                -

                Cheridan updated:

                -
                  -
                • Adds pet collars. Use them to rename pets; Can also be worn if you're a weirdo.
                • -
                • Adds the matyr objective to be dead at the round's end. You can get this objective if you have no others that would need you to survive.
                • -
                -

                Dannno updated:

                -
                  -
                • GAR glasses return. Yes, I got permission. Just who the hell do you think I am?
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Summon Events has been reworked to be a bit less manic.
                • -
                • Summon Events will turn off if the wizard and all apprentices die. Any lingering effects are not reversed however.
                • -
                • Improving Summon Events will reduce the average time between events, but not the minimum time between events.
                • -
                • Departmental Uprising has been made admin only for being an RP heavy event that creates antags in an enviroment that doesn't really support that.
                • -
                -

                Mandurrrh updated:

                -
                  -
                • Added a cyborg upgrade module for the satchel of holding.
                • -
                -

                Miauw updated:

                -
                  -
                • Added a fancy white dress and a jester outfit. Sprites by Nienhaus.
                • -
                -

                Steelpoint, RemieRichards, and Gun Hog updated:

                -
                  -
                • Nanotrasen Security has authorized modifications to the standard issue helmets for officers. These helmets are now issued with a mounted camera, automatically synced to your assigned station's camera network. In addition, the helmets also include mounting points for the Seclite model of flashlights, found in your station's security vendors.
                • -
                • Nanotrasen Security helmets are designed for easy repairs in the field. Officers may remove their Seclite attachment with any screwdriver, and the camera's assembly may be pried off with a crowbar. The assembly is also compatible with analyzer upgrades for scanning through the station's structure, and EMP shielding is possible by applying a sheet of plasma.
                • -
                - -

                22 March 2015

                -

                Iamgoofball updated:

                -
                  -
                • Fixes Morphine not knocking you out.
                • -
                • Hotline no longer exists. Poppies now have Saline-Glucose Solution.
                • -
                • Muriatic Acid, Caustic Soda, and Hydrogen Chloride have been removed, along with the secondary meth recipe. The original recipe still works.
                • -
                • Foam now produces more bang for your buck when mixed. Your foam grenades should be larger now to account for the slowdown.
                • -
                • Adds Stabilizing Agent. This chemical will prevent the effects of certain reactions from taking place in the container it is in, such as Smoke Powder and Flash Powder, allowing you to get the raw forms of the powders for heating up later.
                • -
                • Smoke Powder and Flash Powder can now be mixed along with Stabilizing Agent in order to produce a powder version of the effects. You can then heat these powders to 374K to get the effects.
                • -
                • Mixing them without Stabilizing Agent gives the default effects.
                • -
                • Adds Sonic Powder, a chemical that deafens and stuns within 5 tiles of the reaction. It also follows the reaction rules of the other 2 powders.
                • -
                • Liquid Dark Matter and Sorium also now work like the powders do.
                • -
                • CLF3 now has a higher chance of making plating tiles, and the burn damage caused by it scales based on your fire stacks.
                • -
                • Adds Pyrosium. This reagent heats up mobs by 30 degrees every 3 seconds if it has oxygen to react with. Useful for when you want a delayed mix inside of someone.
                • -
                • Adds Cryostylane. This reagent does the opposite of Pyrosium, but it still requires oxygen to react with.
                • -
                • Adds Phlogiston. This reagent ignites you and gives you a single fire stack every 3 seconds. Burn damage from this chemical scales up with the fire stacks you have. Counter this with literally any chemical that purges other chems.
                • -
                • Smoke now transfers reagents to the mob, and applies touch reactions on them. This means smoke can run out of reagents, no more infinite acid smoke. It also only works on mobs, use the new Reagent Foam(tm) for your hellfoam needs. I suggest a CLF3 Fluoroacid Black Powder mix.
                • -
                • Added a derelict medibot to the code, will place a few on the derelict when this PR is merged as to prevent mapping conflicts.
                • -
                • Adds Initropidril. 33% chance to hit with 5-25 TOX damage every 3 seconds, and a 5-10% chance to either stun, cause lots of oxygen damage, or cause your heart to stop. Get it from the traitor Poison Kit.
                • -
                • Adds Pancuronium. Paralyses you after 30 seconds, with a 7% chance to cause 3-5 loss of breath. Get it from the traitor Poison Kit.
                • -
                • Adds Sodium Thiopental. Knocks you out after 30 seconds, and destroys your stamina. Get it from the traitor Poison Kit.
                • -
                • Adds Sulfonal. +1 TOX per 3 seconds, knocks you out in 66 seconds. Mix it with Acetone, Diethylamine, and Sulfur.
                • -
                • Adds Amantin. On the last second it is in you, it hits you with a stack of TOX damage based on how long it's been in you. Get it from the traitor Poison Kit.
                • -
                • Adds Lipolicide. +1 TOX unless you have nutriment in you. Mix it with Mercury, Diethylamine, and Ephedrine.
                • -
                • Adds Coiine. +2 TOX and +5 loss of breath every 3 seconds. Get it from the traitor Poison Kit.
                • -
                • Adds Curare. +1 TOX, +1 OXY, paralyzes after 33 seconds. Get it from the traitor Poison Kit.
                • -
                • Adds a reagent_deleted() proc to reagents for effects upon the last time it processes in you, like with Amantin.
                • -
                • Neurotoxin required temperature for mixing has been set to 674K, it was 370K for some reason.
                • -
                -

                xxalpha updated:

                -
                  -
                • Added Cybernetic Implants: Security HUD, Medical HUD, X-Ray, Thermals, Anti-Drop, Anti-Stun. All implants are researchable and producible by R&D and Robotics.
                • -
                - -

                21 March 2015

                -

                Chocobro updated:

                -
                  -
                • Checkered skirts can be adjusted to be worn shorter.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Player controller uplifted mobs will be attacked by gold core mobs of different species.
                • -
                -

                Jordie0608 updated:

                -
                  -
                • The backup shuttle will no longer be called if the emergency shuttle is coming.
                • -
                -

                MMMiracles updated:

                -
                  -
                • Adds 3 new bear-based foods.
                • -
                • Beary Pie -1 Plain Pie, 1 berry, 1 bear steak
                • -
                • Filet Migrawr - 5u of Manlydorf, 1 bear steak, 1 lighter(not used in crafting)
                • -
                • Bearger - 1 bun, 1 bear steak
                • -
                -

                Phil235 updated:

                -
                  -
                • Silicons are no longer blinded by welding.
                • -
                -

                Xhuis updated:

                -
                  -
                • A new software update has given the Orion Trail game a cheat code menu. While you can't access this normally (that's for a later software update), maybe an emag could do it...?
                • -
                -

                Zelacks updated:

                -
                  -
                • The chemical implant action button should now correctly inject all chemicals when pressed.
                • -
                -

                pudl updated:

                -
                  -
                • Adds normal security headsets to security lockers.
                • -
                -

                xxalpha updated:

                -
                  -
                • Fixed cables on catwalks being destroyed when creating plating floor.
                • -
                • Portable machines can be mounted on any turf that could hold a cable.
                • -
                - -

                19 March 2015

                -

                Dannno updated:

                -
                  -
                • Adds a few cosmetic glasses to the autodrobe and clothing vendors.
                • -
                -

                Gun Hog updated:

                -
                  -
                • Explosion relics will now destroy themselves in their own explosion.
                • -
                • The Experimentor can no longer re-roll the function of an already scanned relic.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Ghosts are now asked if they want to become a sentient slime.
                • -
                -

                JJRcop updated:

                -
                  -
                • Adds the :rollie: and :ambrosia: emojis.
                • -
                -

                MMMiracles updated:

                -
                  -
                • ERT suits now use helmet toggling like hardsuits.
                • -
                -

                Menshin updated:

                -
                  -
                • Fixed mechs ballistic guns not firing bullets.
                • -
                -

                Xhuis updated:

                -
                  -
                • Allows alt-clicking to adjust breath masks, flip caps, and unlock/lock closets.
                • -
                -

                xxalpha updated:

                -
                  -
                • Fixed jetpack trail animation.
                • -
                - -

                17 March 2015

                -

                CandyClownTG updated:

                -
                  -
                • Syndicate cigarettes' non-healing Doctor's Delight replaced with Omnizine.
                • -
                -

                Fayrik updated:

                -
                  -
                • Expanded the Centcom area for better station-Centcom interaction.
                • -
                -

                Iamgoofball updated:

                -
                  -
                • Acid blobs are less likely to melt equipment.
                • -
                -

                MMMiracles updated:

                -
                  -
                • Adds the ability to spike bears on a meatspike for their pelt. Yields 5 meat instead of the usual 3 from a knife.
                • -
                -

                Thunder12345 updated:

                -
                  -
                • Two new shotgun shells. Ion shell, shotgun version of ion rifle, built with 1 x techshell, 1 x ultra microlaser, 1 x ansible crystal.
                • -
                • Laser slug, regular laser shot in a shotgun shell, build with 1 x techshell, 1 x high power microlaser, 1 x advanced capacitor.
                • -
                • Improvised shotgun shells can now be constructed with 1 x grenade casing, 1 x metal sheet, 1 x cable, 10 units welding fuel. Can be packed with a further 5 units of gunpowder to turn it into a potentially lethal diceroll.
                • -
                • FRAG-12 shotgun shells are no longer duds, now explosive.
                • -
                -

                phil235 updated:

                -
                  -
                • Makes the message when you're attacked slightly bigger for better visibility.
                • -
                - -

                15 March 2015

                -

                Ahammer18 updated:

                -
                  -
                • Adds ass photocopying for drones
                • -
                -

                Gun Hog updated:

                -
                  -
                • Nanotrasen has approved distribution of Loyalty Implant Firing Pin designs. They can be fabricated with sufficient research into weapons technology. If inserted into a firearm, it will only fire upon successful interface with a user's Loyalty Implant.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Utilizing a new magic mirror found in the den, wizards can now customize their appearance to a high degree before descending upon the station.
                • -
                -

                Miauw updated:

                -
                  -
                • Firing delay for tasers has been shortened.
                • -
                -

                RemieRichards updated:

                -
                  -
                • Beginning major rework and refactor of messy Ninja code.
                • -
                • Roleplay based, kill deathsquad and kill alien queen objectives removed.
                • -
                • Kamikaze mode removed.
                • -
                • AIs and pAIs can no longer be integrated into a ninja suit.
                • -
                • Energy blade power replaced by an energy katana that can be dropped but is more deadly.
                • -
                • Energy katana can emag any object it hits.
                • -
                -

                phil235 updated:

                -
                  -
                • Bookbags have a new sprite and can hold bibles.
                • -
                • Adds egg yolk reagent. You can break eggs in open reagent containers or blend them to get egg yolk.
                • -
                • Changes how doughs are made. 10water+15flour chem reaction for dough. 15flour+15eggyolk+5sugar for cake batter (or alternatively soymilk instead of the eggyolk for vegans).
                • -
                • Change customizable snack max volume to 60 and buffs nutriment amount in doughs.
                • -
                • Doors can damage mechs by crushing them now.
                • -
                • The tablecrafting window now also shows partial recipes in grey, if there's some of the things required for the recipe on the table. Recipe requirements are now also shown next to the names of recipes listed in the window.
                • -
                - -

                11 March 2015

                -

                MMMiracles updated:

                -
                  -
                • Adds a new vending machine, the Liberation Station. Consult your local patriot for more details.
                • -
                • Adds a patriotic jumpsuit and freedom sheets. Consult your local patriot for more details.
                • -
                -

                Miauw updated:

                -
                  -
                • Added a toggle for ghosts that allows them to hear radio chatter without having to hover near intercoms/over people with headsets.
                • -
                - -

                10 March 2015

                -

                Ahammer18 updated:

                -
                  -
                • Adds the Slimecake and Slimecake slice.
                • -
                -

                AnturK updated:

                -
                  -
                • Non-player Alien Queens/Drones now spread weeds. Can also lay eggs when enabled.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Nanotrasen scientists have recently reported mutations within the pink family line of slimes. Station scientists are encouraged to investigate.
                • -
                -

                Paprika updated:

                -
                  -
                • Reworked reskinning and renaming guns, and added the ability to reskin the bartender's shotgun. Click it with an empty active hand to reskin. Warning, you can only do this once! Rename by hitting it with a pen. This also works with miner shotguns now.
                • -
                -

                RemieRichards updated:

                -
                  -
                • Embedding from explosions has been tweaked, they will always throw objects fast enough to get embedded if they can.
                • -
                • Damage taken when embedded spears and throwing stars fall out has been increased.
                • -
                • Damage dealt by objects getting embedded has been decreased.
                • -
                -

                Sometinyprick updated:

                -
                  -
                • The Horsemask spell has now been altered, instead of just a horse mask it will now choose between three animal masks including the horse mask.
                • -
                -

                Xhuis updated:

                -
                  -
                • You can now burn papers using lighters, welders, or matches. Useful for secret messages.
                • -
                -

                phil235 updated:

                -
                  -
                • Airlocks with their safety on no longer closes on dense objects (like mechs or tables).
                • -
                • Aliens, slimes, monkeys and ventcrawler mobs can now climb into disposal. Cyborgs and very large mobs can no longer climb into it.
                • -
                • Stuffing another mob in a disposal unit is now only done via grabbing.
                • -
                • Coffee now causes jittering only when you overdose on it.
                • -
                • Turrets now target occupied mechs and don't target drones.
                • -
                - -

                08 March 2015

                -

                Dannno updated:

                -
                  -
                • Added more barsigns, sprites by yours truly.
                • -
                -

                Ikarrus updated:

                -
                  -
                • Gang mode has been reworked to functionality without some of it's features.
                • -
                • Gang membership visibility, conversion pens and weapons are removed until fixed.
                • -
                • Gang bosses have been given uplinks to use in the meantime.
                • -
                • Chance of deconversion from repeated head trauma has been increased.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • The skeleton and zombie hordes have been quelled, at least for the time being.
                • -
                -

                Mandurrrh updated:

                -
                  -
                • Added the ability to stop table climbers by clicking the table if present.
                • -
                -

                Paprika updated:

                -
                  -
                • Labcoats and uniforms no longer use action buttons and object verbs to adjust their aesthetic style. Alt click them to adjust.
                • -
                • Alt click a PDA with an ID inside to eject the ID.
                • -
                • Renamed 'remove ID' verb to 'eject ID' so it's not immediately next to 'remove pen'.
                • -
                • Rebalanced the flash protection of thermal scanners. Syndicate thermals have the same flash weakness as the meson scanners they come disguised as.
                • -
                -

                Xhuis updated:

                -
                  -
                • Adds the Adminbus and Coderbus bar signs.
                • -
                • Added a mining satchel of holding to R&D that can hold an infinite amount of ores. It requires gold and uranium, in addition to a decently high bluespace and materials research.
                • -
                -

                phil235 updated:

                -
                  -
                • Harvesting meat from dead simple animals now takes time, has a sound, and can be done with any sharp weapon.
                • -
                -

                pudl updated:

                -
                  -
                • Updated the sprite for Fire extinguishers.
                • -
                -

                xxalpha updated:

                -
                  -
                • Disposable lighters now come in more colors of the rainbow.
                • -
                - -

                05 March 2015

                -

                Jordie0608 updated:

                -
                  -
                • Server hosts can set a config option to make an unseen changelog pop up on connection.
                • -
                -

                MrPerson updated:

                -
                  -
                • Expanded the on-screen alerts you get on the top-right of your screen. You can shift-click them to get a description of what's wrong and maybe how to fix it.
                • -
                • Getting buckled to something will show an alert of what you're buckled to. If you're not handcuffed, you can click the alert to unbuckle.
                • -
                -

                RemieRichards updated:

                -
                  -
                • Added Necromantic Stones as a buyable item for wizards
                • -
                • Necromantic Stones can cause up to 3 people to rise up as Skeleton Thralls bound to the wielder of the stone
                • -
                • Skeleton Thralls have a 33% Chance to drop all their gear on the floor and autoequip a more fitting thematic uniform
                • -
                • Allows the blob to reroll it's chemical for a cost.
                • -
                -

                phil235 updated:

                -
                  -
                • Adds a lot of new plants to botany (red beet, parsnip, snap corn, blumpkin, rice, oat, vanilla, steel cap, mimana, blue cherries, holy melon, geranium, lily, sweet potato) and two new reagents (vanilla, rice).
                • -
                • Fixes taser stun duration being longer due to jitter animation.
                • -
                - -

                03 March 2015

                -

                Delimusca updated:

                -
                  -
                • Improvised heating! use lighters or other heat sources to heat beakers, bottles, etc.
                • -
                -

                Gun Hog updated:

                -
                  -
                • We have purged the xenomicrobes contamination in the E.X.P.E.R.I-MENTOR using sulfuric acid. Take care not to be exposed should it leak!
                • -
                -

                Iamgoofball updated:

                -
                  -
                • Colorful reagent can be washed off with water now.
                • -
                • Oxygen blob deals breath loss damage.
                • -
                • Radioactive blobs irradiate for more.
                • -
                -

                Ikarrus updated:

                -
                  -
                • Antagonist roles are now restricted by player client age.
                • -
                -

                Jordie0608 updated:

                -
                  -
                • The server's current local time is now displayed in status panel in ISO date format (YYYY-MM-DD hh:mm)
                • -
                -

                Mandurrrh updated:

                -
                  -
                • Added the ability to see pda messages as a ghost with GHOSTWHISPER toggled on.
                • -
                • Removed the degrees symbol from Kelvin units on atmos meters because not gramatically correct
                • -
                -

                RemieRichards updated:

                -
                  -
                • Drones now see either Static, Black icons or Roguelike style lettering instead of actual mob icons
                • -
                • Icons for the above, from VG's SkowronX
                • -
                • Many icon procs for the above, from VG's ComicIronic
                • -
                • Drones moved into their own folder
                • -
                • Drone.dm replaced with seperate files for each functionality
                • -
                • Drones now have 2 skins to choose from, Maintenance Drone (Current drones) and Repair Drone (A modified VG/Bay sprite)
                • -
                • Much more support for alternate skins on drones
                • -
                -

                Xhuis updated:

                -
                  -
                • Added four new emoji: :1997:, :rune:, :blob:, and :onisoma:.
                • -
                -

                xxalpha updated:

                -
                  -
                • Added a surgery table and tools to the Nuke Ops ship.
                • -
                • Added Engineering Scanner Goggles to Engivend, Engineering Secure Closets and Research.
                • -
                • Doubled T-ray scanner range.
                • -
                - -

                01 March 2015

                -

                Dannno updated:

                -
                  -
                • Resprited all owl items. Wings are now a seperate toggleable suit item.
                • -
                • Added The Griffin costume, arch nemesis of The Owl. Found in the autodrobe.
                • -
                • Added admin-only Owl hardsuit. Only for the most vengeful souls.
                • -
                • Added Owl and Griffin merchandise; The Nest barsign, Owl/Griffin toys and posters.
                • -
                -

                Iamgoofball updated:

                -
                  -
                • Fixes the recipe for meth.
                • -
                • Fixes sorium blobs flinging themselves.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • On servers where malfunction, wizard, and blob rounds do not end with the death of the primary antagonist, new antagonists from traitor and changeling derivative modes will now sometimes spawn. This depends on the state of the station, the state of the crew, and the server's gamemode probabilities. Server owners should consider revising their configuration choices
                • -
                • If you would like to opt out of this midround chance, a new toggle to disable it can be found in your preferences
                • -
                -

                Mandurrh updated:

                -
                  -
                • The barsign can be emagged and EMP'd; EMP'd signs can be repaired with a screwdriver and cables.
                • -
                -

                Menshin updated:

                -
                  -
                • Fixed the protolathe not requiring displayed amount of materials to make items.
                • -
                -

                MrStonedOne updated:

                -
                  -
                • Adds a Panic Bunker!
                • -
                • This will prevent any player who has never connected before from connecting. (Admins are exempt)
                • -
                • Can be enabled per-round by any admin with +SERVER or for longer periods via the config.
                • -
                • Also adds some other config options relating to new joins, server operators should consult their configuration file for more details.
                • -
                • Fixes toggle darkness vision hiding other ghosts.
                • -
                • Admins fucking around with ghost icon and icon states in VV are going to want to change the icon and icon state of the ghostimage var of the ghost to change the image shown to ghosts who have darkness disabled. doing this via mass edit is not yet supported.
                • -
                • New ghost verb, toggle ghost vision. Switches the ghost's invisibility vision between that of a ghost and that of a human. So ghosts can hide other ghosts and in general get the old behaviour of toggle darkness.
                • -
                -

                Paprika updated:

                -
                  -
                • Messages with two exclamation marks will be yelled in bold.
                • -
                -

                Phil235 updated:

                -
                  -
                • Cultists can't summon narsie until their sacrifice objective is complete.
                • -
                -

                Razharas updated:

                -
                  -
                • Vines now can grow on any tiles that is not dense, including space, shuttles, centcomm tiles and so on.
                • -
                • Added 'Bluespace' mutation to vines, makes them be able to grow through absolutely everything.
                • -
                • Added 'Space Proofing' mutation to vines, after they grow if the tile under them is space it will become special vinetile, which is just resprited regular floor, if the vine on the vinetile dies the vineturf changes back to space.
                • -
                • Made vines spreading speed depend on the seed's production, can be both slower and faster than current.
                • -
                • Made vines mutation chance be 1/10th of the potency of the seed it is spawned from.
                • -
                • Special chemicals added to vine seeds durning their growth can increase/decrease their potency and productivity.
                • -
                • Special chemicals now can remove good, bad or neutral mutations from vine seeds while they are growing, cultivating actually helpful vines is now possible.
                • -
                • Plant analyzers now show the vine seeds mutations.
                • -
                • Buffed numbers in some of the more useless mutations.
                • -
                -

                Steelpoint updated:

                -
                  -
                • Reworked and expanded areas of the derelict station. Featuring a repairable gravity generator, turret filled computer core and functional solars.
                • -
                -

                Vekter updated:

                -
                  -
                • Chemists, Geneticists, Virologists, Scientists, and Botanists will now spawn with alternative backpacks and satchels in their lockers. Try them out!
                • -
                - -

                25 February 2015

                -

                Cheridan updated:

                -
                  -
                • Foam now carries reagents, similarly to smoke. Have fun with that.
                • -
                -

                Dannno updated:

                -
                  -
                • Altered the sprite for security hardsuits. The old sprite is now a HoS specific hardsuit.
                • -
                -

                Delimusca updated:

                -
                  -
                • Turret's guns replaced by a heavy energy cannon.
                • -
                • Firing a heavy weapon sindlehandedly can make it recoil out of your hands.
                • -
                -

                Gun Hog updated:

                -
                  -
                • Nanotrasen has released new design specifications for the prototype combination of standard night-vision equipment and Optical Meson Scanners. We believe that this will address the concerns many Shaft Miners have concerning visiblity in a potentially unsafe environment.
                • -
                • Fixed malf AI's turret upgrade power not working.
                • -
                -

                Iamgoofball updated:

                -
                  -
                • You can now go into hyperglycaemic shock from having over 200u of Sugar in your body at once. Treat it with Insulin.
                • -
                • Insulin has been added. Use it to treat hyperglycaemic shock. You can get it from medical vendors and the Cargo medicine supplies crate.
                • -
                • Medvendor contents have been reduced from medical having everything they need at roundstart level, and toxin bottles are now back in the vendor.
                • -
                • Medical Analyzers now have 2 modes, Medical Scan and Chemical Scan. You can swap between modes by clicking on it in hand.
                • -
                • Medical scan is default behavior. Chemical scan shows all the reagents inside the mob at the time, along with any addictions the mob has. It will show if a reagent is overdosing.
                • -
                • Tobacco and Space Tobacco have Nicotine in them now.
                • -
                • Adds Methamphetamine to the game. Mix it with Ephedrine, Iodine, Phosphorous, and Hydrogen at 374K.
                • -
                • Or, look around in Maintenance for Muriatic Acid, Caustic Soda, and Hydrogen Chloride. Recipes are also available for these.
                • -
                • Adds Saltpetre. Mix it with Potassium, Nitrogen, and Oxygen.
                • -
                • Adds Bath Salts, mix it with Bad Food, Saltpetre, Nutriment, Space Cleaner, Universal Enzyme, Tea, and Mercury at 374K.
                • -
                • Adds Aranesp. Mix it with Epinephrine, Atropine, and Morphine.
                • -
                • Adds Hotline. Get it from Poppy plants.
                • -
                • Strange Reagent revival was changed. It now won't gib but won't do anything above a 100 BRUTE or BURN threshold.
                • -
                • Carpet now applies carpet tiles to whatever flooring it hits. Mix it with Space Drugs and Blood.
                • -
                • Colorful Reagent colors have been improved drastically and now are more varied and nicer looking.
                • -
                • Corn Starch has been added. Get it from juicing Corn.
                • -
                • Corn Syrup has been added. Mix it with Corn Starch and S-Acid.
                • -
                • Corgium has been added. Mix it with Nutriment, Colorful Reagent, Strange Reagent, and Blood at 374K.
                • -
                • Adds Quantum Hair Dye. Mix it with Colorful Reagent, Radium, and Space Drugs.
                • -
                • Adds Barber's Aid. Mix it with Carpet, Radium, and Space Drugs.
                • -
                • Adds Concentrated Barber's Aid. Mix it with Barber's Aid and Unstable Mutagen.
                • -
                • Re-adds Chlorine Trifluoride. Mix it with Chlorine and Fluorine at 424K.
                • -
                • Adds Black Powder. Mix it with Saltpetre, Charcoal, and Sulfur. Ignite it for an explosion at 474K
                • -
                • Finally nerfs Salglu Solution. It's no longer tricord 2.0.
                • -
                • Salt recipe is now Water + Sodium + Chlorine due to problems with recipe mixing.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • A new event has been added to the wizards summon events spell that gives the game a distictively more roleplaying flair.
                • -
                • Optional population cap functionality has been added to the game. By default all caps are disabled. Server owners should refer to config.txt to see their list of options.
                • -
                • Medical borgs now have access to a deployable onboard roller bed. Should the bed become lost, any roller bed may be used in its place.
                • -
                -

                Mandurrh updated:

                -
                  -
                • Added interchangeable bar signs
                • -
                • Added bays fountain machines for the bar. Soda and Beer. Allowing bartenders to have easier access to mix drinks and making more to make their job a little less boring
                • -
                • Added color tiles to the light up floor tiles.
                • -
                • Added metal, glass, and cable to bar back room at round start for bartender to make colored light dance floors.
                • -
                • Fixes clown cuffing both mobs and two pairs of cuffs appearing.
                • -
                • Fixes clown not being able to use his stamp as antag so no meta.
                • -
                • Fixes sleepypen bug. Instead of only dispensing 50u it dispenses the full 55u.
                • -
                • Fixed silicons not being able to pass through holotape.
                • -
                -

                Menshin updated:

                -
                  -
                • Getting into regenerative statis now puts the changeling into unconscious state.
                • -
                • Fixed changeling 'Revive' not working.
                • -
                -

                Paprika updated:

                -
                  -
                • Added an assistant cap, unlimited by default.
                • -
                • Batons now slowly lose power when left on.
                • -
                • Batons, flashbangs and handcuffs can be seen when stored in a belt.
                • -
                • Computers now emit light when they're on or bluescreened.
                • -
                • Added hotkeys for OOC and custom emotes. Press O and M when in hotkey mode. Remember not to ick in ock!
                • -
                • Added megaphones for the HoS, HoP, and QM.
                • -
                • Goliath tentacles no longer stun you if you cross them. They will only stun you if you don't move away from them in time. They can do up to 15 damage now, in addition to stunning you.
                • -
                • Changed a lot of mining gear and prices. The resonator can now have up to 3 fields active at once, but the fields need to detonate before they crush rocks. Tweak the time before the fields detonate with a screwdriver and upgrade the amount of fields at once with sheets of diamond.
                • -
                • Overhauled straight mining tools. The sonic jackhammer is now the wall-crushing tool, the diamond drill simply mines really fast. Borgs can now get upgraded with the diamond mining tool with only a single sheet of diamond and an upgrade module, they don't need illegal tech modules.
                • -
                • Mining drills and the sonic jackhammer now draw power when they drill down mineral walls. They can be recharged using any type of weapon recharger or by replacing the cell using a screwdriver on them.
                • -
                • Space now emits a dim light to adjacent tiles.
                • -
                • Syndicate shotguns now start with buckshot instead of stunslugs.
                • -
                • Fixed welding tools using fuel when being switched on.
                • -
                • Added a firing delay between shooting electrodes.
                • -
                • Changed the stun revolver from a high-capacity taser to a disabler with extra capacity.
                • -
                • Added a unique stun pistol for the head of security. It is a double capacity taser with no shot delay and a loyalty pin, though it lacks a disable mode.
                • -
                -

                RandomMarine updated:

                -
                  -
                • New brute treatment kit crate for cargo.
                • -
                -

                RemieRichards updated:

                -
                  -
                • Explosions will now blow (throw) items away from the epicenter
                • -
                • Sharp items can now embedd themselves in a human's limbs
                • -
                • Embedded items have a chance to do some damage (based on w_class) each life() call
                • -
                • Embedded items have a chance to fall out doing some more damage, but removing the object
                • -
                • Adds a surgery to remove Embdedded objects
                • -
                • Adds throwing stars as an uplink item (100% chance to embedd)
                • -
                • Items placed on tables now center to exactly where you clicked.
                • -
                -

                Steelpoint updated:

                -
                  -
                • SS13's Head of Security has received a new attempted 1:1 recreation of a Old Earth weapon, the Captain's Antique Laser Gun.
                • -
                • This gun features three firing modes, Tase, Laser and Disable. However this gun lacks the capability to recharge in the field, and is extreamly expensive.
                • -
                • INTERCEPTED TRANSMISSION REPORT: The Syndicate have expressed a interest in this new weapon, and have instructed field agents to do whatever they can to steal this weapon.
                • -
                -

                TheVekter updated:

                -
                  -
                • Box's vault nuke has been replaced with immobile Nuclear Self-Destruct Mechanism.
                • -
                • Vault's new green floor tiles turn flashing red when Self-Destruct is activated.
                • -
                -

                Xhuis updated:

                -
                  -
                • Syndicate contacts have discovered new ways to manipulate alarms. Malfunctioning AIs, as well as normal emags, can now disable the safeties on air and fire alarms.
                • -
                • When one emags an air alarm, its safeties are disabled, giving it the Flood environmental type, which disables scrubbers as well as vent pressure checks. Burn, pigs, burn!
                • -
                • When one emags a fire alarm, its thermal sesnsors are disabled. This will prevent automatic alerts due to the alarm being unable to recognize high temperatures.
                • -
                • Malf AIs gain two new modules, both of which disable ALL air alarm safeties and thermal sensors. These are separate abilities.
                • -
                • Air and fire alarms have notifications on their interface about the current safety status. One only has to glance at one to see that it's been tampered with.
                • -
                -

                phil235 updated:

                -
                  -
                • Fixed arcade machines being usable from inside lockers to dupe rewards.
                • -
                • Silicons can no longer be grabbed by anyone. No more silicon choking
                • -
                • Custom food can now be renamed using a pen.
                • -
                -

                xxalpha updated:

                -
                  -
                • Added new graffiti: cyka, prolizard, antilizard
                • -
                - -

                18 February 2015

                -

                Dannno updated:

                -
                  -
                • Added proper slurring when drunk.
                • -
                -

                Deantwo updated:

                -
                  -
                • Updated the ChemMaster's UI to the station default along with some functionality improvements.
                • -
                • You can now spam the 'Ready' button as much as you want without it setting you 'Not ready' again.
                • -
                -

                Dorsidwarf updated:

                -
                  -
                • Added the ability to call Central Command for the nuclear authentication codes. This requires a captain-level ID and admin approval, and warns the crew.
                • -
                -

                Gun Hog updated:

                -
                  -
                • E.X.P.E.R.I-MENTOR meteor storm event replaced with a fireball that targets random nearby mob.
                • -
                • EMP event range reduced.
                • -
                • Self-duplicating relics limited to 10 copies.
                • -
                • Nanotrasen has released a firmware patch for the Ore Redemption Machine, which now allows the station AI, cyborgs, and maintenance drones to smelt raw resources without the need of a valid identification card installed.
                • -
                • Nanotrasen scientists have completed their designs for a lightweight, compacted anti-armor ion weapon, without loss of efficiency. In theory, such a weapon could easily fit within standard issue backpacks and satchels. With sufficient research into weapons technology and materials, Nanotrasen believes a working prototype can be fabricated.
                • -
                -

                Iamgoofball updated:

                -
                  -
                • Buffs the shit out of Styptic Powder and Silver Sulf, they're now viable to use. Synthflesh is still good for general treatment, but overall Styptic and Silver Sulf deal with their respective damage types way better now.
                • -
                • Improves kinetic accelerator's mining strength.
                • -
                • The kinetic accelerator's cooldown can be decreased by tweaking the thermal exchanger with a screwdriver.
                • -
                • The range can be increased with plasma sheets
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Medical borgs have been given full surgery tools which they can't fail a procedure with.
                • -
                • Medical borgs also have rechargeable gauze.
                • -
                • Borg hypo's omnizine replaced with salbutamol, salglu and charcoal.
                • -
                • Slime and Jelly mutant races now actually contain slime jelly. While playing as these races you will naturally produce it internally from your nutrition, so be sure to stay well fed as losing too much is dangerous to your health! Beware of chems that clear toxins, as they can be rapidly fatal to you!
                • -
                -

                Jordie0608 updated:

                -
                  -
                • Veil render and rifts changed to spawn with vars, rifts can be closed with a nullrod.
                • -
                -

                Lo6a4evskiy updated:

                -
                  -
                • General record now contains a species field and a photo of the crewmember. Security record consoles allow you to update it with a photo taken in-game (with detective's camera, for example, or AI's).
                • -
                • Expanded functionality of security HUDs to display rank and photos.
                • -
                • Medical HUDs allow user to change others' mental and physical status. Currently has no gameplay effect other than changing records.
                • -
                • Medical HUDs can perform an examination at the distance, similrarly to how attacking yourself with help intent works. Has limited ability to detect suffocation and toxin damage.
                • -
                -

                Menshin updated:

                -
                  -
                • Solar control computers remade with NanoUI
                • -
                -

                Paprika updated:

                -
                  -
                • Added rnd requirements to borg upgrades and tweaked the prices.
                • -
                • Changed some clothing around. Added a turtleneck for the HoS. Removed 'civillian' armor and gave normal armor to the bartender and HoP. Added flash protection to the HoS' eyepatch, it functions like sunglasses now. Additionally, added the ability to store ammo on your shoulder holster as detective.
                • -
                • Removed the defib steal objective. Added a compact defibrillator for the CMO that equips to the belt. Removed defibrillators from RND. Removed the requirement for people to not wear armor to revive them with defibs. Doubled defib timers (10 minutes before permadeath).
                • -
                • Flashlights can be attached to pulse carbines.
                • -
                • Miners spawn with a brute patch.
                • -
                • Mining station's sleeper has been replaced with an IV drip and more medkits.
                • -
                • Added hard-light holotape. This holographic police or engineering hazard tape will block entry if you're running, so you need to walk to pass it. This is to help people not sprint into breaches or crime scenes.
                • -
                • Fixed being able to sling the strapless improvised shotgun on your back.
                • -
                • Reverted the price of the ebow to 12tc and removed the range limit of its bolts.
                • -
                -

                Sometinyprick updated:

                -
                  -
                • Novely pig mask prize from arcade machines, can be toggled to make you squeal like a pig.
                • -
                -

                Steelpoint updated:

                -
                  -
                • Security Cyborgs Advance Taser have been replaced with a Disabler.
                • -
                -

                TZK13 updated:

                -
                  -
                • Adds plaid skirts and new schoolgirl outfits to the autodrobe
                • -
                • Novaflowers apply a firestack for every 20 potency they have.
                • -
                • You can now juice whole watermelons instead of slices.
                • -
                • Advancements in the field of virology by Nanotrasen's finest have uncovered three new disease symptoms for further research:
                • -
                • Ocular Restoration heals any eye trauma experienced by the infected patient.
                • -
                • Revitiligo increases the pigmentation levels of the infected patient's skin.
                • -
                • Spontaneous Combustion ignites the infected patient without warning.
                • -
                -

                Vekter updated:

                -
                  -
                • Added shot glasses! Can be obtained from the Booze-o-Mat.
                • -
                • A new premium vodka has been added to the game, hidden in a special spot.
                • -
                • Due to constant attempts to break into the station's safe, the contents have been relocated and have not been replaced. We promise. There is absolutely NO reason to break into the vault's safe anymore.
                • -
                • Each shot glass now holds 15 units, and the drinker takes all 15 in one gulp instead of 5 at a time. This means you can mix certain shots (namely, the B-52 and Toxins Special) in a shot glass. Both drinks have been made exclusive to shot glasses and will no longer show their specific sprite in a normal glass.
                • -
                • Added new sprites for most alcohol in shot glasses. Everything else will show up as 'shot of... what?'.
                • -
                • Fixed shot glasses turning into regular glasses when filled.
                • -
                • Fixed tequila bottle sprites not showing up.
                • -
                • Fixed a typo!
                • -
                • Roboticists will now start with a full toolbelt instead of a toolbox.
                • -
                -

                drovidi updated:

                -
                  -
                • Blobs who burst in space will be given a 30 second warning before dying.
                • -
                • If a blob does die from bursting in space, a new crewmember will be infected.
                • -
                • If there are no blobs or infected crew left after bursting, the round will now end properly, rather than becoming endless.
                • -
                -

                phil235 updated:

                -
                  -
                • You no longer need to cut the syphon wire to deconstruct air alarms.
                • -
                • Cooking overhaul: microwave recipes are converted to tablecraft recipes.
                • -
                • Added customizable food (burger,sandwich,spaghettis,soup,salad,cake,bread,kebab,pie) with specific color depending on ingredients used.
                • -
                • Added new food ingredients (dough, flat dough, cake batter, pie dough, doughslice, bun, raw pastry base, pastry base, raw cutlet, cutlet, pizzabread). Dough is made by mixing egg and flour, then transformed by using knife, rollingpin, milk and microwave. Meat is now sliceable into rawcutlets (specific color for each meat type).
                • -
                • Changed food recipes a bit (replacing stuff with new food ingredients).
                • -
                • Repurposed the microwave to actually cook certain food items(no reagents required), renamed it microwave oven and added a power setting to it.
                • -
                • Bowl is no longer trash but a beaker-like container that is used in salad&soup recipes. Also, milk and soymilk cartons as well as flour packs are now condiment bottles.
                • -
                • Changed the hunger system a bit, sugar becomes a normal reagent again. Faster nutrition drain is now handled by a variable in junk food. Also added a slight buff to vitamin.
                • -
                -

                xxalpha updated:

                -
                  -
                • You now hear the voice of the chosen deity in prayers.
                • -
                - -

                05 February 2015

                -

                Danno updated:

                -
                  -
                • Added suicide messages for all gun types and various different items.
                • -
                -

                Paprika updated:

                -
                  -
                • Compacted ERT room by removing equipment rooms, filler rooms and pre-spawned mechs.
                • -
                • Rebalanced Pulse weapons to have more shots, faster charge, EMP immunity and modified sprites.
                • -
                • ERT spawns fully equipped with no-slowdown suits.
                • -
                • Added blood and bleeding. If you take enough brute damage on a certain limb, you will start bleeding, and will need to patch your bleeding with gauze from medbay. You can replace lost blood by eating nutritious food and waiting for your heart to reproduce it, or by getting a blood transfusion from an IV drip.
                • -
                -

                Vekter updated:

                -
                  -
                • Added a toy red button to the Arcade cabinets.
                • -
                - -

                04 February 2015

                -

                Danno updated:

                -
                  -
                • Fixes dufflebag's storage capacity.
                • -
                -

                Deantwo updated:

                -
                  -
                • Removes duplicate fluorine from Chemical Dispenser.
                • -
                • Added GoonChems to the Portable Chem Dispenser.
                • -
                • Added cancel buttons to the ChemMaster and CondiMaster when making bottles, pills, etc.
                • -
                -

                Delimusca updated:

                -
                  -
                • Added new foam armblade toy to arcade prizes.
                • -
                • Slimes can drag themselves to mobs to start feeding.
                • -
                • Attacking another slime will pull them off their victim if they're feeding or steal some of their mass.
                • -
                -

                GunHog updated:

                -
                  -
                • Five new silicon emotes: *buzz2, *chime, *honk, *sad and *warn
                • -
                -

                Incoming5643 updated:

                -
                  -
                • New contraband crate containing fifty butterflies.
                • -
                • Shuttle will now always arrive, but won't launch in rounds where it would've been previously auto-recalled.
                • -
                -

                Menshin updated:

                -
                  -
                • Fixed being unable to place cables on plating.
                • -
                -

                Paprika updated:

                -
                  -
                • Helmets for hardsuits are stored inside the suit and cannot be equipped without the suit on.
                • -
                • EVA space suits are now lighter for faster movement but lack flash or welding protection, don't hide your identity and have less radiation protection.
                • -
                -

                Perakp updated:

                -
                  -
                • Tweaked slowdown due to coldness when in in low gravity.
                • -
                -

                Steelpoint updated:

                -
                  -
                • ERT base equipment rooms are now access restriction to each role.
                • -
                • ERT commander spawns wearing hardsuit for identification.
                • -
                • Two boxes of metal foam grenades added for ERT engineers.
                • -
                • Deep space sensors recently detected the derelict remains of the NTSS Omen, a outdated Medical Frigate thought lost, within close proximity to Space Station 13.
                • -
                • If any SS13 EVA Personal are able to locate this ship, they will be able to pilot it back to the station for recovery. The ship may also have cordinates for other locations.
                • -
                -

                Xhuis updated:

                -
                  -
                • Changelings now have the ability Strained Muscles, which lets them move at inhuman speeds at the cost of rapid stamina damage. It costs one evolution points=.
                • -
                • Changelings now have the ability Augmented Eyesight, which lets them see creatures through walls and see in the dark. It costs two evolution points.
                • -
                • After many failed raids, the Syndicate have finally done something about poor communications between cyborgs and operatives.
                • -
                • Syndicate Cyborgs now come equipped with operative pinpointers, which will automatically point to the nearest nuclear operative.
                • -
                • Handheld crew monitors can now be stored in medical belts.
                • -
                - -

                26 January 2015

                -

                Dannno updated:

                -
                  -
                • Added fannypacks of multiple colors, 3 of which are accessible from the clothing vendor.
                • -
                • Added dufflebags to multiple lockers throughout the station and to the syndicate shuttle, more storage slots at the cost of move speed.
                • -
                -

                Deantwo updated:

                -
                  -
                • Fixed some bugs in PDA's NTRC Chatroom feature.
                • -
                • You can now use PaperBBCode/PenCode when writing news articles on the newscaster.
                • -
                • PDA's Notekeeper now uses PaperBBCode/PenCode rather than HTML.
                • -
                • Scanning paper with your PDA is now more informative.
                • -
                -

                Firecage updated:

                -
                  -
                • We, at NanoTrasen, have recently upgraded the Kitchen Spikes to include the skin of both Monkeys and Aliens when you harvest their flesh!
                • -
                • In other news, a station in a nearby Sector has been making monkey and xeno costumes. We are not sure why.
                • -
                • Added the ability to reform mineral floortiles back into sheets/bars
                • -
                -

                Ikarrus updated:

                -
                  -
                • Added a new config option MINIMAL_ACCESS_THRESHOLD that can be used to automatically give players more access during low-population rounds. See game_options.txt for more information.
                • -
                -

                Lo6a4evskiy updated:

                -
                  -
                • The kitchen now contains a brand new food cart. It can store foods and liquids, mix them into cocktails and dispense where needed. Food delivery has never been easier!
                • -
                -

                fleure updated:

                -
                  -
                • Added briefcase filled with cash to uplink.
                • -
                - -

                21 January 2015

                -

                Iamgoofball updated:

                -
                  -
                • Addiction and Overdosing has been added to Chemistry.
                • -
                • Deal with Overdosing by purging the ODing chemical with Calomel or Penetic Acid.
                • -
                • Deal with Addiction by either going cold turkey on it and weathering the effects, eventually causing the addiction to subside, or by taking more of the addictive substance to keep the effects down.
                • -
                • Cryoxadane was heavily buffed. Clonex was removed as it was merged into Cryox.
                • -
                • Fixes a shitton of stuff adding Morphine instead of Ephedrine. Sorry, miners.
                • -
                • Fixes instances of Alkysine not being replaced with Mannitol. Same with Ryetalyn being replaced with Mutadone.
                • -
                • Adds Itching Powder. Welding Fuel and Ammonia.
                • -
                • Adds Antihol. Ethanol and Charcoal.
                • -
                • Adds Triple Citrus. Lemon Juice, Lime Juice, and Orange Juice.
                • -
                • Adds Colorful Reagent. Stable Plasma, Triple Citrus,
                • -
                • Morphine replaces sleep toxin now, not Hyperzine. Hyperzine is replaced by Ephedrine now.
                • -
                • Cryotubes will now autoeject you once you are fully healed.
                • -
                • Synthflesh damage values have been fixed.
                • -
                • Charcoal has been buffed to heal 3 TOX damage a cycle and it purges other chemicals faster.
                • -
                • Saline-Glucose Solution has been buffed. It now has a 50% chance per cycle to heal 3 BRUTE and BURN per tick.
                • -
                • Fixes Strange Reagent revivals dying right after being revived. They'll still need immediate medical treatment, however.
                • -
                • Cryoxadone has had a recipe change. It is now Stable Plasma, Acetone, and Unstable Mutagen.
                • -
                • Adds 9 new Disabilites and 2 new genetic powers.
                • -
                • You can now heat donk pockets once more. Rejoice.
                • -
                - -

                17 January 2015

                -

                Cheridan updated:

                -
                  -
                • The lockbox system for R&D has been replaced with a firing pin system. Ask the Warden or HoS for firing pins to allow normal operation of your firearms. Want to test them out at the range? Try printing a test-range pin.
                • -
                • Nuke Ops have used this new technology to lock some of their heavier weapons so that crew cannot use them. These implants replace their explosive implants.
                • -
                • Explosive implants are now purchasable in the uplink, and are much stronger.
                • -
                -

                Firecage updated:

                -
                  -
                • NanoTrasen would like to report that our Mechanical Engineers at NTEngiCore has recently upgraded the capacity of the machines known as Biogenerators. They are now have the added ability to create various belts, and also weed/plantkillers and cartons of milk and cream!
                • -
                -

                Iamgoofball (Look Ma, I didn't mispell my name this time!) updated:

                -
                  -
                • Replaced Star Trek chemistry with Goon Chemistry.
                • -
                • Emergency Medic's Handbook Tip 1: Styptic Powder = BRUTE, Silver Sulfadiazine = BURN, Charcoal = TOX, Salbutamol = OXY
                • -
                • In recipes that used removed reagents, they have been replaced with their goon chemistry counterpart:
                • -
                • Hyperzine -> Morphine
                • -
                • Inaprovaline -> Epinephrine
                • -
                • Kelotane -> Saline-Glucose Solution
                • -
                • Bicardine -> Saline-Glucose Solution
                • -
                • Dermaline -> Saline-Glucose Solution
                • -
                • Dexalin -> Salbutamol
                • -
                • Dexalin Plus -> Salbutamol
                • -
                • Tricordrazine -> Omnizine
                • -
                • Anti-Toxin -> Charcoal
                • -
                • Hydronalin -> Penetic Acid
                • -
                • Arithrazine -> Penetic Acid
                • -
                • Imidazoline -> Oculine
                • -
                • Mannitol -> Alkysine
                • -
                • Ryetalyn -> Mutadone
                • -
                -

                Steelpoint updated:

                -
                  -
                • The Emergency Response Team project has been activated by Nanotrasen. The ERT are a seven man squad that can be deployed by the Central Command (admins) to attempt to rescue the station from a overwhelming threat, or to render assistance in dealing with a significant problem.
                • -
                • The ERT consists of a single Commander, two Security Officers, two Engineering Officers and two Medical Officers. The Commander is preselected when the squad is being spawned in, however the remaining six officers are free to select their role in the squad.
                • -
                • Two new Pulse gun variants have been added to the game. They are the Pulse Carbine and the Pulse Pistol, both have significantly smaller ammunition capacities than the Pulse Rifle, however they both can fit in a backpack and the pistol can fit in a pocket. All ERT Officers have a Pulse Pistol for a sidearm, however the Security Officers get a Pulse Carbine as their primary longarm.
                • -
                -

                phil235 updated:

                -
                  -
                • The two library consoles are now real buildable computers. The circuitboard design is available in R&D.
                • -
                -

                sawu-tg updated:

                -
                  -
                • Added a new R&D machine, to replace the telescience room. The Machine is based on RND and various interdepartmental creations.
                • -
                - -

                14 January 2015

                -

                Boggart updated:

                -
                  -
                • Adds a new spell: Repulse. (Cooldown: 40 seconds, 15 at max upgrade.) Repulse throws objects and creatures within 5 tiles of the caster away and stuns them for a very short time. Distance thrown decreases as the distance from the caster increases. (Max of 5, minimum of 3) Casting it while standing over someone will stun them for longer and cause brute damage but will not throw them.
                • -
                • Fixes the Exosuit Control Console having the unpowered icon state while powered.
                • -
                -

                Iamgootball updated:

                -
                  -
                • Added Goonstation Chemistry!
                • -
                • Adds 14 medicines!
                • -
                • Adds 3 pyrotechnics!
                • -
                • Adds 3 drugs with actual gameplay uses!
                • -
                • Adds 7 toxins!
                • -
                • Adds a Chemical Heater for some new recipes!
                • -
                • Adds Chemical Patches for touch based applications!
                • -
                • Adds a Traitor Poison Kit!
                • -
                • Adds Morphine Medipens!
                • -
                • Adds Morphine bottles to Cargo!
                • -
                - -

                13 January 2015

                -

                Dannno updated:

                -
                  -
                • Adds a bandanas in basic colors to lockers in the station. Activate in hand to switch between head and mask modes.
                • -
                • Adds more colored jumpsuits to the mixed locker.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Player controlled slimes can now change their face to match AI controlled slimes in kawaiiness. use *help as a slime to find the commands.
                • -
                -

                Studley updated:

                -
                  -
                • Fixed NTSL division.
                • -
                • Added new NTSL math functions, including 'sin,' 'cos,' 'asin,' 'acos,' and 'log.'
                • -
                - -

                06 January 2015

                -

                JJRcop, Nienhaus updated:

                -
                  -
                • Adds emoji to OOC chat.
                • -
                - -

                27 December 2014

                -

                Dorsidwarf updated:

                -
                  -
                • Buffs the RCD from 30 matter units to max capacity 100, and increases the value of matter charges.
                • -
                -

                Paprika updated:

                -
                  -
                • Taser electrodes now have a limited range of 7 tiles.
                • -
                • The energy gun's stun mode has been replaced with a disable mode.
                • -
                • Security officers will now spawn with tasers.
                • -
                • Disabler beams now go through windows and grilles.
                • -
                • Disabler beams now do 33 stamina damage instead of 34. This means they will only stun in 3 hits on someone that is NOT 100% healthy.
                • -
                • Most ballistic projectiles now do stamina damage in addition to their regular damage instead of stunning. This includes nuke op c-20r .45, detective's revolver, etc. They are effectively better than disablers at stunning people, as well as do significant damage, but they do not stun instantly like a taser. Expect engagements to be a little longer and plan ahead for this.
                • -
                • Added an advanced taser for RnD, the child of a disabler and a taser.
                • -
                -

                Tkdrg updated:

                -
                  -
                • Envoys from the distant Viji sector have brought an exotic contraption to Centcomm. Known as the 'supermatter shard', it is a fragment of an ancient alien artifact, believed to have a dangerous and mysterious power. This precious item has been made available to the Space Station 13 cargo team for the cost of a hundred supply points, as a reward for all your hard work. Good luck, and stay safe.
                • -
                - -

                16 December 2014

                -

                Paprika updated:

                -
                  -
                • Reverted security armor and helmets to the old (Grey) style of armor/helmets.
                • -
                • Updated security jumpsuits to be more consistent across officer/warden/HoS ranks.
                • -
                • Added tactical armor and helmets to replace the largely unused bulletproof vest in the armory. This armor has higher bullet and explosive resistance than normal armor, and uses the previous, more military armor/helmet sprites.
                • -
                • Added a new injury doll to go along with the overall health meter. This will show you locational overall limb-based burn/brute damage, so you can tell the difference between being hurt with burn/brute or toxins/stamina damage which does not affect limbs.
                • -
                • Reverted some UI icons back to their older status and changed some around for readability. Thank you for your feedback.
                • -
                -

                Thunder12345 updated:

                -
                  -
                • Added a new FRAG-12 explosive shotgun shell, built from the unloaded technological shell, using tablecrafting. Constructed using an unloaded technological tech shell, 5 units sulphuric acid, 5 units polytrinic acid, 5 units glycerol, and requires a screwdriver.
                • -
                -

                phil235 updated:

                -
                  -
                • Change to the hunger system. Eating food with sugar temporarily worsens your nutrition drop rate. Eating too much sugar too quickly can make you temporarily unable to eat any sugary food. The nutritional effect of sugar depends on how hungry you are thus it cannot easily raise your nutrition level past well fed and is best used when hungry. Lots of sugar can make you a bit jittery at times. Eating food with vitamin (new reagent) counteracts the sugar effects and can give you temporary buffs when well fed: it lowers your chance to catch or spread viruses,it makes your body's metabolism more efficient that is it keeps healing reagents longer and evacuate toxins faster and reduces damage from radioactive reagents. Your metabolism gets less efficient if starving.
                • -
                • New hunger hud icons: starving, hungry, fed, well fed, full, fat.
                • -
                • Snack vending machine get a chef compartment that can be loaded with non-sugary food by hand or with a tray by anyone with kitchen access (unless you hack the machine with multitool or emag). The food can be unloaded by anyone, like regular snacks
                • -
                • Cargo can get a nutritious pizza crate order for 60 points.
                • -
                • Grown and cooked food gets some vitamin while junk food gets less nutriment and more sugar (only hot donkpocket and ramen don't have sugar)
                • -
                • The number of junk food in snack vending machine is lowered from 6 to 5 of each type.
                • -
                • Fixing not being able to load an omelette du fromage on a tray.
                • -
                - -

                10 December 2014

                -

                GunHog updated:

                -
                  -
                • The Nanotrasen firmware update 12.05.14 for station AI units and cyborgs includes a patch to give them more control of their automatic speech systems. They can now chose if the message should be broadcasted with their installed radio system and any frequency available to them.
                • -
                -

                Paprika updated:

                -
                  -
                • Added winter coats and winter boots! Bundle up for space xmas! They are available in most wardrobe and job lockers, and come with special features depending on the flavor!
                • -
                -

                Paprka updated:

                -
                  -
                • Added a new weapon for syndicate boarding parties, the C-90gl assault rifle. This is a hybrid rifle with a lot of damage and a grenade launcher alt-fire. Purchasable for 18TC.
                • -
                • Suppressors can now be used on the protolathe SMG and the C-20r.
                • -
                -

                RemieRichards updated:

                -
                  -
                • Sounds in areas of low pressure (E.g. Space) are now much quieter
                • -
                • To hear someone in space you need to be stood next to them, or use a radio
                • -
                -

                as334 updated:

                -
                  -
                • We've discovered that slimes react to the introduction of certain chemicals in their bloodstream. Inaprovaline will decrease the slimes children's chance of mutation, and plasma will increase it.
                • -
                • We've also found that feeding slimes solid plasma bars makes them more friendly to humans.
                • -
                - -

                08 December 2014

                -

                Ergovisavi updated:

                -
                  -
                • Allows using replica pods to clone people without bodies.
                • -
                • Speeds up replica pod cloning by adjusting the starting production.
                • -
                • Replica pods will now only create plantmen.
                • -
                • Mixing blood in a beaker or taking it from a suicide victim will render the blood sample useless for replica pod cloning.
                • -
                -

                GunHog updated:

                -
                  -
                • Our top Nanotrasen scientists have provided improved specifications for the Rapid Part Exchange Device (RPED)! The RPED can now contain up to 50 machine components, and will display identical items in groups when using its inventory interface.
                • -
                • Note that power cells, being somewhat bulkier, will take up more space inside the device. In addition, the RPED's firmware has also been updated to assist in new machine construction! Simply have the required components inside the RPED, apply it to an unfinished machine with its circuit board installed, and watch as the device does the work!
                • -
                • Nanotrasen has updated station bound Artificial Intelligence unit design specifications to include an integrated subspace radio transmitter. The transmitter is programmed with all standard department channels, along with the inclusion of its private channel. It is accessed using :o in its interface.
                • -
                • {INCOMING CLASSIFIED SYNDICATE TRANSMISSION} Greetings, agents! Today, we are happy to announce that we have successfully reverse engineered the new subspace radio found in Nanotrasen's newest AI build plans. We have included our encryption data into these transmitters.
                • -
                • Should you be deployed with one of our agent AIs, it may be wise of you to purchase a syndicate encryption key in order to contact it securely. Remember, as with other agents, a Syndicate AI is unlikely to be of the same Syndicate corporation as you, and you both may actually have conflicting mission parameters! Your channel, as always, is accessed using :t.
                • -
                -

                Paprika updated:

                -
                  -
                • Changed the way flashbang protection works. Currently, only security helmets (and their derivatives like swat and riot), and some hardsuit helmets have flashbang protection. Ear items like earmuffs and 'bowman' headsets (alternative, larger headsets) have flashbang protection as well, so you're able to go hatless as security. The Head of security, warden, and detective's hat do NOT have flashbang protection, but their earpieces do, from the 'noise' part of the flashbang. The flashbang blind still works as before, with only sunglasses/hardsuit helmets protecting you.
                • -
                -

                Tkdrg updated:

                -
                  -
                • Security/Medical HUDs are now more responsive and less laggy.
                • -
                - -

                10 November 2014

                -

                Menshin updated:

                -
                  -
                • Made meteors more threatening.
                • -
                • Added three levels of danger for meteors waves, randomly selected at spawn.
                • -
                • Rumors speaks of a very rare meteor of unfathomable destructive power in the station sector...
                • -
                • Fixed infinigib/infinisplosion with meteors bumping
                • -
                -

                Paprika updated:

                -
                  -
                • Added a defibrillator and an EMT alternative clothing set for medical doctors. These defibrillators will revive the recently deceased as long as their souls are occupying their body and they have no outstanding brute or burn damage.
                • -
                • Yes, they actually work now too!
                • -
                • Added a way for you to unload a round from a chambered projectile weapon. Simply click on the gun when it is in your active hand, and assuming it does not have a magazine, the round should pop out. You can also use this system to unload individual bullets from magazines and ammo boxes, like the detective's speed loaders.
                • -
                • Added glass tables and overhauled how table construction works. Now, you need to build a frame using either metal rods or wood planks, and then place a top on the table.
                • -
                • Table disassembly now works in two stages. Screwdriver the table to remove the top of it and reduce it back to a frame. Frames can be moved around and walked on top of. Wrench the table to deconstruct it entirely back to parts, this is quicker and much more like the old fashion. To deconstruct a frame, use your wrench on it as well.
                • -
                • Fixed traitor med analyzers not fitting in medical belts.
                • -
                • Fixed syringe box bug with medipens.
                • -
                -

                Perakp updated:

                -
                  -
                • Added escape-with-identity objective for changelings. Completion requires the changeling to have transformed to their target and be wearing an ID card with the target's name on it when escaping.
                • -
                • Changelings can absorb someone even if they have that DNA in storage already.
                • -
                • Fixes hivemind being lost when readapting evolutions.
                • -
                - -

                04 November 2014

                -

                MrPerson updated:

                -
                  -
                • You can use AI Upload modules directly on unslaved cyborgs to change their laws. The panel has to be open to do this.
                • -
                • Newly emagged cyborgs get locked down for a little bit until their new laws get shown. Thank you to whoever bitched about it on singulo and filed an issue report.
                • -
                -

                Paprika updated:

                -
                  -
                • Added an advanced mop for the janitor, available at the protolathe. This hi-tech custodian's friend can mop twice as many floor tiles before needing to return to your bucket!
                • -
                • Moved holosign projector to the protolathe and gave the janitor back his old signs. We hope you've enjoyed this free trial run of holo projectors, but our budget is tight!
                • -
                • Added a holosign projector to the janitor module for cyborgs.
                • -
                • Soap now has a delay. Different soap types clean faster than others, but expect to put it some extra arm work if you want to clean that fresh blood!
                • -
                -

                Tkdrg updated:

                -
                  -
                • Cutting-edge research in a secret Nanotransen lab has shed light into a revolutionary form of communication technology known as 'chat'. The Personal Data Assistants of Space Station 13 have been deployed with an experimental module implementing this system, dubbed 'Nanotransen Relay Chat'.
                • -
                - -

                30 October 2014

                -

                MrPerson updated:

                -
                  -
                • Everything now drifts in space, not just mobs.
                • -
                • Mechs are just as maneuverable in space as you are.
                • -
                • Being weightless now slows movement down by a lot rather than speeding it up. Having your hands full will make movement even slower.
                • -
                • Removed spaceslipping (You slipped! x1000)
                • -
                • Jetpacking after being thrown out a mass driver will cancel out your momentum.
                • -
                • Shooting a gun causes you to drift the other direction like spraying a fire extinguisher.
                • -
                - -

                27 October 2014

                -

                Jordie0608 updated:

                -
                  -
                • Adds a new riot shotgun that spawns in the armory with a 6 round beanbag magazine.
                • -
                -

                Paprika updated:

                -
                  -
                • Allowed constructs to assist in activating most runes. This includes conversion runes, but does NOT include sacrifice runes.
                • -
                -

                Patchouli Knowledge updated:

                -
                  -
                • In light of the recent achievements of the Robotics department, new and upgraded exosuit schematics were delivered to SS13 exosuit fabricators as a reward from CentComm.
                • -
                • The countless hours of research, experimentation, and reverse-engineering of wrecks from the mining asteroid have paid off - the Research team has rediscovered the process of building Phazon exosuits. The parts and circuits have been made available in the exofabs and circuit printers.
                • -
                • The APLU Ripley exosuit has been refitted with asteroid mining purposes in mind. Its armour plating will provide extra protection against the brute attacks of asteroid alien lifeforms, and protect from gibtonite explosions. The leg servos were upgraded to provide faster movement.
                • -
                • There are rumours of some creative shaft miners discovering a way of augmenting APLU Ripley armour with trophy hides from goliath-type asteroid lifeforms...
                • -
                • Durand construction routines now require a phasic scanner module and a super capacitor to match Nanotrasen industry standards.
                • -
                • The exosuit fire extinguisher now boasts a larger watertank, and carries a whole 1000u of water from the old 200u tank.
                • -
                • The exosuit plasma converter's efficiency has been vastly improved upon, and its fuel consumption has been cut to nearly half of old values.
                • -
                • The exosuit sleeper interiors have been refitted to allow the occupants more freedom of movement, and the occupants may now eject at will.
                • -
                • The armour plating on all exosuits was fitted with reactive deflection systems specifically designed to stop the rapid slashes from xenomorph threats.
                • -
                • The Firefighter Ripley chassis were moved from the Exosuit Equipment section to the APLU Ripley section of exofab menus.
                • -
                • Fixed some minor errors in the exosuit construction message syntax.
                • -
                -

                phil235 updated:

                -
                  -
                • Closets and its children can no longer contain a near infinite amount of mobs. Large mobs (juggernaut, alien emperess, etc) don't fit, small mobs (mouse, parrot, etc) still fit in the same amounts, human-sized mob (humanoid, slime, etc) fit but maximum two per body bag and three for all the other closets. Adding bluespace body bag to R&D, it can hold even large mobs and in large amounts.
                • -
                • girders and displaced girders now have a chance to let projectiles pass through them.
                • -
                - -

                19 October 2014

                -

                Donkie updated:

                -
                  -
                • Major atmos update. Featuring pretty colors.
                • -
                • 4-Way pipes added.
                • -
                • Flip-able trinary devices (mixer & filter).
                • -
                • Pipe-painter added.
                • -
                • Most pipe icons have been tweaked with shading and such, making it look more uniform.
                • -
                -

                JJRcop updated:

                -
                  -
                • We have managed to re-discover drones! We improved the formula, and have discovered some cool stuff.
                • -
                • After some fiddling with audio recognition, drones can now understand human speech!
                • -
                • One of our engineers found some screws that were loose on some of our tests, screwing them in made the drone work as if it had just come out of robotics.
                • -
                • We had to sacrifice some of our NT brand EMP protection for the speech recognition, so drones can get damaged if subjected to heavy EMP radiation.
                • -
                • We figured out why drones couldn't remove objects from people, that has been fixed.
                • -
                • The standby light wasn't wired correctly.
                • -
                • Unfortunately one of our assistants tasked with transporting the only copy of the data dropped the flash drive in hot water because he wanted to see what "drone tea" tasted like. You'll have to discover them again.
                • -
                -

                Lo6a4evskiy updated:

                -
                  -
                • Station's crayons have been upgraded to the version 2.0 of ColourOS. Don't worry, they are still edible!
                • -
                • Many items now have different stripping delays. Heavier and more armored pieces of clothing are generally more difficult to take off someone else, while smaller items can be very easy to snatch. Pockets always take 4 seconds to search.
                • -
                -

                MrPerson updated:

                -
                  -
                • Implants are activated via action buttons on the top-left corner of your screen instead of emotes.
                • -
                -

                Paprika updated:

                -
                  -
                • Added zipties for security and nuke operatives. These have a slightly shorter 'breakout time' as normal cuffs but they are destroyed upon removal. Beepsky and sec borgs now use these by default.
                • -
                • Changed the medical HUD in the tactical medkit to a night vision HUD so syndicate medics don't stumble around in the dark. Can be purchased through nuke op uplinks.
                • -
                • Added a second roboticist roundstart job slot.
                • -
                • Added a third cargo tech job slot.
                • -
                -

                Paprka updated:

                -
                  -
                • Added undershirts as a companion piece to jumpsuit adjusting. Beat the heat with a comfortable tank top, or warm up with an extra layer tshirt!
                • -
                • Added a holographic sign projector to replace the old wet floor signs to encourage janitors to warn crewmembers about wet floors.
                • -
                -

                optimumtact updated:

                -
                  -
                • Medical supplies crate now comes with a boxy full of bodybags
                • -
                - -

                09 October 2014

                -

                Paprika updated:

                -
                  -
                • Added inaprovaline MediPens (autoinjectors) that replace inaprovaline syringes in medkits. Like the syringes, these can be used to stabilize patients in critical condition, but require no medical knowledge to use.
                • -
                • Reverted the mining scanner to a manual scanner.
                • -
                • Buffed the rate of the automatic scanner
                • -
                • Added automatic scanner to equipment locker. It is also available by redeeming your voucher.
                • -
                • Reduced the size of the mining jetpack, it now fits in backpacks. The capacity of the jetpack has been reduced as well, however.
                • -
                • Removed resonator from the mining voucher redemption. Replaced it with a mining drill.
                • -
                - -

                08 October 2014

                -

                Cheridan updated:

                -
                  -
                • Flash mechanics changed. Instead of a knockdown effect, it will cause a stumbling effect. This includes area-flashing. The five-use-per-minute limit has been removed. "Synthetic" flashes made in the protolathe are now normal flashes.
                • -
                -

                Paprika updated:

                -
                  -
                • Added a unique shield to the Head of Security's locker. It can be concealed for storage and activated like an energy shield, but functions like a riot shield. It can also be made at the protolathe.
                • -
                • Marauders: Due to your extremely great performance as of late, we've added a new equipment room on Mother Base. A lot of your equipment has been moved to this room, as well as some new equipment added onto the Mothership. Take some extra time to familiarize yourself with the placement of your equipment.
                • -
                • Oh, and you guys have a space carp now too.
                • -
                -

                Paprka updated:

                -
                  -
                • Added jumpsuit adjusting. Adjust your jumpsuit to wear it more casually by rolling it down or roll up those sleeves of that hard-worn suit!
                • -
                • Added a grey alternative to the detective's suit and trenchcoat. Now you can be a noir private investigator!
                • -
                -

                Tkdrg updated:

                -
                  -
                • Space Station 13 has been deployed with transit tube dispensers. Central Command encourages the engineering department to repair and improve the station using this equipment.
                • -
                - -

                05 October 2014

                -

                Ikarrus updated:

                -
                  -
                • Character Save Files will now remember Species.
                • -
                -

                Miauw updated:

                -
                  -
                • Added 5 new maintenance ambience tracks, made by Cuboos.
                • -
                -

                Nobalm updated:

                -
                  -
                • Added examine descriptions to twenty eight objects.
                • -
                -

                Paprika updated:

                -
                  -
                • Due to some payroll cuts affecting lower-rank crewmembers on Space Station 13, we fear that uprisings and mutinies might be approaching. To help defend themselves, all heads of staff have been given telescopic batons to subdue attackers should they be assaulted during their shifts.
                • -
                • Toolbelts can now hold pocket fire extinguishers. Atmos technicians spawn with them.
                • -
                • Remember those magboots we had our undercover agents steal? Well we've finally reverse engineered them. Not only that, but we gave them a killer paintjob. They will be available in the uplinks of boarding parties we send out to Nanoscum stations.
                • -
                • Listen up, Marauders! The Syndicate has recently cut back funding into boarding parties. What this means is no more hi-tech fancy energy guns. We've had to roll back to using cheaper, mass-produced automatic shotguns. But in retrospect, could this possibly be a downgrade?
                • -
                - -

                01 October 2014

                -

                Cheridan updated:

                -
                  -
                • Experienced smugglers have joined the Syndicate, adding their bag of tricks to its arsenal. These tricky pouches can even fit under floor plating!
                • -
                -

                Ikarrus updated:

                -
                  -
                • Adds Gang War, a new game mode still in alpha development.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • After a public relations nightmare in which a horde of 30 assistants violently dismembered a sole commander the revolution has begin tactically reducing their initial presence in stations with weak command structures. Should stations add to their command staff during the shift additional support will be provided.
                • -
                • Too many instances of crying CMOs hiding in the lockers of supposedly secured revoulutionary stations has lead to a crackdown on revolutionary policy. You must kill or maroon the entire command staff, reguardless of when they reached the station.
                • -
                • Intense study into the underlying structure of the universe has revealed that everything fits into a massive SPACECUBE. This deep and profound understanding has led to a revolution in cross surface movement, and spacemen can look forward to a new era of predictable and safe space-fairing.
                • -
                -

                Jordie0608 updated:

                -
                  -
                • Grille damage update, humans now deal less unarmed damage to grilles while projectiles and items deal more.
                • -
                • Replaced all AI turrets with porta-turrets that work on LoS instead of area and can be individually configured
                • -
                -

                Miauw updated:

                -
                  -
                • Voice changer masks work again.
                • -
                • You can now turn voice changer masks on/off by activating them in your hand. They start off.
                • -
                • The amount of TC has been increased to 20, but the price of all items has been doubled as well
                • -
                • With this, the prices of stealthy items has been slightly decreased and the prices of offensive items slightly increased (+/- 1 TC)
                • -
                -

                RemieRichards, JJRcop updated:

                -
                  -
                • New research in the area of robotics has been published on Maintenance Drones, little robots capable of repairing and improving the station. Unfortunately, all of the research was lost in a catastrophic server fire, but we know they can be made, so it's up to you to re-discover them! Make us proud!
                • -
                -

                phil235 updated:

                -
                  -
                • Changed all clone damage descriptions to use the term cellular damage to avoid confusion with genetics mutations.
                • -
                - -

                07 September 2014

                -

                Gun Hog updated:

                -
                  -
                • Nanotrasen scientists have released a Heads-Up-Display (HUD) upgrade for all AI and Cyborg units! Cyborgs have had Medical sensors and a Security datacore integrated into their core hardware, replacing the need for a specific module.
                • -
                • AI units are now able to have suit sensor or crew manifest data displayed to them as a visual overlay. When viewing a crewmember with suit sensors set to health monitoring, the health status will be available on the AI's medical HUD. In Security mode, the role as printed on the crewmember's ID card will be displayed to the AI.
                • -
                • Any entity with a Security or Medical HUD active will recieve appropriate messages on that HUD.
                • -
                -

                Ikarrus updated:

                -
                  -
                • Server Operators: A new config option added to restrict command and security roles to humans only. Simply add/uncomment the line ENFORCE_HUMAN_AUTHORITY in game_options.txt. -
                • Players: To check if this config option is enabled on your server, type show-server-revision into the game's command line. -
                • -
                - -

                06 September 2014

                -

                Ikarrus updated:

                -
                  -
                • Tampered supply ordering consoles can now order dangerous devices directly from the Syndicate for 140 points. -
                • Securitrons line units will no longer arrest individuals without IDs if their faces can still be read. -
                • Enemy Agent ID cards have been reported to be able to interfere with the new securitron threat assessment protocols. -
                • The Syndicate base has been modified to only allow the leading operative to launch the mission. -
                • -
                - -

                04 September 2014

                -

                KyrahAbattoir updated:

                -
                  -
                • All the objects found in BoxStation maintenance are now randomized at round start.
                • -
                - -

                03 September 2014

                -

                Ikarrus updated:

                -
                  -
                • Cost of Syndicate bombs increased to 6 telecrystals.
                • -
                -

                JStheguy updated:

                -
                  -
                • Many new posters have been added.
                • -
                - -

                01 September 2014

                -

                ChuckTheSheep updated:

                -
                  -
                • Adds preference toggle for intent selection mode. -
                • Direct selection mode switches intent to the one you click on instead of cycling them. -
                • -
                -

                Ikarrus updated:

                -
                  -
                • New more detailed End-Round report. -
                • Removes count of readied players from the lobby. -
                • -
                -

                Jordie0608 updated:

                -
                  -
                • Virology has been made the right color.
                • -
                -

                Miauw updated:

                -
                  -
                • A major rework of saycode has been completed which fixes numerous issues and improves functionality. -
                • Please report any issues you notice with any form of messaging to Github. -
                • -
                - -

                31 August 2014

                -

                Tokiko1 updated:

                -
                  -
                • Adds two new hairstyles.
                • -
                - -

                30 August 2014

                -

                Ikarrus updated:

                -
                  -
                • Space Station 13 has been authorized to requisition additional Tank Transfer Valves from Centcom's supply lines for the price of 60 cargo points.
                • -
                - -

                26 August 2014

                -

                Ikarrus updated:

                -
                  -
                • Security forces are advised to increase scrutiny on personnel coming into contact with AI systems. Central Intelligence suggests that Syndicate terrorist groups may have begun targeting our AIs for destruction. -
                • For the preservation of corporate assets, Central Command would like to remind all personnel that evacuating during an emergency is mandatory. We suspect that terrorist groups may be attempting to abduct marooned personnel who failed to evacuate. -
                • R&D; teams have released an urgent update to station teleporters. To eliminate the risk of mutation, personnel are advised to calibrate teleporters before attempting each use. -
                • Centcom has approved the Captain's request to rig the display case in his office with a burglar alarm. Any attempts to breach the case will now trigger a lockdown of the room. -
                • **Crew Monitoring Consoles now scan their own Z-level for suit sensors, instead of only scanning Z1. -
                • **Server operators can now set a name for the station in config.txt. Simply add the line "STATIONNAME Space Station 13" anywhere on the text file. Replace Space Station 13 with your station name of choice.
                • - -
                - -

                24 August 2014

                -

                Ikarrus updated:

                -
                  -
                • Ore redemption machine can now smelt plasteel.
                • -
                • Mulebot speed has been vastly increased by up to 300%.
                • -
                • Removed exploit where false walls can be used to cheese the AI chamber's defenses
                • -
                - -

                21 August 2014

                -

                Ikarrus updated:

                -
                  -
                • The Phase Shift ability will no longer instantly gib players. Instead, they will be knocked out for a few seconds. Mechas will be damaged.
                • -
                • The Phase Slayer ability will no longer instantly gib players. Instead, they will be dealt 190 brute damage. Mechas will be dealt 380 damage.
                • -
                • ADMIN: Most Event buttons have been moved out of the secrets menu and into a Fun verb.
                • -
                - -

                20 August 2014

                -

                Cheridan updated:

                -
                  -
                • Three new shotgun shells added. Create them by first researching and printing an unloaded tech shell in R&D.; Afterwards, use table-crafting with the appropriate components with a screwdriver in-hand.
                • -
                • Meteorshot: tech shell + compressed matter cartridge + micro(or better) manipulator.
                • -
                • Pulse Slug: tech shell + advanced capacitor + ultra micro-laser.
                • -
                • Dragonsbreath: tech shell + 5 phosphorus (in container).
                • -
                • Incendiary rounds now leave a blazing trail as they pass. This includes existing incendiary rounds, new dragonsbreath rounds, and the Dark Gygax's carbine.
                • -
                - -

                19 August 2014

                -

                Ikarrus updated:

                -
                  -
                • Brig cells and labour shuttle have both been modified to send a message to all security HUDs whenever a prisoner is automatically released.
                • -
                • The labour camp has been outfitted with a points checking console to allow prisoners to check their quota progress easier and better explain the punishment system of forced labour.
                • -
                - -

                18 August 2014

                -

                Iamgoofball updated:

                -
                  -
                • 25 new hairstyles have been added.
                • -
                - -

                15 August 2014

                -

                AndroidSFV updated:

                -
                  -
                • AI photography has been extended to Cyborgs. While connected to an AI, all images taken by cyborgs will be placed in the AI's album, and viewable by the AI and all linked Cyborgs.
                • -
                • When a Cyborgs AI link wire is pulsed, the images from the Cyborgs image album will be synced with the AI it gets linked to.
                • -
                • Additonally, Cyborgs are able to attach images to newscaster feeds from whichever album is active for them. Cyborgs are also mobile, inefficent, printers of same images.
                • -
                - -

                11 August 2014

                -

                Ikarrus updated:

                -
                  -
                • Boxstation map updates:
                  - -Singularity engine walled from from outer space
                  - -Shutters for exterior Science windows
                  - -Teleporter board moved from RD's Office to Tech Storage now that the teleporter can be built again.
                  - -Security Deputy Armbands (red) added in HoS's Office



                • -
                - -

                14 July 2014

                -

                Miauw updated:

                -
                  -
                • Added a new mutetoxin that can be made with 2 parts uranium, 1 part water and 1 part carbon and makes people mute temporarily
                • -
                • Parapens were renamed to sleepy pens and now contain 30 units of mutetoxin and 30 units of sleeptoxin instead of zombie powder.
                • -
                • C4 can no longer be attached to mobs
                • -
                • Reduced the C4 price to 1 telecrystal
                • -
                - -

                07 July 2014

                -

                Firecage updated:

                -
                  -
                • We, of the NanoTrasen Botany Research and Developent(NTBiRD), has some important announcements for the Botany staff aboard our stations.
                • -
                • We have recently released a new strain of Tower Cap. It is now ensured that more planks can be gotten from a single log.
                • -
                • Head Scientist [REDACTED] is thanked for his new strain of Blood Tomatoes. It now not only contains human blood, but also chunks of human flesh!
                • -
                • We have also discovered a way to replant the infamous Cash Trees and 'Eggy' plants. We can now harvest their seeds, and the rare products are inside the fruit shells.
                • -
                • Similar to the new variety of Tower Caps, it is now possible to get various grass flooring depending on the plants potency!
                • -
                • Our sponsors at Knitters United has released a new variation of the botany Aprons and Coveralls which can hold a larger variety of items!
                • -
                • A new version of Plant-B-Gone was produced which are much more lethal to plants, yet at the same time has no extra effect on humans besides the norm.
                • -
                -

                Paprika updated:

                -
                  -
                • Tweaked mining turf mineral droprates and reverted mesons to the 'old' style of mesons. This means mesons will be more powerful with the added side effect of not being able to track individual light sources through walls and such. This is a trial run; please provide feedback on the /tg/ codebase section of the forum.
                • -
                - -

                05 July 2014

                -

                Firecage updated:

                -
                  -
                • Recently the Space Wizard Federation, with some assistance from the Cult of Nar-Sie, recently created a new strain of Killer Tomato. A sad side effect is this effected all current and future Killer Tomato strains in our galaxy. They are now reported to be extremely violent and deadly, use extreme caution when growing them.
                • -
                - -

                03 July 2014

                -

                Miauw updated:

                -
                  -
                • Added slot machines to the game
                • -
                • Slot machines use reels. All prizes except for jackpots can be won on all lines
                • -
                • Simply put a coin into one of the slot machines in the bar to play!
                • -
                • Slot machines can also be emagged.
                • -
                - -

                01 July 2014

                -

                Ikarrus updated:

                -
                  -
                • Central Security has approved an upgrade of the Securitron line of bots to a newer model.
                  Highlights include:
                • -
                • -An optional setting to arrest personnel without an ID on their uniform (Disabled by default)
                • -
                • -An optional setting to notify security personnel of arrests made by the bots (Enabled by default)
                • -
                • -A new "Weapon Permit" access type that securitrons will use during their weapons checks. Personnel who work with weapons will be granted this access by default. More can be assigned by the station's Head of Security and Personnel.
                • -
                • -A more robust threat assessment algorithm to improve accuracy in identifying perpetrators.
                • -
                -

                Rolan7 updated:

                -
                  -
                • Fixed the irrigation system in hydroponics trays. Adding at least 30 units of liquid at once will activate the system, sharing the reagents between all connected trays.
                • -
                • Changelings can communicate while muzzled or monkified, and being monkified doesn't stop them from taking human form.
                • -
                • Mimes cannot break the laws of physics unless their vow is currently active.
                • -
                • Trays are rewritten to make sense and so borgs can actually use them. Service borgs can discretely carry small objects, hint hint. The items are easily knocked out of them.
                • -
                - -

                20 June 2014

                -

                Ikarrus updated:

                -
                  -
                • The 'Enter Exosuit' button was removed, and you have to click + drag yourself to enter a mech now; like you would to enter a disposal unit.
                • -
                - -

                12 June 2014

                -

                Cheridan updated:

                -
                  -
                • NT Baton Refurbishment Team Notice: 'Released' option in security records has been renamed 'Discharged', and the sechud icon has been changed from a blue R to a blue D. Stop beating your released prisoners, dummies; they're not revheads.
                • -
                • NT Entertainment Department Notice: The prize-dispensing mechanism in Arcade machines has been replaced with a cheaper gas-powered motor. It should still be completely safe, unless an electromagnetic field disrupts it!
                • -
                • Pill code tweaked. In short, you can feed monkeys pills now #whoa
                • -
                • Casings ejected from guns will now spin #wow
                • -
                - -

                11 June 2014

                -

                Ikarrus updated:

                -
                  -
                • Having the nuke disk will no longer prevent you from using teleporters or crossing Z-levels. Leaving the Z-level however, will cause you to "suddenly lose" the disk.
                • -
                • Pulled objects will now transition with you when as you transition Z-levels in space.
                • -
                -

                Malkevin updated:

                -
                  -
                • IEDs have been removed
                • -
                • Firebombs have been added
                • -
                • They're as reliable and predictable as you would expect from a soda can filled with flammable liquid and set off with an igniter contected to two wires. Use your branes and take proper precautions or leave a charred corpse, your choice.
                • -
                - -

                06 June 2014

                -

                Gun Hog updated:

                -
                  -
                • Nanotrasen programmers have discovered a potential exploit involving a station's door control networks. With sufficient computing power, the network can be overloaded and forced to open or bolt closed every airlock at once.
                • -
                • Some reports suggest that the latest firmware update to Artifical Intelligence units installed in our research stations may contain this exploit. Any such reports that state that should a station's AI unit "malfunction", it may gain the ability to use this exploit to initate a full lockdown of the station. Fire locks and blast doors are also said to be affected.
                • -
                • Nanotrasen officially holds no validity to these reports. We recommend that station personnel disregard such rumors.
                • -
                - -

                20 May 2014

                -

                Kelenius updated:

                -
                  -
                • After the years of extensive research into xenobiology, and thousands of scientists lost to the slimes, we have finally come to admitting that we don't have a clue how they work. However, we've outlined a few facts, and managed to breed a new kind of slimes. They will be shipped to your station on the next shifts. They are different from the ones they are used to in the following:
                • -
                • Their sight is better than ever, and now they can finally see things that are not directly pressed against them. Beware, they may appear more aggressive!
                • -
                • Their memory has also become better, and they will now remember the people who fed them. As a note, you need to grab the monkey or push it to show to the slime that you're giving it to them, and it's not just walking in.
                • -
                • Apparently, not only we have studied them, but they have also studied us. Slimes are now capable of showing various emotions, mimicking humans. Use it to your advantage.
                • -
                • There are reports of slimes showing signs of sentience. Further research is recommended.
                • -
                • One of the reported signs is their speech capability. Following facts have been gathered: they talk more often to those who feed them; they react to being called by the number, but 'slimes' is also acceptable; they are able to understand being ordered to "follow" someone, or "stop". There is a report of slime releasing the assistant after a scientist shouted at it, and then calmly ordered a slime to stop.
                • -
                • We've made a modification of a health scanner that is intended for the use on slimes. Two are available in the smartfridge.
                • -
                • We've come to a conclusion that 5 units of reagents are not necessary to cause slime core reactions. You actually only need one unit.
                • -
                - -

                16 May 2014

                -

                Menshin updated:

                -
                  -
                • Thanks to Nanotrasen's Engineering division, the power wiring of the station has a received a complete overhaul. Here are the highlight features in CentCom report :
                • -
                • Improved way of connecting cables! Finished is the time of "laying and praying" : it's now as simple as matching ends of each cables!
                • -
                • Unified way of connecting machines! Every machine is connected to a powernet through a node cable (it still needs to be anchored to receive power though)!
                • -
                • Lots of improvements in the cable cutting tools! Gone is the odd magic of physically separated yet still connected powernets (contact CentCom Engineering Division if you still witness something weird, we always have a fired form ready for our engineers!)!
                • -
                • Any conscientious (or not!) Station Engineer may check an "updated wiring manual" at http://www.tgstation13.org/wiki/Wiring#Power_Wires
                • -
                - -

                14 May 2014

                -

                Ikarrus updated:

                -
                  -
                • A recent breakthrough in Plasma Materials Research has led to the development of a stronger, tougher plasteel alloy that can better resist extreme heat by up to four times. A live demonstration preformed during a press conference earlier today showed a segment of reinforced wall resisting an attack by Thermite. The corporation also announces that upgrades to its existing stations is already underway.
                • -
                - -

                07 May 2014

                -

                /Tg/station Code Management updated:

                -
                  -
                • /tg/station code is now under a feature freeze until 7th June 2014. This means that the team will be concentrating on fixing bugs instead of adding features for the month.
                • -
                • You can find more information about the feature freeze here. http://tgstation13.org/phpBB/viewtopic.php?f=5&t;=273
                • -
                • This is the perfect time to report bugs, which you can do so here. https://github.com/tgstation/-tg-station/issues/new
                • -
                - -

                30 April 2014

                -

                Giacom updated:

                -
                  -
                • You now have to give a reason when calling the shuttle.
                • -
                • The amount of time it takes for the shuttle to refuel is now a config option, the default is 20 minutes.
                • -
                -

                Gun Hog updated:

                -
                  -
                • Certain Enemies of the Corporation have discovered a critical exploit in the firmware of several NanoTrasen robots that could prevent the safe shutdown of units corrupted by illegally modified ID cards, dubbed "cryptographic sequencers".
                • -
                • NanoTrasen requires more research into this exploit, this we have issued a patch for AI and Cyborg software to simulate this malfunction. All NanoTrasen AI units are advised to only allow testing in a safe and contained environment. The robot can safely be reset at any time.
                • -
                • Unfortunately, a robot corrupted by a "cryptographic sequencer" still cannot be reset by any means. NanoTrasen urges regular maintenance and care of all robots to reduce replacement costs.
                • -
                - -

                27 April 2014

                -

                Ikarrus updated:

                -
                  -
                • NT Human Resources announces an expansion of the List of Company-Approved Hair Styles, as well as more relaxed gender restrictions on hair styles. Check with your local company-sponsored hairstylist to learn more.
                • -
                - -

                22 April 2014

                -

                Ikarrus updated:

                -
                  -
                • ID Computers have been modified to allow Station Heads of Staff to assign and remove access to their departments, as well as stripping the rank of their subordinates. An ID computer on the bridge is available for the use of this function.
                • -
                - -

                20 April 2014

                -

                Gun Hog updated:

                -
                  -
                • NanoTrasen Emergency Alerts System 4.20 has been released!
                • -
                • In the unfortunate event of any nuclear device being armed, the station will enter Code Delta Alert.
                • -
                • Should the nuclear explosive be disarmed, the station shall automatically return to its previous alert level.
                • -
                -

                Steelpoint updated:

                -
                  -
                • A new AI satellite has been constructed in orbit of Space Station 13!
                • -
                • The satellite exclusivly is used to hold a station's Artifical Intelligence Unit, the satellite contains a miried of turret and motion sensor defences. A transit tube network is used to connect the satellite to the station, with the transit room being located at engineering south of the engi escape pod.
                • -
                • The AI Upload however has been moved north slightly and is connected directly to the bridge.
                • -
                • In addition, the Gravity Generator has been relocated from its prior position in engineering to the location of the old AI Upload, increasing its defence and logical positioning.
                • -
                - -

                11 April 2014

                -

                Jordie0608 updated:

                -
                  -
                • Wood planks can be used to make wood walls and airlocks; flammability not included
                • -
                - -

                10 April 2014

                -

                Ikarrus updated:

                -
                  -
                • Centcom officials have announced a new initiative to combat misuse of their Emergency Shuttle Service. After the shuttle has been recalled several times over the course of a simple work shift, Centcom will attempt to trace the signal origin and pinpoint its source for station authorities.
                • -
                -

                Steelpoint updated:

                -
                  -
                • Thanks to Nanotrasen's Construction division, the brig has recieved a overhaul in its design, to increase ease of movement, including an addition of a "prisioner release room" and a insanity ward. -
                • Furthing concerns among commentators, Nanotrasen has shipped out additional security equipment to station armories, including Riot Shotguns, tear gas grenades, additional securitron units and military grade Ion Guns.
                • -
                • JaniCo have released a new unique product called the JaniBelt. Capable of holding most standard issue janitorial equipment, designed to help relive inventory management among station janitors. In addition the Jani Water Tank has had its reserve of space clenaer increased to 500 units, up from 250. -
                • -
                -

                Validsalad updated:

                -
                  -
                • After concerns raised by security personel, new armor has been shipped that covers the lower waist and readjusts the helmet for comfort. -
                • In addition, the aging riot shield has been replaced with a newer, more modern, apperance. -
                • -
                - -

                03 April 2014

                -

                Ikarrus updated:

                -
                  -
                • NT AI Firmware version 2554.03.03 includes a function to notify the master AI when a new cyborg is slaved to it.
                • -
                - -

                02 April 2014

                -

                Giacom updated:

                -
                  -
                • The syndicate suit is now black and red, because black and red is the new red.
                • -
                • The Ruskie DJ Station now has a classy orange syndicate suit.
                • -
                • Security officers have been equipped with the latest in flashlight technology. You can find a SecLite™ inside your security belt or you can dispense one from the security vending machine.
                • -
                -

                Ikarrus updated:

                -
                  -
                • Nanotrasen Construction division is pleased to announce the unveiling of our brand now state-of-the-art Space Station 13 v2.1.3!
                • -
                • -Scaffolding used during construction has been repurposed as an expanded maintenance around the station.
                • -
                • -Our new Emergency Transport Shuttle Mk III will be making its debut in times of crisis. Includes a new cargo area and extra security features.
                • -
                • -A redesigned brig will give security staff more peace of mind with added security features
                • -
                • -The armory is now protected by a motion alarm.
                • -
                • -The armory is now equipped with security hardsuits.
                • -
                • -A new command EVA wing has been added to EVA Storage. Station heads of staff will be able to make use of suits stored here.
                • -
                • -A new garden area will be made publicly available for everyone to enjoy some R&R; during their company-endorsed break periods
                • -
                • -A new Testing Lab has been added to the Science department for any field experiments that need to be run.
                • -
                • -The mining ore redemption machine has been relocated to the Cargo Delivery Office.
                • -
                - -

                01 April 2014

                -

                Aranclanos updated:

                -
                  -
                • Made combat more hardcore for hardcore players like myself. This won't be reverted.
                • -
                -

                Malkevin updated:

                -
                  -
                • Sacrifice Cult - Cult has received a massive overhaul to how it works, focusing more on cult magics and sacrificing unbelievers. The most important changes are:
                • -
                • Cult starts off with alot more cultists, 6 cultists below 30 players and 9 above. The HoP can no longer be a round start cultist but is still convertible.
                • -
                • Cultists start off with join, blood, self, and hell. These give the runes for the sacrifice and convert.
                • -
                • Conversions now require three cultists and converts no longer reward words, words must be obtained via sacrifice
                • -
                • Sacrificing players traps their soul in a soul stone, which can then be used on construct shells to implant the souls in them
                • -
                • Constructs shells can be summoned via a new rune (travel, hell, technology)
                • -
                • You can no longer convert the sacrifice target, you dozzy sods
                • -
                • Summoning Narsie when she is not an objective leads to !!FUN!!
                • -
                • Wrote a system to allow all mob types to be sacrificable - currently only corgis are in the new system
                • -
                • Holy Water will DECONVERT cultists, takes around 35 units and two minutes to succeed
                • -
                • Hitting a mob that contains holy water with a tome will convert that water to unholy water, conversely hitting any container that contains unholy water with a bible as chaplain will 'cleanse' the taint.
                • -
                • Unholy water works like a combination synaptazine and hyperzine, with a twist of branes dimarge and highly toxic to non-cultists
                • -
                • Cargo can order a religious supplies crate - it contains flasks of holy water, candles, bibles, and robes
                • -
                • Cultists have an innate ability to communicate to the cult hive mind (BE AWARE - THIS DOES A LOT OF DAMAGE), cultists can also communicate via the new Commune option on their tomes (the Read Tome option has been shifted under the Notes option)
                • -
                -

                MrStonedOne updated:

                -
                  -
                • Cyborgs can now use AI click shortcuts. (shift click on doors opens them, etc) (including ones added below)
                • -
                • New silicon click shortcut: Control+Shift click on doors toggles access override
                • -
                • New silicon click shortcut: Control click turret controls toggles them on or off
                • -
                • New silicon click shortcut: Alt click turret controls toggles lethal on or off
                • -
                • Cyborg hotkey mode was massively improved. 1-3 selects module slot, q unloads the active module. use hotkeys-help as cyborg for more details
                • -
                - -

                31 March 2014

                -

                Ikarrus updated:

                -
                  -
                • Fabricator blueprints for cyborg parts have been updated to allow the debugging of cyborg units prior to boot. To access the debug functions, assemble a cyborg following standard procedure. Before inserting the MMI, use a multitool on the cyborg body. Note that this update also moves designation data into the system files, so pens can no longer be used to name cyborgs.
                • -
                -

                MrPerson updated:

                -
                  -
                • Toggling the "Play admin midis" preference will pause any playing songs. Turning midis back on will resume the current song where it was.
                • -
                • If there's a song playing while you have midis off and you then turn midis on, the current song will begin playing for you.
                • -
                - -

                28 March 2014

                -

                Aranclanos updated:

                -
                  -
                • Workaround with the click cooldowns. They should feel faster. If you think that anything needs a click cooldown (like grilles, mobs, etc.), report it to me.
                • -
                • Here's the fun part, this is a test for the community and players, I hope I won't be forced to revert this and hear "this is why we can't have nice things". Again, if anything needs a cooldown, report it to me. Have fun with your fast clicks spessmans.
                • -
                - -

                25 March 2014

                -

                Ikarrus updated:

                -
                  -
                • A security review committee has approved an update to all Nanotrasen airlocks to better resist cryptographic attacks by the enemy. -

                  *New internal wiring will be able to withstand attacks, and panel wires will no longer be left unusable after an attack. -
                  *Welding tools will now be able to cut through welded doors that have been damaged. -
                  *Damaged electronics are now removable and replaceable following standard procedure. -
                  *Airlocks will not be able to operate autonomously until the electronics are replaced.




                • -
                • Hair sprites have been given a visual lift. For best results, set your hair color to be slightly lighter than how you want it to look. For dark hair, do not use a color darker than 80% black.
                • -
                - -

                22 March 2014

                -

                Giacom updated:

                -
                  -
                • Gravity is no longer beamed from CentCom! Instead, there is a new gravity generator stored near Engineering that will provide all the gravity for the station and the z level. Please remember that it gives off a lot of radiation when charging or discharging, so wear protection.
                • -
                -

                MrPerson updated:

                -
                  -
                • Added a new, somewhat experimental system to delete things. This is basically a port from /vg/, so big thanks to N3X15.
                • -
                • As a result, bombs and the singularity should be much less laggy.
                • -
                • There may be issues with "phantom objects" or other problems with stuff that's suppoed to be deleted or objects interacting with deleted objects. Please report any issues right away!
                • -
                - -

                18 March 2014

                -

                Ikarrus updated:

                -
                  -
                • NT R&D; releases AI Patch version 2553.03.18, allowing all Nanotrasen AIs to override the access restrictions on any airlock in case of emergency. Nanotrasen airlocks have been outfitted with new amber warning lights that will flash while the override is active. Should maintenance teams need to restore an airlock's restrictions without using the AI, pulsing the airlock's ID Scanner wire will reset the override.
                • -
                - -

                17 March 2014

                -

                Giacom updated:

                -
                  -
                • Machines that had their programming overriden by a Malf AI will no longer attack loyal cyborgs; they will still attack cyborgs that aren't loyal, to the Malf AI that hacked them, though.
                • -
                • You only require a single unit of blood to mutate viruses now, instead of 5.
                • -
                - -

                15 March 2014

                -

                Steelpoint/Validsalad updated:

                -
                  -
                • After a review with CentCom, all Security Officers will now begin their shifts with a Stun Baton in their backpack. To avoid inflating costs however all Stun Batons have been removed from Security lockers except from the brig.
                • -
                • After decades of usage CentCom has replaced the Security Uniform, Armor, Belt and Helmet with a newer, more modern design.
                • -
                - -

                14 March 2014

                -

                Ikarrus and Nienhaus updated:

                -
                  -
                • Nanotrasen Corporate announced a revised dress codes primarily affecting senior station officers. A new uniform for Heads of Personnel will be shipped out to all NT Research stations.
                • -
                - -

                12 March 2014

                -

                Yota updated:

                -
                  -
                • Cameras finally capture the direction you are facing.
                • -
                - -

                10 March 2014

                -

                Giacom updated:

                -
                  -
                • Added two new plasma-level disease symptoms, both are very !FUN! and mess with your genetics.
                • -
                • Virologists now only need to use a single unit of virus food, mutagen or plasma to generate symptoms. You can take a single unit by using a dropper, if you change its unit transfer amount in the object tab..
                • -
                • Changed the map to make it harder to escape from the Labor Camp. Please continue using it.
                • -
                -

                Miauw updated:

                -
                  -
                • Added changeling space suits. These allow changelings to survive in space, internals are not needed. They do not provide any sort of armor and slow your chemical regeneration.
                • -
                - -

                05 March 2014

                -

                Various Coders updated:

                -
                  -
                • Ling rounds are LINGIER, with more lings at a given population.
                • -
                • Many simple animals can ventcrawl, including bats. All ventcrawlers must alt-click to ventcrawl.
                • -
                • An automatic tool to store and replace machine parts has been added to R&D.;
                • -
                • Most stuns have been reduced in from 10+ to 5 ticks in duration.
                • -
                • Shoes no longer speed you up.
                • -
                • Added fancy suits, which can be orderd from cargo.
                • -
                • Added sombrerors and ponches.
                • -
                • Cuff application time reduced 25%.
                • -
                • Fire will cause fuel tanks to explode.
                • -
                • Mutagen has been reduced in lethality. Notably, 15 units are not guaranteed to crit the recipient.
                • -
                - -

                03 March 2014

                -

                ChuckTheSheep updated:

                -
                  -
                • Round-end report now shows what items were bought with a traitor's or nuke-op's uplink and how many TCs they used.
                • -
                -

                Drovidi Corv updated:

                -
                  -
                • Facehuggers no longer die to zero-force weapons or projectiles like lasertag beams.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Improved blob spores to zombify people adjacent to their tile.
                • -
                • If a hand-teleporter is activated while you're stuck inside a wall, you'll automatically go through it.
                • -
                -

                Miauw updated:

                -
                  -
                • Decreased the lethality of Mutagen in small doses.
                • -
                -

                TZK13 updated:

                -
                  -
                • Added Incendiary Shotgun Shells made at a hacked autolathe; Xenos be wary.
                • -
                -
                -

                26 February 2014

                -

                Incoming5643 updated:

                -
                  -
                • Color blending Kitty Ears have returned.
                • -
                -
                - -
                -

                25 February 2014

                -

                Incoming5643 updated:

                -
                  -
                • Colored burgers! Include a crayon in your microwave when cooking a burger and it'll come out just like a Pretty Pattie.
                • -
                • Fixed AI's being able to interact with syndicate bombs.
                • -
                -
                - -
                -

                24 February 2014

                -

                Ergovisavi updated:

                -
                  -
                • Added Advanced Mesons and Night Vision Goggles to the protolathe.
                • -
                -

                Giacom updated:

                -
                  -
                • Silicons no longer stop statues from moving when in sight.
                • -
                • Increased the health of statues.
                • -
                • Increased the range of statues's blinding spell.
                • -
                -

                HornyGranny updated:

                -
                  -
                • Reduced the range of Violin notes.
                • -
                • Low quality violin midi-notes replaced with better .ogg-notes.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Improved the Syndicate Implant bundle to contain freedom, uplink, EMP, adrenalin and explosive implanters.
                • -
                • Added Mineral Storeroom to R&D; containing the Ore Redemption Machine; no more assistants stealing your ore in the open hallway.
                • -
                -

                Miauw updated:

                -
                  -
                • No more removing cursed horseheads.
                • -
                -

                Razharas updated:

                -
                  -
                • Fixed infinite telecrystal exploit.
                • -
                -
                - -
                -

                19 February 2014

                -

                Aranclanos updated:

                -
                  -
                • Removed the chance of your hat falling off when slipping.
                • -
                -

                Perakp updated:

                -
                  -
                • Added Iatot's cyborg module selection transformation animations.
                • -
                -
                - -
                -

                17 February 2014

                -

                Ergovisavi updated:

                -
                  -
                • Mining has been significantly overhauled. Hostile alien life has infested the western asteroid! Miners are given an equipment voucher to obtain equipment to protect themselves. There is a spare voucher in the HoP's office, should someone want to switch to mining mid-shift.
                • -
                • The Ore Redemption Machine has been added just outside of the Science wing. Ore goes in, Sheets go out, and points are tallied on the machine. Insert your ID to claim points, then spend them on goods at Mining Equipment Lockers. You require specific accesses to tell the Ore Redemption Machine to unload its sheets.
                • -
                • Should you not care for being eaten alive by horrible alien life, it is suggested you stick to the eastern asteroid, where there is no hostile alien life... though the yield on ore is not as good.
                • -
                • Most ore is no longer visible to the naked eye. You must ping it with a Mining Scanner (Available in all mining lockers) to locate nearby ore. Make sure to equip your mesons first, or it won't be visible.
                • -
                • Mesons no longer remove the darkness overlay, you must properly light your environment. Hull breaches to space can still be clearly seen, and it will still protect you from the singulo.
                • -
                • Mineral spawn rates have been significantly tweaked to cut down on unnecessary inflation of mineral economy.
                • -
                • The asteroid no longer has ambient power on the entire asteroid. AI's who go onto asteroid turf will slowly die of power loss. Mining outposts are unaffected.
                • -
                • Fixed an issue where projectiles shot by simple mobs or thrown would hit multiple times.
                • -
                -

                Fleure updated:

                -
                  -
                • Reduced chicken crates to contain 1-3 chicks, down from 3-6
                • -
                • Increased fertile chicken egg chance from 10% to 25%
                • -
                -

                Yota updated:

                -
                  -
                • Photograph now rendered crisp and clean, so that we may enjoy them in their 0.009 megapixel goodness.
                • -
                • Cameras should now capture more of what they should, and less of what they shouldn't.
                • -
                -
                - -
                -

                09 February 2014

                -

                ADamDirtyApe updated:

                -
                  -
                • The lawyer now spawns with a Sec Headset.
                • -
                -

                Demas updated:

                -
                  -
                • Banana peel size is based on banana size.
                • -
                • Bigger peels slip for longer than smaller ones. Remember that produce size is based on potency.
                • -
                • The clown now spawns with a decent sized banana.
                • -
                • The banana mortar now shoots 65 potency peels.
                • -
                -

                Giacom updated:

                -
                  -
                • A new hostile statue mob, can only move when not being observed, tends to break lights and cause blindness.
                • -
                -

                Incoming5643 updated:

                -
                  -
                • Blob Zombies! When a Blob Spore moves over a dead human body it will infect the body and revive it as a more powerful varient of the spore. Has double the health and deals more damage.
                • -
                -

                Neerti updated:

                -
                  -
                • Sec Belts can now hold Stun Batons.
                • -
                • Stun Batons now only take 10 seconds to fully recharge.
                • -
                -

                Razharas updated:

                -
                  -
                • Tables can now be used as an alternative way to craft makeshift items. Simply click-drag table to yourself bring up a list.
                • -
                -

                adrix89 updated:

                -
                  -
                • Spray Bottles can no longer wet up to three tiles with water.
                • -
                • Spray Bottles have a third higher release volume that wets a single tile.
                • -
                • Water slip times are reduced to the same stun times as soap.
                • -
                -
                - -
                -

                08 February 2014

                -

                MrPerson updated:

                -
                  -
                • Added a NanoUI for the SMES
                • -
                -

                Razharas updated:

                -
                  -
                • Adds more constructible and deconstructable machines!
                • -
                • Added constructible miniature chemical dispensers, upgradable
                • -
                • Sleepers are now constructible and upgradable, open and close like DNA scanners, and don't require a console
                • -
                • Cryogenic tubes are now constructible and upgradable, open and close like DNA scanners, and you can set the cryogenic tube's pipe's direction by opening its panel and wrenching it to connect it to piping
                • -
                • Telescience pads are now constructible and upgradable
                • -
                • Telescience consoles are now constructible
                • -
                • Telescience tweaked (you can save data on GPS units now)
                • -
                • Teleporters are now constructible and upgradable, the console has a new interface and you can lock onto places saved to GPS units in telescience
                • -
                • Teleporters start unconnected. You need to manually reconnect the console, station and hub by opening the panel of the station and applying wire cutters to it.
                • -
                • Biogenerators are now constructible and upgradable
                • -
                • Atmospherics heaters and freezers are now constructible and upgradable and can be rotated with a wrench when their panel is open to connect them to pipes. Screw the board to switch between heater and freezer.
                • -
                • Mech chargers are now constructible and upgradable
                • -
                • Microwaves are now constructible and upgradable
                • -
                • All kitchen machinery can now be wrenched free
                • -
                • SMES are now constructible
                • -
                • Dragging a human's sprite to a cryogenic tube or sleeper will put them inside and activate it if it's cryo
                • -
                • Constructible newscasters, their frames are made with autolathes
                • -
                • Constructible pandemics
                • -
                • Constructible power turbines and their computers
                • -
                • Constructible power compressors
                • -
                • Constructible vending machines. Screw the board to switch vendor type.
                • -
                • Constructible hydroponics trays
                • -
                • Sprites for all this
                • -
                • This update will have unforeseen bugs, please report those you find at https://github.com/tgstation/-tg-station/issues/new if you want them fixed.
                • -
                • As usual, machines are deconstructed by screwing open their panels and crowbarring them. While constructing machines, examining them will tell you what parts you're missing.
                • -
                -
                - -
                -

                05 February 2014

                -

                Yota updated:

                -
                  -
                • Handling a couple flashlights will no longer transform you into the sun. Each light source will have deminishing returns.
                • -
                • Inserting lights into containers should no longer dim other lights.
                • -
                -
                - -
                -

                03 February 2014

                -

                Demas updated:

                -
                  -
                • Changes windoor and newscaster attack messages to be consistent with windows and grilles. This removes the distracting boldness from windoor attack messages, which is reserved for userdanger, and names the attacker.
                • -
                • Thrown items now play a sound effect on impact. The volume of the sound is based on the item's throwforce and/or weight class.
                • -
                • The fireaxe now has 15 throwforce, when previously it had only 1. But why would you throw it away, anyway?
                • -
                • Projectiles now play a sound upon impact. The volume of the sound depends on the damage done by the projectile. Damageless projectiles such as electrodes have static volumes. Practise laser and laser tag beams have no impact sound.
                • -
                • Added sear.ogg for the impacts of damaging beams such as lasers. It may be replaced if player feedback proves negative.
                • -
                -
                - -
                -

                02 February 2014

                -

                Demas updated:

                -
                  -
                • Attack sounds for all melee weapons! No more silent attacks.
                • -
                • The volume of an object's attack sound is based on the weapon's force and/or weight class.
                • -
                • Welders, lighters, matches, cigarettes, energy swords and energy axes have different attack sounds based on whether they're on or off.
                • -
                • Weapons that do no damage play a tap sound. The exceptions are the bike horn, which still honks, and the banhammer, which plays adminhelp.ogg. Surely nothing can go wrong with that last one.
                • -
                • When you tap someone with an object, the message now uses "tapped" or "patted" instead of attacked. The horn still uses HONKED, and the banhammer still uses BANNED.
                • -
                • You won't get the "armour has blocked an attack" message for harmless attacks anymore.
                • -
                • Adds 5 force to the lighter when it's lit. Same as when you accidentally burn yourself lighting it.
                • -
                • Removes boldness from item attack messages on non-human mobs. The attack is bolded for a player controlling a non-human mob. Now your eyes won't jump to the chat when it's Pun Pun who's being brutalised.
                • -
                • Blood will no longer come out of non-human mobs if the attack is harmless.
                • -
                • Adds a period at the end of the catatonic human examine message. That's been bugging me for years.
                • -
                • The activation and deactivation sounds of toy swords, energy swords and energy shields are slightly quieter. Energy swords and shields are now slightly louder than toys.
                • -
                • You can no longer light things with burnt matches.
                • -
                • Match, cigarette and lighter attack verbs, forces and damage types change based on whether the object is lit or not.
                • -
                • Fixes a bug with the energy blade that kept it at weight class 5 after it was deactivated. Who cares, it disappears upon deactivation.
                • -
                • Changes the welder out of fuel message slightly to be less fragmented.
                • -
                • Removes dead air from a lot of weapon sound effects to make them more responsive. In other words, the fire extinguisher attack sound will play a lot sooner after you attack than before.
                • -
                • Equalised the peak volumes of most weapon sounds to be -0.1dB in an attempt to make volumes based on force more consistent across different sounds.
                • -
                -
                - -
                -

                01 February 2014

                -

                Ergovisavi updated:

                -
                  -
                • Walking Mushrooms will now attack and eat each other! They're all a little unique from each other, no two shrooms are exactly alike, and a better quality harvest means stronger Walking Shrooms. Pit them against each other for your entertainment.
                • -
                • Each mushroom will have a different colored cap to identify them. When mushrooms eat each other, they get stronger. The resulting mushroom will drop more slices when cut down to harvest, and will have better quality slices.
                • -
                • Don't hurt them yourself, though, or you'll bruise them, and mushrooms won't get stronger from eating a bruised mushroom. If your mushroom faints, feed it a mushroom such as a plump helmet to get it back on its feet. It will slowly regenerate to full health eventually.
                • -
                -
                - -
                -

                30 January 2014

                -

                Balrog updated:

                -
                  -
                • Syndicate Playing Cards can now be found on the Syndicate Mothership and purchased from uplinks for 1 telecrystal.
                • -
                • Syndicate Playing Cards are lethal weapons both in melee and when thrown, but make the user's true allegiance to the Syndicate obvious.
                • -
                • Sprites are courtesy of Nienhaus.
                • -
                -

                Demas updated:

                -
                  -
                • Adds thud sounds to falling over
                • -
                • Known bug: Thuds play when cloning initialises or someone is put into cryo. This will be fixed.
                • -
                -

                Ergovisavi updated:

                -
                  -
                • Gibtonite, the explosive ore, can now be found on the asteroid. It's very hard to tell between it and diamonds, at first glance.
                • -
                • Gibtonite deposits will blow up after a countdown when you attempt to mine it, but you can stop it with an analyzer at any time. It makes for a good mining explosive.
                • -
                • The closer you were to the explosion when you analyze the Gibtonite deposit, the better the Gibtonite you can get from it.
                • -
                • Once extracted, it must be struck with a pickaxe or drill to activate it, where it will go through its countdown again to explode!
                • -
                • Explosives will no longer destroy the ore inside of asteroid walls or lying on the floor.
                • -
                -

                Miauw updated:

                -
                  -
                • Adds changeling arm blades that cost 20 chems and do 25 brute damage.
                • -
                • Arm blades can pry open unpowered doors, replace surgical saws in brain removal, slice tables and smash computers.
                • -
                -

                MrPerson updated:

                -
                  -
                • Mobs now lie down via turning icons rather than preturned sprites.
                • -
                • You can lie down facing up or down and the turn can be 90 degrees clockwise or counterclockwise.
                • -
                • Resting will always make you lie to the right so you look good on beds.
                • -
                • Please report any bugs you find with this system.
                • -
                -

                Petethegoat updated:

                -
                  -
                • Increased the walk speed. Legcuffed speed is unaffected, and is still suffering.
                • -
                • Sped up alien drones, they are now the same speed as sentinels.
                • -
                -
                - -
                -

                28 January 2014

                -

                Demas updated:

                -
                  -
                • Adds thud sounds to falling over
                • -
                • Known bug: Thuds play when cloning initialises or someone is put into cryo. This will be fixed.
                • -
                -
                - -
                -

                26 January 2014

                -

                Balrog updated:

                -
                  -
                • Syndicate Playing Cards can now be found on the Syndicate Mothership and purchased from uplinks for 1 telecrystal.
                • -
                • Syndicate Playing Cards are lethal weapons both in melee and when thrown, but make the user's true allegiance to the Syndicate obvious.
                • -
                • Sprites are courtesy of Nienhaus.
                • -
                -
                - -
                -

                25 January 2014

                -

                Miauw updated:

                -
                  -
                • Adds changeling arm blades that cost 20 chems and do 25 brute damage.
                • -
                • Arm blades can pry open unpowered doors, replace surgical saws in brain removal, slice tables and smash computers.
                • -
                -
                - -
                -

                24 January 2014

                -

                Ergovisavi updated:

                -
                  -
                • Gibtonite, the explosive ore, can now be found on the asteroid. It's very hard to tell between it and diamonds, at first glance.
                • -
                • Gibtonite deposits will blow up after a countdown when you attempt to mine it, but you can stop it with an analyzer at any time. It makes for a good mining explosive.
                • -
                • The closer you were to the explosion when you analyze the Gibtonite deposit, the better the Gibtonite you can get from it.
                • -
                • Once extracted, it must be struck with a pickaxe or drill to activate it, where it will go through its countdown again to explode!
                • -
                • Explosives will no longer destroy the ore inside of asteroid walls or lying on the floor.
                • -
                -
                - -
                -

                21 January 2014

                -

                MrPerson updated:

                -
                  -
                • Mobs now lie down via turning icons rather than preturned sprites.
                • -
                • You can lie down facing up or down and the turn can be 90 degrees clockwise or counterclockwise.
                • -
                • Resting will always make you lie to the right so you look good on beds.
                • -
                • Please report any bugs you find with this system.
                • -
                -
                - -
                -

                19 January 2014

                -

                KazeEspada updated:

                -
                  -
                • The water cooler is now stocked with paper cups. You can refill the cups by putting paper in it.
                • -
                -

                Rolan7 updated:

                -
                  -
                • You can now sell mutant seeds, from hydroponics, to Centcom via the supply shuttle.
                • -
                • Fixes powersinks causing APCs to stop automatically recharging.
                • -
                -
                - -
                -

                17 January 2014

                -

                ManeaterMildred updated:

                -
                  -
                • Changed the way the Gygax worked. It now has less defense and shot deflection, but is faster and have less battery drain per step.
                • -
                • Nerfed the Carbine's brute damage and renamed it to FNX-99 "Hades" Carbine.
                • -
                • Nerfed the Gygax defense from 300 to 250.
                  - Nerfed the Gygax projectile deflection chance from 15 to 5.
                  - Buffed the Gygax speed from 3 to 2, making it faster.
                  - Reduced the battery use when moving from 5 to 3.
                  -
                  - The Mech Ion Rifle now has a faster cooldown, from 40 to 20.
                  - Nerfed the carbine's Brute Damage from 20 to 5.
                  -
                  - Please post feedback to these changes on the forums. -







                • -
                -
                - -
                -

                15 January 2014

                -

                Dumpdavidson updated:

                -
                  -
                • EMPs affect the equipment of a human again.
                • -
                • EMP flashlight now recharges over time and its description no longer reveals the illegal nature of the device.
                • -
                • EMP implant now has two uses.
                  EMP kit now contains two grenades.
                • -
                -
                - -
                -

                14 January 2014

                -

                Fleure updated:

                -
                  -
                • Added spider butchery.
                • -
                • Added spider meat, legs, and edible eggs.
                • -
                • Added new spider related meals.
                • -
                -

                Giacom updated:

                -
                  -
                • Because of an emagged cyborg's explosion, the MMI will die with it.
                • -
                • The self-respiration symptom will now properly keep you from dying of oxygen loss.
                • -
                • The Stimulant symptom's activation chance was increased so you had a constant flow of hyperzine.
                • -
                -

                Incoming updated:

                -
                  -
                • A new training bomb has been added to the armoury, which will allow you to train your wire cutting skills to disarm real syndicate bombs.
                • -
                -

                ManeaterMildred updated:

                -
                  -
                • Updated research designs.
                • -
                • The Protolathe can now build a Ion Rifle.
                • -
                • The Exosuit Fabricator can now build a Mech Ion Rifle, a Mech Carbine and a Mech-Mounted Missile Rack.
                • -
                -

                SirBayer updated:

                -
                  -
                • Armor now reduces damage by the protection percentage, instead of randomly deciding to half or full block damage by those percentages.
                • -
                • Shotguns, with buckshot shells, will fire a spread of pellets at your target, like a real shotgun blast.
                • -
                -
                - -
                -

                12 January 2014

                -

                VistaPOWA updated:

                -
                  -
                • Added Syndicate Cyborgs.
                • -
                • They can be ordered for 25 telecrystals by Nuclear Operatives. A ghost / observer with "Be Operative" ticked in their game options will be chosen to control it.
                • -
                • Their loadout is: Crowbar, Flash, Emag, Esword, Ebow, Laser Rifle. Each weapon costs 100 charge to fire, except the Esword, which has a 500 charge hitcost. Each borg is equipped with a 25k cell by default.
                • -
                • Syndicate borgs can hear the binary channel, but they won't show up on the Robotics Control computer or be visible to the AI. Their lawset is the standard emag one.
                • -
                • Added two cyborg recharging stations to the Syndicate Shuttle.
                • -
                -
                - -
                -

                11 January 2014

                -

                Cheridan updated:

                -
                  -
                • You can now upgrade laser pointers with micro laser parts. It will increase the chance of blinding people.
                • -
                -

                Errorage updated:

                -
                  -
                • Cyborg modules now use a new UI, which is much quicker than a menu.
                • -
                -

                Giacom updated:

                -
                  -
                • The game will now have all background operations disabled, this will result in smoother gameplay but may result in some spike lags, before being set back to normal. The gradually increasing lag should now be gone.
                • -
                • You can now emag the crusher, in disposals, to remove the safety. You can use a screwdriver to reset it to the default factory settings.
                • -
                • The toxin compensation symptom will stop giving you toxin damage while at full health.
                • -
                -

                JJRCop updated:

                -
                  -
                • The new nuke toy can now be found in your local arcade machine.
                • -
                -
                - -
                -

                10 January 2014

                -

                ChuckTheSheep updated:

                -
                  -
                • Morgue Trays can detect players in their bodies and will now change colour depending on a few things. Red = Dead body with no player inside. Orange = No body but items. Green = A dead body with a player inside.
                • -
                -

                Giacom updated:

                -
                  -
                • You can whisper while in critical, but you will immediately die afterwards DRAMATICALLY. The closer you are to death, the less you can say.
                • -
                • The wizard won't spawn so much smoke after they blink.
                • -
                • The detective's forensic scanner was upgraded so that it can now scan from afar.
                • -
                • Made the ghost's follow button less buggy. Please make an issue report if it still bugs out.
                • -
                -

                Rumia29, Nienhaus updated:

                -
                  -
                • A new alt RD uniform spawns in his locker.
                • -
                -
                - -
                -

                07 January 2014

                -

                Fleure updated:

                -
                  -
                • Janitor now spawns with a service headset.
                • -
                • Backpack watertank slowdown decreased.
                • -
                -
                - -
                -

                04 January 2014

                -

                Razharas updated:

                -
                  -
                • Constructable machines now depend on R&D; parts!
                • -
                • DNA scanner: Laser quality lessens irradiation. Manipulator quality drastically improves precision (9x for best part) and scanner quality allows you to scan suicides/ling husks, with the best part enabling the cloner's autoprocess button, making it scan people in the scanner automatically.
                • -
                • Clone pod: Manipulator quality improves the speed of cloning. Scanning module quality affects with how much health people will be ejected, will they get negative mutation/no mutations/clean of all mutations/random good mutation, at best quality will enable clone console's autoprocess button and will try to clone all the dead people in records automatically, together with best DNA scanner parts cloning console will be able to work in full automatic regime autoscanning people and autocloning them.
                • -
                • Borg recharger: Capacitors' quality and powercell max charge affect the speed at which borgs recharge. Manipulator quality allows borg to be slowly repaired while inside the recharges, best manipulator allows even fire damage to be slowly repaired.
                • -
                • Portable power generators: Capacitors' quality produce more power. Better lasers consume less fuel and reduce heat production PACMAN with best parts can keep whole station powered with about sheet of plamsa per minute (approximately, wasn't potent enough to test).
                • -
                • Autolathe: Better manipulator reduces the production time and lowers the cost of things(they will also have less m_amt and g_amt to prevent production of infinity metal), stacks' cant be reduced in cost, because thatll make production of infinity metal/glass easy
                • -
                • Protolathe: Manipulators quality affects the cost of things(they will also have less m_amt and g_amt to prevent production of infinity metal), best manipulators reduces the cost 5 times (!)
                • -
                • Circuit imprinter: Manipulator quality affects the cost, best manipulator reduce cost(acid insluded) 4 times, i.e. 20 boards per 100 units of acid
                • -
                • Destructive analyzer: Better parts allow items with less reliability in. Redone how reliability is handled, you now see item reliability in the deconstruction menu and deconstructing items that has same or one point less research type level will rise the reliability of all known designs that has one or more research type requirements as the deconstructed item. Designs of the same type raise in reliability more. Critically broken things rise reliability of the design drastically. Whole reliability system is not used a lot but now at least on the R&D; part it finally matters.
                • -
                -
                - -
                -

                02 January 2014

                -

                Demas updated:

                -
                  -
                • Added different colours to departmental radio frequencies. Now you'll be able to filter out or pay attention to each frequency a lot easier.
                • -
                -

                Fleure updated:

                -
                  -
                • Fixed bruisepacks and ointments not working.
                • -
                • Fixed injecting/stabbing mouth or eyes when only thick suit worn.
                • -
                -
                - -
                -

                01 January 2014

                -

                Errorage updated:

                -
                  -
                • The damage overlay for humans starts a little later than before. It used to start at 10 points of fire + brute damage, it now starts at 35.
                • -
                -
                - -
                -

                31 December 2013

                -

                Fleure updated:

                -
                  -
                • Paper no longer appears with words on it when blank input written or photocopied
                • -
                • Vending machine speaker toggle button now works
                • -
                • Building arcade machines works again
                • -
                -
                - -
                -

                27 December 2013

                -

                Giacom updated:

                -
                  -
                • Light explosions will no longer gib dead bodies anymore. C4 will function the same and gib anything attached.
                • -
                • Syringe gun projectiles will now display a message when shots are deflected by space suits, biosuits and etc...
                • -
                • You can now move out of sleepers by moving, again.
                • -
                -

                Miauw updated:

                -
                  -
                • Monkeys now have a resist button on their HUD.
                • -
                -
                - -
                -

                21 December 2013

                -

                Bobylein updated:

                -
                  -
                • Labcoats can now store bottles, beakers, pills, pill bottles and paper.
                • -
                -

                Giacom updated:

                -
                  -
                • The Labor Camp has been changed based on feedback. Ore boxes added, internals added, safety pickaxes/shovels (might revert if unliked).
                • -
                • Labor Camp prisoners, who have earned enough points for freedom, will now have to be alone in the shuttle to move it and to open the middle door; this is in order to prevent free'd prisoners from releasing their comrades.
                • -
                • Monkeys no longer walk away when being pulled or grabbed.
                • -
                • Anti-breach shield generators have a greater range, and will cover more exposed space tiles.
                • -
                -

                Jordie updated:

                -
                  -
                • Blowing up borgs from the Robotics Console will now actually make them explode. Emagged Cyborgs will explode even more.
                • -
                -

                Nienhaus updated:

                -
                  -
                • Added more poster.
                • -
                -

                Perakp updated:

                -
                  -
                • You can no longer slip while laying down.
                • -
                -

                RobRichards, Validsalad updated:

                -
                  -
                • Added new sprites for the sec-hailer, SWAT gear and riot armour.
                • -
                -
                - -
                -

                18 December 2013

                -

                Adrinus updated:

                -
                  -
                • Playing cards, just like the real thing! Play poker, blackjack, go fish, the limits are limitless! Recommended to use space cash as the poker chips for now
                • -
                -

                Giacom updated:

                -
                  -
                • THE REALISM: Injectors such as syringes, parapens and hypos will not penetrate coveralls with thick material, this includes space suits, biosuits, bombsuits, and their head slot equivalent. Injectors now also consider where you aim, if you aim for the head it will try to use it through the head slot, otherwise it will aim for the body. To clarify, if you are wearing a space helmet and a doctor tries to inject you while aiming at your head, it will protect you until they aim for the unprotected body; same story for wearing a space suit and not a helmet.
                • -
                • The syndicate shuttle has been heavily upgraded with a brand new technology which allows the blast doors to the entrance of the shuttle to AUTOMATICALLY close. Whoa. This brand new technology will help keep out those snoopy crew members.
                • -
                • Lowered the cooldown of creating virus cultures to 5 seconds. You now only need to mix one unit of synaptizine, in a blood full of an advance virus, to get it to remove a random symptom.
                • -
                -

                Incoming updated:

                -
                  -
                • Rounds will no longer end when the wizard dies and there are still apprentices or traitors/survivors..
                • -
                -

                JJRcop updated:

                -
                  -
                • Transit tube tweaks. You can now put someone into a transit tube pod using grabs and you can empty a transit tube pod by clicking on it.
                • -
                -

                Jordie0608 updated:

                -
                  -
                • Replaced the digital valves in atmospherics with pumps.
                • -
                -
                - -
                -

                14 December 2013

                -

                Incoming updated:

                -
                  -
                • Magic Mania! Powerful new magical tools and skills for wizard and crew alike!
                • -
                • Beware the new Summon Magic spell, which will grant the crew access to magical tools and spells and cause some to misuse it!
                • -
                • One Time Spellbooks that can be spawned during summon magic that can teach a low level magic skill to anyone! Beware the effects of reading pre-owned books, the magical rights management is potent! -
                • Magical Wands that can be spawned during Summon Magic! They come in a variety of effects that mimic both classical wizard spells and all new ones. They come precharged but lack the means to refill them once their magical energy is depleted... Fire efficently!
                • -
                • Be aware of the new Charge spell, which can take normally useless spent wands and give them new life! This mysterious effect has been found to wear down the overall magical potency of wands over time however. Beyond wands the clever magical user can find ways to use this spell on other things that may benefit from a magical charge...
                • -
                • The Staff of Resurrection, which holds intense healing magics able to defeat death itself! Look out for this invaluable magical tool during castings of Summon Magic.
                • -
                • Be on the lookout for a new apprentice! This noble mage is a different beast from most wizards, trained in the arts of defending and healing. Too bad he still works for the wizard!
                • -
                -
                - -
                -

                09 December 2013

                -

                Giacom updated:

                -
                  -
                • New colourful ghost sprites for BYOND members. Sprites by anonus.
                • -
                -
                - -
                -

                08 December 2013

                -

                Rolan7 updated:

                -
                  -
                • Leather gloves can be used to removes lights.
                • -
                • Plant, ore, and trash bags have a new option to pick up all items of single type
                • -
                • Creating astroturf now works like sandstone, converting all the grass at once.
                • -
                • Uranium and radium can be used instead of mutagen. 10 can mutate species, 5 or 2 mutate traits. Highly toxic.
                • -
                • Plants require a little light to live. Mushroom require even less (2 units vs 4) and take less damage.
                • -
                -
                - -
                -

                05 December 2013

                -

                Razharas updated:

                -
                  -
                • Reworked how ling stings are done, now when you click a sting in the changeling tab it becomes current active sting, the icon of that sting appears under the chem counter, alt+clicking anyone will sting them with current sting, clicking the icon of the sting will unset it.
                • -
                • Monkeys have ling chem counter and active sting icons in their UI.
                • -
                • Going monkey -> human will try to equip the human with everything on the ground below it.
                • -
                -
                - -
                -

                02 December 2013

                -

                Giacom updated:

                -
                  -
                • A less annoying virology system! From now on, you can only get low level virus symptoms from virus food, medium level virus symptoms from unstable mutagen and high level virus symptoms from liquid plasma. You can find a list of symptoms, and which chemicals are required to get them, here: http://wiki.ss13.eu/index.php/Infections#Symptoms_Table
                • -
                • The virologist starts with a bottle of plasma in his smart fridge.
                • -
                • Made it so you cannot accidentally click in the gaps between chem masters.
                • -
                -
                - -
                -

                01 December 2013

                -

                cookingboy3 updated:

                -
                  -
                • Added three new buttons to the sandbox panel.
                • -
                • Removed canister menu, replaced it with buttons.
                • -
                • Players can no longer spawn "dangerous" canisters in sandbox, such as Plasma, N20, CO2, and Nitrogen.
                • -
                -
                - -
                -

                30 November 2013

                -

                Yota updated:

                -
                  -
                • The identification console will now require that ID and job names follow the same restrictions as player names.
                • -
                • NTSL scripts and parrots should now handle apostrophes and such properly. It&#39;s about time.
                • -
                • NTSL scripts now have a better sense of time.
                • -
                -
                - -
                -

                28 November 2013

                -

                Malkevin updated:

                -
                  -
                • Made the suit storage on the Captain's Tunic more useful than just a place to store your e-o2 tank. You can now store the nuke disk, stamps, medal box, flashes and melee weapons (mainly intended for the Chain of Command), and of course - smoking paraphernalia
                • -
                -
                - -
                -

                27 November 2013

                -

                RobRichards updated:

                -
                  -
                • Nanotrasen surgeons are now certified to perform Limb replacements, The robotic parts used in construction of Nanotrasen Cyborgs are the only parts authorized for crew augmentation, these replacement limbs can be repaired with standard welding tools and cables.
                • -
                -
                - -
                -

                17 November 2013

                -

                Laharl Montgommery updated:

                -
                  -
                • AI can now anchor and unanchor itself. In short, it means the AI can be dragged, if it wants to.
                • -
                -
                - -
                -

                29 September 2013

                -

                RobRichards updated:

                -
                  -
                • Nanotrasen Cyborg Upgrades:
                  -Standard issue Engineering cyborgs now come equipped with replacement floor tiles which they can replenish at recharge stations.
                • -
                -
                - - -
                -

                28 September 2013

                -

                Ergovisavi updated:

                -
                  -
                • Mobs can now be lit on fire. Wearing a full firesuit (or similar) will protect you. Extinguishers, Showers, Space, Cryo, Resisting, being splashed with water can all extinguish you. Being splashed with fuel/ethanol/plasma makes you more flammable. Water makes you less flammable.
                • -
                -
                - - -
                -

                26 September 2013

                -

                Cheridan updated:

                -
                  -
                • Nanotrasen Anomaly Primer:
                  - Unstable anomalies have been spotted in your region of space. These anomalies can be hazardous and destructive, though our initial encounters with these space oddities has discovered a method of neutralization. Method follows.
                  -

                  Step 1. Upon confirmation of an anomaly sighting, report its location. Early detection is key.
                  - Step 2. Using an atmospheric analyzer at short range, determine the frequency that the anomaly's core is fluctuating at.
                  - Step 3. Send a signal through the frequency using a radio signaller. Note that non-specialized signaller devices may possibly lack the frequency range needed.

                  - With the anomaly neutralized and the station brought out of danger, inspect the area for any remnants of the anomaly. Properly researched, we believe these events could provide vast amounts of valuable data.
                  - Did you find this report helpful?
                • -
                -
                - - - -
                -

                21 September 2013

                -

                Malkevin updated:

                -
                  -
                • Due to complaints about Security not announcing themselves before making arrests NT has now issued it's Sec team with loud hailer integrated gas masks, found in their standard equipment lockers. Users can adjust the mask's level of aggression with a screwdriver.
                • -
                • The sprites could be shaded better. Think you can do better? Post your submissions here.
                • -
                -
                - - -
                -

                19 September 2013

                -

                Malkevin updated:

                -
                  -
                • Juggernaut's ablative armor has been adjusted. They have a greater chance to reflect lasers however on reflection they take half damage instead of no damage, basically this adjustment means you should be able to kill a Juggernaut with two laser guns instead of four! Also their reflection spread has been greatly widened, enjoy the lightshow
                • -
                • Cargo can now order exile implants.
                • -
                • Checking a collector's last power output via analyzers has been moved to multitools, because that actually made sense (betcha didn't know this existed, I know I didn't)
                • -
                • Analyzers can now be used to check the gas level of the tank in a loaded radiation collector (yay no more crowbars), you can also use them on pipes to check gas levels (yay no more pipe meters)
                • -
                -
                - -
                - - -

                17 September 2013

                -

                SuperSayu updated:

                -
                  -
                • You can no longer strip people through windows and windoors
                • -
                • You can open doors by hand even if there is a firedoor in the way, making firedoor+airlock no longer an unbeatable combination
                • -
                • Ghosts can now click on anything to examine it, or double click to jump to a turf. Double clicking a mob, bot, or (heaven forbid) singularity/Nar-Sie will let you follow it. Double clicking your own corpse re-enters it.
                • -
                • AI can double click a mob to follow it, as well as double clicking turfs to jump.
                • -
                • Ventcrawling mobs can alt-click a vent to start ventcrawling.
                • -
                • Telekinesis is now part of the click system. You can click on buttons, items, etc, without having a telekinetic throw in hand; the throw will appear when you click on something you can move (with your mind).
                • -
                -
                - - -
                -

                13 September 2013

                -

                JJRcop updated:

                -
                  -
                • We at Nanotrasen would like to assure you that we know the pain of waiting five minutes for the emergency shuttle to be dispatched in a high-alert situation due to our confirmation-of-distress policy. Therefore, we have amended our confirmation-of-distress policy so that, in the event of a red alert, the distress confirmation period is shortened to three minutes and we will hurry in preparing the shuttle for transit. This totals to 6 minutes, in hope that it will give our very expensive equipment a better chance of recovery.
                • -
                -
                - - -
                -

                3 September 2013

                -

                Cael_Aislinn updated:

                -
                  -
                • Terbs Fun Week Day 5: Chef gets a Nanotrasen-issued icecream machine with four pre-approved icecream flavours and two official cone types.
                • -
                -
                - - -
                -

                2 September 2013

                -

                Cael_Aislinn updated:

                -
                  -
                • Terbs Fun Week Day 4: Humans, aliens and cyborgs now show speech bubbles when they talk.
                • -
                -
                - - -
                -

                1 September 2013

                -

                Cael_Aislinn updated:

                -
                  -
                • Terbs Fun Week Day 3: Detective can reskin his gun to one of five variants: Leopard Spots, Gold Trim, Black Panther, Peacemaker and the Original.
                • -
                -
                - - -
                -

                12 September 2013

                -

                AndroidSFV updated:

                -
                  -
                • AI Photography: AIs now have two new verbs, Take Picture and View Picture. The pictures the AI takes are centered on the AI's eyeobj. You can use these pictures on a newscaster, and print them at a photocopier. -
                -
                - - -
                -

                31 August 2013

                -

                Cael_Aislinn updated:

                -
                  -
                • Terbs Fun Week Day 2: RD, lawyers and librarians now spawn with a laser pointer. Don't point them in anyone's eyes!
                • -
                -
                - - -
                -

                30 August 2013

                -

                Cael_Aislinn updated:

                -
                  -
                • Terbs Fun Week Day 1: Added ghost chilis as a mutation of chili plants. Be careful, they're one of the hottest foods in the galaxy!
                • -
                -
                - - -
                -

                21 August 2013

                -

                Dumpdavidson updated:

                -
                  -
                • Replaced the EMP grenades from the uplink with an EMP kit. The kit contains a grenade, an implant and a flashlight with 5 uses that can EMP any object or mob in melee range.
                • -
                -
                - - -
                -

                18 August 2013

                -

                Delicious updated:

                -
                  -
                • Made time and date consistent across medical and security records, mecha logs and detective scanner reports
                • -
                • Added date to PDA
                • -
                -
                - - -
                -

                13 August 2013

                -

                Giacom updated:

                -
                  -
                • Malf AIs now have a new power which will spawn a "borging machine". This machine will turn living humans into loyal cyborgs which the AI can use to take over the station with. The AI will limit themselves by using this ability, such as no shunting, and the machine will have a long cooldown usage.
                • -
                -
                - - - - - -
                -

                12 August 2013

                -

                Giacom updated:

                -
                  -
                • Changed the blob balance to make the blob start strong but grow slower, resulting in rounds where the blob doesn't instantly get killed off if found out and doesn't immediately dominate after being left alone long enough. AIs no longer have to quarantine the station.
                • -
                -
                - - -
                -

                10 August 2013

                -

                Malkevin updated:

                -
                  -
                • Cargo Overhaul: Phase 1
                • -
                • Ported Bay's cargo computer categoy system
                • -
                • Crates have been tweaked significantly. Crates have been reduced to single item types where possible, namely with expensive crates such as weapons and armor. A total of 28 new crates have been added, including chemical and tracking implants, and raw materials can also be bought from cargo for a significant number of points (subject to change)
                • -
                • This was a pretty large edit of repetitive data, so no doubt I've made a mistake or two. Please report any bugs to the usual place
                • -
                -
                - - -
                -

                6 August 2013

                -

                Giacom updated:

                -
                  -
                • NTSL no longer allows you to use a function within another function parameter. This was changed to help prevent server crashes; if your working script no longer compiles this is why.
                • -
                    -
                - - -
                -

                5 August 2013

                -

                Kaze Espada updated:

                -
                  -
                • Nanotrasen has recentely had to change its provider of alcoholic beverages to a provider of lower quality. Cases of the old ailment known as alcohol poisoning have returned. Bar goers are to be weary of this new condition.
                • -
                -
                - - -
                -

                4 August 2013

                -

                Giacom updated:

                -
                  -
                • Nanotrasen has re-arranged the station blueprint designs to have non-essential APCs moved to the maintenance hallways. Non-essential rooms that aren't connected to a maintenance hallway will have their APC remain. Station Engineers will now have easy access to a room's APC without needing access themselves. Nanotrasen also wishes to remind you that you should not sabotage these easy to access APCs to cause distractions or to lockdown someone in a location. Thank you for reading.
                • -
                -
                - - -
                -

                31 July 2013

                -

                Ricotez updated:

                -
                  -
                • Atmospherics now has its own hardsuit. Instead of radiation protection it offers fire protection.
                • -
                -
                - - -
                -

                21 July 2013

                -

                Malkevin updated:

                -
                  -
                • Cultists now start with two words each, and the starting talisman no longer damages you when you use it
                • -
                -
                - - - -
                -

                21 July 2013

                -

                Cheridan updated:

                -
                  -
                • Instead of a level-up system where it is possible to acquire all the skills, each skill now costs 1 point, and you can pick up to 5.Husking people, instead of giving you more XP to buy skills, now gives you a skill reset, allowing you to pick new ones.
                • -
                • DNA Extract Sting is now free, and is your main mode of acquiring DNA. You can hold up to 5 DNAs, and as you acquire more, the oldest one is removed. If you're currently using the oldest DNA strand, you will be required to transform before gaining more.
                • -
                • New abilities! An UI indicator for chemical storage! Fun!
                • -
                -
                - - -
                -

                16 July 2013

                -

                Malkevin updated:

                -
                  -
                • Summary of my recent changes: Added a muzzle and a box of Prisoner ID cards to the perma wing, RnD can make a new combined gas mask with welding visor, added some atmos analyzers to atmospherics, air alarm circuit boards have their own sprites, package wrapped objects will now loop back round to the mail chute instead of auto-rerouting to disposals, and the detective and captain have access to securitrons through their PDA cartridges.
                • -
                -
                - -
                -

                15 July 2013

                -

                Giacom updated:

                -
                  -
                • A new item has been added to the syndicate catalog. The AI detector is a device disguised as a multitool; it is not only able to be used as a real multitool but when it detects an AI looking at it, or it's holder, it will turn red to indicate to the holder that he should cease supiscious activities. A great and cheap, to produce, tool for undercover operations involving an AI as the security system.
                • -
                -
                - - -
                -

                7 July 2013

                -

                Giacom updated:

                -
                  -
                • Revamped blob mode and the blob random event to spawn a player controlled overmind that can expand the blob and upgrade pieces that perform particular functions. This will use resources which the core can slowly generate or you can place blob pieces that will give you more resources.
                • -
                -
                - -
                -

                27 June 2013

                -

                Ikarrus updated:

                -
                  -
                • Nanotrasen R&D released a new firmware patch for their station AIs. Included among the changes is the new ability for AIs to interact with fire doors. R&D officials state they feel giving station AIs more influence could only lead to good things.
                • -
                -
                - -
                -

                16 June 2013

                -

                Khub updated:

                -
                  -
                • Job preferences menu now not only allows you to left-click the level (i.e. [Medium]) to raise it, but also to right-click it to lower it. That means you don't have to cycle through all the levels to get rid of a [Low].
                • -
                -
                - - -
                -

                15 June 2013

                -

                Carnie updated:

                -
                  -
                • DNA-scanner pods (DNA-modifier + cloning), now open and close in a similar fashion to closets. This means you click on them to open/close them. This change was to fix a number of issues, like items being lost in the scanner-pods.
                • -
                • As a side-effect, borgs can now clone humans. No harm can become a dead human, so they are not necessarily lawbound to clone them, and such tasks are probably best left to qualified genetics staff.
                • -
                -

                Petethegoat updated:

                -
                  -
                • Updated chemical grenades. The build process is much the same, except they require an igniter-X assembly instead of a single assembly item. You can also just use a cable coil to get regular grenade behaviour.
                • -
                -
                - -
                -

                9 June 2013

                -

                Ikarrus updated:

                -
                  -
                • Server operators may now allow latejoiners to become antagonists. Check game_options.txt for more information.
                • -
                • Server operators may now set how traitors and changelings scale to population. Check game_options.txt for more information.
                • -
                -
                - -
                -

                6 June 2013

                -

                Giacom updated:

                -
                  -
                • Emptying someone's pockets won't display a message. In theory you can now pickpocket!
                • -
                -
                - - -
                -

                4 June 2013

                -

                Dumpdavidson updated:

                -
                  -
                • Headsets can no longer broadcast into a channel that is disabled.
                  Headsets now have a button to turn off the power instead of the speaker. This button disables all communication functions.
                  EMPs now force affected radios off for about 20 seconds.
                • -
                -
                - -
                -

                2 June 2013

                -

                Ikarrus updated:

                -
                  -
                • To reduce costs of security equipment, mounted flashers have been adjusted to use common handheld flashes as their flashbulbs. Although these flashbulbs are more prone to burnout, they can easily be replaced with wirecutters.
                • -
                -
                - -
                -

                25 May 2013

                -

                Ikarrus updated:

                -
                  -
                • CentCom announced some minor restructuring within Space Station 13's command structure. Most notable of these changes is the removal of the Head of Personnel's access to the security radio channel. CentCom officials have stated the intention was to make the HoP's role more specialized and less partial towards security.
                • -
                -
                - -
                -

                14 May 2013

                -

                Ikarrus updated:

                -
                  -
                • Nanotrasen seeks to further cut operating costs on experimental cyborg units. -
                  -Cyborg chassis will now be made from a cheaper but less durable design. -
                  -RCDs found on engineering models have been replaced with a smaller model to make room for a metal rods module. -
                  -Cyborg arms will no longer be long enough to allow for self-repairs. -
                  NOTE: A cyborg's individual modules have been found to become non-operational should the unit sustain too much structural damage.
                • -
                -
                - -
                -

                11 May 2013

                -

                Malkevin updated:

                -
                  -
                • SecHuds now check for valid clearance before allowing you to change someone's arrest status. There is only one way to bypass the ID check, and its not the usual way.
                • -
                - -
                - -
                -

                7 May 2013

                -

                Ikarrus updated:

                -
                  -
                • As a part of the most recent round of budget cuts, toolboxes will now be made with a cheaper but heavier alloy. HR reminds employees to avoid being struck with toolboxes, as toolbox-related injuries are not covered under the company's standard health plan.
                • -
                -
                - -
                -

                5 May 2013

                -

                Rolan7 updated:

                -
                  -
                • Cargo manifests from CentComm may contain errors. Stamp them DENIED for refunds. Doesn't apply to secure or large crates. Check the orders console for CentComm feedback.
                • -
                -
                - -
                -

                2 May 2013

                -

                Malkevin updated:

                -
                  -
                • You can now weld four floor tiles together to make a metal sheet
                • -
                • The All-In-One Grinder can now grind Metal, Plasteel, Glass, Reinforced Glass, and Wood sheets
                • -
                • Made grey backpacks slightly less ugly
                • -
                -
                - -
                -

                30 April 2013

                -

                Ikarrus updated:

                -
                  -
                • Researchers have discovered that glass shards are, in fact, dangerous due to their typically sharp nature. Our internal Safety Committee advises that glass shards only be handled while using Nanotrasen-approved hand-protective equipment.
                • -
                -
                - -
                -

                24 April 2013

                -

                Carnie updated:

                -
                  -
                • DNA reworked: All SE blocks are randomised. DNA-modifier emitter strength affects the size of the change in the hex-character hit. Emitter duration makes it more likely to hit the character you click on. Almost all DNA-modifier functions are on one screen.
                  Balancing -will- be off a bit. Is getting halk to hard/easy? Please report bugs/balancing issues/concerns here: http://forums.nanotrasen.com/viewtopic.php?f=15&t=13083 <3
                • -
                -
                - -
                -

                26 April 2013

                -

                Aranclanos updated:

                -
                  -
                • Exosuit fabricators will now need to be manually updated
                • -
                -

                Ikarrus updated:

                -
                  -
                • Commanding Officers of Nanotrasen Stations have been issued a box of medals to be awarded to crew members who display exemplary conduct.
                • -
                -
                - - -
                -

                23 April 2013

                -

                Malkevin updated:

                -
                  -
                • Replaced the captain's run of the mill armored vest with his very own unique vest. Offers slightly better bullet protection.
                • -
                -
                - - -
                -

                22 April 2013

                -

                Malkevin updated:

                -
                  -
                • Overhauled the thermal insulation system
                • -
                • All clothing that protected from one side of the thermal spectrum now protects from the other.
                • -
                • Armor (although most don't have full coverage) protects between 160 to 600 kelvin
                • -
                • Firesuits protect between 60 to 30,000 kelvin (Note: Hotspot damage still exists)
                • -
                • CE's hardsuit got its firesuit level protection back
                • -
                • Bomb suits function as ghetto riot gear
                • -
                -
                - -
                -

                22 April 2013

                -

                Cheridan updated:

                -
                  -
                • Stungloves removed 5eva.
                • -
                • Don't rage yet. Makeshift stunprods(similar in function to stungloves) and spears are now craftable. Make them by using a rod on cable restraits, then adding something.
                • -
                • Stun batons/prods now work off power cells, which can be removed and replaced! Use a screwdriver to remove the battery.
                • -
                -
                - - -
                -

                17 April 2013

                -

                Giacom updated:

                -
                  -
                • If the configuration option is enabled, AIs and or Cyborgs will not be able to communicate vocally. This means they cannot talk normally and need to use alternative methods to do so
                • -
                -
                - - -
                -

                10 April 2013

                -

                Cheridan updated:

                -
                  -
                • You can now condense capsaicin into pepper spray with chemistry.
                • -
                • Pepper spray made slightly more effective.
                • -
                • Teargas grenades can be obtained in Weapons and Riot crates.
                • -
                • Riot crate comes with 2 sets of gear instead of 3, made cheaper. Beanbag crate removed entirely. Just make more at the autolathe instead. Bureaucracy crate cheaper, now has film roll.
                • -
                • NT bluespace engineers have ironed-out that little issue with the teleporter occasionally malfunctioning and dropping users into deep space. Please note, however, that bluespace teleporters are still sensitive experimental technology, and should be Test Fired before use to ensure proper function.
                • -
                -
                - - -
                -

                9 April 2013

                -

                Ikarrus updated:

                -
                  -
                • Liquid Plasma have been found to have strange and unexpected results on virion cultures. The executive chief science officer urges virologists to explore the possibilities this new discovery could bring.
                • -
                • After years or research, our scientists have engineered this cutting-edge technology born from the science of shaving. The Electric Space-Razor 5000! It uses moisturizers to refuel your face while you shave with not three, not four, but FIVE lazer-precise inner blades for maximum comfort.
                • -
                -
                - -
                -

                4 April 2013

                -

                Cheridan updated:

                -
                  -
                • When an AI shunts into an APC, the pinpointer will begin tracking it. When the AI returns to its core, the pinpointer will go back to locating the nuke disc.
                • -
                • New sechud icons for sec officers/HoS, medical doctors, and loyalty implants.
                • -
                -
                - -
                -

                28 March 2013

                -

                Carnie updated:

                -
                  -
                • Empty character slots in your preferences screen will now randomize. So they won't initialise as bald, diaper-clad, white-guys.
                • -
                • Reworked the savefile versioning/updating code. Your preferences data is less likely to be lost.
                • -
                -
                - -
                -

                14 March 2013

                -

                Major_sephiroth updated:

                -
                  -
                • You can now light cigarettes with other cigarettes, and candles. And cigars. Light a cigar with a candle! It's possible now!
                • -
                -
                - -
                -

                13 March 2013

                -

                Elo001 updated:

                -
                  -
                • You can now open and close job slots for some jobs at any IDcomputer. There is a cooldown when opening or closing a position.
                • -
                -
                - -
                -

                8 March 2013

                -

                Kor updated:

                -
                  -
                • You can now construct/destroy bookcases. This is super exciting and game changing.
                • -
                -
                - -
                -

                6 March 2013

                -

                Petethegoat updated:

                -
                  -
                • Petethegoat says, "Added a new feature involvi-GLORF"
                • -
                • Overhauled how grabs work. There aren't any interesting mechanical differences yet, but they should be a lot more effective already. You don't have to double click them anymore. Report any bugs with grabbing directly to me, or on the issue tracker.
                • -
                -
                - -
                -

                24 February 2013

                -

                Ikarrus updated:

                -
                  -
                • AI has been moved back to the center of the station. Telecoms has been moved to engineering.
                • -
                -

                Faerdan updated:

                -
                  -
                • Competely new UI overhaul! Most user interface have been converted.
                • -
                - -
                -

                22 February 2013

                -

                Petethegoat updated:

                -
                  -
                • Added cavity implant surgery.
                • -
                • Additionally, surgery must now be performed with help intent. Some procedures have also been updated. - As always, check the wiki for details.
                • -
                -
                - -
                -

                18 February 2013

                -

                Ikarrus updated:

                -
                  -
                • The AI has been moved to Research Division, and Telecomms has been moved into the former AI chamber. Affected areas: Telecoms Satellite, Research Division South & Command Sector.
                • -
                -

                Incoming updated:

                -
                  -
                • Added three new types of surgery- lipoplasty, plastic surgery, and gender reassignment.
                • -
                -

                Kor updated:

                -
                  -
                • The RD has lost access to telecomms, and basic engineers have gained it.
                • -
                -
                - -
                -

                14 February 2013

                -

                Petethegoat updated:

                -
                  -
                • Updated surgery: you now initiate surgery with surgical drapes or a bedsheet. Most procedures have changed, check the wiki for details. Currently it's pretty boring, but this paves the way for exciting new stuff- new procedures are very simple to add. Report any bugs directly to me, or on the issue tracker.
                • -
                -
                - -
                -

                13 February 2013

                -

                Giacom updated:

                -
                  -
                • There are now hackable wires for the PA computer. To open the interface, click on it while the wires are exposed/panel is open. All but one wire will do something interesting, see if you can figure it out. You can also attach signallers to the wires so have fun remotely releasing the singularity.
                • -
                • New staff of animation icon by Teh Wolf!
                • -
                • You can now hack plastic explosives (C4)!
                • -
                • You can now use NTSL to send signals! With the function signal(frequency, code) you can create some clever ways to trigger a bomb. NTSL also has two new additions; return in the global scope will now stop the remaining code from executing and NTSL now has "elseif"s, huzzah!
                • -
                -

                Kor "I'm quitting I swear" Phaeron updated:

                -
                  -
                • You've been asking for it for years, it's finally here. Wizards can spend points to buy apprentices.
                • -
                • A new wizard artefact, the scrying orb.
                • -
                • The spellbook now has descriptions of spells/items visible BEFORE you purchase them.
                • -
                -

                Petethegoat updated:

                -
                  -
                • Traitors with the station blueprints steal objective can now use a photo of the blueprints instead!
                • -
                -
                - -
                -

                11 February 2013

                -

                SuperSayu updated:

                -
                  -
                • Signallers, prox sensors, mouse traps and infrared beams can now be attacheed to grenades to create a variety of mines.
                • -
                • A slime core can be placed in a large grenade in place of a beaker. When the grenade goes off, the chemicals from the second container will be transfered to the slime core, triggering the usual reaction.
                • -
                -
                - -
                -

                10 Feburary 2012

                -

                Ikarrus updated:

                -
                  -
                • Implants can now be surgically removed. Hint: They're inside the skull.
                • -
                -
                - -
                -

                07 February 2012

                -

                Giacom updated:

                -
                  -
                • The return of the Nanotrasen Scripting Language! (NTSL) If you haven't heard of NTSL, it is a scripting language within a game for telecomms. Yes, you can create scripts to interact with the radio! For more information, head here: http://wiki.nanotrasen.com/index.php?title=NT_Script But before you do, if you are not an antag, do not create bad scripts which hinders communication.
                • -
                • Cameras, mulebots, APCs, radios and cyborgs can have signallers attached to their wires, like airlocks!
                • -
                • Cameras have non-randomized wires and the power wire when pulsed will now toggle the camera on and off.
                • -
                • Cyborgs have a new wire, the lockdown wire! It will disable/enable the lockdown of a Cyborg when pulsed.
                • -
                • The traffic control computer (or more commonly known as the computer which lets you add NTSL scripts) will now have a user log, which will log all user activity. Other users can then view that log and see who has been uploading naughty scripts!
                • -
                • NTSL has two new functions! time() and timestamp(format) will help you increase the range of types of scripts you can make, especially time(); since you can then make the scripts know the different between each execution by storing the results in memory.
                • -
                • Two new advance disease symptoms! Their names are "Longevity" and "Anti-Bodies Metabolism". Have fun experimenting with them!
                • -
                -
                - - -
                -

                31 January 2013

                -

                Kor "Even in death I still code" Phaeron updated:

                -
                  -
                • Four new slime types with their own reactions and two new reactions for old slimes.
                • -
                • Put a suit of reactive teleport armour back in the RD's office.
                • -
                • Chemistry now has two dispensers (with half charge each) so both chemists can actually work at the same time.
                • -
                -
                - - -
                -

                27 January 2013

                -

                Ikarrus updated:

                -
                  -
                • Security frequency chatter now appears in cyan (Similar to how command is gold)
                -

                Cheridan updated:

                -
                  -
                • The plant bags in Hydroponics lockers have been replaced with upgraded models with seed-extraction tech. Activate with via right-click menu or Objects verb tab.
                • -
                • Obtaining grass tiles works a bit different now: grass is harvested as a normal plant item, clicking on it in-hand produces the tile.
                • -
                -
                - -
                -

                26 January 2013

                -

                Pete updated:

                -
                  -
                • Added hugging and kicking. I also updated the text styles for clicking on humans in most intents, but they should be pretty much the same.
                • -
                -
                - -
                -

                25 January 2013

                -

                Errorage updated:

                -
                  -
                • All the equipment you spawn with will now contain your fingerprints, giving the detective more ability to tell where items came from and if a crewmember has changed clothing.
                • -
                -
                  -
                • Better explosions: Explosion spreading will now be determined by walls, airlocks and poddoors and not just a flat circle.
                • -
                -
                - -
                -

                20 January 2013

                -

                Cheridan updated:

                -
                  -
                • Chickens will now lay a certain number of eggs after being fed wheat, rather than just laying them whenever they felt like it. No more chickensplosions.
                • -
                -
                - -
                -

                16 January 2013

                -
                  -
                • Department Security can now be runned: -
                  Department Security decentralizes security by assigning each officer to a different department. They will be given the radio channel and access to their assigned department along with a security post. The brig has been remapped to be smaller to accomodate this change. -
                  To run DeptSec: Before compiling, server operators must
                  tick jobs.dm (in WorkInProgress/Sigyn/Department Sec) and use map 2.1.1 instead of 2.1.0.
                • -
                -
                - -
                -

                /tg/station 13 Presents

                -

                Directed by S0ldi3rKr4s0

                -

                & produced by Petethegoat

                -
                  -
                • Curse of the Horseman.
                • -
                -
                - -
                -

                12 January 2013

                -

                Cael Aislinn updated:

                -
                  -
                • Spiders which will breed and spread through vents. Different classes of vents. AI controlled only at the moment.
                • -
                • Farm animals! Cows, goats and chickens are now available. You can order them at Cargo Bay.
                • -
                -

                Giacom updated:

                -
                  -
                • Staff of animation mimics will no longer care whether you are holding the staff or not, they will never attack their creator.
                • -
                • Brainrot will only need alkysine to be cured.
                • -
                • New spider infestation event based on Cael's spiders. The announcement will be the same as alien infestations.
                • -
                -
                - -
                -

                11 January 2013

                -

                Giacom updated:

                -
                  -
                • Plasma (Air) will give the breather the plasma reagent, for a toxic effect, instead of just straight damage.
                • -
                • The agent card will now work inside PDAs/Wallets; meaning the AI won't be able to track you.
                • -
                -
                - -
                -

                09 January 2013

                -

                Malkevin updated:

                -
                  -
                • The owl mask now functions as a gasmask for increased crimestopping power.
                • -
                -
                  -
                • Adds the missing icons for the arrest statuses of Parolled and Released, as well as a little blinking icon for chemical implants.
                • -
                - - -
                -

                08 January 2013

                -

                Cael Aislinn & WJohnston updated:

                -
                  -
                • Many new icons for aliens (death, sleeping, unconscious, neurotox, thrown/impregnated facehugger etc)
                • -
                • Alien larva can now be removed by dangerous and unnecessary surgery (and actually chestburst if they aren't).
                • -
                • Alien larva now have sprites to represent their growth: bloody at 0%, pale at 25% and 75% the normal deep red.
                • -
                • New icon overlays for representing alien embryo progression.
                • -
                - -
                -

                07 January 2013

                -

                Kor updated:

                -
                  -
                • Four new slime types with their own extract reactions have been added. Sprites this time were created by Reisyn, SuperElement, and LePinkyFace.
                • -
                - -
                -

                02 January 2013

                -

                Kor updated:

                -
                  -
                • Slime breeding! There are now 13 varities of slime, each with its own extract reaction (inject five units of plasma). Some of these reactions are reused from the old cores, some are new. As to breeding, each colour of slime has a series of other slimes it may mutate into when it reproduces.
                • -
                -

                Giacom updated:

                -
                  -
                • You can now use wallets as IDs and equip them in your ID slot.
                • -
                • Firesuits are once again effective at protecting you from heat. The flames themselves will still hurt you, even with a firesuit. The damage protection is much better with a firesuit though.
                • -
                • Engineering starts with a PACMAN generator for jump starting the singularity if the power runs out of the SMES. 30 plasma spawns in Secure Storage inside the crate, to use as fuel for the generator.
                • -
                - -
                -

                31 December 2012

                -

                Giacom updated:

                -
                  -
                • Simple animals (Corgis, Cats, Constructs, Mice, Etc...) can now pull people.
                • -
                • You can quickly stop pulling on someone by pulling them, while they're already being pulled by you. For example, CTRL+Click on something you are pulling to quickly stop pulling it.
                • -
                - -
                -

                30 December 2012

                -

                Giacom updated:

                -
                  -
                • Emitters now require to be wired in order to work. When there is not enough power it will stop shooting until there is enough power, meaning you do not have to turn it back on, just get the power flowing.
                • -
                • You can order shield generators from cargo. Teleporter access is required to open the crate.
                • -
                -

                Ikarrus updated:

                -
                  -
                • Map: Reorganized the Command Sector. The Captain has his own private quarters in addition to his office.
                • -
                - -
                -

                26 December 2012

                -

                Ikarrus updated:

                -
                  -
                • An agent card is now required to use doors and controls on the Syndicate Shuttle (Nuke).
                • -
                • Scanning gas tanks is now a PDA-cart function. Only Atmos and Science PDA carts have this function. Have fun mislabelling gas tanks!
                • -
                - -
                -

                23 December 2012

                -

                Giacom updated:

                -
                  -
                • The syndicate Military PDA will not show up on possible PDAs to message anymore, even when the receive/signal is turned on. You can still send messages and people can still reply to you.
                • -
                • You can now sell processed plasma for supply points. The conversion rate is 2 plasma sheets for 1 point. You must put the plasma in a crate for it to count.
                • -
                • The mecha toy prize promotion has officially ended. You can no longer redeem all 11 action mecha figures for a real mech. New toy redeeming promotions in the future will be considered.
                • -
                - -
                -

                21 December 2012

                -

                Ikarrus updated:

                -
                  -
                • You can now use . to speak over headset department channels in addition to the : and # characters.
                • -
                - -
                -

                19 December 2012

                -

                Nodrak updated:

                -
                  -
                • You can now use # to speak over headset department channels. For example say "#e Hello" will say "Hello" over the engineering channel. say ":e Hello" will still work as it always has.
                • -
                - -
                -

                16 December 2012

                -

                Giacom updated:

                -
                  -
                • You can now create your own solar arrays! Order the solar pack crate and you'll receive 21 solar assemblies, 1 electronic which you can put into an assembly to make it a solar tracker and finally the solar computer circuit board. You will get more detailed instructions in the crate, on a piece of paper. Engineering will also start with this crate to help repair destroyed solar arrays.
                • -
                -

                Petethegoat updated:

                -
                  -
                • Added a new option to the key authorisation devices. It removes the maintenance access requirement from all doors. It's irreversible, so only use it in an emergency!
                • -
                - -
                -

                15 December 2012

                -

                Ikarrus updated:

                -
                  -
                • Swapped the locations of the Library and Chapel. Thanks to killerz104 for the remap.
                • -
                • Partial remap of atmos. Monitoring and Refill stations are now the same room.
                • -
                • Toxins Mixing should be working properly again.
                • -
                - -
                -

                12 December 2012

                -

                Ikarrus updated:

                -
                  -
                • Robotics is now a full Science department.
                • -
                • Completely remapped Research Division, Robotics, Medbay, and the Library.
                • -
                • Partially remapped Cargo Bay, Mining Dock, Engineering, and Atmospherics.
                • -
                • Changed the access of the HoS and HoP. For a list, refer to their respective wiki pages.
                • -
                -

                Errorage updated:

                -
                  -
                • Miners now have to go through cargo to reach the Mining Dock.
                • -
                -

                Petethegoat updated:

                -
                  -
                • The Detective's revolver no longer cares about how cool you look. It now spawns in his locker.
                • -
                • Added new inhands for most energy weapons, by Flashkirby!
                • -
                -

                Giacom updated:

                -
                  -
                • Disintegrate (EI NATH) will leave behind the brain of the victim. Possible productive uses include: trophies, looking awesome as you gib someone and only their brain remains, people to talk to when you get an MMI, pocket brains, a way to get back into the game if the wizard didn't grab your brain and stuffed it into his bag.
                • -
                - -
                -

                07 December 2012

                -

                Giacom updated:

                -
                  -
                • The detective's scanner was upgraded, it can now scan for reagents in items and living beings. Potential uses include, scanning dead bodies for leftover poison or scanning items to see if they have been spiked.
                • -
                • You can now attach photos to newscaster news feeds and wanted posters.
                • -
                • You can now emag buttons to remove access from them.
                • -
                • The CentComm. Report has been changed so it no longer names potential antagonists. It will just announce the potential round type instead.
                • -
                - -
                -

                05 December 2012

                -

                Cheridan updated:

                -
                  -
                • Agent cards have been upgraded with microscanners, allowing operatives in the field to copy access levels off of other ID cards.
                • -
                -

                Ikarrus updated:

                -
                  -
                • The Chief Medical Officer, Research Director, Chief Engineer, and Lawyers now have basic Brig access (corridor only)
                • -
                • Merged Mining and Cargo radio channels into the Supply Radio. To use the supply channel, use :u
                • -
                • Mining Dock remapped to be more compact and closer to cargo.
                • -
                -

                Giacom updated:

                -
                  -
                • The wizard's fireball spell is once again dumbfire. It will fire in the direction of the wizard instead of having to choose from a list of targets and then home in on them.
                • -
                -
                - -
                -

                02 December 2012

                -

                Giacom updated:

                -
                  -
                • Added a new artefact called the "Staff of Animation". You can get it in the Wizard's Spellbook. It will animate objects and items, but not machines, to fight for you. The animated objects will not attack the bearer of the staff which animates them, meaning if you lose your staff, or if it gets stolen, your minions will turn on you.
                • -
                -
                - -
                -

                30 November 2012

                -

                Petethegoat updated:

                -
                  -
                • Janitor has recieved a slightly upgrade mop bucket. The old one is still there too.
                • -
                -

                Ikarrus updated:

                -
                  -
                • Swapped the locations of the Vault and Tech Storage.
                • -
                • Cargo Techs, Miners, and Roboticists no longer start with gloves. They are still available from their lockers.
                • -
                -
                - -
                -

                28 November 2012

                -

                Kor updated:

                -
                  -
                • Slimes have replaced roros (finally)! Right now they are functionally identical, but massive expansion of slimes and xenobio is planned. Sprites are by Cheridan.
                • -
                -
                - -
                -

                25 November 2012

                -

                Giacom updated:

                -
                  -
                • Added new very high level symptoms which are only obtainable in the virus crate. Virus crate will also come with mutagen.
                • -
                -

                Petethegoat updated:

                -
                  -
                • Removed clown planet! It'll return shortly in away mission form.
                • -
                -

                Ikarrus updated:

                -
                  -
                • Added Gateway access. Only the RD, HoP, and Captain start with this.
                • -
                • New access levels in the brig:
                  -Brig access now opens the front doors of the brig, as well as other lower-risk security areas.
                  -Security access allows you into the break room and equipment lockers.
                  -Holding Cells allows you to use brig timers and lets you in the Prison Wing.
                  -The Detective no longer has Security Equipment access.
                • -
                • Significantly increased max cloneloss penalty for fresh clones to 40%.
                • -
                -
                - -
                -

                23 November 2012

                -

                Giacom updated:

                -
                  -
                • Simplified detective stuff. The high-res scanner is gone and instead the detective's normal scanner will instantly report all fingerprints, dna and cloth fibers in full. This was needed because the system took too long to work with and disencouraged detectives. Not only that, it made detectives less of a threat for antagonists and made possible scenerios, such as framing someone by changing fingerprints with someone else, impratical. To replace the computer, the detective will have a full medical computer with access to it. Not only that, but his useless filing cabinet will be replaced with an empty one for serious investigators. Along with this, are fingerprint cards and built-in PDA scanning, as all of security had access to it which was really the detective's thing. The new scanner will also log every finding and you can print them out as a report by clicking the scanner while it is in your active hand.
                • -
                • You can toggle the pressure of your sprayer by clicking on it while it is in your active hand. With pressure, the sprayer will spray 10 units on the floor, otherwise it sprays 5. You'll need to turn pressure on to spray water on the floor and make it slippery.
                • -
                • AIs in intellicards can no longer move their camera. This will limit them in ability but without making creating and carding an AI to have as a personel door opener impossible.
                • -
                • Telecommunication Busses can now be set to change the frequency of a signal. (Allowing you to say.. set the command channel to broadcast to the common channel).
                • -
                • Telecommunication was changed to be more effecient. Because of this, Relays don't need a broadcaster or a receiver and you can setup a relay on it's own. You can still disable sending and or receiving from the relay's interface.
                • -
                -

                Zelacks updated:

                -
                  -
                • Plant Analysers now work on seed bags.
                • -
                -
                - -
                -

                21 November 2012

                -

                Petethegoat updated:

                -
                  -
                • The nuke shuttle can now travel at will, and to any location. When travelling from syndicate space to the station, (and vice versa), it will travel through hyperspace.
                • -
                -

                Carn updated:

                -
                  -
                • Changed savefile structure. There's a bunch of unused files left lying around so old savefiles will be purged. Sorry for the inconvenience. Many preferences have been moved to the Preferences verb tab. Everything in that tab is persistent between rounds (it updates your savefile, so even DCing won't reset them). Enjoy x
                • -
                -

                Phol updated:

                -
                  -
                • Added female sprites for most mutant races.
                • -
                -

                Cheridan updated:

                -
                  -
                • SSU manufacturers have issued a product recall! It seems old models shipped with faulty wiring, causing them to short-circuit.
                • -
                -
                - -
                -

                20 November 2012

                -

                Kor updated:

                -
                  -
                • Added Exile Implants to the Gateway room. Someone implanted with an Exile Implant will be able to enter the away mission, but unable to return from it. Not only can they be used for getting rid of dangerous criminals, but revs/stationheads count as dead while on the away mission, and traitor/changeling/wizard assassination targets count as dead if they're on the away mission at round end, allowing for those objectives to be completed peacefully.
                • -
                • Added medical hardsuits, sprited by Majorsephiroth. Two of them spawn in EVA. Their most unique/medical oriented feature is being able to hold a medkit in the suit storage slot, allowing you to easily access medicine while keeping your hands free.
                • -
                -
                - -
                -

                19 November 2012

                -

                Giacom updated:

                -
                  -
                • Malf AIs can only shunt to APCs from their core. Meaning their core needs to be alive before they can shunt to another APC. Malf AIs can start a takeover inside an APC now.
                • -
                • When taking damage, the next sequence of the overlay will show for a bit before reverting to the overlay you should have. This allows you to know you are taking damage without having to check the text screen.
                • -
                -
                - -
                -

                18 November 2012

                -

                Petethegoat updated:

                -
                  -
                • Ported over BS12 style cameras. They now take a photo of a 3x3 area!
                • -
                • Catatonic people (those that have ghosted while alive) now count as dead for assasinate objectives.
                • -
                -
                - -
                -

                17 November 2012

                -

                Donkie updated:

                -
                  -
                • You can now deconstruct and construct Air Alarms and Fire Alarms. Read wiki on howto.
                • -
                -

                Giacom updated:

                -
                  -
                • Medical Cyborgs no longer lose the reagents in their hypospray when switching modes.
                • -
                • Spaceacillin will now help stop the spread of diseases.
                • -
                • You can once again make floors slippery by spraying water. This was done by increasing the amount the sprayer uses, which is from 5 to 10. You can also empty your sprayer's contents onto the floor with a verb in the Object tab.
                • -
                -
                - -
                -

                16 November 2012

                -

                Kor updated:

                -
                  -
                • Fixed the syndicate teleporter door, making teleport assaults possible. It will once again open when you open the outter door.
                • -
                -
                - - -
                -

                15 November 2012

                -

                Giacom updated:

                -
                  -
                • You can now name your advance diseases! You can't name already known diseases though.
                • -
                • Chemical implants can now hold 50 units instead of 10 units.
                • -
                -
                - -
                -

                13 November 2012

                -

                Giacom updated:

                -
                  -
                • More work to advance diseases. Please report any bugs to the bug tracker, I have tried everything that I can on my own but I'll need lots of people playing to fix the more minor bugs. You can find a guide to making your own diseases here: LINK!
                • -
                • Reduced the cost to use Hive Absorb from 40 to 20. This is to help encourage people to use this power more and to use team work.
                • -
                • New symptom added! See if you can find it.
                • -
                • You can now remove symptoms from a disease using synaptizine.
                • -
                • Kor: You can once again debrain changelings. They won't make anyone half-lings though, and you won't be able to tell if the body of a debrained changeling is a changeling by putting a player brain in there.
                • -
                -

                Nodrak updated:

                -
                  -
                • Wizards can no longer cast spells when muzzled. It iss now actually possible to capture a live wizard without constantly injecting them with chloral.
                • -
                • You can no longer take bags of holding or mechs to the clown planet.
                • -
                -
                - -
                -

                11 November 2012

                -

                Carn updated:

                -
                  -
                • Admin-ranks changes
                  - Lots of changes. This is just a brief summary of the most recent changes; still working on proper documentation.
                  - All admins have access to view-vars, player-panel(for individual mobs), game panel and secrets panel. Most of the things on those pages have their own rights requirements. For instance, you can only use event orientated secrets in the secret panel if you have FUN rights. Debug secrets if you have DEBUG rights. etc.
                  - Spawn xeno and toggle gravity procs were moved into the secrets panel (fun).
                  - This may help with understanding which flags do what. Unfortuanately it's still somewhat vague.
                  - If you have any problems, feel free to PM me at irc.rizon.net #coderbus. I go by the username carn or carnie. -
                • -
                -
                - -
                -

                11 November 2012

                -

                Kor updated:

                -
                  -
                • New cyborg upgrade available for production that requires illegal and combat tech
                • -
                • Summon Guns has a new gun type created by Ausops. It also lets the user know when its been cast now to prevent people trying to buy it multiple times
                • -
                • Grilles are no longer immortal in regards to solid projectiles, you can now shoot out windows.
                • -
                -
                - -
                -

                09 November 2012

                -

                Giacom updated:

                -
                  -
                • Cyborgs can now ping and beep! (Say "*beep" and "*ping") Thanks to Rahlzel for the proposal.
                • -
                • HULKS WILL NOW TALK IN ALL CAPS AND WILL RANDOMLY SAY HULK THINGS. Thanks to Brotemis for the proposal.
                • -
                • Sorry for the inconveniences with advance diseases. They are working much better now!
                • -
                • An improved APC sprite by TankNut!
                • -
                -
                - - -
                -

                05 November 2012

                -

                Giacom updated:

                -
                  -
                  -
                • AIs can now tweak with a bot's setting like a human who unlocked the bot.
                • -
                -
                - -
                -

                05 November 2012

                -

                Errorage updated:

                -
                  -
                • Being in an area with extremely low pressure will now deal some damage, if you're not protected.
                • -
                • Space suits and the captain's armor now protect against pressure damage
                • -
                • Slightly lowered all environment damage intakes (temperature, oxygen deprevation) to make up for low pressure damage.
                • -
                • Pressure protection finally works properly. Items that protect from pressure (firesuits, space suits, fire helmets, ...) will now properly protect. The pressure damage indicator will update properly based on the pressure effects on you. Black (low) and red (high) mean you are taking damage.
                • -
                • Slightly slowed down the speed at which your body temperature changes if you are in a very hot or very cold area. The speed at which you recover from an abnormal body temperature remains the same.
                • -
                -
                - - -
                -

                03 November 2012

                -

                TankNut updated:

                -
                  -
                • New APC sprite.
                • -
                • New Wraith sprite and jaunting animation.
                • -
                -
                - -
                -

                03 November 2012

                -

                WJohnston updated:

                -
                  -
                • New Ablative Armor sprite.
                • -
                -
                - -
                -

                03 November 2012

                -

                Giacom updated:

                -
                  -
                  -
                • Airborne diseases will not spread through walls now.
                • -
                • Reduced queen healing rate to 5. The maximum health will be enough.
                • -
                • Aliens can now clear hatched eggs by clicking on them.
                • -
                -
                - -
                -

                02 November 2012

                -

                Errorage updated:

                -
                  -
                • You can once again travel to the station, derelict, satellite and mining z-levels through space. You will also never loop into the same level on transition - So if you are exiting the derelict z-level, you will enter one of the other z-levels.
                • -
                -
                - - -
                -

                01 November 2012

                -

                Giacom updated:

                -
                  -
                • Aliens now take x2 as much damage from fire based weaponary, instead of x1.5.
                • -
                • Doors are now weaker than walls; so normal weapons can destroy them much more easily.
                • -
                -
                - -
                -

                31 October 2012

                -

                Giacom updated:

                -
                  -
                • Advance evolving diseases! Virology can now create, mutate and mix advance diseases together. I replaced the two bottles of blood in Virology with the advance disease. I'll write a wiki article soon enough. Here's a tip: Putting mutagen or virus food (a mixture of milk, water and oxygen) in blood with an existing disease will mutate it to gain symptoms. It can potentially lose old symptoms in the process, so keep backups!
                • -
                -
                - -
                -

                28 October 2012

                -

                Errorage updated:

                -
                  -
                • You can now set your character's age up to 85. This used to be 45.
                • -
                -
                - -
                -

                27 October 2012

                -

                Petethegoat updated:

                -
                  -
                • Mousetraps are now assemblies.
                • -
                • Added a new crate for cargo to order.
                • - -
                -
                - -
                -

                27 October 2012

                -

                Petethegoat updated:

                -
                  -
                • Player Weekend begins!
                • -
                • Added a camera and hand labeler to art storage.
                • -
                • Added a medical records cabinet to the Detective's office.
                • -
                • Added a safe to the vault. Who'll be the first to crack it?
                • -
                -

                Nodrak updated:

                -
                  -
                • The CE has a new pet!
                • -
                -
                - -
                -

                25 October 2012

                -

                Flashkirby99 updated:

                -
                  -
                • Added 18 new hairstyles!
                • -
                -
                - -
                -

                24 October 2012

                -

                Giacom updated:

                -
                  -
                • Throwing eggs will result in the reagents of the egg reacting to the target. (Which can be a turf, object or mob) This creates possibilities like chloral eggs, lube eggs, and many more.
                • -
                • Aliens can now acid walls and floors! Not R-Walls though.
                • -
                • Facehugger throw range reduced to 5, so aim at humans that are 2 tiles apart from the edge of your screen.
                • -
                • Making eggs is a little more expensive but secreting resin is cheaper. (Both cost 75 now)
                • -
                • Aliens no longer have a random duration of stunning humans, it's a constant value now of the lower based value.
                • -
                • Acid is less random and will be more reliable. Don't bother aciding stuff more than once, as it will waste plasma.
                • -
                • You can now target non-dense items (such as facehuggers) with a gun.
                • -
                • You can now shoot canisters, computers and windoors to break them.
                • -
                -
                - -
                -

                18 October 2012

                -

                Giacom updated:

                -
                  -
                • As an AI, you can type in the "track with camera" command and get a list of names to show up there. This also works with "list camera" verb. Remember to use space to auto-fill.
                • -
                • Welding goggles have been added. They are like welding helmets but they are for the glasses equipment slot. Science and the assembly line are given a pair.
                • -
                • Thanks to WJohnston for the welding goggle icons.
                • -
                • Small change to the Assembly Line. Instead of six normal flashes, the Assembly Line will instead have two normal flashes and eight synthetic flashes. Synthetic flashes only work once but are designed to be used in construction of Cyborgs.
                • -
                • Nar-Sie put on a few pounds. Thanks HornyGranny.
                • -
                -
                - -
                -

                16 October 2012

                -

                Giacom updated:

                -
                  -
                • New changeling powers!
                • -
                • Hive Channel/Hive Absorb. Allows you to share your DNA with other changelings, very expensive chemical wise to absorb (download), not so much to channel (upload)! You cannot achieve your objective by sharing DNA.
                • -
                • Mimic Voice! You can form your voice of a name you enter. You won't look like them but when you talk, people will hear the name of who you selected. While you're mimicing, you can't regenerate chemicals.
                • -
                • Extract DNA! A power that allows you to silently sting someone and take their DNA! Meaning you do not have to absorb someone to become them. Extracting their DNA doesn't count towards completing your objectives.
                • -
                • You can now get flares from red emergency toolboxes. Has a 50% chance of a flash-light or a flare spawning.
                • -
                • Flare icon by Ausops!
                • -
                • Thanks to RavingManiac (Smoke Carter), Roros now lay eggs which can grow into baby roros or be used for cooking recipes. Scientists will need to expose the egg to plasma for it to hatch; while it is orange (grown).
                • -
                • A new icon for the map spawned x-ray cameras. Icon by Krutchen.
                • -
                -
                - -
                -

                13 October 2012

                -

                Giacom updated:

                -
                  -
                • Facehuggers have a new animation, thanks to Sly.
                • -
                • Firelocks, glass-less airlocks and walls will stop heat.
                • -
                • Fires are now more deadly, especially the flames.
                • -
                • Fires will now break windows.
                • -
                -
                - -
                -

                10 October 2012

                -

                Giacom updated:

                -
                  -
                • Larva grow a little bit faster when on weeds or when breathing in plasma.
                • -
                -
                - -
                -

                8 October 2012

                -

                Giacom updated:

                -
                  -
                • Thanks to Skasi. Atmospherics has been changed to be made simpler and spawn with the new atmos features, such as the heaters.
                • -
                • Radio headsets can only be heard by people wearing them on their ear slot. This will let us do more fun stuff with headsets, such as a traitor encryption key which can listen to all the channels, but not talk in them.
                • -
                -
              -

              Kor updated:

              -
                -
              • A pen no longer spawns in your pocket. Instead, each PDA will spawn with a pen already in it.
              • -
              - - -
              -

              5 October 2012

              -

              Giacom updated:

              -
                -
              • Aliens can now be harmed by fire. They now also take double fire damage, meaning flame based weaponry is very effective.
              • -
              • Buffed alien facehuggers and eggs. Facehuggers don't go idle anymore, and they attach to anyone who walks past them. Eggs do the same; fully grown eggs will open to potential hosts. If you are still in the range of them, the facehugger inside will leap out and hug you. Removed "activate facehuggers", since it's useless now. Emote "roar" if you want to roar now.
              • -
              • There can be only one living queen at a time, if the queen dies then a drone can take her place as a princess.
              • -
              • Buffed queen regeneration a bit, so it's not the same as her underlings. It's also more important because there can only be one queen at a time.
              • -
              • Aliens don't slip in space anymore.
              • -
              • Hulks don't paralyze aliens anymore, they instead slow them down to a slow crawl. It is very effective for punching aliens out of weeds, so it can't regenerate it's health.
              • -
              • New egg opening and opened egg icons by WJohnston.
              • -
              -

              Aranclanos updated:

              -
                -
              • A buncha crud nobody cares about lol Added a light to the airlock wiring interface to show the status of the timing.
              • -
              • You can't fill sprays without being next to the dispenser.
              • -
              • Simple animals no longer freeze to death in places with normal temperature.
              • -
              • Mechs no longer freeze on the spot when they are using the Energy Relay on powerless areas.
              • -
              • Improvements to showers, they now clean gear on beltslot, back, ears and eyes. Showers only clean visible gear.
              • -
              • Replica pods works again! But you can't make potato people without a key or clone people who ghosted alive (Catatonic).
              • -
              • Engiborgs can deconstruct airlocks with their RCDs once again.
              • -
              • You can construct airlocks while standing on another airlock with RCDs.
              • -
              -
              - -
              -

              3 October 2012

              -

              Agouri updated:

              - -
              - -
              -

              1 October 2012

              -

              Cheridan updated:

              -
                -
              • Wizards have a new artifact added to their spellbooks.
              • -
              -
              - - -
              -

              30 September 2012

              -

              Numbers updated:

              -
                -
              • Readded Volume Pumps - now they work as intended and are constructable
              • -
              • Readded Passive Gates - now they work as intended and are constructable
              • -
              • Readded Heat Exchangers - now they work as intended and are constructable
              • -
              • Added Heater - to warm up gasses to 300C
              • -
              • Pipe dispensers can produce the readded pieces.
              • -
              • New graphics for all of the above - courtesy by Ausops.
              • -
              -
              - -
              -

              30 September 2012

              -

              Giacom updated:

              -
                -
              • Airlocks now use the Environmental power channel, since they are airlocks after-all. Meaning, when power is low the airlocks will still work until the environmental channel on the APC is turned off. This applies to all the door control buttons too. Pipe meters now use the environmental power channel. If you have any comments have this change, please let me know in the feedback section of the forums.
              • -
              -
              - -
              -

              26 September 2012

              -

              Carnwennan updated:

              -
                -
              • Added new hotkeys. Type hotkeys-help for details or see the drop-down help menu at the top of the game window.
              • -
              -

              Aranclanos updated:

              -
                -
              • Mechs are once again spaceproof!
              • -
              • The YouTool machine is now all access
              • -
              • Cutting tower caps in hand no longer deletes the wood, and planks now auto stack
              • -
              -
              - -
              -

              25 September 2012

              -

              Donkie updated:

              -
                -
              • Reworked the Piano, now really optimized and new interface!
              • -
              -
              - -
              -

              24 September 2012

              -

              Petethegoat updated:

              -
                -
              • Hopefully fixed the stop midis button. It should now stop any midis that are currently playing.
              • -
              -
              - -
              -

              23 September 2012

              -

              Petethegoat updated:

              -
                -
              • Fixed an exploit which would allow the janitor to magically mop floors.
              • -
              • Added lipstick~ It's not available on station, as Nanotrasen has deemed it contraband.
              • -
              • If you encounter any issues with computers, notify an admin, or ask for assistance on #coderbus, on irc.rizon.net.
              • -
              -

              Donkie updated:

              -
                -
              • Updated the Package Tagger with new interface!
              • -
              • You can now dispense, remove and retag sort junctions properly!
              • -
              -
              - -
              -

              17 September 2012

              -

              Cheridan updated:

              -
                -
              • Metroids have been replaced with Rorobeasts. Roros are strange latex-based lifeforms that hate light, fun, and gloves.
              • -
              -
              - -
              -

              17 September 2012

              -

              Carn updated:

              -
                -
              • F5 is now a hotkey for adminghosting. F8 toggles ghost-like invisibility for admins.
              • -
              • Catatonia makes you fall down. Admins appear braindead when admin-ghosting.
              • -
              • "Set-observe"/"Set-play" renamed and merged into "Aghost".
              • -
              • "Lay down/Get up" renamed to "Rest"
              • -
              • Closets can't be sold on the supply shuttle anymore
              • -
              • Fixed all dat light
              • -
              -
              - -
              -

              13 September 2012

              -

              Carn updated:

              -
                -
              • New Hotkeys (Trial period). Details can be found in the help menu or via the hotkeys-help verb. It's all client-side. It shouldn't intefere with regular controls (except ctrl+A, ctrl+S, ctrl+D and ctrl+W).
              • -
              -
              - -
              -

              10 September 2012

              -

              Giacom updated:

              -
                -
              • AIs can double click on mobs to instantly start tracking them.
              • -
              -
              - -
              -

              Important note for server hosts!

              -

              Important note for server hosts!:

              -
                -
              • The file /code/defines/hub.dm was moved into /code/hub.dm. To get your server back on the hub, open /code/hub.dm and set the hub variables again. Sorry for the inconvenience.
              • -
              -
              -
              -

              8 September 2012

              -

              Carn updated:

              -
                -
              • Added an additional check to stop changelings sharing powers/becomming un-absorbable/etc by absorbing eachother and then rejuvinating from death.
              • -
              • Cloaked Aliens are now slightly easier to see, so they should avoid strongly lit areas when possible. They can still lay down to become even stealthier though. Let me know what you think, it's only a minor sprite change.
              • -
              -
              - -
              -

              6 September 2012

              -

              Cheridan updated:

              -
                -
              • -Changes flour from an item to a container-held reagent. All recipes have been updated to use 5 units of reagent flour for every item required previously. This has a few advantages: The 16(!) sacks of flour previously in the kitchen cabinet have been condensed to an equivalent 3 sacks. Beer is now brewable with universal enzyme, and converting lots of wheat into flour should be less tedious. Also, flour grenades, etc. Because of this, flour is now obtained from the all-in-one blender rather than the processor, and spaghetti noodles are made with 5 units of flour in the microwave.
              • -
              -
              - -
              -

              6 September 2012

              -

              Giacom updated:

              -
                -
              • Removed cameras from bots (NOT BORGS). They weren't working well with freelook and I felt that since they weren't used at all, they wouldn't be missed.
              • -
              -
              - -
              -

              3 September 2012

              -

              Giacom updated:

              -
                -
              • Cameras has changed quite a bit. They are no longer created from grenade canisters, instead you make them from an autolathe. The construction and deconstruction for them has also changed, so look it up or experiment it with yourself to see how to setup the cameras now. Cameras also get wires, like airlocks and APCs. There's two duds, a focus wire, a power wire, an alarm wire and a light wire. Protip: You can see which one is the alarm wire by pulsing it.
              • -
              • Added a red phone and placed it in the Cyborg Station. Sprite by Pewtershmitz! You'll also find an AI restorer there, replacing the computer frame.
              • -
              • Cameras aren't all X-ray anymore. The AI won't be able to see what room you are in if there's no normal camera inside that room or if there's no X-ray camera nearby..
              • -
              • Cameras get upgrades! Currently there's X-ray, EMP-Proof and Motion. You'll find the EMP-Proof and Motion cameras in the normal places (Singularity Pen & EVA), the new X-ray cameras can be found in the Dormitory and Bathrooms, plus some extra ones to invade your privacy. See if you can smash them all.
              • -
              • Alien Larva can bite simple animals (see: Ian, Runtime, Mice) to kill them and gain a small amount of growing points.
              • -
              • Space travel was tweaked to be more random when changing Z levels. This will stop people and items from being stuck in an infinite loop, as they will eventually hit something to make them stop.
              • -
              -
              - -
              -

              31 August 2012

              -

              Agouri updated:

              -
                -
              • Overhauled newscasters. No visual additions but the thing is much more robust and everything works as intended. Wanted issues are fixed. Admins, check out Access News Network under Fun.
              • -
              -
              - -
              -

              30 August 2012

              -

              Giacom updated:

              -
                -
              • You can now create an EMP Pulse. Like an explosion, it is the mixing of two reagents that trigger this to happen. I will tell you the first required reagent. Uranium. Have fun!
              • -
              • I have made most chemicals need 3-5 or more chemicals in order to react to a turf. For instance, you need at least 5 units of thermite splashed on a wall for it to burn down."
              • -
              • The EMP kit, that you can buy through the uplink, has two more grenades in them now. Making the box full of EMP grenades!
              • -
              • Changed the EMP grenade's range to be much bigger.
              • -
              -
              - -
              -

              29 August 2012

              -

              Nodrak updated:

              -
                -
              • Mice now work with the admin player panel. Admins can now turn players into mice with the 'Animalize' button in the player panel!
              • -
              • Space bear AI no longer runs when a player is controlling it. Admins can now turn players into space bears with the 'Animalize' button in the player panel!
              • -
              • The holodeck beach program once again has a beach.
              • -
              • The nuke op shuttle floor was pressure-washed a few days ago. We have since re-painted it with nanotrasen blood. Sorry for any confusion.
              • -
              -
              - -
              -

              28 August 2012

              -

              Giacom updated:

              -
                -
              • You can now toggle the bolt light of airlocks. An extra wire, that controls the airlock's bolt light, has been added.
              • -
              • Aliens can now tell who is and who isn't infected. They get a special facehugger icon that appears over mobs that have been impregnated.
              • -
              • Cameras have temporary X-Ray for the time being.
              • -
              -
              - -
              -

              August 26, 2012

              -

              Nodrak updated:

              -
                -
              • Admins now have an 'Animalize' button on a mob's player panel. This button allows admins to turn players into simple animals.
                There are a few exceptions. Mice, Parrots, Bears and Space Worms all have issues that, until fixed, prevent me from allowing players those transformations.
              • -
              -

              August 25, 2012

              -

              Carnwennan updated:

              -
                -
              • New lighting. It should look and feel the same as the old lighting whilst being less taxing on the server. Space has a minimum brightness (IC starlight) and areas that do not use dynamic lighting default to a lighting level of 4, so they aren't dark, but they aren't superbright. Replacing turfs should preserve dynamic lighting. Singulo/bombs should cause a lot less lighting-related lag. There are some minor known issues, see the commit log for details.
              • -
              • Admins can now access most controller datums with the "Debug Controller" verb. Time to break all the things!
              • -
              • Supply shuttle now uses a controller datum. This means admins can see/edit supply orders etc.
              • -
              • Changeling fakedeath can be initiated after death again. Next time you want something reverted, just ask rather than being obnoxious.
              • -
              -

              Giacom updated:

              -
                -
              • AIs can now look around like a ghost with the exception that they cannot see what cameras cannot see. Meaning if you're in maintenance, and there's no cameras near you, the AI will not know what you are doing. This also means there's no X-Ray vision cameras anymore.
              • -
              • AIs can add links to Telecommunication Machines. Added some cameras for areas that should have it but instead relied on cameras nearby for vision.
              • -
              • Choking has been changed. You have to stand still while lethally choking someone. It takes time to get into that lethal choke. When you are lethaling choking someone, they are still concious until the lack of oxygen knocks them out.
              • -
              -

              trubble_bass updated:

              -
                -
              • Nerfed the Neurotoxin drink, it is now less effective than a stunbaton. But more effective than a Beepsky Smash.
              • -
              • Updated descriptions on various cocktails to be more accurate or more relevant to the drink itself.
              • -
              -
              - -
              -

              August 24, 2012

              -

              Sieve updated:

              -
                -
              • Floorbots now actually pull up tiles when emagged
              • -
              • All helper bots (excluding MULEs) have an access panel and maint panel, access being for behavior and maint for internal work
              • -
              • To open the maint panel, the access panel needs to be unlocked, then you use a screwdriver. There you can emag/repair it to your heart's content. (Emagging the access panel will also unlock it permanently)
              • -
              • Helper bots are now repaired by using a welder when their maint panel is open
              • -
              -
              - -
              -

              August 23, 2012

              -

              Nodrak updated:

              -
                -
              • In-hand sprites once again update correctly when equipping items.
              • -
              -
              - - -
              -

              August 16, 2012

              -

              Errorage updated:

              -
                -
              • Changes were made to how heating and cooling of humans works.
              • -
              • You must wear both a space suit and space helmet to be protected from space! Likewise you must wear a firesuit and a fire helmet to be protected from fire! Fire helmets are red and white hardhats, found in all fire closets.
              • -
              • Fire suits now only protect from heat and space suits only protect from cold, so make your choice count.
              • -
              -
              - - -
              -

              August 14, 2012

              -

              Sieve updated:

              -
                -
              • DNA modifiers can be used if there is no occupant, primarily to handle the buffer.
              • -
              • Ion Rifles are only effected by max severity EMPs, so AOE from its own shot won't effect it
              • -
              • Pepper Spray fits on Sec belts again
              • -
              -
              - -
              -

              August 11, 2012

              -

              Sieve updated:

              -
                -
              • Turrets now properly fire at simple_animals.
              • -
              • Borgs, AIs, and brains/MMIs can be sacrificed by cultists.
              • -
              • Grenades now automatically set throw on again.
              • -
              -
              - -
              -

              August 6, 2012

              -

              Dingus updated:

              -
                -
              • Library has been redesigned. It's a whole lot more classy now.
              • -
              • Significant changes to Medbay. CMO's office is more centralized, genetics has a new exit into cryogenics, and a new break room has been installed
              • -
              -
              - -
              -

              August 4, 2012

              -

              Icarus updated:

              -
                -
              • Changes to Med-Sci south and surrounding maintenance areas. Virology is more isolated and Science gets a new Misc. Research Lab.
              • -
              • Atmos techs get construction access now to do their little projects in.
              • -
              • Transformation Stings now work on living humans.
              • -
              -
              - -
              -

              August 2, 2012

              -

              Errorage updated:

              -
                -
              • Gas masks now protect you from reagent smoke clouds
              • -
              • Changed the 'black overlay' you get when paralyzed, blind or in critical condition to include a small circle around you.
              • -
              • Dramatically lowered the amount of damage you get per breath while in critical condition. Critical condition now lasts for about 5 minutes if nothing is causing you any additional harm. This in combination with the new black image overlay is an attempt at making doctors more willing to help.
              • -
              -

              Icarus updated:

              -
                -
              • Borgs now have flashlights to allow them to see in lightless areas
              • -
              • Changes to Medbay: The sleeper and storage rooms have been swapped around. Hopefully this leads to more healing and less looting.
              • -
              -
              - -
              -

              August 1, 2012

              -

              Sieve updated:

              -
                -
              • Borgs can now have an encryption key installed into their internal radios. Simply ID, open the panel, and use the key to insert it (Screwdriver to remove)
              • -
              • Due to that as well, borgs have a 'Toggle Broadcast Mode' button for their radios, which changes the broadcast type between station-bounced (Non-reliant on TComms), and subspace (Required for department channels)
              • -
              • Also changed the binary chat for consistency, now for the prefix is ':b' for everyone, not just one for humans and one for borgs/AIs/pAIs
              • -
              • Based on feedback, Nuke Op pinpointers now automagically change between shuttle and disk mode when the nuke is armed or disarmed.
              • -
              -
              - -
              -

              01-August-2012

              -

              Carn updated:

              -
                -
              • Please update your BYOND clients! Ideally everybody should be running the latest version of byond (v496). People who fail to update to at least version 494 within a month's time may find themself unable to connect. Currently our code has no restrictions at all, which is rather bad. By getting the user-base to keep their clients up-to-date we can make use of newer BYOND features reliably.
              • -
              -

              Giacom updated:

              -
                -
              • I've made some adjustments to the Fireball spell. I've changed it to shoot in the player's facing direction instead of you having to pick a name from a list. It will explode upon contact of a person, if it hits an obstacle or if it shoots for too long. To make up for the fireball not being able to go diagonal I've shortened the cooldown to 10 seconds. It still can hurt you badly and knock you down if you shoot it at a wall. Lastly, it now lights up so it'll show up in dark rooms easily.
              • -
              -
              - -
              -

              31 July 2012

              -

              Giacom updated:

              -
                -
              • Removed passive throwing. You need at least an aggressive hold of the mob before you can throw them.
              • -
              • New map changes by Ikarrus. AI Upload Foyer is now Secure Tech Access, and the outer door only requires Bridge access. Attached to it are two new rooms: The messaging server room and the communications relay. The comms relay room runs off its own SMES unit like the AI, so it won't be affected by powersinks
              • -
              -
              - - -
              -

              29 July 2012

              -

              Giacom updated:

              -
                -
              • All radios now only work in their Z level. This means that the CommSat has a few more additions to work with this change. There is now a new Telecomms Machine called the Relay which allows information to travel across Z levels. It it then linked to a new machine called the Hub, which will receive information from the Relays and send it to the buses. Because every Z level needs these relays, which are linked up with Receivers/Broadcasters, every Z level will get one. There is one in the station, in the RD's office, one in Telecomms as always, one in the Ruskie station which is turned off and hidden from the HUB's linked list. The last one is in Mining but the location for it has not been decided yet.
              • -
              • PDAs now need to be in a Z level with a functioning Relay/Comms Network in order to send messages. It will also send uncompressed (scrambled) messages like you would with the ordinary voice messages.
              • -
              • Added some of WJohnston's sprites. Added a new mining borg sprite, Added a new high tech security airlock, Added the new telecomm sprites for Relays. Hubs were given old Bus sprites.
              • -
              -
              - -
              -

              29 July 2012

              -

              Errorage updated:

              -
                -
              • You can now use crayons to color eggs
              • -
              • Mice have invaded the station!
              • -
              -
              - -
              -

              26 July 2012

              -

              Giacom updated:

              -
                -
              • Added a new mushroom for Hydroponics, the Reishi Mushroom! It is obtained like any other mushroom and it has relaxing properties.
              • -
              -
              - -
              -

              July 25, 2012: The day of updates!

              -

              Nodrak updated:

              -
                -
              • Attacking mobs with items will now give new messages. Instead of "Monkeyman was attacked in the head with a wrench by Nodrak." it will read "Monkeyman was bashed in the head with a wrench by Nodrak." Diffrent items have diffrent verbs and some have multiple verbs.
              • -
              • Cultists can now read what words a rune was made with by examining the rune. Due to an error in the code, this was not possible before.
              • -
              • Clowns no longer have practice lasers or staves of change blow up in their face due to clumsyness.
              • -
              • Engineering cyborgs can now actually repair a cut AI wire in APCs.
              • -
              • I've removed a ton of pointless checks and redundant loops from metroid's which have been causing lag due to how often they get called. If metroids are behaving strangly ping me in #coderbus
              • -
              -

              Sieve updated:

              -
                -
              • Made a 'default' save slot (D), and whenever you connect it automatically selects the default slot to load from, but manually selecting a different slot will allow you to play on that one before it returns to default.
              • -
              • Added the ability to name your save slots with the '*'. Names can be up to 16 characters and contain letters, numbers, and basic symbols
              • -
              • The preview icon on the preference screen now takes into account any job you have set on high, and dresses up the icon accordingly. If assistant is set to 'yes', or AI/Cyborg are on high it will put the icon in a grey suit (So you can still customize).
              • -
              • Nuke Ops get a new pinpointer, changing modes with the verb will switch between pointing to the disk, and pointing to the shuttle. Also provides a notification when you leave the station z-level
              • -
              • Reworked how MMI Life() was done, now they will never lose consciousness, and many less things affect them now(Like deafening/blindness from explosions). However, they are vulnerable to EMPs, but all damage is temporary.
              • -
              • Clowns will no longer be killed trying to use holo eswords
              • -
              • Major tweaking to try and optimize many operations on the game's backend. Hopefully, this will reduce a large amount of lag by steamlining CPU-intensive operations, but at the same time there was so much changed that there is no real way for a small group to test everything. If anyone spots a bug involving being unable to 'find' mobs, characters, whatever, then put it on the issue tracker or at the very least let #coderbus know. We can't fix shit unless we know about it.
              • -
              -

              Icarus updated:

              -
                -
              • Players not buckled in when the emergency shuttle/pod starts moving get will get knocked down.
              • -
              • Added a YouTool vending machine to primary tool storage.
              • -
              -
              - -
              -

              24 July 2012

              -

              Errorage updated:

              -
                -
              • Both the chef and bartender have access to the bar area so both can serve if the other is incompetent or does not exist. Bartender's shotgun and shaker were moved to his back room and the booze-o-mat is now ID restricted to the bartender.
              • -
              • Added powercells into vending machines in engineering
              • -
              • Gave two beartraps to the janitor for pest control purposes................
              • -
              -
              - -
              -

              22 July 2012

              -

              Errorage updated:

              -
                -
              • Mech toys can now be redeemed at the quartermaster's for a great reward! If you collect the full set of 11 toys you should put them in a crate and send them to centcom via the supply shuttle.
              • -
              • Supply shuttle arrival time reduced to 2 minutes
              • -
              • Hopefully fixed the toy haul problem which made it possible to get a million toys from arcade machines.
              • -
              -

              Giacom updated:

              -
                -
              • You can now make newlines with your PDA notes.
              • -
              • You can now research and build the Light Replacer.
              • -
              • You can now store donuts in the donut box. The next donut you pull out will be the last one you put in.
              • -
              • APCs will auto-turn on if there is enough power in the grid, even if the powercell is below 30%. The APC needs to be charged for a long enough time before it starts turning equipment on, to avoid spazzing out. If you have any problems with it, such as equipment turning off and on repeatedly then please make an issue report with a screenshot of the APC.
              • -
              -
              - -
              -

              18 July 2012

              -

              Giacom updated:

              -
                -
              • Added the Light Replacer. This is a device that can auto replace lights that are broken, missing or burnt. Currently it is found in the Janitor's closet and Janitor Borgs can equip it. You can refill it with glass, or if you're a Cyborg, just recharge. It is emaggable and will replace lights with rigged lights. The light's explosion was nerfed to help balance it and it is very noticable when you are holding an emagged Light Replacer.
              • -
              • The Janitor's equipment locator, on their PDA, will now tell you the direction of the equipment.
              • -
              -
              - -
              -

              17 July 2012

              -

              Icarus updated:

              -
                -
              • Added department satchels
              • -
              • Added Captain's Backpack and Satchel
              • -
              • Added three new hairstyles by Sly: Gelled, Flat Top, and Pigtails. Hair list has also been sorted by grouping similar styles.
              • -
              -

              Giacom updated:

              -
                -
              • Added a new wire for Cyborgs. See if you can figure out what it does.
              • -
              • You can now fill any container with a sink. You can change the amount to fill, from sinks, by setting your container's transfer amount.
              • -
              -
              - -
              -

              14 July 2012

              -

              Carn updated:

              -
                -
              • All living mobs can now ghost whenever they want. Essentially making the suicide verb obsolete. If you ghost whilst still alive however, you may not re-enter your body for the rest of the round.
              • -
              • Humans can no longer suicide whilst restrained (this is purely to prevent meta whilst I finish up the new FUN suicides)
              • -
              • Fixed dem evidence bags. Fixed metroids getting at it like rabbits. Fixed stuff like welding masks not hiding your face. Bunch of other things
              • -
              -

              Willox and Messycakes updated:

              -
                -
              • pAI Emoticons! Allows each pAI to set their screen to display an array of faces! Click on 'Screen Display' in the pAI OS for a list.
              • -
              -
              - - -
              -

              Saturday July 14th 2012

              -

              Giacom updated:

              -
                -
              • Added Russian Revolvers. This is a special Revolver that can only hold a single bullet randomly in it's chamber. This will allow you to play Russian Roulette with your fellow crew members! You can use it like a normal gun but you will need to cycle through the chamber slots until you hit the bullet. Only admin spawnable.
              • -
              -
              - -
              -

              Friday July 13th 2012

              -

              Carn updated:

              -
                -
              • Added FLOORLENGTH HAIR. YEESSSSSSSS!!!! :3 If you like it say thanks to Ausops for fixing it up. Credits to Powerfulstation for the original sprite.
              • -
              -

              Giacom updated:

              -
                -
              • Save Slots! You can now have separate save slots for different character setups, with a customizable maximum of 3 slots per account. If you are wondering, you will not lose your old saved setup.
              • -
              • The character setup screen was updated to look nicer and to fit on the screen.
              • -
              -

              Icarus updated:

              -
                -
              • Added new Dwarf and Very Long hairstyles. Dwarf hair and beard by SuperCrayon.
              • -
              -
              - -
              -

              Thursday July 12th 2012

              -

              Giacom updated:

              -
                -
              • pAI gets a better PDA that can actually receive messages from people. They can also instantly reply like everybody else now and they can toggle their receiver/signaller/ringer.
              • -
              • You can show the AI the notes on your PDA by holding it up to a camera. When you show up a paper/pda to the camera the AI can now click on your name to go to you, if you're near a camera. People who are Unknown will not have a link; which would've allowed the AI to track them.
              • -
              • Made the" common server" and the "preset right receiver" listen for frequencies 144.1 to 148.9. This will allow people to use different frequencies to talk to eachother without bothering the common channel. It will also allow Revs and Cultists to work with each other; everything is still logged though so it still has risks.
              • -
              • Increased the maximum frequency limit for handheld radios and intercoms. It will give you the option to just use station bounced radios on a higher frequency so that anyone with a headset can't simply tune in.
              • -
              • Created an All-In-One Grinder that is suppose to replace the blender, juicer and reagent grinder all together. Meaning any department that has a juicer, blender and grinder will instead get this. It will help people be more independent from Chemistry by recycling foods and plants.
              • -
              -
              - -
              -

              Wednesday July 11th 2012

              -

              Nodrak, Cheridan and Icarus updated:

              -
                -
              • Added a couple of Emergency Shield Projectors to Engineering secure storage. -

                Note: Credit goes to Barhardar for the original code and functionality. -

                These devices can be used to quickly create an air-tight seal across a hull breach until repairs can been made. -

                Wrench them in place and activate them near a hull breach. The shield should extend to all space tiles in range. -

                They can be (un)locked by engineering IDs and can also be emagged and otherwise malfunction. As they can not be constructed, you can repair damage and malfunctions by opening the panel with a screwdriver and replacing the wires with a cable coil

              • -
              -

              Giacom updated:

              -
                -
              • Chemistry update: Pills can now be ground up in reagent grinders. You can now put custom amounts of reagent into things using chemmasters. Can now load pill-bottles into chemmasters for mass pill-production.
              • -
              -

              Carn updated:

              -
                -
              • Clicks on inventory slots with items in now act like a click on the thing in that slot. So clicking smaller things (like pens) is easier and you can remove clothing that borks/goes invisible. Please continure to report those kinds of bug though please. Thanks x
              • -
              • Can no longer interact with your inventory with a mech.
              • -
              -

              Errorage updated:

              -
                -
              • You can now only adminhelp once every 2 minutes so please provide all necesary information in one adminhelp instead of 5! Also reply to admins in PM-s and not additional adminhelps.
              • -
              -
              - -
              -

              Saturday July 7th, 2012

              -

              Icarus updated:

              -
                -
              • A basketball simulation is now available at the holodeck. Credit to Sly and Ausops for the sprites.
              • -
              -
              - -
              -

              Friday July 6th, 2012

              -

              Giacom updated:

              -
                -
              • Bottles can now be broken over people's heads! To do this, you must have the harm intent on and you must be targeting the person's head. This change affects alcoholic bottles only. It does not change pill bottles or chemistry bottles. Helmets help protect you from damage and the regents of the bottles will splash over the victim.
              • -
              • AI's now have access to a PDA. Note: It is not PDA-bomb-able
              • -
              • Health analyzers and medical PDAs now give a time of death when used on corpses.
              • -
              -
              - -
              -

              Thursday July 5th, 2012

              -

              Carn updated:

              -
                -
              • Alien larva now chestburst even after their host has died.
              • -
              • Aliens can now slap facehuggers onto faces so they can infect mobs which lay down (or those stuck to nests).
              • -
              • Aliens can now slash security cameras to deactivate them.
              • -
              -
              - -
              -

              Wednesday July 4th, 2012

              -

              39kk9t & Carn updated:

              -
                -
              • Added alien nests. They're basically beds made of thick sticky resin which aliums can 'stick' (buckle) people to for sexytimes
              • -
              • Weed nodes are no longer dense.
              • -
              • Queens can secrete resin for walls/nests/membranes
              • -
              • Various bugfixes
              • -
              -
              - -
              -

              Saturday June 30th, 2012

              -

              Icarus updated:

              -
                -
              • Added Petethegoat's basic mirrors to the map. They allow you to change hairstyles.
              • -
              • Remapped Bar, Theatre, and Hydroponics. Bar and Kitchen are now more integrated with each other.
              • -
              -
              - -
              -

              Thursday, June 28th

              -

              Nodrak updated:

              -
                -
              • I'm currently working on cleaning up and moving around a large portion of the mob code. These changes do not directly affect players; HOWEVER, bugs, oversights or simple mistakes may cause problems for players. While I have tested as much as I can, there may be some lingering bugs I have missed.

                This part of the mob code cleanup mainly focuses on damage variables and procs. So if you suspect something related to taking, dealing or examining damage is not working as intended please fill out an issue report here and be as detailed as possible. This includes what you were doing, steps to reproduce the problem, who you were doing it to, what you were using ect... Thank you.

              • -
              -

              Carn updated:

              -
                -
              • Alien hunters will now cloak when using the 'stalk' movement intent. Whilst cloaked they will use up their plasma reserves. They can however cloak as long as they like. Using the Lay-down verb will allow them to remain cloaked without depleting their plasma reserves, hence allowing them to lay ambushes for unsuspecting prey. Should a hunter attack anybody, or get knocked down in any way, they will become visible. Hopefully this allows for more strategic/stealthy gameplay from aliens. On the other hand, I may have totally screwed the balance so feedback/other-ideas are welcome.
              • -
              • Removed the invisibility verb from the alien hunter caste.
              • -
              -
              - -
              -

              Wednesday, June 27th

              -

              Errorage updated:

              -
                -
              • Fixed the bug which prevented you from editing book's titles and authors with a pen. Also fixed the bug which prevented you from ordering a book by it's SS13ID.
              • -
              • Added the F12 hotkey which hides most of the UI. Currently only works for humans.
              • -
              -

              Donkie updated:

              -
                -
              • Pizza boxes! Fully stackable, tagable (with pen), and everythingelse-able. Fantastic icons by supercrayon!
                Created with a sheet of cardboard.
              • -
              -
              - -
              -

              Tuesday, June 26th

              -

              Errorage updated:

              -
                -
              • Changeling parasting now only weakens for 10 game ticks. It no longer silences your target.
              • -
              -
              - -
              -

              Saturday, June 23rd

              -

              Donkie updated:

              -
                -
              • Reworked job randomizing system. Should be more fair.
              • -
              • List of players are now randomized before given antag, this means that declaring as fast as possible doesn't mean shit anymore!
              • -
              -

              Carn updated:

              -
                -
              • Putting a blindfold on a human with lightly damaged eyes will speed up the healing process. Similar with earmuffs.
              • -
              • More overlay bug fixes. Most of it to do with little robots and construction stuff. Bugs go here if you have 2 minutes http://nanotrasen.com/phpBB3/viewtopic.php?f=15&t=9077
              • -
              • Weakening is instant! That means if you stunbaton somebody they're gonna fall-down immediately.
              • -
              -

              Icarus updated:

              -
                -
              • Medical storage now requires Surgery access (Medical Doctors only)
              • -
              -
              - -
              -

              Wednesday, June 20th

              -

              Nodrak updated:

              -
                -
              • AIs, Borgs are no longer able to be cultists or revolutionaries as their objectives completely contradict their laws. They can still be subverted of course.
              • -
              • pAI's no longer keep cult or rev icons.
              • -

              Cheridan updated:

              -
                -
              • -Both Ambrosia forms have had their reagent contents modified to prevent going over the 50-unit cap at high potencies. Ambrosia Deus now contains space drugs instead of poison.
              • -
              • -Drinking milk removes capsaicin from your body. REALISM!
              • -
              • -Frost oil hurts less upon consumption.
              • -
              • -Liquid plasma can be converted into solid plasma sheets, by mixing 20 plasma, 5 iron, and 5 frost oil.
              • -
              • -Added effects for holy water injection on hydroponics plants.
              • -
              -
              - -
              -

              Monday, June 18th

              -

              Giacom updated:

              -
                -
              • Fix for special characters on paper and from announcements
              • -
              -

              Sieve updated:

              -
                -
              • Various small bugfixes, check the commit log for full details
              • -
              • Fixed falsewalls not working
              • -
              • Made Lasertag ED-209's and turrets much more useful, including making their emag function more fitting
              • -
              • Added Mesons to the EngiVend to make up for how many lockers were removed
              • -
              • New Item: Sheet Snatcher. Right now only borgs have them, but they can hold up to 500 sheets of minerals (Of any combination), and auto-stacks them to boot. Used just like the mining satchels, meaning minerborgs can actually deliver metal
              • -
              • Mech drills can mine sand, and the diamond gets a much larger volume
              • -
              • If a borg has the satchel in its modules (Doesn't have to be the active one), it will auto-magically pick up any ores it walks over.
              • -
              • Bumping an asteroid wall with a pickaxe/drill in your hand makes you auto-magically start drilling the wall, making mining much less tedious (humans and borgs)(Also, gustavg's idea)
              • -
              -

              Icarus updated:

              -
                -
              • New afro hairstyles. Big Afro by Intigracy.
              • -
              -
              - -
              -

              Friday, June 15th

              -

              Carnwennan updated:

              -
                -
              • First update for update_icons stuffs:
                Fixed husking and fatties
                Fixed floor tiles still appearing in hand when laying them
                Fixed runtimes with fatties.
              • -
              • Fixes for pre-existing bugs:
                Fixed being unable to put belts & backpacks on other people
                nodamage (godmode) now prevents all organ damage. It does not stop healing however.
                Nerd stuff...
              • - -
              -

              Errorage updated:

              -
                -
              • Greatly reduced the amount of damage high pressure does.
              • -
              • Fire suits, firefighting helmets (red harhats) and the chief engineer's white hardhat now protect against high pressure.
              • -
              -

              Icarus updated:

              -
                -
              • Ported over ponytail sprites from Baystation
              • -
              -
              - -
              -

              Thursday, June 14th

              -

              Carn updated:

              -
                -
              • FEAR NOT! You can now store a pen in your pda. (aka Best commit all commits)
              • -
              -
              - -
              -

              Wednesday, June 13th

              -

              Carn updated:

              -
                -
              • Massive Mob-Icon Overhaul:
                A large amount of the mob code has been replaced. The systems replaced were causing immense performance issues so the following are very necessary optimisations.
                However, there is a downside: SS13 code is the equivilant of monkeys on typewriters. Despite weeks of constant coding/testing there -will- be things I've missed. The kinds of bugs I'm expecting are overlays not updating and/or in rare cases things not appearing in your hud. Most of these issues can be worked around simply by dropping the item and picking it back up. If all else fails ask an admin to regenerate your icons through view-vars.
                Please report any bugs to me on #coderbus IRC or make an issue on the tracker as a matter of urgency. I will fix them ASAP.
                Also a massive thankyou to Nodrak, Erro, Pete and Willox. :)
              • -
              • Massive rewrite of the overlays system (particularly for humans). Stuff is cached and only updates when necessary. In effect this means faster updates, less overheads/lag, and less reliance on the game-ticker.
              • -
              • Numerous bugfixes and tweaks for damage-procs and damage-overlays for humans. They should now be almost seamless, use very little overhead and update instantly.
              • -
              • TK grab can now be cancelled using the throw hotkey. (so now it toggles on/off like it used to).
              • -
              • Added verbs to the view-var drop-down list: "Regenerate Icons" will fix a mob's hud/overlays. "Set Mutantrace" will change a mob's mutantrace.
              • -
              • Damage icons were split up. They kinda look a bit crap so spriters feel free to replace them. Templates are provided in dam_human.dmi
              • -
              • More to come...
              -

              Cheridan updated:

              -
                -
              • -Added Lezowski's overalls to hydroponic supply closets. 50% chance per closet for them to replace the apron. -Removed some covers tags from things that made no sense to have them.
              • -
              -
              - -
              -

              Monday, June 11th

              -

              Donkie updated:

              -
                -
              • Fixed being able to lock yourself in or out of a locker using the verb.
              • -
              -

              Xerux updated:

              -
                -
              • Added lightfixture creating.
              • -
              -

              Errorage updated:

              -
                -
              • You can now use the resist verb or UI button when welded or locked in a closet. Takes 2 minutes to get out tho (Same as getting out of handcuffs or unbuckling yourself)
              • -
              • Making single-pane windows will now make the window in the direction you're facing. If a window already exists in that direction it will make it 90 degrees to your left and so on.
              • -
              -
              - -
              -

              Sunday, June 10th

              -

              Agouri updated:

              -
                -
              • Cyborgs and AIs can now use the newscaster. It was a mistake on my part, forgetting to finish that part of them.
              • -
              -
              - -
              -

              Saturday, June 9th

              -

              Errorage updated:

              -
                -
              • You can now make restraints from cable. It takes 15 lengths of cable to make a pair of restraints, they are applied the same way as handcuffs and have the same effects. It however only takes 30s to remove them by using the resist verb or button. You can also remove them from someone by using wirecutters on the handcuffed person.
              • -
              • Added four new cable colors: pink, orange, cyan and white. Engineer belts spawn with yellow, red or orange cables while toolboxes and tool closets spawn with all 8 colors.
              • -
              -
              - -
              -

              Thursday, June 7st

              -

              Icarus updated:

              -
                -
              • Added a second ZIS suit to engineering.
              • -
              • Remapped CE office and surrounding areas.
              • -
              -
              - -
              -

              Wednesday, June 6th

              -

              Sieve updated:

              -
                -
              • Radiation now works properly, watch out for that Singularity!
              • -
              • Disposals are no longer the loudest machines in existence.
              • -
              • Building portable turrets with lasertag guns now makes them fire lasertag bolts based on team, and they will automatically target and prioritize people wearing opposing team gear.
              • -
              • The same can be done for ED-209s, simply using a lasertag vest and gun (same color) where you would use a security vest and taser in construction.
              • -
              • Added mineral walls and powered mineral door construction. More information can be found in the commit thread, but basically they are built the same way others are, apply mineral to girder for a mineral wall, mineral to airlock assembly for a powered mineral door.
              • -
              • Commit Thread
              • -
              • Throw hotkey (end) now works with TK.
              • -
              • Swap hands hotkey (page up) now cycles through borg modules.
              • -
              -

              Nodrak updated:

              -
                -
              • Cargo's 'shuttle: station' and 'shuttle: dock' has been changed to 'shuttle: station' and 'shuttle: away' to help avoid confusion.
              • -
              -

              Icarus updated:

              -
                -
              • Maintenance shafts changed around. Renamed, less windows, more turns, and expanded in a few areas.
              • -
              -

              Neek updated:

              -
                -
              • You can now add chemicals into cigarettes by injecting them directly or dipping individual cigarettes into a beaker. You can inject into cigarette packs directly to affect multiple cigarettes at once.
              • -
              -

              Willox updated:

              -
                -
              • You can now click individual blocks/subblocks in the genetics console instead of having to scroll through the blocks with a forward/back button!
              • -
              -
              - -
              -

              Sunday, June 3rd

              -

              Donkie updated:

              -
                -
              • You can now Drag-Drop disposal pipes and machinery into the dispenser, in order to remove them.
              • -
              • You must now use wrench before welding a pipe to the ground
              • -
              • You can no longer remove a trunk untill the machinery ontop is unwelded and unwrenched
              • -
              • You are now forced to eject the disposal bin before unwelding it.
              • -
              -
              - -
              -

              Friday, June 1st

              -

              SkyMarshal updated:

              -
                -
              • Readded fingerprints and detective work, after lots of debugging and optimization.
              • -
              • Any PDA with access to the Security Records can now, by the normal forensic scanner function, store data the same way as the detective's scanner. Scanning the PDA in the detective's computer will copy all scanned data into the database.
              • -
              • If something goes wrong, please contact SkyMarshal on the #bs12 channel on irc.sorcery.net
              • -
              -
              - -
              -

              Friday, June 1st

              -

              Nodrak updated:

              -
                -
              • Windoor's are now constructable! Steps are found here.
              • -
              -
              - -
              -

              Tuesday, May 29th

              -

              Nodrak updated:

              -
                -
              • Glass Doors are now breakable.
              • -
              • Added more treasures to the secret mining room.
              • -
              • Changed mineral lockers from secret mining rooms: Instead of giving you two stacks of everything, you get stacks of ore based on rarity
              • -
              -

              Icarus updated:

              -
                -
              • Moved Engineering and Bridge deliveries.
              • -
              -
              - -
              -

              This 28th day of May, in the year of our Lord, Two Thousand Twelve

              -

              Cheridan updated:

              -
                -
              • -Adjusted balaclavas and added luchador masks. Wearing luchador masks give you latin charisma. They replace the boxing gloves in the fitness room. Boxing gloves are still available in the holodeck. -Fake moustache tweaked and given new sprites.
              • -
              -
              - -
              -

              Monday, May 28th

              -

              Donkie updated:

              -
                -
              • You can now dispense Disposal Bins, Outlets and Chutes from the disposal dispenser. These are movable and you can attach them above open trunks with a wrench, then weld them to attach them completely. You can remove Bins by turning off their pump, then screwdriver, then weld, then wrench. Same with outlet and chute except for the pump part.
              • -
              -
              - -
              -

              Saturday, May 26th

              -

              Icarus updated:

              -
                -
              • Ported over Flashkirby99's RIG suit sprites from Bay12
              • -
              • Department PDA Carts moved out of lockers and into head offices.
              • -
              -
              - -
              -

              Wednesday, May 23rd

              -

              Cheridan updated:

              -
                -
              • -Reverted default UI sprites to Erro's old-style UI. Config options for UI color styles coming soon. -Driest Martinis will no longer be invisible. -Braincakes are now sliceable.
              • -
              • -Medical borg overhaul. Instead of a dozen random pills and syringes, they get a hypospray that can switch between auto-replenishing tricordrazine, inprovaline, and spaceacillin.
              • -
              -

              Errorage updated:

              -
                -
              • Some of the more pressing issues with the new user interface were addressed. These include the health indicator being too far up, the open inventory taking a lot of space, hotkey buttons not being removable and suit storage not being accessible enough.
              • -
              • A toggle-hotkey-buttons verb was added to the OOC tab, which hides the pull, drop and throw buttons for people who prefer to use hotkeys and never use the buttons.
              • -
              • Added a character setup option which allows you to pick between the Midnight, Orange and Old iconsets for the user interface.
              • -
              -
              - -
              -

              Tuesday, May 22nd

              -

              Icarus updated:

              -
                -
              • RIG helmets can now be used as flashlights, just like hardhats. Credit to Sly for the sprites.
              • -
              • HoP's office has been remapped and made into his private office. Conference Room can now be accessed from the main hall.
              • -
              -
              - -
              -

              Sunday, May 20th

              -

              Errorage updated:

              -
                -
              • The new user interface is here. If anything is broken or something should be done differently please post feedback on the forum. Spriters are encouraged to make new sprites for the UI.
              • -
              • When you receive a PDA message, the content is displayed to you if the PDA is located somewhere on your person (so not in your backpack). You will also get a reply button there. This will hopefully make PDA communication easier.
              • -
              • New hotkeys! delete is the now the 'stop dragging' hotkey, insert is the 'cycle intents' hotkey.
              • -
              -
              - -
              -

              Saturday, May 19th

              -

              Doohl updated:

              -
                -
              • You can now swap hands by clicking with your middle mouse button (you have to click on a visible object though, that's the catch).
              • -
              • Tweaked the DNA modifier consoles a little bit so that it's much easier to see individual blocks instead of one jumbled mess of hexadecimal.
              • -
              • You can now properly emag AI turret controls and commsat turret controls.
              • -
              -

              Invisty updated:

              -
                -
              • Brand new ending animations!
              • -
              -
              - -
              -

              Friday, May 18th

              -

              Errorage updated:

              -
                -
              • Removed hat storage, which was useless.
              • -
              • Implanting someone now takes 5 seconds, both people need to remain still. Implanting yourself remains instant.
              • -
              • Wallets once again spawn in the cabinets in the dormitory
              • -
              • Wallets now fit in pockets
              • -
              -
              - -
              -

              Thursday, May 17th

              -

              Icarus updated:

              -
                -
              • Individual dorms now have a button inside that bolts/unbolts the door
              • -
              • New sprites for Cargo, HoP, and Captain's lockers
              • -
              • More department-specific door sprites. Most noticable changes in medsci and supply departments.
              • -
              -
              - -
              -

              Tuesday, May 15th

              -

              Icarus updated:

              -
                -
              • Added WJohnston's scrubs to Medical Doctor lockers. Comes in blue, green, and purple.
              • -
              • Added two new syndicate bundles
              • -
              • Reduced cost of thermals to 3 telecrystals (formerly 4)
              • -
              • Singularity Beacons are now spawned from a smaller, portable device.
              • -
              • CMO and QM jumpsuits made more unique.
              • -
              -
              - -
              -

              Monday, May 14th

              -

              Icarus updated:

              -
                -
              • Reinforced table parts are now made by using four metal rods on regular table parts. No plasteel involved.
              • -
              • Beakers, small and large can now be made/recycled in autolathes.
              • -
              -

              Nodrak updated:

              -
                -
              • Added a 'random item' button to traitor uplinks. You can potentially get ANY item that shows up on the traitor item list, provided you have enough crystals for it.
              • -
              -
              - -
              -

              Friday, May 11th

              -

              Icarus updated:

              -
                -
              • New design for security. This should be the last time it sees major changes for a while.
              • -
              • Added a new construction area. What could it be for?
              • -
              -

              Petethegoat updated:

              -
                -
              • Readded the RD's genetics access.
              • -
              • RD is still without chemistry access, but I'm going to review this decision in a week and see if R&D is useless due to lack of acid.
              • -
              • Added Flashkirby99's SMES sprites!
              • -
              -

              Invisty updated:

              -
                -
              • Sexy new warpspace (or whatever) tiles.
              • -
              -

              Important changes below!

              -
              - -
              -

              Thursday, May 10th

              -

              Sieve updated:

              -
                -
              • Reverted dismemberment, the recent gun changes, and Tarajans. Before you shit up the forums, read this:
              • -
              • Dismemberment was ported from Bay12, but only halfway, and there were several problems with it. I know many people really liked it, but as it stood it did not fit the playstyle here at all. This had to be removed, there is work on a more fitting system, but this had to be taken out first regardless, and the longer people beat around the bush the worse the situation got.
              • -
              • The gun change was made for no real reason and was pretty problematic, so reverting that should mean there are a lot less 'accidental suicides.'
              • -
              • Tarjans were reverted by request as well, and since keeping them working after removing dismemberment would be a stupid amount of work.
              • -
              -
              - -
              -

              Sunday, May 6th

              -

              Cheridan updated:

              -
                -
              • -New booze sprites for the drinks that were removed! Re-enabled the recipes for the removed drinks. Get cracking, bartenders. -You now need 10 sheets of metal instead of 2 to make a gas canister, people can't FILL ENTIRE ROOMS WITH THEM.
              • -
              • -Emergency Toolboxes now contain smaller, lighter fire extinguishers that actually fit inside them! -
              -
              - -
              -

              Saturday, May 5th

              -

              Petethegoat updated:

              -
                -
              • RD get the fuck out of chemistry and genetics
              • -
              • CHEMISTS, DON'T BE DICKS TO RESEARCH, GIVE THEM ACID YOU TIGHT FUCKS
              • -
              -

              Icarus updated:

              -
                -
              • Updates to Sec, including a stationary scrubber for the prison area.
              • -
              • Swapped around cryogenics and the patient rooms in medbay.
              • -
              -
              - -
              -

              Friday, May 4th

              -

              Cheridan updated:

              -
                -
              • -Added fat jumpsuit sprites for orange, pink, yellow, owl, security, and warden jumpsuits.
              • -
              • -Somatoray is hopefully more useful and less buggy when used on trays. -Botanists now have morgue access, because of their ability to clone via replica pods. Try not to get this removed like all your other access, okay hippies?
              • -
              -
              - -
              -

              Thursday, May 3rd

              -

              Ikarrus updated:

              -
                -
              • Updated genetics, medbay and security.
              • -
              -

              Petethegoat updated:

              - -
              - -
              -

              Tuesday, May 1st

              -

              PolymorphBlue updated:

              -
                -
              • Adds BS12 dismemberment!
              • -
              • Adds greater changeling for 30 points
              • -
              -
              - - -
              -

              Monday, April 20th

              -

              Erro updated:

              -
                -
              • Added a verb to the PDA which you can use to remove an ID in it. If your active hand is empy, it puts it there otherwise it puts it on the floor under you.
              • -
              -
              - -
              -

              Wednesday, April 27th

              -

              Cheridan updated:

              -
                -
              • -New sprites for lemons, oranges, and walking mushroom critters. -Added Invisty's new blob sprites.
              • -
              • -Added a new chemical: lipozine, a weight loss drug. Made with sodium chloride, ethanol, and radium.
              • -
              -
              - -
              -

              Wednesday, April 25th

              -

              Ikarrus & Flazeo updated:

              -
                -
              • New layout for Security, including a prison area instead of permacells.
              • -
              • New layout for the library, bar, and botany.
              • -
              • Medbay and R&D now have three-tile halls.
              • -
              -

              Scroll down for more commits! There's a bunch of new shit.

              -
              - -
              -

              Tuesday, April 24th

              -

              PolymorphBlue updated:

              -
                -
              • Fakedeath changelings can no longer have their brains cut out.
              • -
              • Rev checkwin changed to fire every five ticks (from twenty) and actually use the right objective type so revs being off station counts as success.
              • -
              -

              Sieve updated:

              -
                -
              • Powercells now have unique icons for cell types
              • -
              • Implemented mech construction sprite by WJohnston for the Ripley, Firefighter, Gygax, and Durand
              • -
              • Durand construction is reversible
              • -
              • Power Cells can now be made in Mechfabs, provided the proper research level has been achieved
              • -
              • Added a new item, the Synthetic Flash. Works just like a normal flash, except they can only withstand one use, but can be produced in the Mechfab(To replace the need for normal flashes)
              • -
              • Added a new type of gloves, ones that are cheap copies of the coveted Insulated Gloves, but be warned, quality control wasn't too thorough
              • -
              • Added a new Cyborg Upgrade, a jetpack for use by Miner Cyborgs. Can be refilled on any air canister
              • -
              • Miner Cyborgs now have a Diamond Drill equivalent along with an upgraded Ore Satchel
              • -
              • Mechfabs no longer brick if there are parts in the quene on sync
              • -
              • MMIs can be built in the Mechfabs again
              • -
              • Crabs are no longer immortal, and are now especially vulnerable to wirecutters
              • -
              • Bibles printed in the library now retain the religion's deity
              • -
              • Added Construction Sprites for the Ripley, Firefighter, Gygax, and Durand by WJohnston
              • -
              • Added Particle Accelerator sprites by Invisity
              • -
              • Added Power Cell, Synthetic Flash, Robot Upgrades, and made some modifications to the PA sprites
              • -
              -

              Petethegoat updated:

              -
                -
              • Added Invisty's field generator sprites.
              • -
              -
              - -
              -

              April 1-22, 2012

              -

              Cheridan updated:

              -
                -
              • CATCHING UP ON MY CHANGELOG. Some of this has been in for a while: -Added carved pumpkins and corncob pipes. -Added mutations for ambrosia and lemon trees. -Added more wood items for tower cap wood construction. -Added soil to plant seeds in. Make it by crushing up sandstone. Soil does not have indicators like trays do! Watch your plants carefully!
              • -
              • -The biogenerator is now more robust. It can dispense fertilizer in batches, and make simple leather items. -RnD can create a new tool for botanists: The floral somatoray. Has two modes. Use it on your plants to induce mutations or boost yield.
              • -
              • -Added plump helmet biscuits, mushroom soup, pumpkin pie and slices, chawanmushi, and beet soup recipes for the chef to make.
              • -
              • -Added transparency to biohelmets. -Normalized grass harvests. -Changed the name of "Generic Weeds". -Blenders can now be filled directly from plant bags. -Added low chance for a species mutation whenever a plant's stats mutate. -You now get more descriptive messages when applying mutagen to plant trays. -Removed sugarcane seeds from the vending machine. Added the sugarcane seeds to the seeds crate.
              • -
              -
              - -
              -

              Sunday, April 22nd

              -

              Petethegoat updated:

              -
                -
              • New gasmask sprites. Removed emergency gasmasks, so there's only one type now.
              • -
              • New shotgun sprites by Khodoque!
              • -
              • The barman's double-barrel actually works like a double-barrel instead of a pump-action! Rejoice!
              • -
              • Sneaky barmen may be able to illegally modify their shotgun, if they so choose.
              • -
              • Trimmed the changelog, vastly.
              • -
              -
              - -
              -

              Saturday, April 21st

              -

              Errorage updated:

              -
                -
              • Maintenance door outside of tech storage now requires maintenance OR tech storage access instead of maintenance AND robotics accesses.
              • -
              -
              - -
              -

              Thursday, April 19th

              -

              Carn updated:

              -
                -
              • Rewrote the cinematic system to try and simplify and optimise it. Please report any bugs asap to me or coderbus, thanks.
              • -
              -
              - -
              -

              Tuesday, April 17th

              -

              Kor updated:

              -
                -
              • Engineering jobs now have their PDA spawn in their pocket, and their toolbelt on their belt
              • -
              • The nuke going off on station will now gib everyone on the Z level.
              • -
              • Two more core displays are available for the AI
              • -
              • The Artificer can now build cult floors and walls
              • -
              -
              -

              Friday, April 13th

              -

              Sieve updated:

              -
                -
              • Updated the robotics layout.
              • -
              -

              Petethegoat updated:

              -
                -
              • Nerfed the librarian by removing the r-walls from his cubbyhole thing, fuck WGW readers hiding out in there.
              • -
              -
              -
              -

              Thursday, April 12th

              -

              Agouri updated:

              -
                -
              • Fixed the ability to move while lying down/resting.
              • -
              • Sleep has been fixed and works as intended again. Anaesthetic and toxins can now properly put people to sleep, permanently if you keep the administration stable. Sleeplocs are now viable again. The sleep button and *faint emote work again.
              • -
              -
              -
              -

              Wednesday, April 11th

              -

              PolymorphBlue updated:

              -
                -
              • Droppers are now used at the eyes, and thus, access to the eyes is required to have an effect.
              • -
              -
              - - -
              -

              April 10, the year of our lord 2012

              -

              Agouri updated:

              -
                -
              • CONTRABAND-CON UPDATE: Added posters. I'm sorry to add it seperately with the rest of contraband, but there was a lack of sprites for everything else. Hopefully, people will gain interest and get me some damn sprites this way :3 - As I said, this is an ongoing project of mine. So starting of, we've got...
              • -
              • POSTERS! Posters come in rolled packages that can adhere to any wall or r_wall, if it's uncluttered enough. -

                How they get on-board: The quartermaster can now set the receiver frequency of his supplycomp circuit board. A bit simplistic as of now, will work on it later. Building a supplycomp with a properly set up circuitboard will give access to the Contraband crate. -

                How they're used: Unfold the rolled poster on any wall or r_wall to create the poster. There are currently 17 designs, with the possibility of me adding more. -

                How to get rid of them: You can rip them using your hand... To cleanly extract them and not ruin them for future use, however, you can use a pair of wirecutters. -

                How they're classified: They're contraband, so it's perfectly okay for security officers to confiscate them. Punishment for contraband-providers (or end-users, if you want to go full nazi) is up to the situational commanding officers. - -
              -

              Nodrak updated:

              -
                -
              • Merged 'Game' and 'Lobby' tabs during pre-game into one tab
              • -
              • Added the little red x to the late-join job list
              • -
              • Late-joiners are warned if the shuttle is past the point of recall, and if the shuttle has already left the station
              • -
              • Late-joiners now see how long the round has been going on.
              • -
              • Mining shuttle computer no longer spits out both 'Shuttle has been sent' and 'The shuttle is already moving' every time.
              • -
              - -
              - -
              -

              Monday, April 9th

              -

              Petethegoat updated:

              -
                -
              • TORE OUT DETECTIVE WORK! THIS IS A TEMPORARY PATCH TO SEE IF THIS FIXES THE CRASHING.
              • -
              • DETECTIVE SCANNERS AND EVIDENCE BAGS (AND FINGERPRINTS) ARE GONE.
              • -
              -
              - -
              -

              Sunday, April 8th

              -

              PolymorphBlue updated:

              -
                -
              • Secret little rooms now spawn on the mining asteroid, containing various artifacts.
              • -
              • Added the beginnings of a borg upgrade system. Currently, can be used to reset a borg's module.
              • -
              -
              - -
              -

              2 April 2012

              -

              PolymorphBlue updated:

              -
                -
              • ERP is gone. Hope you enjoyed it while it lasted!
              • -
              -
              - -
              -

              April Fools Day! Get hype!

              -

              Doohl updated:

              -
                -
              • Security officers can modify people's criminal status by simply examining them with a security hud on and clicking a link that will show up as part of the character's description.
              • -
              • Less jobs have maintenance access. The only jobs that will have it now are engineers, atmos techs, cargo techs, heads, and the detective.
              • -
              • Changed Runtime's sprite to look more catlike.
              • -
              • View Variables can now better list associative lists.
              • -
              • Miscellaneous bugfixes for the NT Script IDE.
              • -
              -

              PolymorphBlue updated:

              -
                -
              • Minor bugfixes to borg deathsquad, adds borg deathsquad to potential tensioner (set so high it's never going to happen)
              • -
              • Adds consiterable support for ERP! (If enabled.)
              • -
              • Increases cost for changeling unstun to 45
              • -
              -
              - -
              -

              30 March 2012

              -

              Donkie updated:

              -
                -
              • You can now stick papers back in to paperbins, text will persist.
              • -
              • Added a [field] bbcode tag to the pen writing. Lets your start writing from that point.
              • -
              • Changed fonts a bit for papers to make [sign] stand out more.
              • -
              -

              Doohl updated:

              -
                -
              • Gave pill bottles the ability to scoop up pills like ore satchels scoop ore. (There you go, /vg/ Anon.)
              • -
              • Security Officers and Wardens now start with maintenance acceess.
              • -
              -
              - -
              -

              29 March 2012

              -

              PolymorphBlue updated:

              -
                -
              • Exosuits now provide a message when someone is getting in, someone getting in must remain stationary and unstunned, and getting in takes four seconds.
              • -
              -
              - -
              -

              28 March 2012

              -

              Carn updated:

              -
                -
              • Fixed turrets shooting people that leave the area and the telecomm turret controls.
              • -
              -

              Donkie updated:

              -
                -
              • Updated air alarm's GUI.
              • -
              -
              - -
              -

              27 March 2012

              -

              Nodrak updated:

              -
                -
              • Security borgs now have modified tasers.
              • -
              • Security borgs have gone back to having the same movement speed as all other borgs.
              • -
              -
              - -
              -

              23 March 2012

              -

              Doohl updated:

              -
                -
              • Escape shuttles/pods now spend about 2 minutes in high-speed transit before they reach centcom/recon shuttle. This is a warning: regular after-round shuttle grief is NOT OKAY while the shuttle is still in transit! Save it for when the shuttle gets to centcom! The purpose of this is to give potential antagonists and protagonists a chance to have a final showdown in the shuttle. The round does not end until the shutle comes to a stop and docks. Don't step outside while the shuttle is moving!

                For example; if you are a traitor and have an escape-alone objective and a couple of people manage to squeeze in the shuttle, you have two minutes to kill/toss them out to win. Or you can just chill for the duration and reflect on the round.
              • -
              - -

              Donkieyo updated:

              -
                -
              • A bunch new standard-namespace NTSL functions added! Check them out at the NT Script wiki page!
              • -
              -
              - -
              -

              22 March 2012

              -

              Ricotez updated:

              -
                -
              • Medical Lockers, Security Lockers, Research Lockers, Warden Locker, CMO Locker, and RD locker all have new sprites.
              • -
              • Encryption keys now each have their own invidual sprites.
              • -
              - -

              PolymorphBlue updated:

              -
                -
              • Added a prototype holodeck to fitness!
              • -
              • Assorted tensioner fixes
              • -
              - -
              -

              20 March 2012

              -

              Kor updated:

              -
                -
              • Lasertag vests and guns have been added to fitness.
              • -
              • Art storage has replaced the emergency storage near arrivals. Emergency storage has replaced chem storage (has anyone ever used that?)
              • -
              • Wraiths can now see in the dark
              • -
              -
              - - -
              -

              19 March 2012

              -

              PolymorphBlue updated:

              -
                -
              • Added LSD sting to modular changeling by popular demand.
              • -
              • Silence sting no longer provides a message to the victim.
              • -
              • Tensioner will no longer assign dead people as assassination targets.
              • -
              -
              - -
              -

              18 March 2012

              -

              Quarxink updated:

              -
                -
              • The medical record computers can finally search for DNA and not just name and ID.
              • -
              -
              - -
              -

              14 March 2012

              -

              PolymorphBlue updated:

              -
                -
              • Modular changeling added! Changelings now purchase the powers they want. Balancing still underway, but should be playable.
              • -
              -

              Petethegoat updated:

              -
                -
              • Janitor cyborgs have been massively upgraded. Suffice to say they're pretty ballin' now...
              • -
              -

              Nodrak updated:

              -
                -
              • You can now choose whether to spawn with a backpack, satchel, or nothing. Excess items will spawn in your hands if necessary.
              • -
              • You can now choose what kind of underwear you'd like to wear, per a request.
              • -
              -
              - -
              -

              14 March 2012

              -

              Carn updated:

              -
                -
              • Added 6 female hairstyles -- credits go to Erthilo of Baystation. Added a male hairstyle -- credits go to WJohnston of TG. If you can sprite some unique and decent-looking hair sprites, feel free to PM me on the TG forums.
              • -
              -
                -
              • The way objects appear to be splattered with blood has been rewritten in an effort to fix stupid things happening with icons. It should be called far less frequently now. PLEASE, if you experience crashes that could in anyway be related to blood, report them with DETAILED information. Thanks
              • -
              -
              - -
              -

              13 March 2012

              -

              Nodrak & Carn updated:

              -
                -
              • Fixed the way flashes break. Long story short: They'll never break on first use so rev don't get screwed over. They run out of charge temporarily when spammed but recharge. Spamming them also increases the chance of them breaking a little, so use them sparingly.
              • -
              -

              Doohl updated:

              -
                -
              • Ablative Armor now has a high chance of reflecting energy-based projectiles.
              • -
              • Riot shields were buffed; they now block more attacks and they will prevent their wielder from being pushed (most of the time).
              • -
              -
              - -
              -

              12 March 2012

              -

              PolymorphBlue updated:

              -
                -
              • PDA messages now require an active messaging server to be properly sent.
              • -
              -
              - -
              -

              11 March 2012

              -

              PolymorphBlue updated:

              -
                -
              • The AI can now open doors with shift+click, bolt them with ctrl+click, and shock them with alt+click
              • -
              • Tratior borgs who hack themselves cannot be blown by the robotics console, and can override lockdowns.
              • -
              • Adds a new wire to doors that controls the time delay before they close. If pulsed, they close like a sliding glass door. If cut, they do not close by themselves.
              • -
              • Borgs who have died, ghosts, and are then blown up will now have their ghosts properly transfered to their dropped MMIs.
              • -
              -

              Carnwennan updated:

              -
                -
              • You can now request AI presence at a holopad for immediate private communication with the AI anywhere. AIs can click a quick button to zoom to the holopad.
              • -
              -
              - -
              -

              08 March 2012

              -

              Nodrak and Carnwennan updated:

              -
                -
              • Nodrak: Fixed crayon boxes and stuff getting stuck in pockets.
              • -
              • Nodrak: 'Steal item' objectives will report correctly when wrapped up in paper now.
              • -
              • Carn: fixed the vent in the freezer...poor chef kept suffocating.
              • -
              -
              - -
              -

              02 March 2012

              -

              Carn updated:

              -
                -
              • Fixed a number of issues with mob examining. Including: not being able to see burns unless they were bruised; vast amounts of grammar; and icons. Updated them to use stylesheet classes.
              • -
              • Borgs can no-longer drop their module items on conveyor belts.
              • -
              • Names input into the setup screen are now lower-cased and then have their first letters capitalised. This is to fix problems with BYOND's text-parsing system.
              • -
              • Runtime fix for lighting.
              • -
              • Over the next few commits I will be updating a tonne of item names to fix text-parsing. Please inform me if I've typoed anything.
              • -
              -
              - -
              -

              03 March 2012

              -

              Petethegoat updated:

              -
                -
              • Removed cloakers. Removed Security's thermals. Added disguised thermals as a traitor item.
              • -
              -
              - -
              -

              01 March 2012

              -

              SkyMarshal updated:

              -
                -
              • Tweak/Bugfix for Hallucinations. Much more robust.
              • -
              -
              - -
              -

              01 March 2012

              -

              SkyMarshal updated:

              -
                -
              • Ported BS12 Detective Work System
              • -
              -
              - -
              -

              1 March 2012

              -

              Petethegoat updated:

              -
                -
              • Head revolutionaries no longer spawn with traitor uplinks.
              • -
                  -
                -
              - -
              -

              29 February 2012

              -

              SkyMarshal updated:

              -
                -
              • BS12 Hallucination and Dreaming port
              • -
                  -
                -
              - - -
              -

              29 February 2012

              -

              muskets updated:

              -
                -
              • Integrated BS12's improved uplink code
              • -
              -
              - - -
              -

              26 February 2012

              -

              Doohl updated:

              -
                -
              • The insane crashing has finally been fixed!
              • -
              -
              - -
              -

              25 February 2012

              -

              Doohl updated:

              -
                -
              • Telecommunications has been refined, with many new features and modules implemented.
              • -
              • NTSL (Nanotrasen Scripting Language) is ONLINE! This is a brand new, fully operational scripting language embedded within SS13 itself. The intended purpose is to eventually expand this scripting language to Robotics and possibly other jobs, but for now you may play with the TCS (Traffic Control Systems) implementation of NTSL in the Telecommunications Satellite. Recommended you read the NT Script wiki page for information on how to use the language itself. Other than that, there's not a lot of documentation.
              • -
              • Radio systems have been further optimized, bugfixed, etc. Should be more stable.
              • -
              • Intercoms now require power to work.
              • -
              -
              - - -
              -

              24 February 2012

              -

              PolymorphBlue updated:

              -
                -
              • Headsets are now modular! Use a screwdriver on them to pop out their encrpytion keys, and use a key on one to put it in. A headset can hold two keys. Normal headsets start with 1 key, department headsets with two. The standard chip does nothing, and is not required for listening to the common radio.
              • -
              • Binary translators made into a encrpytion key, and fixed. They now broadcast to AIs properly.
              • -
              -
              - -
              -

              23 February 2012

              -

              PolymorphBlue updated:

              -
                -
              • MMIs/pAIs no longer lip read, and thus can now hear in the dark.
              • -
              • Borg rechargers are no longer Faraday cages, and thus can now receive radio while they're recharging.
              • -
              -

              LastyScratch updated:

              -
                -
              • Glass airlocks now make a different sound than regular airlocks.
              • -
              • in 1997 nanotrasen's first AI malfunctioned
              • -
              • Toggle ambience probably works now! -
              • Runtime is dead. -
              • The Research Director's consoles were moved into the completely empty cage in the back of his office.
              • -
              -
              - - -
              -

              22 February 2012

              -

              PolymorphBlue updated:

              -
                -
              • Changed alt+click to ctrl+click for pulling.
              • -
              -

              Petethegoat updated:

              -
                -
              • New stationary scrubber sprites~
              • -
              • Removed the mint. Coins can still be found and used in vending machines. PACMANs now run off sheets.
              • -
              -

              coolity updated:

              -
                -
              • New sprites for HoS and Captain lockers.
              • -
              • New sprites for the orebox.
              • -
              -
              - -
              -

              21 February 2012

              -

              Petethegoat updated:

              -
                -
              • The jetpacks now display correctly when worn.
              • -
              • Buckling to chairs no longer causes you to drop your weapon
              • -
              -

              Nodrak updated:

              -
                -
              • Ghosts now have a "Jump to Mob" verb.
              • -
              -

              Sieve updated:

              -
                -
              • Mining lanterns work properly once again!.
              • -

                Skaer updated:

                -
                  -
                • The armoury now includes a box of spare Sec cartridges.
                • -
                -
              - -
              -

              19 February 2012

              -

              Petethegoat updated:

              -
                -
              • The jetpacks in EVA have been replaced with CO2 ones, painted a classy black.
              • -
              • Additionally, jetpacks will now run on gases other than oxygen, as you would expect.
              • -
              • Chair overhaul! You shouldn't notice anything different, but if you encounter bugs with chairs or beds, please report those asap.
              • -
              • New electric chair sprites, by myself.
              • -
              • Electric chairs will only electrocute people buckled into them.
              • -
              • Karma should be fixed.
              • -
              -

              KorPhaeron updated:

              -
                -
              • A new construct type: Artificer. It is capable of constructing defenses, repairing fellow constructs, and summoning raw materials to construct further constructs
              • -
              • Simple animals (constructs, Ian, etc) can now see their health in the Status tab
              • -
              • Detective's revolver is non-lethal again. Was fun while it lasted
              • -
              -
              - -
              -

              18 February 2012

              -

              Petethegoat updated:

              -
                -
              • Foam has a reduced range to prevent spamming
              • -
              -

              Sieve updated:

              -
                -
              • Stopped the unholy Radium/Uranium/Carbon smoke that crashed the server. And for anyone that did this, you are a horrible person
              • -
              • Cleanbots clean dirt
              • -
              • Cleanbots automatically patrol on construction
              • -
              • Removed silicate because it is not useful enough for how much lag it caused
              • -
              -
              - -
              -

              16 February 2012

              -

              Smoke Carter updated:

              -
                -
              • Newscasters now alert people of new feeds and wanted-alerts simultaneously.
              • -
              -
              - -
              -

              15 February 2012

              -

              Kor updated:

              -
                -
              • Terrorists Win! Desert Eagles and Riot Shields now spawn on the syndicate shuttle, replacing the c20r
              • -
              • The Detectives gun still uses .38, but they're now fully lethal bullets. Go ahead, make his day.
              • -
              • The Veil Render has been nerfed, the Nar-Sie it spawns will not pull anchored objects. This is a temporary measure, more nerfs/reworking to come
              • -
              -
              - -
              -

              14 February 2012

              -

              Carn updated:

              -
                -
              • Spacevines added to the random events.
              • -
              • The bug where doors kept opening when a borg tried to close them at close range is now fixed.
              • -
              -
              - -
              -

              13 February 2012

              -

              Khodoque updated:

              -
                -
              • Security officers, the warden and the HoS have new jumpsuits.
              • -
              -

              Erro updated:

              -
                -
              • Clicking the internals button on your user interface (The one that shows if you have internals on or not) will now toggle internals even if they are in your pockets. (humans only) - It now works if your internals are on your back, suit storage, belt, hands and pockets.
              • -
              • The public autolathe has been removed. If you want some some stuff from a lathe, go to cargo.
              • -
              -

              Kor updated:

              -
                -
              • A new item, the null rod, protects the one bearing it from cult magic. One starts in the chaplains office, and this replaces the job based immunity he had. The null rod also is capable of dispelling runes upon hitting them (the bible can no longer do this)
              • -
              • Shooting fuel tanks with lasers or bullets now causes them to explode
              • -
              • A construct shell is now waiting to be found in space.
              • -
              • Chaplains can no longer self heal with the bible
              • -
              • Simple animals, including constructs, can now attack mechs and critters
              • -
              - -
              - -
              -

              12 February 2012

              -

              Erro updated:

              -
                -
              • You can no longer attach photos to ID cards. This never worked properly and if anything, it was misleading.
              • -
              • Backpacks can now hold 7 normal sized items (box size) as opposed to 6 normal sized items + 1 small item
              • -
              • Added several fire alarms to areas around the station including the brig, engineering and others
              • -
              • The atmospherics department now has a few hazard vests available for atmos techs to wear if they don't like the fire suit
              • -
              • Roboticist now have engineering + science headsets, virologists now have medsci headsets with medical + science channels
              • -
              • Added some headsets to the jobs that didn't have any extras: roboticist, qm, scientist, virologist and geneticist.
              • -
              • Station engineers now have construction site access (vacent office by arrivals)
              • -
              • Replaced a few airlocks with glass airlocks (detective, autolathe, assistant storage, robotics, checkpoint)
              • -
              • Removed the wall that was blocking the entrance to the theater
              • -
              • Made a small redesign for the HoP's office so that people running towards it from the escape hallway don't run right into the queue, annoying everyong
              • -
              • The engineering, command and security airlocks now glow green when closing instead of red to match all the other airlocks
              • -
              • The disposal units now auto trigger every 30 game ticks, if there is something (or someone) in them. So no more hiding in disposal units!
              • -
              • You can no longer control the disposal unit from within it. You will have to wait for it to trigger itself.
              • -
              • You can no longer strip items off of Ian while dead / a ghost
              • -
              -

              Pete updated:

              -
                -
              • Updated fitness, athletic shorts are now available!
              • -
              -
              - - -
              -

              11 February 2012

              -

              Erro updated:

              -
                -
              • You can now take individual crayons out of the crayon box the same way as from boxes
              • -
              • Clicking a grille with a glass or reinforced glass pane in your hand will glaze the grille from the direction you're looking from (don't forget to fasten the window tho)
              • -
              • When you click somewhere with the intent to interact, you will automaticaly face the item you're trying to interact with. This won't slow you down when running and firing guns behind you.
              • -
              -

              Kor updated:

              -
                -
              • A new passive mob ability: Relentless. Relentless mobs cannot be shoved (though may still swap places with help intent)
              • -
              • Alien Queens, Juggernaut constructs, and Medical Borgs are all Relentless. Maybe the medborg can actually drag people to medbay on time now
              • -
              • Two constructs, the Juggernaut and the Wraith are now available for wizards and cultists to use soul stones with
              • -
              • A new highly destructive artefact, Veil Render, is now available for wizards
              • -
              • A new one time use global spell, Summon Guns, is now available for wizards.
              • -
              • DEEPSTRIKING! There is now a partially constructed teleporter on the nuke shuttle, and for a large sum of telecrystals they may purchase the circuitboard needed to complete it.
              • -
              • The Chaplain is immune to cult stun, blind, deafen, and blood boil
              • -
              -
              - -
              -

              10 February 2012

              -

              Quarxink updated:

              -
                -
              • Added a new toy: Water balloons. They can be filled with any reagent and when thrown apply the reagents to the tile and everything on it.
              • -
              -
              - -
              -

              9 February 2012

              -

              Erro updated:

              -
                -
              • Engineering and security lockers now spawn with their respective backpacks in them so job-changers can look as they should. HoS locker now also contains an armored vest, for the convenience of the HoS who wants to play with one.
              • -
              • Slightly changed the spawn order of items in the CE and HoS lockers to make starting up a hint less tedious.
              • -
              -
              - -
              -

              8 February 2012

              -

              ConstantA updated:

              -
                -
              • Added Exosuit Jetpack
              • -
              • Added Exosuit Nuclear Reactor (runs of normal, everyday uranium, maybe I'll switch it to run on enriched) - requires research (level 3 in Materials, Power Manipulation and Engineering)
              • -
              • Added Ripley construction steps sprites (courtesy of WJohnston - man, you're awesome)
              • -
              • Exosuit Sleeper can now inject occupant with reagents taken from Syringe Gun
              • -
              • Exosuit Cable Layer will now auto-dismantle floors
              • -
              • Exosuit Heavy Lazer cooldown increased, Scattershot now fires medium calibre ammo (less damage)
              • -
              • Exosuit wreckage can be pulled
              • -
              • EMP now drains half of current exosuit cell charge, not half of maximum charge.
              • -
              • Fixed several possible exosuit equipment runtimes
              • -
              • Introduced new markup to changelog. Javascript is extremely slow (in byond embedded browser) for some reason.
              • -
              -
              -
              -

              4 February 2012, World Cancer Day

              -

              Erro updated:

              -
                -
              • Examining humans now works a bit differently. Some external suits and helmets can hide certain pieces of clothing so you don't see them when examining. Glasses are also now displayed when examining.
              • -
              • The job selection screen has been changed a little to hopefully make making changes there easier.
              • -
              -
              -31 January 2012 -
                -
              • Carn updated: -
                  -
                • Grammar & various bug-fixes
                • -
                • Thank-you to everyone who reported spelling/grammar mistakes. I'm still working on it, so if you spot anymore please leave a comment here. There's still lots to fix.
                • -
                • Mining station areas should no longer lose air.
                • -
                -
              - - -30 January 2012( -
                -
              • Sieve updated: -
                  -
                • This stuff is actually already implemented, it just didn't make it to the changelog
                • -
                • Firefighter Mech - A reinforced Ripley that is more resistant to better cope with fires, simply look in the Ripley Contruction manual for instructions.
                • -
                • Mech contruction now has sounds for each step, not just 1/4 of them.
                • -
                • Mech Fabricators are fixed, Manual Sync now works and certain reseach will reduce the time needed to build components.
                • -
                • Added special flaps to the mining station that disallow air-flow, removing the need to shuffle Ore Boxes through the Airlocks.
                • -
                • Each outpost has it's own system for the conveyors so they won't interfere with each other.
                • -
                • Powercell chargers have been buffed so now higher capacity cells are actually useable.
                • -
                • A diamond mech drill has been added. While it isn't any stronger than the standard drill, it is much faster.
                • - -
                -
              - -29 January 2012, got Comp Arch exams on Wednesday :( -
                -
              • Agouri updated: -
                  -
                • UPDATE ON THE UPDATE: Newspapers are now fully working, sorry for that. Some minor icon bugs fixed. Now I'm free to work on the contest prizes :3
                • -
                • Newscasters are now LIVE! Bug reports, suggestions for extra uses, tears etc go here.
                • -
                • What ARE newscasters? Fans of the Transmetropolitan series might find them familiar. Basically, they're terminals connected to a station-wide news network. Users are able to submit channels of their own (one per identified user, with channels allowing feed stories by other people or, if you want the channel to be your very own SpaceJournal, being submit-locked to you), while others are able to read the channels, either through the terminals or a printed newspaper which contains every news-story circulating at the time of printing.
                • -
                • About censorship: You can censor channels and feed stories through Security casters, found in the HoS'es office and the Bridge. Alternatively, if you want a channel to stop operating completely, you can mark it with a D-Notice which will freeze it and make all its messages unreadable for the duration it is in effect. If you've got the access, of course.
                • -
                • Basically I think of the newscaster as nothing more as an additional Roleplaying tool. Grab a newspaper along with your donuts and coffee from the machines, read station rumors when you're manning your desk, be a station adventurer or journalist with your very own network journal!
                • -
                • I would ask for a bit of respect when using the machine, though. I removed all and any channel and story restrictions regarding content, so you might end up seeing channels that violate the rules, Report those to the admins.
                • -
                • Finally, due to the removal of the enforced "Channel" string, it's recommended to name your channels properly ("Station Paranormal Activity Channel" instead of "Station Paranormal Activity", for example") - -
                -
              - -28 January 2012 -
                -
              • BubbleWrap updated: -
                  -
                • Arresting buff!
                • -
                • A person in handcuffs being pulled cannot be bumped out of the way, nor can the person pulling them. They can still push through a crowd (they get bumped back to behind the person being pulled, or pushed ahead depending on intent).
                • -
                -
              - -27 January 2012 -
                -
              • LastyScratch updated: -
                  -
                • Toggle-Ambience now works properly and has been moved from the OOC tab to the Special Verbs tab to be with all the other toggles.
                • -
                -
              • RavingManiac updated: -
                  -
                • The bar now has a "stage" area for performances.
                • -
                -
              • Blaank updated: -
                  -
                • Added a vending machine to atmopherics reception desk that dispenses large -oxygen tanks, plasma tanks, emergency oxegen tanks, extended capacity emergency -oxygen tanks, and breath masks.
                • -
                -
              • Petethegoat updated (for a bunch of other people): -
                  -
                • Lattice is now removed when you create plating or floor (credit Donkieyo).
                • -
                • Monkeys now take damage while in crit (credit Nodrak).
                • -
                • The warden now has his own jacket. (credit Shiftyeyesshady).
                • -
                • Spectacular new dice that will display the proper side when rolled!! (credit TedJustice)
                • -
                • Spectacular new dice that will display the proper side when rolled!! (credit TedJustice)
                • - -
                • Borg RCDs can no longer take down R-walls. (headcoder orders)
                -
              - -19 January 2012 -
                -
              • Petethegoat updated: -
                  -
                • Exciting new pen additions! Get the low-down at the wiki.
                • -
                -
              - -17 January 2012 -
                -
              • Doohl updated: -
                  -
                • Syndicate shuttle now starts with a All-In-One telecommunication machine, which acts as a mini-network for the syndie channel. It intercepts all station radio activity, too, how cool is that?
                • -
                -
              - -15 January 2012 -
                -
              • Doohl updated: -
                  -
                • The radio overhaul 'Telecommunications' is now LIVE. Please submit any opinions/feedback in the forums and check the wiki article on Telecommunications for some more info for the curious.
                • -
                • The AI satellite has been replaced with a communications satellite. You can get there via teleporter or space, just like the AI satellite. I highly recommend not bum-rushing the new satellite, as you may be killed if you don't have access. It's a very secure place.
                • -
                • Once a human's toxicity level reaches a certain point, they begin throwing up. This is a natural, but overall ineffective method of purging toxins from the body.
                • -
                • You can now travel Z-levels in Nuclear Emergency mode (the nuke disk is still bound to the station). This means the nuclear agents can and probably will fly off into space to blow up the comm satellite and shut down communications.
                • -
                -
              - -9 January 2012 -
                -
              • ConstantA updated: -
                  -
                • Reworked exosuit internal atmospherics (the situation when exosuit is set to take air from internal tank, otherwise cabin air = location air): -
                    -
                  • If current cabin presure is lower than &quot;tank output pressure&quot;, the air will be taken from internal tank (if possible), to equalize cabin pressure to &quot;tank output pressure&quot;
                  • -
                  • If current cabin presure is higher than &quot;tank output pressure&quot;, the air will be siphoned from cabin to location until cabin pressure is equal to &quot;tank output pressure&quot;
                  • -
                  • Tank air is not altered in any way even if it's overheated or overpressured - connect exosuit to atmos connector port to vent it
                  • -
                  • &quot;Tank output pressure&quot; can be set through Maintenance window - Initiate maintenance protocol to get the option
                  • -
                  -
                • -
                • Fixed bug that prevented exosuit tank air updates if exosuit was connected to connector port
                • -
                • Combat exosuits melee won't gib dead mobs anymore
                • -
                • QM exosuit circuit crates cost lowered to 30 points
                • -
                • Exosuit plasma converter effectiveness +50%
                • -
                -
              - - -8 January 2012 -
                -
              • Agouri updated: -
                  -
                • I'm back home and resumed work on Newscasters and Contraband.
                • -
                • But I got bored and made cargo softcaps instead. Flippable! Enjoy, now all we need is deliverable pizzas.
                • -
                • Oh, also enjoy some new bodybag functionality and sounds I had ready a while ago, with sprites from Farart. Use a pen to create a visible tag on the bodybag. Wirecutters to cut it off. Also it's no longer weldable because it makes no goddamn sense. -
                -
              - -7 January 2012 -
                -
              • Donkieyo updated: -
                  -
                • You must now repair damaged plating with a welder before placing a floor tile.
                • -
                • You can now relabel canisters if they're under 1kPa.
                • -
                -
              • Polymorph updated: -
                  -
                • Dragging your PDA onto your person from your inventory will bring up the PDA screen.
                • -
                • You can now send emergancy messages to Centcomm (Or, with some.. tampering, the Syndicate.) via a comms console. (This occurs in much the fashion as a prayer.)
                • -
                -
              -3 January 2012 -
                -
              • Erro updated: -
                  -
                • Shift-clicking will now examine whatever you clicked on!
                • -
                -
              • Polymorph updated: -
                  -
                • Alt-clicking will now pull whatever you clicked on!
                • -
                -
              - -1 January 2012 (12 more months until doomsday) -
                -
              • Doohl updated: -
                  -
                • XENOS ARE NOW IMMUNE TO STUNNING! To compensate, stunning via tasers/batons now slows them down significantly.
                • -
                - -
              • Polymorph updated: -
                  -
                • Doors no longer close if they have a mob in the tile. (Generally!) Door safties can now be overriden to close a door with a mob in the tile and injure them severely.
                • -
                - -
              - -29 December 2011 -
                -
              • ConstantA updated: -
                  -
                • Added some new Odysseus parts and tweaked old ones.
                • -
                • Added Exosuit Syringe Gun Module
                • -
                • New Odysseus sprites - courtesy of Veyveyr
                • -
                -
              • Polymorph updated: -
                  -
                • Air Alarms can now be hacked.
                • -
                • Too much of a good thing is just as bad as too little. Pressures over 3000 kPa will do brute damage.
                • -
                -
              - -28 December 2011 -
                -
              • RavingManiac updated: -
                  -
                • Wrapped objects can now be labelled with a pen
                • -
                • Wrapped small packages can be picked up, and are now opened by being used on themselves
                • -
                • Mail office remapped such that packages flushed down disposals end up on a special table
                • -
                • Package wrappers placed in most of the station departments
                • -
                • In short, you can now mail things to other departments by wrapping the object, labelling it with the desired destination using a pen, and flushing it down disposals. At the mail room, the cargo tech will then tag and send the package to the department.
                • -
                -
              - -27 December 2011 -
                -
              • Errorage updated: -
                  -
                • Engineering's been remapped
                • -
                -
              • RavingManiac updated: -
                  -
                • Refrigerators and freezer crates will now preserve meat
                • -
                -
              - -25 December 2011 -
                -
              • ConstantA updated: -
                  -
                • Circuit boards for Odysseus mech can be ordered by QM
                • -
                • Designs for them were added to R&amp;D
                • -
                -
              • -
              • Kor updated: -
                  -
                • Soul Stones Added: Like intellicards for dead or dying humans! Full details are too long for the changelog
                • -
                • A belt full of six soul stones is available as an artefact for the wizard
                • -
                • Cultists can buy soulstones with their supply talisman
                • -
                • The chaplain has a single soulstone on his desk
                • -
                • The reactive teleport armour's test run is over. It no longer spawns in the RD's office.
                • -
                -
              • -
              - - - -24 December 2011 -
                -
              • Rockdtben updated: -
                  -
                • Added sprites for soda can in left and right hands on mob: sodawater, tonic, purple_can, ice_tea_can, energy_drink, thirteen_loko, space_mountain_wind, dr_gibb, starkist, space-up, and lemon-lime.
                • -
                -
              • - -
              - -23 December 2011 -
                -
              • ConstantA updated: -
                  -
                • Mech Fabricators now require robotics ID to operate. Emag removes this restriction.
                • -
                • Added Odysseus Medical Exosuit. Has integrated Medical Hud and ability to mount medical modules.
                • -
                • Added Sleeper Medical module for exosuits. Similar to common sleepers, but no ability to inject reagents.
                • -
                • Added Cable Layer module for exosuits. Load with cable (attack cable with it), activate, walk over dismantled floor.
                • -
                • Added another exosuit internal damage type - short circuit. Short-circuited exosuits will drain powercell charge and power relay won't work.
                • -
                • You should be able to send messages to exosuit operators using Exosuit Control Console
                • -
                • Gygax armour and module capacity nerfed.
                • -
                • Exosuit weapon recharge time raised.
                • -
                • Bugfix: EMP actually drains exosuit cell and damages it
                • -
                -
              • -
              • RavingManiac updated: -
                  -
                • Meat will now spoil within three minutes at temperatures between 0C and 100C.
                • -
                • Rotten meat has the same nutritional value as normal meat, and can be used in -the same recipes. However, it is toxic, and ingesting a badly-prepared big bite -burger can kill you.
                • -
                • Because refrigeration serves a purpose now, the kitchen cold room freezing unit -is turned off by default. Chefs should remember to turn the freezer on at the -start of their shift.
                • -
                -
              • -
              - -21 December 2011 -
                -
              • RavingManiac updated: -
                  -
                • Kitchen cold room is now cooled by a freezing unit. Temperature is about 240K by default, but can be raised to room temperature or lowered to lethal coldness.
                • -
                -
              - -19 December 2011 -
                -
              • Kor updated: -
                  -
                • General/Misc Changes -
                    -
                  • Escape pods no longer go to the horrific gibbing chambers. Rather, they will be picked up by a salvage ship in deep space. (This basically changes nothing mechanics wise, just fluff)
                  • -
                  • An ion rifle now spawns on the nuclear operative shuttle. Maybe this will help with them getting destroyed by sec borgs every round?
                  • -
                  -
                • -
                • Wizard Changes -
                    -
                  • The wizard can now purchase magic artefacts in addition to spells in a subsection of the spellbook.
                  • -
                  • The first (and currently only) new artefact is the Staff of Change, which functions as a self recharging energy weapon with some special effects.
                  • -
                  • The wizard has a new alternative set of robes on his shuttle.
                  • - -
                  -
                • Cult Changes
                    -
                  • Cultists now each start with three words (join, blood, self). No more will you suffer at the hands of cultists who refuse to share words.
                  • -
                  • The starting supply talisman can now be used five times and can now be used to spawn armor and a blade.
                  • -
                  • Replaced the sprites on the cultist robes/hood.
                  • -
                • -
                -
              - -18 December 2011 -
                -
              • Carnwennan updated: -
                  -
                • Thanks to the wonders of modern technology and the Nanotrasen steel press Ian's head has been shaped to fit even more silly hats. The taxpayers will be pleased.
                • -
                -
              • -
              • Doohl updated: -
                  -
                • Vending machines got yet another overhaul! Good lord, when will they stop assfucking those damned vendors??
                • -
                -
              • -
              - -17 December 2011 -
                -
              • Erro updated: -
                  -
                • Your direct supervisors are now displayed when you are assigned a job at round start or late join.
                • -
                -
              • -
              - -14 December 2011 -
                -
              • Erro updated: -
                  -
                • Meteor mode is hopefully deadly again!
                • -
                -
              • -
              • Kor updated: -
                  -
                • Research director has a new toy: Reactive Teleport Armour. Click it in your hand to activate it and try it out!
                • -
                -
              • -
              - -11 December 2011 -
                -
              • NEO updated: -
                  -
                • AIs actually consume power from APCs now
                • -
                • Bigass malf overhaul. tl;dr no more AI sat, instead you have to play whackamole with APCs.
                • -
                -
              • -
              - -10 December 2011 -
                -
              • Doohl updated: -
                  -
                • Title music now plays in the pregame lobby. You can toggle this with a verb in "Special Verbs" if you really want to.
                • -
                • User Interface preferences now properly get transferred when you get cloned.
                • -
                -
              • -
              • Erro updated: -
                  -
                • Escape pods have been added to test the concept.
                • -
                • Escaping alone in a pod does not count towards the escape alone objective, it counts towards the escape alive objective tho. Escape alone only requires you to escape alone on the emergency shuttle, it doesn't require you to ensure all pods are empty. Cult members that escape on the pods do not ocunt towards the cult escaping acolyte number objective. Escaping on a pod is a valid way to survive meteor.
                • -
                -
              • -
              • Polymorph updated: -
                  -
                • Fire is now actually dangerous. Do not touch fire.
                • -
                -
              • - - -
              - -8 December 2011 -
                -
              • Errorage updated: -
                  -
                • Fixed the comms console locking up when you tried to change the alert level.
                • -
                • Added a keycard authentication device, which is used for high-security events. The idea behind it is the same as the two-key thing from submarine movies. You select the event you wish to trigger on one of the devices and then swipe your ID, if someone swipes their ID on one of the other devices within 2 seconds, the event is enacted. These devices are in each of the head's offices and all heads have the access level to confirm an event, it can also be added to cards at the HoP's ID computer. The only event that can currently be enacted is Red alert.
                • -
                -
              • -
              • Kor updated: -
                  -
                • The chef now has a fancy dinner mint in his kitchen. It is only wafer thin!
                • -
                -
              • -
              - -3 December 2011 -
                -
              • Pete & Erro Christmas update: -
                  -
                • Reinforced metal renamed to steel and steel floor tile renamed to metal floor tile to avoid confusion before it even happens.
                • -
                • It is no longer possible to make steel from metal or vice versa or from the autolathe. You can however make metal from the autolathe and still insert steel to increase the metal resource.
                • -
                • To make steel you can now use the mining smelting unit and smelt iron and plasma ore.
                • -
                • The RCD can no longer take down reinforced walls.
                • -
                -
              • -
              • Errorage updated: -
                  -
                • Grass plants in hydro now make grass floor tiles instead of the awkward patches.
                • -
                -
              • -
              • Petethegoat updated: -
                  -
                • Fixed all known vending machine issues.
                • -
                • Fixed a minor visual bug with emagged lockers.
                • -
                • Clarified some of the APC construction/deconstruction messages.
                • -
                -
              • -
              • Numbers updated: -
                  -
                • Potency variations tipped in favour of bigger changes over smaller periods of time.
                • -
                -
              • -
              • PolymorphBlue updated: -
                  -
                • Traitors in the escape shuttle's prison cell will now fail their objective.
                • -
                • Lockers are no longer soundproof! (or flashproof, for that matter)
                • -
                • Headrevs can no longer be borged, revs are dereved when borged. -
                -
              • -
              - -30 November 2011 -
                -
              • Vinyl Scratch updated: -
                  -
                • New ambient sounds for the station and AI Sat.
                • -
                • He's baaaaaack...
                • -
                -
              • -
              - -27 November 2011 -
                -
              • Kor updated: -
                  -
                • Changeling husks are now borgable again (though not clonable) and genome requirements were lowered
                • -
                • De-revved revolutionaries had their message clarified a bit. You remember the person who flashed you, and so can out ONE revhead
                • -
                • Light tubes/bulbs can now be created in the autolathe. Recycle those broken lights!
                • -
                -
              • -
              - -22 November 2011 -
                -
              • Doohl updated: -
                  -
                • The firing range now has a purpose. Go check it out; there's a few surprises!
                • -
                -
              • -
              - -19 November 2011 -
                -
              • Doohl updated: -
                  -
                • Toggling admin midis will now DISABLE THE CURRENT MIDI OH MY GOSH!
                • -
                -
              • -
              • Tobba updated: -
                  -
                • We're looking for feedback on the updated chem dispenser! It no longer dispenses beakers of the reagent, and instead places a variable amount of the reagent into the beaker of your choosing.
                • -
                -
              • -
              • Petethegoat updated: -
                  -
                • Diagonal movement is gone on account of them proving to be a bad idea in practice. A grand experiment nonetheless.
                • -
                -
              • -
              • Kor updated: -
                  -
                • The PALADIN lawset in the AI upload has been replaced with the corporate lawset
                • -
                -
              • -
              - -16 November 2011 -
                -
              • Tobba updated: -
                  -
                • Report any issues with the updated vending machines!
                • -
                -
              • -
              - -16 November 2011 -
                -
              • Petethegoat updated: -
                  -
                • Security, Engineer, Medical, and Janitor borgs no longer get a choice of skin. This is for purposes of quick recognition, and is the first part of a series of upcoming cyborg updates.
                • -
                -
              • -
              - -7 November 2011 -
                -
              • Kor updated: -
                  -
                • Repair bots (mechs) are now adminspawn only
                • -
                • Extra loyalty implants are now orderable via cargo bay (60 points for 4 implants)
                • -
                • Changeling regen stasis now takes two full minutes to use, but can be used while dead. Burning and gibbing are the only way to keep them dead now.
                • -
                -
              • -
              - -29 October 2011 -
                -
              • ConstantA updated: -
                  -
                • Added step and turn sounds for mechs
                • -
                • Added another mecha equipment - plasma converter. Works similar to portable generator. Uses solid plasma as fuel. Can be refueled either by clicking on it with plasma in hand, or directly from mecha - selecting it and clicking on plasma.
                • -
                • Added mecha laser cannon.
                • -
                • Added damage absorption for mechs. Different mechs have different absorption for different types of damage.
                • -
                • Metal foam now blocks air movement.
                • -
                -
              • -
              • Petethegoat updated: -
                  -
                • Fixed sticking C4 to containers.
                • -
                • Rearranged the armoury, and changed the medical treatment room in Sec to an interrogation room.
                • -
                • Mr Fixit has been powered off and returned to the armoury. Deploying him every round is still recommended!
                • -
                -
              • -
              - -29 October 2011 -
                -
              • Petethegoat updated: -
                  -
                • Stunglove overhaul: part one. Stun gloves are now made by wiring a pair of gloves, and then attaching a battery- this shows up on the object sprite, but not on your character. Stungloves use 2500 charge per stun! This means that some low capacity batteries will make useless stungloves. To get your old inconspicous gloves back, simply cut away the wire and battery. Note that insulated gloves lose their insulation when you wire them up! Yet to come: stungloves taking extra damage from shocked doors.
                • -
                • Removed sleepypens! Paralysis pens have been changed to look like normal pens instead of penlights, and have been slightly nerfed. They will paralyse for about fifteen seconds, and cause minor brain damage and dizziness. -
                • Uplink Implants now have five telecrystals instead of four. -
                -
              • -
              • Doohl updated: -
                  -
                • More hairs added and a very long beard.
                • -
                • Finally fixed the request console announcements going AMP AMP AMP all the time when you use punctuation.
                • -
                -
              • -
              - -27 October 2011 -
                -
              • Mport updated: -
                  -
                • New WIP TK system added. To activate your TK click the throw button with an empty hand. This will bring up a tkgrab item. Click on a non-anchored (currently) non mob Object to select it as your "focus". Once a focus is selected so long as you are in range of the focus you can now click somewhere to throw the focus at the target. To quit using TK just drop the tkgrab item.
                • -
                -
              • -
              - -21 October 2011, Tuesday: -
                -
              • Errorage updated: -
                  -
                • Old keyboard hotkey layout option available again! home, end, page down and page up now once again do what they did before by default. To use diagonal movement you will need to use your numpad with NUM LOCK enabled.
                  - The new list of hotkeys is as follows: (Valid as of 21.10.2011) -
                    -
                  • Numpad with Num Lock enabled = movement in wanted direction.
                  • -
                  • Numpad with Num Lock disabled = as it was before. movement north-south-east-west and throw, drop, swap hands, use item on itself.
                  • -
                  • Page up (also numpad 9 with num lock disabled) = swap hands
                  • -
                  • Page down (also numpad 3 with num lock disabled) = use item in hand on itself
                  • -
                  • home (also numpad 7 with num lock disabled) = drop
                  • -
                  • end (also numpad 1 with num lock disabled) = throw
                  • -
                  • CTRL + A = throw
                  • -
                  • CTRL + S = swap hands
                  • -
                  • CTRL + D = drop
                  • -
                  • CTRL + W = use item in hand on itself
                  • -
                  • Numpad divide (/) = throw
                  • -
                  • Numpad multiply (*) = swap hands
                  • -
                  • Numpad subtract (-) = drop
                  • -
                  • Numpad add (+) = use item in hand on itself
                  • -
                  - In short, use Num Lock to swap between the two layouts. -
                • -
                -
              • -
              - -18 October 2011, Tuesday: -
                -
              • Errorage updated: -
                  -
                • You can now move diagonally! To do so, use the numpad. The keybaord has been remapped to make this possible: -
                    -
                  • CTRL + A = throw
                  • -
                  • CTRL + S = swap hands
                  • -
                  • CTRL + D = drop
                  • -
                  • CTRL + W = use item in hand on itself
                  • -
                  • Numpad divide (/) = throw
                  • -
                  • Numpad multiply (*) = swap hands
                  • -
                  • Numpad subtract (-) = drop
                  • -
                  • Numpad add (+) = use item in hand on itself
                  • -
                  -
                • -
                -
              • -
              - -15 October 2011, White Cane Safety Day: -
                -
              • Agouri updated: -
                  -
                • There has been a tidal wave of bugfixes over the last 5 or so days. If you had previously tried something on the station, saw that it was bugged and never tried it again, chances are it got fixed. I don't want you to neglect using stuff because you think it was bugged, which it was at one point, but no longer is. Thanks, happy new semester to everyone~
                • -
                -
              • -
              • Errorage updated: -
                  -
                • When you're unconscious, paralyzed, sleeping, etc. you will still see the same blackness as always, but it will rarely flicker a bit to allow you to see a little of your surroundings.
                • -
                -
              • -
              • Doohl updated: -
                  -
                • New hairstyles! YOU never asked for this!
                • -
                -
              • -
              - -11 October 2011: -
                -
              • ConstantA updated: -
                  -
                • Added radios to exosuits. Setting can be found in 'Electronics' menu.
                • -
                • Exosuit maintenance can be initiated even if it's occupied. The pilot must permit maintenance through 'Permissions &amp; Logging' - 'Permit maintenance protocols'. For combat exosuits it's disabled by default. While in maintenance mode, exosuit can't move or use equipment.
                • -
                -
              • -
              - -8 October 2011: -
                -
              • Doohl updated: -
                  -
                • You can put things on trays and mass-transport them now. To put stuff on trays, simply pick up a tray with items underneath/on top of it and they'll be automatically carried on top of the tray. Be careful not to make a mess~!
                • -
                -
              • -
              • Mport updated: -
                  -
                • Telekenesis now only allows you to pick up objects.
                • -
                • Stun gloves ignore intent.
                • -
                • Moved the loyalty implants to the HoS' locker.
                • -
                • Job system redone, remember to setup your prefs.
                • -
                -
              • -
              - -2 October 2011: -
                -
              • Petethegoat updated: -
                  -
                • Pandemic recharge speed is affected by the number of different types of antibodies in the blood sample. For maximum efficiency, use blood samples with only a single type of antibody. (Blood samples with two types of antibodies will still let the Pandemic recharge slightly faster than it used to.)
                • -
                -
              • -
              • Errorage updated: -
                  -
                • Opening a storage item on your belt will now display the proper number of slots even if the number is different from 7.
                • -
                • Less ponies, gryphons, and Tohou.
                • -
                -
              • -
              - -1 October 2011: -
                -
              • Knognob Lungsparkle updated: -
                  -
                • Xenomorphic aliens can now shape resin membranes (organic windows basically).
                • -
                • The AI can no longer see runes. Instead, they will see blood splatters.
                • -
                -
              • -
              - -28 September 2011: -
                -
              • Rolan7 updated: -
                  -
                • New method for job assignment. Remember to review your preferences. Send all your hate and bug reports to me.
                • -
                -
              • -
              • Doohl updated: -
                  -
                • Putting someone inside a cloning machine's DNA scanner will notify the person that they are about to be cloned. This completely removes the necessity to announce over OOC for someone to get back in their body.
                • -
                -
              • -
              - -22 September 2011, OneWebDay: -
                -
              • Errorage updated: -
                  -
                • Added an additional 9 colors, into which you can color bedsheets, jumpsuits, gloves and shoes at the washing machine.
                • -
                • A new click proc will need to be live-tested. The testing will be announced via OOC and you will get a message when it happens. When testing is going on, the new click proc will be used. If testing is going on and you notice a bug, please report it via adminhelp. If you find yourself unable to perform an action, you can double click (By that I mean spam the shit out of clicking) to use the old proc, which is unchanged and will behave like you're used to. Standard roleplay rules apply during tests, they're not an excuse to murder eachother.
                • -
                -
              • -
              - -20 September 2011, 10 year anniversary of the declaration of the "war on terror": -
                -
              • Errorage updated: -
                  -
                • You can no longer clone people who suicided. I REPEAT! You can no longer clone people who have suicided! So use suiciding more carefully and only if you ACTUALLY want to get out of a round. You can normally clone people who succumbed tho, so don't worry about that.
                • -
                • Washing your hands in the sink will now only wash your hands and gloves. You can wash items if you have them in your hands. Both of these actions are no longer instant tho.
                • -
                -
              • -
              - -18 September 2011, World Water Monitoring Day: -
                -
              • Errorage updated: -
                  -
                • Added the most fun activity in your every-day life. Laundry. Check the dormitory. (Sprites by Hempuli)
                • -
                • You can now change the color of jumpsuits, shoes, gloves and bedsheets using the washing machine.
                • -
                • Some religions (currently Islam, Scientology and Atheism) set your chapel's symbols to the symbols of that religion.
                • -
                • A new old-style cabinet's been added to the detective's office. (Sprite by Hempuli)
                • -
                • Runtime the cat ran away!! And it gets even worse! Mr. Deempisi met a premature end during an excursion to the kitchen's freezer! -- I was ORDERED to do this... don't kill me! :(
                • -
                • Kudzu can now be spawned (Currently admin-only. Will test a bit, balance it properly and add it as a random event) (Original code and sprites donated by I Said No)
                • -
                -
              • -
              • Kor updated: -
                  -
                • Added purple goggles to chemistry (you're welcome Lasty)
                • -
                • Added a third MMI to robotics and monkey cubes to xenobio (dont fucking spam them in the halls)
                • -
                -
              • -
              • Mport updated: -
                  -
                • Turns out tasers and a few other weapons were slightly bugged when it came to checking the internal powercell. Tasers and such gained an extra shot.
                • -
                • Ion Rifle shots lowered to 5 per charge.
                • -
                -
              • -
              - -17 September 2011, Operation Market Garden remembrance day: -
                -
              • Errorage updated: -
                  -
                • You can now insert a coin into vending machines. Some machines (currently only the cigarette vending machine) have special items that you can only get to with a coin. No, hacking will not let you get the items, coin only.
                • -
                -
              • -
              - -14 September 2011: -
                -
              • Lasty updated: -
                  -
                • Runtime now actually spawns in medbay because nobody cared whether it is a tiny smugfaced espeon that explodes violently on death or a spacecat that presumably doesn't.
                • -
                • You can no longer put someone into a sleeper and erase them from existence by climbing into the same sleeper.
                • -
                • Players who haven't entered the game will no longer be able to hear administrators in deadchat.
                • -
                -
              • -
              • Pete updated: -
                  -
                • Added new sprites for the light tube and glasses boxes.
                • -
                • Fixed the bug where syndicate bundles would have an emergency O2 tank and breath mask.
                • -
                -
              • -
              • Mport, SECOND REMOVER OF SUNS updated: -
                  -
                • Singularity absorbtion explosion range lowered and is now dependent on singularity size.
                • -
                • Bag of Holding no longer instakills singularity, and the chance for bombs to destroy a singularity has been changed from 10% to 25%.
                • -
                • Removed THE SUN.
                • -
                • Damage and stun duration from shocked doors has been lowered to account for a larger amount of energy in the powernet.
                • -
                -
              • -
              -13 September 2011: -
                -
              • Errorage updated: -
                  -
                • Healing, attacking or 'gently tapping' Ian will now properly display the name of the attacker to all people.
                • -
                -
              • -
              • Vinyl Scratch updated: -
                  -
                • Runtime!
                • -
                • Mine ambience now fades out at the end so that not only is it less jarring when it ends, but it also loops properly.
                • -
                • Malfunctioning AI's runtime message now has a voice clip to go with it (courtesy of Rosen Ritter)
                • -
                • Every headset will now display the commands for their respective channels upon examine.
                • -
                -
              • -
              • Miss Phaeron updated: -
                  -
                • E-Swords now have a chance to deflect projectiles when active
                • -
                • Taser/Laser shots now drain 40 battery per use from a borg, as opposed to 20
                • -
                • EMPs do 60 damage to borgs now, up from 25. They also stun them momentarily and drain 1k charge
                • -
                • As part of a joint effort between Mport, Falazameer, and myself an Ion Rifle has been added to the armoury
                • -
                • The Mutate spell now gives Laser Vision and Hulk
                • -
                -
              • - -
              - - -11 September 2011: -
                -
              • Errorage updated: -
                  -
                • Ian can now be toolboxed.
                • -
                • Ian can now be healed with bruisepacks, unless he's already dead.
                • -
                • You can now walk over Ian when he's killed.
                • -
                • Ian will chase food, if you leave it on the floor. If he notices it and you pick it up, he'll chase you around, if you stay close enough.
                • -
                -
              • -
              - -10 September 2011: -
                -
              • Errorage updated: -
                  -
                • A new pet on the bridge.
                • -
                • The pet can now be buckled and will no longer escape from closed containers, such as closets or the cloning pods.
                • -
                • Vending machines and request consoles are the first to use the new in-built browser in the upper-right of the user interface. If feedback is positive on these, more machines will be added to this. Hoping that this will eventually reduce the number of popup micromanagement.
                • -
                -
              • -
              • Lasty updated: -
                  -
                • The collectible hats have been removed from the theatre, doomed to rot forever in the hat crates they spawned from. No longer shall you see racks full of "collectible hard hat"!
                • -
                -
              • -
              • TLE updated: -
                  -
                • You can now toggle the message for becoming a pAI on and off in your prefs.
                • -
                -
              • -
              • Mport updated: -
                  -
                • Synaptizine now once again helps you recover from being stunned, however it is now also slightly toxic and may cause a small amount of toxins damage for every tick that it is in your system.
                • -
                • Assembly updating!
                • -
                • Original blob is back, though it still has lava sprites.
                • -
                • The bug where you would spawn on the wizard shuttle for a second at the start of the round should no longer occur.
                • -
                -
              • -
              - -8 September 2011: -
                -
              • Lasty updated: -
                  -
                • Suicide has been changed to biting your tongue off instead of holding your breath, and examining someone who has comitted suicide will give you a message stating that their tongue is missing.
                • -
                • Chemsprayers are now large items instead of small, meaning they can no longer fit in your pocket.
                • -
                -
              • -
              - -6 September 2011 -
                -
              • Greek Rioter updated: -
                  -
                • Removed dumb/badly sprited drinks for barman. This is for you, people that love to play barman. Your job is now non-retarded again. EDIT: FIXED DRINK MIXING
                • -
                • More info here: rev2136
                • -
                -
              • -
              • Lasty updated: -
                  -
                • Fixed the karma exploit! Uhangi can rest in peace knowing his -87 karma ways were not his own.
                • -
                • Added new ambient sound for space and the mines.
                • -
                • Examining an oxygen tank will now tell you if it is about to run out of air. If it is, you will recieve a beep and a message, and if not, then you'll just get the default "this is an oxygentank!" message.
                • -
                -
              • -
              • Rageroro updated: -
                  -
                • Sleeper update! Sleepers can now only inject soporific, dermaline, bicaridine, and dexaline into people with 1% or more health. They can also inject inaprovaline into people with -100% or more health. Nothing can be injected into people who are dead.
                • -
                -
              • -
              • Superxpdude updated: -
                  -
                • Virology is now part of medbay, and as such the RD no longer has Virology access and the CMO and Virologist no longer have Research access.
                • -
                -
              • -
              • Uhangi updated: -
                  -
                • Electropacks, screwdrivers, headsets, radio signalers, and station bounced radios can now be constructed from the autolathe.
                • -
                • Added a courtroom to Centcom.
                • -
                • Fixed electropack bug regarding using screwdrivers on electropacks.
                • -
                -
              • -
              • Microwave updated: -
                  -
                • Nuka Cola re-added.
                • -
                -
              • -
              • Urist McDorf updated: - -
              • -
              • Cheridan updated: -
                  -
                • New sprites for beds and air, plasma and nitrogen tanks (The ones found in maintenance shafts).
                • -
                -
              • -
              -5 September 2011. -
                -
              • Rageroro updated: -
                  -
                • Updates made to the HoP's ID computer. New interface and removing a card will now put it directly in your hand, if it's empty. Using your card on the computer will place it in the appropriate spot. If it has access to edit cards it will put it as the authentication card, otherwise as the card to be modified. If you have two cards with ID computer access first insert the authentication card, then the one you wish to midify, or do it manually like before.
                • -
                • Placed extra fire suits in maintenance (Near eva and mining) and an extra fire closet to atmos.
                • -
                -
              • -
              - -4 September 2011. -
                -
              • Urist McDorf updated: -
                  -
                • People who have been infected by facehuggers can no longer suicide.
                • -
                • Stammering has been reworked.
                • -
                • The chaplain can now no longer discern what ghosts are saying, instead receiving flavour text indicating that ghosts are speaking to him.
                • -
                • Walking Mushroom yield decreased from 4 to 1.
                • -
                • Glowshrooms now only spread on asteroid tiles.
                • -
                • Fixed rev round end message reporting everyone as dead.
                • -
                • Gave the changeling an unfat sting.
                • -
                -
              • Erro updated: -
                  -
                • R'n'D and Gas Storage locations have been swapped in order for R&D to be given a hallway-facing table, just like Chemistry.
                • -
                • Buckling someone to a chair now causes them to face in the same direction as the chair.
                • -
                • Conveyor will now move only 10 items per game cycle. This is to prevent miners from overloading the belts with 2000 pieces of ore and slowing down time by turning it on. Does not apply to mobs on belt.
                • -
                -
              • Doohl updated: -
                  -
                • New escape shuttle!
                • -
                • Metroids get hungry slower, but gain more nutrients from eating.
                • -
                -
              • Kor updated: -
                  -
                • Added Necronomicon bible (credit Joseph Curwen)
                • -
                • Removed Doc Scratch clothing from chaplain? locker and added as a random spawn in the theatre.
                • -
                • Shaft Miners now start with regular oxygen tanks.
                • -
                • Mutate now gives the wizard hulk and OPTIC BLAST instead of hulk and TK.
                • -
                • Spiderman suit is no longer armoured or space worthy.
                • -
                • Crayons
                • -
                -
              • Superxpdude updated: -
                  -
                • New, more appropriate arrivals message.
                • -
                • Shuttle escape doors fixed.
                • -
                • New RIG sprite.
                • -
                -
              • Lasty updated: -
                  -
                • Switched xenomorph weeds to run in the background, hopefully causing them to destroy the server slightly less.
                • -
                -
              • Microwave updated: -
                  -
                • Added sink to hydroponics
                • -
                • Cleaned up autolathe menu
                • -
                • Glasses and cups can now be filled with water from sinks.
                • -
                -
              - -31 August 2011. -
                -
              • Lasty updated: -
                  -
                • The costumes that spawn in the theatre are now randomized.
                • -
                • The kitchen now has a Smartfridge which can be directly loaded from the Botanist? plantbags. No more crates! (credit to Rolan7).
                • -
                • Snappops can now be acquired from the arcade machines. Amaze your friends! (credit to Petethegoat)
                • -
                • Bangindonk.ogg added to random end sounds list.
                • -
                -
              • Urist McDorf updated: -
                  -
                • Players can no longer be randomly assigned to Librarian, Atmospherics Technician, Chaplain, and Lawyer unless all other non-assisstant job slots are full or they have those jobs in their preferences.
                • -
                • Changeling mode now has multiple changelings.
                • -
                -
              • Superpxdude updated: -
                  -
                • The mail system actually works properly now! Probably!
                • -
                • Most hats had their ?eadspace?tag removed, meaning they will no longer substitute for a space helmet in protecting you from the dangers of space.
                • -
                -
              - -28 August 2011. -
                -
              • Doohl updated: -
                  -
                • Chaplains can now select different bible icons when they start. The selection is applied globally and the library's bible printer will print the same bibles.
                • -
                • Joy to the world! The Library's bible-printing function now has a one-minute cooldown. One minute may seem a little extreme, but it is necessary to prevent people from spamming the fuck out of everything with 100,000,000,000,000 carbon-copy bibles.
                • -
                • Tweaked Metroids a bit; they are slightly more aggressive and become hungrier faster. To compensate, they now move slightly slower.
                • -
                -
              - -26 August 2011. -
                -
              • Mport updated: -
                  -
                • Rev:
                • -
                    -
                  • Station Heads or Head Revs who leave z1 will count as dead so long as they are off of the z level.
                  • -
                  • Once a player has been unconverted they may not be reconverted.
                  • -
                  -
                • Cult:
                • -
                    -
                  • Heads other than the Captain and HoS are now able to start as or be converted to a cultist.
                  • -
                  • New Item: Loyalty Implant, which will prevent revving/culting. 4 spawn in the armory.
                  • -
                  • If a rev (not cultist) is injected with one he will unconvert, if a revhead is injected it will display a resist message.
                  • -
                  • Loyalty Implants show up on the SecHud
                  • -
                  • New Machine: Loyalty Implanter - Is on the prison station, shove a guy inside it to implant a loyalty implant. It can implant 5 times before it needs a 10 minute cooldown.
                  • -
                  -
                -
              - -20 August 2011. -
                -
              • Doohl updated: -
                  -
                • The smoke chemistry recipe has been upgraded! You can lace smoke with chemicals that can bathe people or enter their lungs through inhalation. Yes, this means you can make chloral hydrate smoke bombs. No, this doesn't mean you can make napalm smoke or foam smoke.
                • -
                -
              - -16 August 2011. -
                -
              • Superxpdude updated: -
                  -
                • Traitor item bundles: Contains a random selection of traitor gear
                • -
                -
              - -
                -
              • Uhangi updated: -
                  -
                • .38 Special Ammo can now be made from unhacked autolathes
                • -
                -
              - -15 August 2011. -
                -
              • Superxpdude updated: -
                  -
                • NEW MINING STATION, POST YOUR OPINIONS AND BUGS HERE
                • -
                • Added some new awesome mining-related sprites by Petethegoat. All credit for the sprites goes to him.
                • -
                -
              - -9 August 2011. -
                -
              • Mport updated: -
                  -
                • Cyborgs once again have open cover/cell icons.
                • -
                • To override a cyborg's laws you must emag it when the cover is open.
                • -
                • Emags can unlock a cyborgs cover.
                • -
                • Xbow radiation damage has been lowered from 100 to 20 a hit
                • -
                -
              - -5 August 2011. -
                -
              • Mport updated: -
                  -
                • The various assemblies should be working now.
                • -
                • Old style bombs and suicide vests temporarily removed.
                • -
                -
              - -3 August 2011. -
                -
              • Superxpdude updated: -
                  -
                • Virology Airlock changed to no longer cycle air. Should work faster and prevent virologists from suffocating.
                • -
                • Server Room APC is now connected to the power grid.
                • -
                • Stun Batons now start OFF. Make sure to turn them on before hitting people with them.
                • -
                -
              - -2 August 2011. The day the earth stood still. -
                -
              • Agouri updated: -
                  -
                • SSUs now correctly cycle and dump the unlucky occupant when designated to supercycle, when the criteria to do so are met.
                • -
                • Fixed bugshit
                • -
                • You can now make normal martinis again, removed a silly recipe conflict (Thanks, muskets.). Good barmen are urged to blend the good ol' recipes since the new ones have sprites that SUCK ASS JESUS CHRIST
                • -
                -
              - -
                -
              • Doohl updated: -
                  -
                • Speech bubbles: you can toggle them on in the character setup window. Basically, whenever someone around you talks, you see a speech bubble appear above them.
                • -
                • You can no longer create wizarditis and xenomicrobes with metroid cores.
                • -
                • Tweak: Using an exclamation mark as an AI, pAI, or cyborg will not longer get rid of the last exclamation mark.
                • -
                -
              - -30 July 2011. -
                -
              • Superxpdude Updated: -
                  -
                • Engineer and CE space helmets now have built-in lights.
                • -
                -
              • Rockdtben updated: -
                  -
                • Bugfix: Fixed a bug where you could dupe diamonds
                • -
                -
              • Doohl updated: -
                  -
                • New virus: Retrovirus. It basically screws over your DNA.
                • -
                • You can now do CTRL+MOVEMENT to face any direction you want. See those chairs in Medbay and the Escape Wing? You can do CTRL+EAST to actually RP that you're sitting on them. Is this cool or what?! -
                -
              - -29 July 2011. - Day of Forum revival! -
                -
              • Doohl updated: -
                  -
                • Bugfix: Metroids should never "shut down" and just die in a corner when they begin starving. And so, hungry Metroids are a force to be feared.
                • -
                • The Cargo computers now have the ability to cancel pending orders to refund credits. This was put in place so that idiots couldn't waste all the cargo points and run off. However, if the shuttle is en route to the station you won't be able to cancel orders.
                • -
                • Bugfix: the manifest has been fixed! Additionally, the manfiest is now updated realtime; job changes and new arrivals will be automatically updated into the manifest. Joy!
                • -
                • Metroids, when wrestled off of someone's head or beaten off, now get stunned for a few seconds.
                • -
                -
              • Agouri updated: -
                  -
                • I was always bothered by how unprofessional it was of Nanotransen (in before >Nanotransen >professionalism) to just lay expensive spacesuits in racks and just let them be. Well, no more. Introducing...
                • -
                • Suit Storage Units. Rumored to actually be repurposed space radiators, these wondrous machines will store any kind of spacesuit in a clean and sterile environment.
                • -
                • The user can interact with the unit in various ways. You can start a UV cauterisation cycle to disinfect its contents, effectively sterilising and cleaning eveyrthing from the suits/helmets stored inside.
                • -
                • A sneaky yordle can also hide in it, if he so desires, or hack it, or lock it or do a plethora of shady stuff with it. Beware, though, there's plenty of dangerous things you can do with it, both to you and your target.
                • -
                • The Unit's control panel can be accessed by screwdriving it. That's all I'm willing to say, I'd like to let the players find out what each hack option does and doesn't. Will add more stuff later.
                • -
                • Added Command Space suit, Chief Engineer space suit and Chief Medical Officer spacesuit (In a new space that you'll probably notice by yourself) to make it easier for you to look like a special snowflake.
                • -
                • EVA and CMO office modified to accomodate the new suits and SSUs. Look, I'm not a competent mapper, okay? Fuck you too. A mapper is strongly recommended to rearrange my half assed shit.
                • -
                • Soda cans, cigarette packets, cigarettes and cigars as well as bullet casings are now considered trash and can be picked up by the trashbag. Now you can annoy the janitor even more!
                • -
                • Sprite credit goes to Alex Jones, his portfolio can be found here: http://bspbox.com. Thanks a lot, bro.
                • -
                • With the recent forum fuss and all that, I've got a thread to specifically contain rants and bug reports about this update. Click me
                • -
                -
              • Uhangi updated: -
                  -
                • EVA redesigned
                • -
                • An electropack is now available once again on the prison station
                • -
                -
              • Errorage updated: -
                  -
                • Hopefully fixed the derelict 'hotspots'. Derelict medbay has also been fixed.
                • -
                -
              - -4 July - 28 July 2011. -
                - -
              • Trubble Bass updated (committed by Superxpdude): -
                  -
                • Hat crates. Hat Station 13.
                • -
                -
              • Matty: -
                  -
                • Engineers and miners now start off with the new industrial backpack.
                • -
                -
              • Agouri: -
                  -
                • Sleepers are now OP and heal every kind of damage.
                • -
                • Made cloning 30% faster, due to popular demand.
                • -
                -
              • Superxpdude updated: -
                  -
                • Added in the Submachine Gun to R&D.
                • -
                • Syndicate agents now have Mini-Uzis.
                • -
                • Added an exosuit recharged to the mining station.
                • -
                • New labcoats for scientists, virologists, chemists, and genetecists.
                • -
                • Moved the vault and added a bridge meeting room next to the HoP's office.
                • -
                • Deathsquad armor now functions like a space suit.
                • -
                • Added in security jackboots.
                • -
                -
              • Uhangi updated: -
                  -
                • Traitors can now purchase syndicate balloons, which serve no purpose other than to blow your cover. For a limited time only, you can get them at a bargain price - just 10 telecrystals!
                • -
                • Removed security shotguns from the armory. No fun allowed.
                • -
                • Changed some bullet damage stuff.
                • -
                -
              • Microwave updated: -
                  -
                • Monkey boxes:
                • -
                • Contains a score of monkey cubes, which you apply water to create monkies. Nanotrasen provides only the finest technology!
                • -
                • You can order monkey crates from the cargo bay. They contain monkey boxes.
                • -
                • Changed the amount of labels labelers have to 30. 10 was too low and 30 should not be too griefy.
                • -
                • Maximum label text length increased from 10 to 64.
                • -
                • You can no longer label people because they can just peel the labels off. Sorry, clowns!
                • -
                • Jelly dooonnuuuutsss! Happy birthday, officers!
                • -
                • Made xenomeat not give any nutrition.
                • -
                • Added some new reagents.
                • -
                • Made it possible to feed monkies and xenos things, as well as making it possible for them to eat themselves (please don't read that too literally).
                • -
                • Added in synthiflesh.
                • -
                • Plasma is now not used in reactions, instead, is treated as a catalyst that is not used up. This only applies to certain reactions.
                • -
                • Made it possible to grind more things in the chemistry grinder.
                • -
                -
              • Errorage updated: -
                  -
                • Increased environmental damage by a factor of 1.5.
                • -
                • Made firesuits a lot more resistant to heat. Previously, they stopped protecting at around 4,500 degrees. They now go up to around 10,000 (which is also the temperature which the floor starts melting)
                • -
                • Edited the quartermaster's office a bit.
                • -
                • Cargo technicians now have access to a cargo ordering console.
                • -
                • Added different-colored hardhats. The CE gets a white hardhat.
                • - -
                -
              • Rastaf.Zero updated: -
                  -
                • Botanists get a new toy: Biogenerator. Insert biological items, recieve biological items.
                • -
                • Added roller beds, otherwise known as stretchers, to medbay. You can buckle people onto them and pull them.
                • -
                • Added egg-smashing and tomato-smashing decals.
                • -
                -
              • Muskets updated: -
                  -
                • The prepackaged songs (Space Asshole, Cuban Pete, Darkest Honk, etc) have been removed. This doesn't mean admins can't play midis, this just gets rid of a lot of unnecessary download time.
                • -
                -
              • Firecage updated: -
                  -
                • A whole bunch of new drinks and food. Seriously, there's alot!
                • -
                • New weapons such as the shock revolver and large energy crossbow.
                • -
                • Hydroponics can now grow a bunch more stuff. A lot of these new crops have some very interesting mutations.
                • -
                • Added a command for AIs to change their icon.
                • -
                • New costumes to the costume room.
                • -
                -
              • Urist McDorf updated: -
                  -
                • Adding shading to pills.
                • -
                • Detective's office noir look has been removed. The icon operations required to render everything in monochrome was too heavy on the players.
                • -
                • Added in an uplink implant.
                • -
                -
              • Doohl updated: -
                  -
                • A new alien race: Metroids! They are a mostly non-sentient race of jellyfish-like organisms that float in the air and feed on the life energy of other organisms. While most Metroids have never shown signs of self-awareness, they do exhibit signs of basic logic and reasoning skills as well as very sophisticated perception. Nanotrasen has shipped two baby Metroids for the xenobiology department. They should be handled with the utmost care - they are some of the deadliest beings in the known universe!
                • -
                • R&D gets a new toy: the freeze gun. Despite popular belief, this will not literally freeze things!
                • -
                • Every single chemical reagent has been assigned a color.
                • -
                • You can now see beakers fill up with chemicals. You can also observe how the colors mix inside the beakers. Spray bottles also will also show the color of whatever you're spraying.
                • -
                • Added a timestamp to combat logs.
                • -
                • Non-insulated gloves now need to be wrapped in wire in order to be electrified.
                • -
                • You can now shoot at people on the ground by simply clicking on the tile they're on.
                • -
                • Changeling husks are now uncloneable. To clarify: when a changeling sucks out a victim's DNA, the victim is said to become a "husk".
                • -
                -
              - -4 July 2011. -
                -
              • Agouri updated: -
                  - Medical stuff overhaul in preparation of Erro's big Medic update: -
                • Sleepers are now able to heal every kind of damage. Drag your ass to medbay and ask a doctor to get you in one.
                • -
                • A lil' bit faster cloning (about 30% faster) due to popular demand.
                • -
                • Sleepers are now all located in the inner part of medbay, except for the examination room one.
                • -
                • Added Dermaline, burn-healing drug that outpowers kelotane (and kelotane is getting a major nerf, so be sure to know how to get this). Recipe is on the wiki if you need to make it.
                • -
                • Drugs no longer heal or metabolise when injected in dead bodies, fuck. Thank god you guys missed this major bug or we'd have cloning-by-injecting-healing-drugs.
                • -
                • Reminder to coders: Goddamn, update the changelog. -
                -
              • - -
              • Matty updated: -
                  -
                • New engineering backpacks. Enjoy!
                • -
                -
              • -
              - -18 June 2011. -
                -
              • Agouri updated: -
                  -
                • Bugfixes: The reagent grinding now works, allowing the miners to FINALLY bring plasma to the chemistry.
                • -
                • Bugfixes: Pill bottles and clipboards can now be removed from the pockets once placed there.
                • -
                -
              • -
              -
              -7 June 2011. -
                -
              • TLE updated: -
                  -
                • Wiped/suicided pAIs should be eligible for being candidates again (5 minute cooldown between prompts.)
                • -
                • pAIs are now affected by EMP bursts. pAIs hit with a burst will be silenced (no speech or PDA messaging) for two minutes and may have their directives or master modified. A sufficiently powerful EMP burst will have a 20% chance of killing a pAI.
                • -
                -
              • -
              • Neo updated: -
                  -
                • Nar-sie is now a more vengeful eldritch being. When summoned into our world, he first rewards his loyal cultists by chasing down and eating them first, then turns his attention to any remaining humans.
                • -
                -
              • -
              • Darem updated: -
                  -
                • Gun Code Overhaul Phase 1.
                • -
                • Taser guns shoot ONE WHOLE SHOT more then they do now.
                • -
                • Energy Crossbow has a slightly higher shot capacity (still automatically recharges).
                • -
                • Revolvers can either be loaded one shell at a time or all at once with an ammo box.
                • -
                • Shotguns no longer need to be pumped before firing (will change back in phase 2).
                • -
                -
              • -
              • K0000 updated: -
                  -
                • Arcane tome now has a "notes" option. Set English translations for runewords which come up when scribing runes. Attack an arcane tome with another arcane tome to copy your notes to the target tome.
                • -
                • Stun rune buffed considerably. Its a 1-use item so it deserved longer stun time.
                • -
                • Tome text: Added missing word description for stun rune.
                • -
                -
              • -
              -
              - -1 June 2011, Canadian Day Against Homophobia -
                -
              • Noise updated: -
                  -
                • Changed holopad speaking to :h on request.
                • -
                • Ninja fixes, changes, etc. Refer to the code changelog for more info.
                • -
                -
              • -
              • Neo updated: -
                  -
                • Department radio chat now shows with the department name instead of the frequency.
                • -
                -
              • -
              • Veyveyr updated: -
                  -
                • Spent casing sprites + SMG sprite.
                • -
                • Sprites for 1x4 and 1x2 pod doors.
                • -
                -
              • -
              • Errorage updated: -
                  -
                • Fixed twohanded weapon throwing, which left the 'off-hand' object in your other hand.
                • -
                • Doors now hide items under them when closed, mobs are still always above.
                • -
                • Singularity engine emitter is now much quieter.
                • -
                • Mining station redesigned.
                • -
                • Atmospherics' misc gasses tank now starts with N2O.
                • -
                • Hitting the resist button while handcuffed and buckled to something will make you attempt to free yourself. The process is the same as trying to remove handcuffs. When the 2 minutes pass you will be unbuckled but still handcuffed.
                • -
                -
              • -
              • ConstantA updated: -
                  -
                • Added exosuit energy relay equipment. Uses area power (any power channel -available) instead of powercell for movement and actions, recharges powercell.
                • -
                • Exosuits can be renamed. Command is in Permissions & Logging menu.
                • -
                • Lowered construction time for Ripley parts.
                • -
                • Exosuit wreckage can be salvaged for exosuit parts (torso, limbs etc).
                • -
                • Speed-up for mecha.
                • -
                • New malf-AI sprite. (Sprite donated by the D2K5 server)
                • -
                -
              • -
              • Cheridan updated: -
                  -
                • Updated mine floor and wall edge sprites.
                • -
                -
              • -
              • Urist McDorf updated: -
                  -
                • AIs no longer bleed when they reach 0HP (Critical health).
                • -
                • Added 2 more security HUDs to security.
                • -
                • Security HUDs now show if a person has a tracking implant.
                • -
                -
              • -
              • Microwave updated: -
                  -
                • Barman renamed to Bartender.
                • -
                • The amount of drink you get when mixing things in the bar has been rebalanced.
                • -
                • Fixed arrivals maintenance shaft not having air at round start.
                • -
                -
              • -
              • Deuryn updated: -
                  -
                • Meteors now do a bit more damage and they're not stopped by grills.
                • -
                -
              • -
              • TLE updated: -
                  -
                • Added personal AIs (pAI).
                • -
                -
              • -
              -
              - -19 May 2011 -
                -
              • Errorage updated: -
                  -
                • Asteroid floors can be built on by adding tiles
                • -
                • Mining satchels now fit in rig suit storage, on belts and in pockets.
                • -
                • Cables now come in four colors: Red, yellow, green and blue.
                • -
                -
              • - -
              • NEO updated: -
                  -
                • Armour overhaul, phase 3. See rev notes for details.
                • -
                • AI cores should now block movement.
                • -
                • MMIs are now properly buildable with the mecha fabricator.
                • -
                -
              • - -
              • Urist updated: -
                  -
                • Added sandstone and mineral doors. Mineral boors cannot be opened by the AI or NPCs.
                • -
                • Removed Imperium robes from map.
                • -
                • Added the ability to draw letters and graffiti with crayons.
                • -
                • Removed fire axes except for bridge and atmospherics.
                • -
                -
              • - -
              • Veyveyr updated: -
                  -
                • New serviceborg sprite option.
                • -
                • Map changes to robotics; removed borg fabricators and added second exosuit fabricator.
                • -
                • Cyborg parts are now built from exosuit fabricators and benefit from research.
                • -
                • New exosuit fabricator and borg frame sprites.
                • -
                -
              • -
              -
              -14 May 2011, late friday 13 update. -
                -
              • K0000 updated: -
                  -
                • Cult updates:
                • -
                • New rune! Stun rune. When used as rune, briefly stuns everyone around (including cultists). When imbued into a talisman, hit someone to stun and briefly mute them. Spawnable with the starter talisman.
                • -
                • Imbue rune doesnt disappear after succesful invocation, only the source rune.
                • -
                • Chaplain's bible now has 20% chance to convert a cultist (was 10%), and gives a message on success.
                • -
                • Also, wrapping paper is back! Find it in the mailroom.
                • -
                -
              • - -
              • NEO updated: -
                  -
                • Beginning of armor overhaul. Armor now has slightly better defence against melee, and weaker against shots. More coming soon...someday
                • -
                • Cyborgs finally drop their MMI when gibbed like they were supposed to back when I added MMIs. Round- start cyborgs use whatever name you have selected for your character for the brain that gets spawned for them.
                • -
                -
              • - -
              • Darem updated: -
                  -
                • Chemistry update
                • -
                • In containers where there isn't a perfect ratio of reagents, reactions won't consume ALL of the related reagents (so if you mix 10 anti-toxin with 20 inaprovaline, you get 10 tricordrazine and 10 inaprovaline rather then just 10 tricodrazine)
                • -
                • Catalysts: some reactions might need presence of an element, while not directly consuming it.
                • -
                • Reactions changed to use catalysts: all recipes that require Universal Enzyme now require 5 units of the enzyme but the enzyme isn't consumed (So Tofu, Cheese, Moonshine, Wine, Vodka, and Kahlua recipes). -
                -
              • -
              • Errorage updated: -
                  -
                • Smooth tables: Tables now automatically determine which direction and sprite they'll use. They will connect to any adjacent table unless there is a window between them (regular, reinforced, tinted, whichever)
                • -
                -
              • -
              -
              -7 May 2011, Mother's day? -
                -
              • Agouri updated: -
                  -
                • Fireaxes now work. Derp.
                • -
                -
              • - -
              • Erro updated: -
                  -
                • New sprites for thermited walls and girders. Rework of thermited walls. Thermited walls leave a remnant damaged wall, crowbar it to scrap it.
                • -
                • More colors for the lightfloors CANCELLED/POSTPONED
                • -
                -
              • - -
              • Noise updated: -
                  -
                • Codephrases for traitors. More details here
                • -
                • New mech sprite
                • -
                -
              • -
              -
              -6 May 2011, AMURRICA FUCK YEAH day -
                -
              • DANGERCON UPDATE:Agouri and Erro updated(I'm in the DangerCon team now, nyoro~n :3): -
                  -
                • Backpacks removed from all players. It was unrealistic. You can now had to the living quarters to get one from the personal closets there.
                • -
                • Any firearms now send you to critical in 1-2 shots. Doctors need to get the wounded man to surgery to provide good treatment. Guide for bullet removal is up on the wiki.
                • -
                • Brute packs and kelotane removed altogether to encourage use of surgery for heavy injury.
                • -
                • Just kidding
                • -
                • Fireaxe cabinets and Extinguisher wall-mounted closets now added around the station, thank Nanotransen for that.
                • -
                • Because of Nanotransen being Nanotransen, the fire cabinets are electrically operated. AIs can lock them and you can hack them if you want the precious axe inside and it's locked. The axe itself uses an experimental two-handed system, so while it's FUCKING ROBUST you need to wield it to fully unlock its capabilities. Pick up axe and click it in your hand to wield it, click it again or drop to unwield and carry it.You can also use it as a crowbar for cranking doors and firedoors open when wielded, utilising the lever on the back of the blade. And I didn't lie to you. It's fucking robust.
                • -
                • Fireaxe, when wielded, fully takes up your other hand as well. You can't switch hands and the fireaxe itself is unwieldy and won't fit anywhere. -
                • A fireaxe cabinet can also be smashed if you've got a strong enough object in your hand.
                • -
                • EXTINGUISHER CLOSETS, made by dear Erro, can be found in abundance around the station. Click once to open them, again to retrieve the extinguisher, attack with extinguisher to place it back. Limited uses, but we've got plans for our little friend. -
                • Sprite kudos go to: Cheridan for most of them, Khodoque for the fantastic fireaxe head. I merged those two. Also thanks to matty and Arcalane for giving it a shot.
                • -
                • Has the piano got TOO annoying? Try the fire axe...
                • -
                • Oh, and tou can now construct Light floors! To do it: Use wires on glass, then metal on the produced assembly, then place it on an uncovered floor like you would when replacing broken floor tiles. To deconstruct: Crowbar on light floor, use crowbar on produced assembly to remove metal, wirecutters to seperate the wires from the glass. Sprites by delicious Hempuli.
                • -
                • Got something to bitch about? Got a bug to report? Want to create drama? Did the clown destroy your piano while you were playing an amazing space remix of the moonlight sonata? Give it here - -
                -
              • - -
              • Rastaf.Zero updated: -
                  -
                • New uniforms added for captain and chaplain, in their respective lockers. Credits to Farart.
                • -
                -
              • - -
              • Urist McDorf updated: -
                  -
                • Mime and Clown now spawn with Crayons. You can eat those crayons. And use them for other nefarious purposes.
                • -
                • Health Scanners (A new type of Goggles) now spawn in medbay. Use them, doctors!
                • -
                • New Arcade toy.
                • -
                • Glowshrooms! What other lifeform will threaten the welfare of the station now?!
                • -
                • Bananas growable in hydroponics. Also soap is now on-board.
                • -
                • Added new "Lights out!" random event.
                • -
                -
              • -
              • ConstantA updated: -
                  -
                • Mech pilots are now immune to zapping, thank you very much.
                • -
                -
              • -
              -
              - -17 April 2011, World Hemophilia Day -
                -
              • Microwave updated: -
                  -
                • Rabbit ears have a small tail, night vision goggle sprites updated.
                • -
                • Space tea has a nice, calming effect.
                • -
                • Space drugs? Liberty cap... something like that. Microwave, make your changelog entries more understandable!
                • -
                • Brobot merged with Service Borg with a Rapid Service Fabricator.
                • -
                • Arcade machine prizes look and sound realistic once again.
                • -
                • New arcade toy: Syndicate space suit costume, can hold arcade toys in suit storage.
                • -
                • Empty cap gun loaders can be recycled in an autolathe.
                • -
                • Seizure man has laying down sprites now. Update to wizard den.
                • -
                • Mech bay has two more borg chargers.
                • -
                • Beepsky is back!
                • -
                • Detective's office grille has been electrified.
                • -
                • You can now see if someone is wearing an emergency oxygen tank on their belt on the mob itself.
                • -
                • Lexorin - Now deals 3 oxygen damage per tick. Countered with Dexalin or Dexalin Pkus, which remove 2 units of it from your body per tick.
                • -
                • Bilk - Shares the effects of beer and milk. Disgusting!
                • -
                • Sugar - Gives nutrition!
                • -
                • Arithrazine - Now extremely good against radiation damage.
                • -
                • Hyronalin - Stronger radiation removal
                • -
                • Space cleaner spray bottles now contain enough cleaner for 50 uses. Making space cleaner now yields more cleaner.
                • -
                -
              • -
              • Errorage updated: -
                  -
                • Shuttle diagonal sprites now work for any kind of floor.
                • -
                • You can now make plaques from gold. Place them on a wall and engrave an epitaph.
                • -
                • Placed a single wall tile in the AI satellite so you don't have a clear LOS of the AI from the door.
                • -
                • Added arrivals lobby. (Map by Superxpdude, updated by Microwave)
                • -
                • Lattice now connects to the solar shields.
                • -
                • Law office maintenance is now connected with Tech storage maintenance. (Some rewiring dont in the area)
                • -
                • Xenobiology now has it's own access level. (Also fixed xeno pen access and blast doors)
                • -
                • You might soon start to see different airlocks and airlock assemblies around the station. (Sprites donated by Baystation 12)
                • -
                • Chemical storage added, discussion on which chemicals it should store is on the forums. You're welcome to contribute.
                • -
                • Hot fires will now melt floors.
                • -
                • Added a pair of market stalls south of the teleporter. LET THERE BE CLOWNMART!
                • -
                • Screwdrivers and wirecutters can now spawn in different colors.
                • -
                • Electrical toolboxes have a 5% chance of spawning a pair of insulated gloves. A set spawns in tech storage.
                • -
                • Oxygen canisters now spawn in emergency storage, near disposal, in the incinerator and the CE's office.
                • -
                • A plasma canister now spawns in toxins, near the maintenance door.
                • -
                • Wooden tables now look nicer.
                • -
                -
              • -
              • Agouri updated: -
                  -
                • 2001 space suits added to AI Satellite!
                • -
                • New look for the 2001 space suit.
                • -
                • 2001 space suit jetpack added.
                • -
                • Improved TRAYS!
                • -
                -
              • -
              • Noise updated: -
                  -
                • Thermals and mesons no longer give slightly better night vision.
                • -
                • NINJAS! (Too many things to list)
                • -
                • Wizards are no longer trackable by the AI when in their den.
                • -
                • Removed all old notes, except for the last one.
                • -
                • Nuke team now cannot return with their shuttle until the bomb is armed and counting down.
                • -
                • Energy blades can no longer cut through r-walls, walls take 7 seconds to cut through.
                • -
                • Turrets are now destructible. Bash them with stuff when they pop out or (more likely) die trying.
                • -
                • Updated Ripley Mech sprite.
                • -
                -
              • -
              • Neo updated: -
                  -
                • You can now aim guns at body parts, armor and helmets properly protect you from projectiles.
                • -
                • Cat ears now match the hair color of the wearer.
                • -
                • Robots can no longer stick their items onto/into things.
                • -
                • Meson, thermal and x-ray vision are now modules for borgs.
                • -
                • Welding now uses less fuel when on and idle but more when welding.
                • -
                • Hopefully fixed the bug when running into airlocks didn't open them and running into objects didn't push them.
                • -
                -
              • -
              • HAL updated: -
                  -
                • Added air alarm to security checkpoint, added cameras to aux. arrival docks so the AI can see everything.
                • -
                • Added fire alarm, fire locks and air alarm to delivery office.
                • -
                -
              • -
              • ConstantA updated: -
                  -
                • Added mecha DNA-locking. Only the person with matching UE can operate such mechs.
                • -
                • Added two mecha armor booster modules and a repair droid module.
                • -
                • Mech fabricator is now buildable.
                • -
                • Gygax construction is now reversible.
                • -
                -
              • -
              • Rastaf0 and Farart updated: -
                  -
                • Ghosts should now always properly hear people.
                • -
                • Monkeyized people (genetics or jungle fever disease) no longer lose their genetic mutations and diseases.
                • -
                • People who get bitten by monkeys get jungle fever.
                • -
                • Most chemicals should now heal and harm humans properly.
                • -
                • Many new (and updated) recipes for the microwave including Pizza, Meatball Soup, Hot Chili and many more.
                • -
                • Items should no longer spawn under vendomats and microwaves.
                • -
                • Runes are now drawn under doors and tables.
                • -
                • Penlights fit in medical belts.
                • -
                • People will scream if they get cremated while still alive.
                • -
                • Diseases should now properly make you loose health.
                • -
                • Monkeys wearing masks now get acid protection too.
                • -
                • You should probably turn off your stun baton before washing it.
                • -
                • latex loves + short piece of wire + some air from tank = balloon!
                • -
                • Kitchen was expanded, also a new look for the kitchen sink.
                • -
                • New dishware vending machine - dispenses knives, forks, trays and drinking glasses.
                • -
                • Water cooler was added to kitchen.
                • -
                • New uniform - Waiter Outfit. Chef can give it to his assistant.
                • -
                -
              • -
              • Deeaych updated: -
                  -
                • Updated satchel, bananimum, shovel, jackhammer and pick-in-hand sprites.
                • -
                • Many unneeded r-walls removed, detective's office reinforced.
                • -
                • Captain armor now acts as a space suit, added a unique captain's space helmet to captain's quarters.
                • -
                • Golems cannot speak, but should be perfectly spawnable. Also added golem fat sprite.
                • -
                • Security borgs have side sprites.
                • -
                -
              • -
              • Matty406 updated: -
                  -
                • AIs can now feel a little more dorfy.
                • -
                • Many ores, both raw and smelted, look much better.
                • -
                -
              • -
              • Urist_McDorf updated: -
                  -
                • You can now light other people's cigarettes by targeting their mouth with a lighter.
                • -
                -
              • -
              • Veyveyr updated: -
                  -
                • New tool sprites.
                • -
                • New sprites for smooth-lattice.
                • -
                -
              • -
              • Muskets updated: -
                  -
                • Kabobs now return the bar used to make them.
                • -
                -
              • -
              -
              - -2 April 2011, International Children's Book Day -
                -
              • Microwave updated: -
                  -
                • New look for the mining cyborg, jackhammer, kitchen sink.
                • -
                • Singularity is now enclosed again (still airless tho).
                • -
                • Wizard has a new starting area.
                • -
                • Chemists and CMOs now have their own jumpsuits.
                • -
                -
              • -
              • ConstantA updated: -
                  -
                • You can now put Mind-machine-interface (MMI)'d brains into mecha.
                • -
                -
              • -
              • Errorage updated: -
                  -
                • Added smooth lattice.
                • -
                -
              • -
              -
              - -26 March 2011 -
                -
              • Rastaf0 updated: -
                  -
                • Food sprites from Farart
                • -
                • New food: popcorn (corn in microwave), tofuburger (tofu+flour in microwave), carpburger (carp meat+floor in microwave)
                • -
                • Medical belts are finally in medbay (credits belong to errorage, I only added it)
                • -
                • Pill bottles now can fit in containers (boxes, medbelts, etc) and in pockets.
                • -
                • Cutting camera now leaves fingerprints.
                • -
                -
              • -
              • Microwave updated: -
                  -
                • Armor Can hold revolvers, and so can the detective's coat.
                • -
                • Chef's apron is going live, it can carry a knife, and has a slight heat -resistance (only slight don't run into a fire).
                • -
                • Kitty Ears!
                • -
                • Various food nutriment changes.
                • -
                • Added RIGs to the Mine EVA.
                • -
                • Night vision goggles. They have a range of five tiles.
                • -
                • Added Foods: Very Berry Pie, Tofu Pie, Tofu Kebab.
                • -
                • Modified foods: Custard Pie is now banana cream pie.
                • -
                -
              • -
              • ConstantA updated: -
                  -
                • Removed redundand steps from Gygax and HONK construction.
                • -
                • Added some mecha equipment designs to R&D.
                • -
                -
              • -
              -
              - -23 March 2011, World Meteorological Day -
                -
              • Neo updated: -
                  -
                • Fixed PacMan (and affiliates) generator construction.
                • -
                • It is now possible to actually eat omelettes with the fork now, instead of just stabbing yourself (or others) in the eye with it.
                • -
                • Welding masks can now be flipped up or down. Note that when they're up they don't hide your identity or protect you from welding.
                • -
                • Reagent based healing should now work properly.
                • -
                • Revolver has been balanced and made cheaper.
                • -
                • Tasers now effect borgs.
                • -
                • Plastic explosives are now bought in single bricks.
                • -
                • Nuke team slightly buffed and their uplink updated with recently added items.
                • -
                • Player verbs have been reorganized into tabs.
                • -
                • Energy swords now come in blue, green, purple and red.
                • -
                • Cameras are now constructable and dismantlable. (Code donated by Powerful Station 13)
                • -
                • Updated the change network verb for AIs. (Code donated by Powerful Station 13)
                • -
                • Added gold, silver and diamond pickaxes to R&D which mine faster.
                • -
                -
              • -
              • Agouri updated: -
                  -
                • New look for the Request consoles.
                • -
                -
              • -
              • Rastaf0 updated: -
                  -
                • Brig cell timers should now tick closer-to-real seconds.
                • -
                • New look for food, including meat pie, carrot cake, loaded baked potato, omelette, pie, xenopie and others. (some sprites by Farart)
                • -
                • Hearing in lockers now works as intended.
                • -
                • Fixed electronic blink sprite.
                • -
                • Added the 'ghost ears' verb, which allows ghosts to not hear anything but deadcast.
                • -
                -
              • -
              • XSI updated: -
                  -
                • New AI core design.
                • -
                • HoP now has a coffee machine!
                • -
                -
              • -
              • Veyveyr updated: -
                  -
                • Replaced nuke storage with a vault.
                • -
                • Redesigned the mint, moved the public autolathe and n2o storage.
                • -
                • New look for the coin press. (Sprite by Cheridan)
                • -
                -
              • -
              • Errorage updated: -
                  -
                • You can now manually add coins into money bags, also fixed money bag interaction window formatting.
                • -
                • QM no longer has access to the entire mining station to stop him from stealing supplies.
                • -
                • New machine loading sprite for mining machinery. (sprites by Cheridan)
                • -
                • Added a messanging server to the server room. It'll be used for messanging, but ignore it for now.
                • -
                • The delivery office now requires delivery office access. It's also no longer called "Construction Zone"
                • -
                • Almost all the mecha parts now have sprites. (Sprites by Cheridan)
                • -
                • Tinted and frosted glass now look darker.
                • -
                • There are now more money sprites.
                • -
                • Department closets now contain the correct headsets.
                • -
                -
              • -
              • Microwave updated: -
                  -
                • Bicaridine now heals a lot better than before.
                • -
                • Added Diethylamine, Dry Ramen, Hot Ramen, Hell Ramen, Ice, Iced Coffee, Iced Tea, Hot Chocolate. Each with it's own effects.
                • -
                • Re-added pest spray to hydroponics.
                • -
                • Carrots now contain a little imidazoline.
                • -
                • HoS, Warden and Security Officer starting equipment changed.
                • -
                • New crate, which contains armored vests and helmets. Requires security access, costs 20.
                • -
                • Miner lockers now contain meson scanners and mining jumpsuits.
                • -
                • Food crate now contains milk, instead of faggots. Lightbulb crates cost reduced to 5. Riot crates cost reduced to 20. Emergency crate contains 2 med bots instead of floor bots. Hydroponics crate no longer contains weed spray, pest spray. It's latex gloves were replaced with leather ones and an apron.
                • -
                • Added chef's apron (can hold a kitchen knife) and a new service borg sprite.
                • -
                • Autolathe can now construct kitchen knives.
                • -
                • Biosuit and syndicate space suits can now fit into backpacks.
                • -
                • Mime's mask can now be used as a gas mask.
                • -
                • Added welding helmet 'off' sprites.
                • -
                -
              • -
              - -
              - -18 March 2011 -
                -
              • Errorage updated: -
                  -
                • You can now use the me command for emotes! It works the same as say "*custom" set to visible.
                • -
                • There is now a wave emote.
                • -
                • Enjoy your tea!
                • -
                -
              • -
              • Deeaych updated: -
                  -
                • The exam room has some extra prominence and features.
                • -
                • A new costume for the clown or mime to enjoy.
                • -
                • Service Cyborgs can be picked! Shaker, dropper, tray, pen, paper, and DOSH to show their class off. When emagged, the friendly butler-borg is able to serve up a deadly last meal.
                • -
                • It should now be possible to spawn as a cyborg at round start. Spawned cyborgs have a lower battery life than created cyborgs and begin the round in the AI Foyer.
                • -
                -
              • -
              • Rastaf0 updated: -
                  -
                • Fixed an issue with examining several objects in your hands (such as beakers).
                • -
                • Fixed bug with random last name being empty in rare cases.
                • -
                -
              • -
              • hunterluthi updated: -
                  -
                • It is now possible to make 3x3 sets of tables.
                • -
                • Fixed some missplaced grilles/lattices on the port solar.
                • -
                • There is now a breakroom for the station and atmos engineers. It has everything an intelligent young engineer needs. Namely, Cheesy Honkers and arcade games.
                • -
                -
              • -
              -
              -15 March 2011, International Day Against Police Brutality -
                -
              • Errorage updated: -
                  -
                • Autolathe deconstruction fixed.
                • -
                • Atmos Entrance fixed.
                • -
                • AI no longer gibs themselves if they click on the singularity.
                • -
                • Fixed all the issues I knew of about storage items.
                • -
                • Redesigned Assembly line and surrounding maintenance shafts.
                • -
                • Redesigned Tech storage. (Map by Veyveyr)
                • -
                -
              • -
              • TLE updated: -
                  -
                • Forum account activation added. Use the 'Activate Forum Account' verb.
                • -
                -
              • -
              • Neo updated: -
                  -
                • New R&D Item: The 'Bag of holding'. (Sprite by Cheridan)
                • -
                • Getting someone out of the cloner now leaves damage, which can only be fixed in the cryo tube.
                • -
                • New reagent: Clonexadone, for use with the cryo tube.
                • -
                • Fixed using syringes on plants.
                • -
                -
              • -
              • Constanta updated: -
                  -
                • Added queueing to fabricator.
                • -
                -
              • -
              • Rastaf0 updated: -
                  -
                • Air alarms upgraded.
                • -
                • Fixed problem with AI clicking on mulebot.
                • -
                • Airlock controller (as in EVA) now react to commands faster.
                • -
                • Fixed toxins mixing airlocks.
                • -
                -
              • -
              - -6 March 2011 -
                -
              • Neo updated: -
                  -
                • Neo deserves a medal for all the bugfixing he's done! --errorage
                • -
                -
              • -
              • Errorage updated: -
                  -
                • No. I did not code on my birthday!
                • -
                • Windows can now be rotated clockwise and counter clockwise.
                • -
                • Window creating process slightly changed to make it easier.
                • -
                • Fixed the newly made reinforced windows bug where they weren't properly unfastened and unscrewed.
                • -
                • Examination room has a few windows now.
                • -
                • Can you tell I reinstalled Windows?
                • -
                • Robotics has health analyzers.
                • -
                • Bugfixing.
                • -
                -
              • -
              • Deeyach updated: -
                  -
                • Roboticists now spawn with a lab coat and an engineering pda
                • -
                -
              • -
              - -2 March 2011, Wednesday -
                -
              • Errorage updated: -
                  -
                • Mapping updates including Atmospherics department map fixes, CE's office and some lights being added here and there.
                • -
                • Mining once again given to the quartermaster and HoP. The CE has no business with mining.
                • -
                • Removed the overstuffed Atmos/Engineering supply room.
                • -
                • Replaced all 'engineering' doors in mining with maintenance doors as they were causing confusion as to which department mining belongs to.
                • -
                • The incinerator is now maintenance access only.
                • -
                -
              • -
              • Neo updated: -
                  -
                • New look for the advanced energy gun. (Sprite by Cheridan)
                • -
                • Mech fabricator accepts non-standard materials.
                • -
                • Mules accesses fixed, so they can be unlocked once again.
                • -
                • Atmospherics department mapping overhaul. (Map by Hawk_v3)
                • -
                • Added more name options to arcade machines.
                • -
                -
              • -
              • ConstantA updated: -
                  -
                • Added mecha control console and mecha tracking beacons.
                • -
                • Some changes to gygax construction.
                • -
                -
              • -
              • Darem updated: -
                  -
                • R&D minor bugfixes.
                • -
                • AI computer can now be deconstructed (right click and select 'accessinternals').
                • -
                • Server room updated, added server equipment to use with R&D.
                • -
                • Wizard and ghost teleport lists are now in alphabetical order, ghosts can now teleport to the mining station.
                • -
                • Rightclicking and examining a constructable frame now tells you what parts still need to be finished.
                • -
                • Large grenades added to R&D.
                • -
                -
              • -
              • Deeyach updated: -
                  -
                • Mining given to the CE. (Reverted by Errorage)
                • -
                • Clowns can now pick a new name upon entering the game (like wizards previously).
                • -
                -
              • -
              - -24 February 2011, Thursday -
                -
              • Darem updated: -
                  -
                • Lighting code fixed for mining and thermite.
                • -
                • R&D instruction manual added to the R&D lab.
                • -
                • Fixed R&D disk commands not working.
                • -
                • Added portable power generators which run on solid plasma.
                • -
                • You can now set the numer of coins to produce in the mint.
                • -
                • Added two more portable power generators to R&D.
                • -
                -
              • -
              • Deeyach updated: -
                  -
                • New uniform for roboticists
                • -
                -
              • -
              • Neo updated: -
                  -
                • Game speed increased
                • -
                • Mining stacking machine no longer devours stacks larger than 1 sheet (it was only increasing its stock by 1 when given a stacked stack)
                • -
                • Stackable uranium ore added (a better sprite is needed, contributions are welcome)
                • -
                • Made Meteor gamemode actually do stuff
                • -
                • Made a bigger class of meteor
                • -
                • New R&D item: Advanced Energy Gun
                • -
                • Law priority clarified with regards to ion laws and law 0.
                • -
                -
              • - -
              • Veyveyr updated: -
                  -
                • Minor mapping fixes
                • -
                -
              • -
              • Uhangi updated: -
                  -
                • New red bomb suit for security.
                • -
                -
              • - -
              • Errorage updated: -
                  -
                • Slight mapping change to arrival hallway.
                • -
                -
              • -
              - -23 February 2011, Red Army Day -
                -
              • Uhangi updated: -
                  -
                • Antitox and Inaprovaline now mixable via chemistry.
                • -
                • Explosive Ordinance Disosal (EOD) suits added to armory and security.
                • -
                • Large beaker now holds 100 units of chemicals. (code by Slith)
                • -
                -
              • -
              • Rastaf0 updated: -
                  -
                • Secbot interface updated.
                • -
                • Syringe auto-toggels mode when full.
                • -
                • Captain's flask volume increased.
                • -
                -
              • -
              • Neo updated: -
                  -
                • Fixed the 'be syndicate' choice to actually work on nuke rounds.
                • -
                • Syndicates no longer win if they detonate the nuke on their ship.
                • -
                -
              • - -
              • Errorage updated: -
                  -
                • Added cloning manual. (Written by Perapsam)
                • -
                -
              • -
              • K0000 updated: -
                  -
                • Cult mode updates.
                • -
                • You can now read the arcane tome. It contains a simple guide for making runes.
                • -
                • Converting people doesnt give them word knowledge.
                • -
                • Sacrifice monkeys or humans to gain new words.
                • -
                • Total number of rune words set to 10
                • -
                • Some minor bugfixes.
                • -
                -
              • -
              - -20 February 2011, Sunday -
                -
              • Errorage updated: -
                  -
                • Slight updates to processing unit and stacking machine at the mining outpost.
                • -
                • Digging now yields sand, which can be smelted into glass.
                • -
                • Stacking machine can now stack reinforced metal, regular and reinforced glass too.
                • -
                • Engineers now have two copies of the singularity safety manual.
                • -
                -
              • -
              • Neo updated: -
                  -
                • Magboots now have a verb toggle like jumpsuit sensors.
                • -
                • Jumpsuit sensors are now in all jumpsuits, except tactical turtlenecks.
                • -
                • Tweaks to the AI report at round start.
                • -
                • Syndi-cakes now heal traitors/rev heads/etc much more than anyone else.
                • -
                • Containment fields zap once again.
                • -
                • Fire damage meter no longer lies about fire damage.
                • -
                -
              • -
              • Darem updated: -
                  -
                • Mass Spectrometer added to R&D. Load it with a syringe of blood and it will tell you the chemicals in it. Low reliability devices may yield false information.
                • -
                • Not all devices have a 100% reliability now.
                • -
                • Miners now have access to mint foyer and loading area. Only captain has access to the vault.
                • -
                • More stuff can be analyzed in the destructive analyzer, protolathe can produce intelicards.
                • -
                -
              • -
              • Rastaf0 updated: -
                  -
                • Added blast door button to atmospherics.
                • -
                • Toxins timer-igniter assemblies fixed.
                • -
                • Engineering secure storage expanded.
                • -
                • Added singularity telescreen.
                • -
                -
              • -
              - -18 February 2011, Friday -
                -
              • Errorage updated: -
                  -
                • New look for the bio suits. (Biosuit and hood sprites by Cheridan)
                • -
                • New radiation suits added along with radiation hoods and masks. Must wear complete set to get full protection.
                • -
                -
              • -
              • Rastaf0 updated: -
                  -
                • Binary translator cost reduced to 1 telecrystal.
                -
              • -
              • AtomicTroop updated: -
                  -
                • Mail Sorter job added.
                • -
                • Disposal system redone to allow for package transfers. Packages are routed to mail sorter room and then routed to the rest of the station
                • -
                • Disposal area moved. Old disposal area now just an incinerator and a small disposal into space.
                • -
                • New wrapping paper for sending packages.
                • -
                -
              • -
              • Veyveyr updates: -
                  -
                • New machine frame sprite.
                • -
                • Braincase sprites for mechs added. Not actually used, yet.
                • -
                -
              • -
              • Darem updates: -
                  -
                • Research and Development system is LIVE. Scientists can now research new advancements in technology. Not much can be made, right now, but the system is there. Technologies are researched by shoving items into the destructive analyzer. Circuit Imprinter, Destructive Analyzer, and Protolathe are controlled from the R&D console.
                • -
                • Autolathe, Protolathe, Destructive Analyzer, and Circuit Imprinter can now be built, taken apart, and upgraded. The basic frame for all of the above requires 5 metal.
                • -
                -
              • -
              - -15 February 2011, Tuesday -
                -
              • Rastaf0 updated: -
                  -
                • Added radio channels and headsets for miners (:h or :d ("diggers" lol)) and for cargo techs (:h or :q )
                • -
                • Added a personal headsets to HoP and QM.
                • -
                • Aliens now attack bots instead of opening control window.
                • -
                • All bots can be damaged and repaired.
                • -
                • All bots are effected to EMP now.
                • -
                • Atmos now starts with nitrous oxide in storage tank.
                • -
                -
              • -
              • Veyveyr updated: -
                  -
                • New look for the pipe dispenser.
                • -
                -
              • Errorage updated: -
                  -
                • Mining station will now charge properly.
                • -
                • Duffle bags (Money bags) can now be emptied.
                • -
                -
              • -
              - -14 February 2011, Valentine's day -
                -
              • Errorage updated: -
                  -
                • New Job! - Shaft Miners have finally been added and are available to play.
                • -
                • Mining outpost - A new mining outpost has been built, the mining dock on SS13 has been updated.
                • -
                -
              • -
              • ConstantA updated: -
                  -
                • Slight speed up for combat mechs..
                • -
                • Added H.O.N.K construction
                • -
                • Fixed bug with switching intent while in mecha.
                • -
                -
              • -
              - -
              12.02.2011, 01.00 GMT, r1021
              -
                -
              • Added Durand combat exosuit.
              • -
              • Players can modify operation permissions of newly constructed civilian mechs. Click on mech with ID card or PDA with ID inside.
              • -
              • Added robotics access to default mecha maintenance permissions (all mechs) and operation permissions (civilian models only).
              • -
              • Fixed double adminlog message of explosion proc.
              • -
              • Fixed accidental mecha wreckage deletion.
              • -
              • Tweaked mecha internal fire processing.
              • -
              • Added some mecha-related sounds.
              • -
              • Moved GaussRand to helpers.dm and added GaussRandRound helper proc.
              • -
              • Other small changes.
              • -
              - -
              11.02.2011, r1001-1020
              -
                -
              • Headsets upgraded. Shortcuts for channels are: :command :security scie:nce :engineering :medical. Also there is :binary :whisper :traitor and old good :h for your department. -
              • "One Click Queue" added: When you quickly click on two things in a row, it will automatically queue the second click and execute it after the default 'action delay' of 1 second after the first click. Previously you had to spam-click until 1 second had passed. THIS AFFECTS EVERYTHING. NEEDS TESTING. - Skie -
              • EMP effects added for further revisions. - Darem -
              • Goon stuff removed, AI/Bots leave behind hidden fingerprints, firedoors fixed, powersinks eat more, small map changes. - Mport -
              • Big map changes in engineering/robotics/science wing. - errorage -
              • Welder Fixed. Now burns your eyes again. - errorage -
              • 9x9 singularity sprite added. - Skie/Mport -
              • Ghetto surgery added. - Neophyte -
              • Nasty vent pump lag fixed. - Neophyte -
              • Mech gameplay and building updates. - ConstantA -
              - -
              08.02.2011, r999-1000
              -
                -
              • The amount of power the station uses should be lower. -
              • The singularity now changes size based upon how much energy it has -
              • New Machine: Particle Accelerator. -
              • It might need a better name/sprite but when put together properly and turned on it will shoot Accelerated Particles. -
              • The particles irradiate mobs who get in the way and move through solid objects for a short time. -
              • The Particle Accelerator parts are set up by using a Wrench followed by a Cable Coil, then finished with a screwdriver. -
              • When you shoot the Singularity Generator with Accelerated Particles it will spawn a Singularity. -
              • New layout for Engineering, might be changed up slightly in the next few days. -
              - -
              06.02.2011, r979
              -
                -
              • Jesus christ it's a new map what the fuck -
              • Just kidding, it's only minor changes to medbay/mechbay/cybernetics/R&D/toxins/robotics/chapel/theatre -
              • Okay so there's too many changes to list completely, but basically: toxins/R&D/virology/xenobiology are south through medbay; robotics/cybernetics/mechbay are where toxins used to be, the theatre and chapel are above medbay. -
              • Theatre is a new place for the Clown and Mime to play, there are some costumes available, a stage and backstage, and seating for the audience. -
              • R&D and Toxins have been combined together. R&D is still work in progress. You need to head south through medbay to get there. -
              • Medbay has been re-arranged slightly. Honestly, it's nothing, I bet you won't even notice anything different. There's also a new surgery suite, complete with pre-op and post-op rooms. -
              • Virology's been rearranged, but it is mostly in the same place still. -
              • Xenobiology is work in progress. [There's some fun stuff still being coded] -
              • The Chapel is now to the north. You'll probably be thinking something like "Goddamn, this place is fucking huge", but it's actually smaller than the previous chapel. -
              • Robotics and related stuff is also work in progress - however, everything needed for making borgs is there. Note: de-braining will now be done by surgeons in medbay, rather than roboticists in robotics, in case you're wondering where your optable went. -
              • I added color-coded pipes in the areas I was working on. Red pipes run from air siphons and feed into the waste loop; blue pipes run from air vents and pump whatever atmos is set to pump. Yeah, there's TWO pipe networks on the station, who knew? Well, some of you probably did, but I didn't! -
              • Please bring all complaints/ideas/suggestions/bugfixes to: This awesome thread on our forums -
              • This update brought to you by: veyveyr and Rookie, with help from friends! [Now you know who to strangle, please be gentle q_q] -
              - -
              05.02.2011, r968
              -
                -
              • This really needs to be updated more often. -
              • Various map updates have been applied with many more to come. Expect overhauls! -
              • Mining system nearing completion. -
              • Massive overhaul to the electricity systems, the way singularity makes power, and various related functions. -
              • Mime spawns with White Gloves instead of Latex Gloves (apparently there's a difference!) -
              • A new event has been- CLANG! What the fuck was that? -
              • Ion storm laws should be much more interesting. -
              • Security reports should no longer list traitor heads/AIs as possible revs/cultists, nor should nuke operatives ever get named for anything. -
              • Pens are much more versatile and user friendly. -
              • Mech building is rapidly on its way! Ripleys can be built, consult your quartermasters. -
              • Traitors now have several new things they can steal. -
              • Some surgeries coded to accompany the new operating room and surgery tools. -
              • Research and Design is continuing development and should be rolled out shortly. -
              • Various sprites added, tweaked, scrapped and fixed. -
              - -

              Changelog

              -
              05.02.2011, r968
              -
                -
              • This really needs to be updated more often. -
              • Various map updates have been applied with many more to come. Expect overhauls! -
              • Mining system nearing completion. -
              • Massive overhaul to the electricity systems, the way singularity makes power, and various related functions. -
              • Mime spawns with White Gloves instead of Latex Gloves (apparently there's a difference!) -
              • A new event has been- CLANG! What the fuck was that? -
              • Ion storm laws should be much more interesting. -
              • Security reports should no longer list traitor heads/AIs as possible revs/cultists, nor should nuke operatives ever get named for anything. -
              • Pens are much more versatile and user friendly. -
              • Mech building is rapidly on its way! Ripleys can be built, consult your quartermasters. -
              • Traitors now have several new things they can steal. -
              • Some surgeries coded to accompany the new operating room and surgery tools. -
              • Research and Design is continuing development and should be rolled out shortly. -
              • Various sprites added, tweaked, scrapped and fixed. -
              - -
              20.01.2011, r894
              -
                -
              • Pipes can now be removed and re-attached by wrenching them. -
              • Mining system continues to develop. Still unaccessible to players. -
              • Various map changes. Some minor lag causing things were fixed. -
              • Admins have a new tool: They can now give any spell to anyone. Hurray! -
              • Imadolazine now works. Maybe? -
              • Singularity now releases itself if fed too much and will potentially explode. -
              • Magboots now successfully prevent you from getting pulled into the singularity. -
              • Strike teams immune to facehuggers. Why? I dunno. -
              • Many reagent containers are adjustable so you can pour the exact amount you need. -
              • No more emitters working in space, Collectors and collector controllers now ID lockable. -
              • Christmas Contest plaque finally added. It's near the armor/warden's office. -
              • Rocks fall, everyone dies. -
              • All cyborgs and robots can now be named. Just use a pen on the cyborg's frame before the brain is inserted. -
              • Knock spell now unbolts doors as well as opens them. -
              • New cultist runs and other changes. -
              • Added surgery tools for eventual surgery system. -
              • Autolathe and Circuit Printer animations redone. Yay pretty icons. -
              • AI law changes/uploads are now tracked (admin viewable). -
              • Revheads now get a PDA uplink instead of a headset one. -
              • Added a penlight. -
              • Science Research and Development tech tree uploaded. Not really accessible by anyone yet, though. -
              - -
              14.01.2011, r853
              -
                -
              • Changlings Overhauled. Now function kinda like alium (using an internal chemical reserve instead of plasma) with each ability requiring a certain amount of chemicals to activate. Both venoms removed. Several "Dart" abiliites added. They allow the changling to deafen, blind, mute, paralyze, or even transform (dead) targets. -
              • Carp meat now contaminated with Carpotoxin. Anti-toxin negates the poison, however. -
              • New Reagent: Zombie Powder: Puts subjects into a deathlike state (they remain aware, though). Each unit of Zombie Powder requires 5 units of Carpotoxin, Sleeping Toxin, and Copper. -
              • Various alium fixes. -
              • Matches now available from smokes machine. -
              • Megabomb bug fixed. Bombs with timers/signalers won't detonate after the timer/ signaler is removed. -
              • New Disease: Rhumba Beat. Functions like GBS with a few exceptions. Not only available by admindickery. -
              • More mining fixes/changes. -
              • Ghost can now teleport to AI sat, Thunderdome, and Derelict. -
              • New gimmick clothes -
              • Constructing Glass Airlocks now use one sheet of R.Glass. -
              • Windows mow always appear above grills and pipes always above lattices. -
              • Added FireFighter mecha and various mecha fixes. -
              • Deconstructed walls now leave proper floor tiles (that can be pried up like normal) and remember what kind of floor they were before the wall was made. -
              • Carded AIs no longer take damage while in unpowered areas. -
              • Chaplains can now name their religion at round start. -
              • New Recipies: Clown Burger (1 Clown wig, 5 flour), Mime Burger (1 beret, 5 flour), Cuban Carp (1 carp fillet, 1 chili, 5 flour). -
              • Napalm nerfed a bit. Produces ~25% less plasma but it's all concentrated in a single tile (will still spread, though). -
              • Reagent bottles can, once again, be put into grenades. -
              • Various minor map changes. -
              - -
              08.01.2011,8:00PST, r820
              -
                -
              • Holograms (AI controled psudo-mobs) added. Currently only admin spawn. -
              • Pre-spawned pills no longer have randomized image. -
              • Bridge reorganized. -
              • Automated turrets now less dumb. Additionally, turrets won't target people laying down. -
              • Cultists now automatically start known words for add cultist ritual. Also, converted cultists start knowning a word. -
              • Supply ship no longer can transport monkeys. Also, monkeys are no longer orderable from QM. -
              • Meat Crate added to QM. -
              • Corn can now be blended in a blender to produce corn oil which is used to create glycern. -
              • Request Consoles added across the station. Can be used to request things from departments (and slightly easier to notice then the radio). -
              • Centcom reoragnized a fair bit. Not that players care but admins might enjoy it. -
              • There is now a toggable verb that changes whether you'll turn into an alium or not. -
              • Hair sprited modified. -
              • Napalm and Incendiary Grenades both work now. Have fun setting things on fire. -
              • Binary Translater traitor item added. It allows traitors to hear the AI (it functions like a headset). -
              • New Disease: Pierrot's Throat. Enjoy, HONK! -
              • Robotic Transformation (from Robrugers) and Xenomorph Transformation (from xenoburgers) now curable. -
              • Mining added. Only accessible by admins (and those sent by admins). Very much WIP. -
              • Alium Overhaul. Divided into multiple castes with distinct abilities. -
              • New AI Modules: The goody two-shoes P.A.L.A.D.I.N. module and it's evil twin T.Y.R.A.N.T. Only the former actually spawns on the station. -
              • Lizards added. They run away and shit. -
              • PDA overhaul. Doesn't change anything for humans, just makes coders happy. -
              • Firesuits redone to look less like pajamas and instead like firesuits. Fire lockers also added. -
              • New Mecha: H.O.N.K. -
              • Deployable barriers added. Can be locked and unlocked. -
              • Mecha bay (with recharging stations) added. -
              • Bunny Ears, Security Backpacks, Medical Backpacks, Clown Backpacks, and skirt added. -
              • Various wizard changes. New Wizard Spell: Mind Swap. Swap your mind with the targets. Be careful, however: You may lose one of your spells. Wizards are no longer part of the crew and get a random name like the AI does. Wizards can change their known spells with their spellbook but only while on the wizard shuttle. -
              • Circuit Imprinter: Using disks from the various deparments, new circuit boards and AI modules can be produced. -
              • Heat Exchanging pipes added and various pipe/atmos changes. -
              • Ghost/Observer teleport now works 100% of the time. -
              - -
              17.12.2010,11:00GMT
              -
                -
              • You need an agressive grip to table now, as well as being within one tile of the table (to nerf teletabling).
              • -
              • Teleport only runs once at the beginning of the round, hopefully reducing the lag in wizard rounds.
              • -
              • Wizards can't telepot back to their shuttle to afk now.
              • -
              • Someone added it a while ago and forgot to update the changelog - syndies in nuke need to move their shuttle to the station's zlevel first.
              • -
              • Bunch of other stuff.
              • -
              - -
              Sunday, November 21, 3:34 PST
              -
                -
              • Bug fixes, not going into detail. Lots and lots of bug fixes, mostly regarding the new map
              • -
              • CMO has a more obvious lab coat, that looks nicer than the original
              • -
              • CMO also has a stamp now
              • -
              • QM has a denied stamp
              • -
              • Cyborgs got tweaked. Laws only update when checked. Also have wires that can be toyed
              • - with for various effects, including AI sync, and law control -
              • Second construction site similar to the original
              • -
              • Prison station has been tweaked, now includes a lounge, and toilets
              • -
              • Detective's revolver starts with a reasonable number of bullets
              • -
              • Prison teleporter to the courtroom now exists
              • -
              • AI related stuff: More cameras, oh, and bug fixes!
              • -
              • Emergency storage moved
              • -
              • Random AI law changes. New law templates, and variables. Old variables were tweaked and some of the crappy templates were removed
              • -
              • Ghosts can teleport to the derelict now
              • -
              • Respriting of a bunch of stuff as well as graphical fixes
              • -
              • Kitchen is attached to the bar again
              • -
              • General map tweaks and fixes
              • -
              • Wardens fixed for rev rounds
              • -
              • 5-unit pills now work
              • -
              • APCs added and moved
              • -
              • Cola machine added. Contains various flavours of soda. Also added new snacks to the now named snack machine. Water cooler added, just apply an empty glass to it
              • -
              • Salt & Pepper have been added, as part of the food overhaul
              • -
              • More bug fixes. There was a lot
              • -
              - -
              Tuesday, November 16, 00:20 GMT
              -
                -
              • Cruazy Guest's map is now live.
              • -
              • Entire station has been rearranged.
              • -
              • Prison Station added, with Prison Shuttle to transport to and from.
              • -
              • New Job: Warden. Distributes security items.
              • -
              • The new map is still in testing, so please report any bugs or suggestions you have to the forums.
              • -
              • AI Liquid Dispensers, Codename SLIPPER, have been added to the AI core. They dispense cleaning foam twenty times each with a cooldown of ten seconds between uses. Mounted flashes have also been included.
              • -
              • Clown stamp added to clown's backpack.
              • -
              - -
              Sunday, November 14, 18:05
              -
                -
              • Major food/drink code overhaul. Food items heal for more but not instantly. Poison, Drug, and "Heat" effects from food items are also non-instant.
              • -
              • Preperation for one-way containers and condiments.
              • -
              • New Reagents: Nutriment, Ketchup, Soysauce, Salt, Pepper, Capsaicin Oil, Frost Oil, Amatoxin, Psilocybin, Sprinkles
              • -
              • New Food Item: Chaos Donut: 1 Hot Sauce + 1 Cold Sauce + 1 Flour + 1 Egg. Has a variable effect. NOT DEADLY (usually).
              • -
              • New Drug: Ethylredoxrazine: Carbon + Oxygen + Anti-Toxin. Binds strongly with Ethanol.
              • -
              • Tape Recorders added! Now you can actually PROVE someone said something!
              • -
              • Amospherics Overhaul Started: It actually works now. You can also build pipes and create function air and disposal systems!
              • -
              • Walls are now smooth.
              • -
              • Alcohol no longer gets you as wasted or for as long.
              • -
              • QM job split into QM and Cargo Technicians. QM has his own office.
              • -
              • Doors can no longer be disassembled unless powered down and unbolted.
              • -
              • New Job: Chief Medical Officer. Counts as a head of staff and is in charge of medbay. Has his/her own office and coat.
              • -
              • Wizarditis Bottle no longer in virus crate.
              • -
              - -
              Friday, November 5, 19:29
              -
                -
              • The ban appeals URL can now be set in config.txt
              • -
              • Secret mode default probabilities in config.txt made sane
              • -
              • Admin send-to-thunderdome command fixed to no longer send people to the other team's spawn.
              • -
              • Having another disease no longer makes you immune to facehuggers.
              • -
              • Magboots are now available from EVA.
              • -
              • Changeling death timer shortened. Destroying the changeling's body no longer stops the death timer.
              • -
              • Cult mode fixes.
              • -
              • Fixed cyborgs pressing Cancel when choosing AIs.
              • -
              • The play MIDIs setting now carries over when ghosting.
              • -
              • Admins can now see if a cyborg is emagged via the player panel.
              • -
              • PAPERWORK UPDATE v1: Supply crates contain manifest slips, in a later update these will be returnable for supply points. -
              • The Supply Ordering Console (Request computer in the QM lobby) can now print requisition forms for ordering crates. In conjunction with the rubber stamps, these can be used to demonstrate proper authorisation for supply orders.
              • -
              • Rubber stamps now spawn for each head of staff.
              • -
              • The use of DNA Injectors and fueltank detonations are now admin-logged.
              • -
              • Removed old debug code from gib()
              • -
              - -
              Tuesday, November 2, 19:11(GMT)
              -
                -
              • Finished work on the "cult" gamemode. I'll still add features to it later, but it is safe to be put on secret rotation now.
              • -
              • Added an energy cutlass and made a pirate version of the space suit in preparation for a later nuke update.
              • -
              • Changeling now ends 15 minutes after changeling death, unless he's ressurected.
              • -
              • Further fixing of wizarditis teleporting into space.
              • -
              • Fixed the wise beard sprite.
              • -
              • Fixed missing sprite for monkeyburgers.
              • -
              • Fixed Beepsky automatically adding 2 treason points to EVERYONE.
              • - -
              - -
              Thursday, October 28, 19:30(GMT)
              -
                -
              • Sleepers and disposals now require two seconds to climb inside
              • - -
              • Hydroponics crate ordered in QMs doesnt spawn too many items
              • -
              • Replacement lights crate can be ordered in QM.
              • -
              • Added space cleaner and hand labeler to Virology.
              • -
              • Welder fuel tanks now explode when you try to refuel a lit welder.
              • -
              • Made clown's mask work as a gas mask.
              • -
              • 9 new cocktails: Irish Coffee, B-52, Margarita, Long Island Iced Tea, Whiskey Soda, Black Russian, Manhattan, Vodka and Tonic, Gin Fizz. Refer to the wiki for the recipes.
              • -
              • Kitchen update: -
                  -
                • -New Microwave Recipies: Carrot Cake (3 Flour, 3 egg, 1 milk, 1 Carrot), Soylen Viridians (3 flour, 1 soybeans), Eggplant Parmigania (2 cheese, 1 eggplant), and Jelly Donuts (1 flour, 1 egg, 1 Berry Jam), Regular Cake (3 flour, 3 egg, 1 milk), Cheese Cake (3 flour, 3 egg, 1 milk), Meat Pies (1 meat of any kind, 2 flour), Wing Fang Chu (1 soysauce, 1 xeno meat), and Human and Monkey Kabob (2 human or monkey meat, metal rods).
                • -
                • - Ingredients from Processor: Soysauce, Coldsauce, Soylent Green, Berry Jam.
                • -
                • - Sink added to kitchen to clean all the inevitable blood stains and as preperation for future cooking changes.
                • -
                • - The food processor can't be abused to make tons of food now.
                • - -
                -
              • Multiple tweaks to virology and diseases: -
                  -
                • - Added wizarditis disease.
                • -
                • - Spaceacillin no longer heals all viruses.
                • -
                • - Some diseases must be cured with two or more chemicals simultaneously.
                • -
                • - New Virology design including an airlock and quarantine chambers.
                • -
                • - Made vaccine bottles contain 3 portions of vaccine.
                • -
                • - Lots of minor bug fixes.
                • - -
                - -
              - -
              Monday, October 18, 06:24(GMT)
              -
                -
              • Added virology profession with a cosy lab in northwestern part of medbay.
              • -
              • Virology related things, like taking blood samples, making vaccines, splashing contagious blood all over the station and so on.
              • -
              • Added one pathetic disease.
              • -
              • Virus crates are now available from the quartermasters for 20 points.
              • -
              • The DNA console bug (issue #40) was fixed, but I still made the DNA pod to lock itself while mutating someone.
              • -
              • Added icons for unpowered CheMaster and Pandemic computers
              • -
              • Added some sign decals. The icons were already there, but unused for reasons unknown.
              • -
              • Some map-related changes.
              • -
              - -
              Wednesday, October 13, 14:12(GMT)
              -
                -
              • Crawling through vents (alien) now takes time. The farther destination vent is, the more time it takes.
              • -
              • Cryo cell healing ability depends on wound severity. Grave wounds will heal slower. Use proper chemicals to speed up the process.
              • -
              • Added sink to the medbay.
              • -
              • Bugfixes: -
                  -
                • - Some reagents were not metabolized, remaining in mob indefinitely (this includes Space Cola, Cryoxadone and cocktails Kahlua, Irish Cream and The Manly Dorf).
                • -
                • - Fixed placement bug with container contents window. Also, utility belt window now doesn't obscure view.
                • -
                -
              • -
              - -
              Sunday, October 10, 14:25(GMT)
              -
                -
              • Scrubbers in the area can be controlled by air alarms. Air alarm interface must be unlocked with an ID card (minimum access level - atmospheric technician), usable only by humans and AI. Panic syphon drains the air from affected room (simple syphoning does too, but much slower).
              • -
              • Sleeper consoles inject soporific and track the amounts of rejuvination chemicals and sleep toxins in occupants bloodstream.
              • -
              • Flashlights can be used to check if mob is dead, blind or has certain superpower. Aim for the eyes.
              • -
              • Radiation collectors and collector controls can be moved. Secured\unsecured with a wrench.
              • -
              • Air sensors report nitrogen and carbon dioxide in air composition(if set to).
              • -
              • Air Control console in Toxins.
              • -
              • Additional DNA console in genetics
              • -
              • Enough equipment to build another singularity engine can be found in engineering secure storage
              • -
              • Air scrubber, vent and air alarm added to library
              • -
              • Air alarm added to brig
              • -
              • Air scrubbers in Toxins turned on, set to filter toxins
              • -
              • Empty tanks, portable air pumps and similar can be filled with air in Aft Primary Hallway, just connect them to the port. Target pressure is set by Mixed Air Supply console in Atmospherics (defaults to 4000kPa).
              • -
              - -
              Wednesday, October 6, 18:36
              -
                -
              • Fixed the Librarian's suit - its worn iconstate wasn't set.
              • -
              • Fixed some typos.
              • -
              • Monkey crates are now available from the quartermasters for 20 points.
              • -
              • Corpse props removed from zlevel 8 as they were causing issues with admin tools and the communications intercept.
              • -
              • Cleaned up the default config.txt
              • -
              • Added a readme.txt with installation instructions.
              • -
              • Changed the ban appeals link to point to our forums for now - this'll be a config file setting soon.
              • -
              - -
              Tuesday, October 5, 01:41
              -
                -
              • Fixes to various nonworking cocktails.
              • -
              • More map and runtime error fixes.
              • -
              • Nuke operative headsets should be on an unreachable frequency like department headsets.
              • -
              • Another AI Malfunction change: Now once the AI thinks enough APCs have been hacked, it must press a button to start the timer, which alerts the station to its treachery.
              • -
              • Blob reskinned to magma and increased in power.
              • -
              • The HoS now has an armored greatcoat instead of a regular armor vest.
              • -
              • Admin logs now show who killswitched a cyborg.
              • -
              • The roboticist terminal now lets you see which AI a cyborg is linked to.
              • -
              • Malf AIs are no longer treated as inactive for the purpose of law updates and cyborg sync while hacking APCs.
              • -
              • Traitor AIs are now affected by Reset/Purge/Asimov modules, except law 0.
              • -
              • AI core construction sprites updated.
              • -
              • Securitrons removed from the Thunderdome.
              • -
              • An APC now supplies power to the bomb testing area, and has external cabling to supply it in turn.
              • -
              • A new variant freeform module has been added to the AI Upload room.
              • -
              • The changeling's neurotoxic dart has been made more powerful - this will likely be an optional upgrade from a set of choices, akin to wizard spells.
              • -
              • Some gimmick clothes moved to a different object path.
              • -
              • The chameleon jumpsuit should now be more useful - it includes job-specific jumpsuits as well as flat colours.
              • -
              - -
              Wednesday, September 29, 15:40
              -
                -
              • Bartender update! Bartender now has a Booze-O-Mat vending machine dispensing spirits and glasses, which he can use to mix cocktails. Recipes for cocktails are available on the wiki.
              • -
              • The barman also now has a shotgun hidden under a table in the bar. He spawns with beanbag shells and blanks. Lethal ammo is obtainable from hacked autolathes.
              • -
              • Dead AIs can once more be intelicarded, however in order to be restored to functionality they must be repaired using a machine in the RD office.
              • -
              • Silicon-based lifeforms have metal gibs and motor oil instead of blood.
              • -
              • Aliens now have a death message.
              • -
              • Intelicarded AIs can now have their ability to interact with things within their view range reactivated by the person carrying the card.
              • -
              • New AI cores can be constructed from victimsvolunteers.
              • -
              • Verbs tweaked.
              • -
              • Intelicarded AIs can be deleted.
              • -
              • RD office redesigned, and the RD now spawns there.
              • -
              • The AI can now choose to destroy the station on winning a Malf round.
              • -
              • General bugfixes to AIs, constructed AIs and decoy AIs.
              • -
              • Hats no longer prevent choking.
              • -
              • Some extra gimmick costumes are now adminspawnable.
              • -
              • AI health is now displayed on their status tab.
              • -
              • AI upload module now requires you to select which AI to upload laws to in case of multiple AIs.
              • -
              • Cyborgs now choose an AI to sync laws with upon creation.
              • -
              • Law office redesigned.
              • -
              • Roboticists no longer have Engineering access.
              • -
              • More fixes to areas which had infinite power due to having no assosciated APC.
              • -
              • Meatbread slices are no longer infinite.
              • -
              • Malf rounds no longer end if a non-malfunctioning AI is killed.
              • -
              • Cigarettes now have directional sprites.
              • -
              • AI Core circuitboard spawns in the RD office.
              • -
              • AI Satellite now has cameras and properly-wired SMES batteries.
              • -
              • Decoy AIs can no longer be moved.
              • -
              • Several runtime errors have been fixed.
              • -
              • Nuke rounds will now end properly on a station or neutral victory.
              • -
              • Riot shields have been nerfed and are now only available in the armory and in riot crates.
              • -
              • Foam dart crossbows, cap guns and caps can now be won as arcade prizes.
              • -
              • AI Malfunction has been redesigned - the AI must now hack APCs in order to win. More APCs hacked makes the timer tick faster.
              • -
              • Hydroponics now has a MULEbot station.
              • -
              • Changeling mode has been added to the game and is now in testing.
              • -
              • Electrified airlocks should now only zap you once if you bump into them while running.
              • -
              • Chemistry and Toxins access has been removed from Botanists.
              • -
              • General bugfixes and map tweaks.
              • -
              - -
              Sunday, September 26, 17:51
              -
                -
              • Riot shields! One in every security closet, and a few in armory. Also orderable from QM.
              • -
              - -
              Tuesday, September 21, 17:51
              -
                -
              • New experimental UI for humans by Skie. Voice out if it has problems or you don't like it.
              • - ---> YOU CAN CHOOSE UI FROM PREFERENCES <--- -
              • Hydroponics: Now you can inject chemicals into plants with a syringe. Every reagent works differently.
              • -
              • Hydroponics: Added a small hoe for uprooting weeds safely. Botanists now have sissy aprons.
              • -
              • New random station/command names and verbs.
              • -
              • Dead AIs can no longer be intellicarded and the steal AI objective is now working.
              • -
              • Aliens now bleed when you hit them, as well as monkeys.
              • -
              • Hurt people and bodies leave blood behind if dragged around when they are lying. Sprites to be updated soon...
              • -
              • Fixed several run-time errors in the code. Also food doesn't deal damage anylonger in some cases.
              • -
              • Blobs and alien weeds slowed down some. Plant-b-gone buffed some.
              • -
              • Fixed monkeys and aliens not being able to deal damage to humans with items.
              • -
              • Monkeys now slip on wet floor.
              • -
              - -
              Friday, September 17, 23:03
              -
                -
              • The Lawyer now starts in his snazzy new office.
              • -
              • Law Office accesslevel added. Currently, the Lawyer and the HoP begin with this.
              • -
              • Robotics access can now be added or removed from the HoP's computer.
              • -
              • Robotics, the captain's quarters, the cargo lobby and the staff heads office now have APCs and can lose power like the rest of the station.
              • -
              • Toxins mixing room is now a separate area for power and fire alarm purposes, as it already had its own APC.
              • -
              - -
              Thursday, September 16, 20:11
              -
                -
              • Added the Lawyer job.
              • -
              • Doors can now be constructed and deconstructed. This is being playtested, expect the specifics to change.
              • -
              • Fixed certain jobs which were supposed to have stuff spawning in their backpack that just wasn't getting spawned.
              • -
              - -
              Monday, September 13, 13:30
              -
                -
              • Bunch of new announcer sounds added
              • -
              • Minor lag fix implementation in the pipe system
              • -
              • You can now hear ghosts... sometimes
              • -
              • Seed bags and nutrients can now be pocketed
              • -
              - -
              Monday, September 12, 12:48
              -
                -
              • New kitchen stuff: New recipes (Meatbread, Cheese, Omelette Du Fromage, Muffins), new chef's knife and trays (Spawn in the meat locker) and milk (spawns in the fridge). Recipes are as follows: - -Cheese: milk on food processor - -Cheese wedge: Slice the cheese wheel with the chef's knife - -Omelette Du Fromage: 2 eggs 2 cheese wedges - -Muffin: 2 eggs 1 flour 1 carton of milk - -Meatbread: 3 meats (whatever meats) 3 flour 3 cheese. Can be sliced. - Cheese_amount is actually displayed on the microwave.
              • -
              • Profession-special radio channels now have color.
              • -
              • AI card not retardedly lethal anymore, for anyone that didn't notice
              • -
              • HYDROPONICS OVERHAUL, credit goes to Skie and Numbers. Wood doesn't have the entity so the tower caps cannot be harvested. For now.
              • -
              • Bar is now barman-only, access-wise. No more shall the entire station trump inside the bar and choke the monkey.
              • -
              • Prepping ground for Barman update (SPRITE ME SOME GODDAMN BOTTLES)
              • -
              -
              Thursday, September 2, 22:45
              -
                -
              • Ghosts can no longer release the singularity.
              • -
              • Sprites added in preparation for a Hydroponics update
              • -
              • A decoy AI now spawns in the AI core during Malfunction rounds to reduce metagaming.
              • -
              • libmysql.dll added to distribution.
              • -
              • Aircode options restored to default configuration.
              • -
              • AIs properly enter powerloss mode if the APC in their area loses equipment power.
              • -
              • Hydroponics crates added to Hydroponics, containing Weed-B-Gone
              • -
              • Airlock electrification now actually works properly.
              • -
              • Karma database error message updated.
              • -
              • Cyborgs choosing the standard module no longer become invisible except for a pair of glowing red eyes.
              • -
              • Aliens now have a hivemind channel, accessed like departmental radio channels or robot talk with ':a'.
              • -
              • Full donut boxes no longer eat whatever item is used on them and disappear.
              • -
              -
              Monday, August 30, 16:24
              -
                -
              • PDA user interface has been given a graphical overhaul. Please report any problems with it on the issue tracker.
              • -
              • Personal lockers are once again available in the lockerroom
              • -
              • BUGFIX: Xenoburger iconstate was accidentally removed in an earlier revision. This has been fixed.
              • -
              • Some of the default messages have been changed.
              • -
              • Additional sprites added for plants and weeds in preparation for an expansion of Hydroponics.
              • -
              • A schema script is now available for setting up the SQL database.
              • -
              - -
              Sunday, August 29, 05:09
              -
                -
              • The Robotics Crate no longer exists. Quartermasters can now order a MULEbot crate for 20 points, or a Robotics Assembly crate for 10 points. The latter provides 4 flashes, 3 proximity sensors, two 10k charge power cells and an electrical toolbox, and requires a roboticist or a head of staff to open.
              • -
              • Traitor AIs no longer lose their Law 0 in the event of power loss.
              • -
              • Administrators can now toggle the availiabilty of their right-click verbs to prevent accidental usage while playing.
              • -
              • Tool Storage vending machine is now a proper object. (code cleanup)
              • -
              • Buckets are now available from autolathes.
              • -
              • Four generic remote signaller PDA cartridges are now stocked in the Tool Storage vending machine.
              • -
              • AI status display density adjusted.
              • -
              - -
              Thursday, August 26, 21:07
              -
                -
              • Open Source Release Thanks to Mport for releasable singularity code.
              • -
              • Cyborgs redone Thanks again to Mport for this, cyborgs are totally different now.
              • -
              • Engine Monitor PDA app is now Power Monitor PDA app, and actually works.
              • -
              • AI State Laws verb now allows the AI to choose which laws to state, in case of traitor AIs or laws ordering it not to state them. Hopefully this will cut down on 'OMG THE AI IS COPYING AND PASTING' metagaming.
              • -
              • Power Monitor circuitboard isn't mislabeled as Mass Driver Control any more.
              • -
              • Traitor and Rev-head clowns lose the clumsiness gene - this should make trying to flash people in Rev mode less of an exercise in frustration.
              • -
              • A sink has been added to the Fitness room - this lets you wash dirty and bloodstained clothing and equipment.
              • -
              • Blast doors and firedoors no longer open by just bumping into them.
              • -
              • The bar and kitchen now open onto the same seating area. The old cafeteria area is now used as a lockerroom to replace the old one which was displaced by Hydroponics.
              • -
              • The bar now has a space piano with which you can entertain and annoy the crew.
              • -
              • LIBRARY A library has been added to the station in the escape arm in order to educate the crew. The new Librarian job is available to manage it. Crewmembers can request and read books, or write and bind their own books for upload to a persistent database.
              • -
              • The supply of flashbangs available from Security has been reduced to cut down on people constantly flashbanging the escape shuttle.
              • -
              • InteliCards are available in various locations to allow the retrieval of valuable AI personality data in the event of catastrophic station damage.
              • -
              - -
              Friday, August 06, 20:32
              -
                -
              • Hydroponics/Botany Added Credit goes to Skie and the folks over at the independent opensource SS13 branch, this is their code. It's lacking a lot, but it's a great start!
              • -
              • Way more tweaks than I can remember. Shouldn't wait so long between changelog updates.
              • -
              -
              Tuesday, July 13, 22:35
              -
                -
              • Singularity Engine Added Oh God we're all going to die (All credit on this one goes to Mport2004)
              • -
              • 'Purge' AI module added - purges ALL laws (except for law 0). Will probably change this to a Syndicate only item
              • -
              • Cyborgs now spawn with a power cell. Should prevent stupid cyborg deaths (and also pave the way for starting as a cyborg once more bugs are fixed)
              • -
              -
              Saturday, July 10, 15:10
              -
                -
              • Examining a player will now tell you if their client has disconnected.
              • -
              • Examining a brain will now tell you if it's owner is still connected to the game.
              • -
              • Alien Queens can make facehuggers. Facehuggers can make larva. Larva can grow into xenos! Xenos can become queens! The circle of life~
              • -
              • Some powernet bug fixes: Bad list and division by zero.
              • -
              -
              Friday, July 09, 05:16
              -
                -
              • Tweaked crate costs for Quartermaster.
              • -
              • Increased metal available in Robotics.
              • -
              • Added department-specific headsets. Engineering, Medical, Command, and Security all receive special headsets capable of broadcasting on a standard frequency PLUS a secure frequency only available to headsets of the same type. Precede say messages with ":h" to use.
              • -
              - -
              Tuesday, July 06, 19:16
              -
                -
              • Prayer command added.
              • -
              • State Laws command for AI added.
              • -
              • Disabled Lockdown command for AI. Too server heavy.
              • -
              • Crew manifest and various station databases should properly update when late arrivals join the game, now.
              • -
              • Quartermasters will receive 10 points every five minutes. This will probably be nerfed heavily, but we'll give it a shot anyhow.
              • -
              • Fixed a bug with doors/airlocks. (Thanks Mport2004)
              • -
              - -
              Sunday, April 25, 18:53
              - -
            • - New graphics: -
                -
              • - Side Facing Sprites: Player sprites will now face in all directions when moving. Holy shit! -
              • -
              -
            • - -
              Monday 2.0, April 19, 2100
              -
                -
              • - New features: -
                  -
                • - Disposal System: The station now has a fully functional disposal system for throwing away nuclear authentication disks and old, dirty clowns. -
                • -
                • - Breakable Windows: Windows are breakable by projectiles and thrown items (including people), shards hurt your feet. -
                • -
                • - Status Display: Station escape shuttle timers now function as status displays modifiable from the bridge. -
                • -
                • - Space Heater: Space heaters for heating up cold spaces, in space. -
                -
              • -
              • - New items: -
                  -
                • - Welding Mask: Helps engineers shield their eyes when welding. -
                • -
                • - Utility Belt: Function as toolboxes equippable in the belt slot. -
                • -
                • - Mouse Trap: Hurt your feet, especially if you aren't wearing shoes! -
                • -
                • - Power Sink: Traitor item that rapidly drains power. -
                • -
                -
              • -
              • - New graphics: -
                  -
                • - North Facing Sprites: Player sprites will now face north when moving north. -
                • -
                • - Hidden Pipes: Pipes are now hidden underneath floor tiles. -
                • -
                -
              • -
              • - New robot: Medibot -
                  -
                • - Automatically attempts to keep crewmembers alive by injecting them with stuff. -
                • -
                -
              • -
              • - New robot: Mulebot -
                  -
                • - Allows quartermasters to automatically ship crates to different parts of the station. -
                • -
                -
              • -
              - -
              Funday, December 31, 2099
              -

              "FINALLY, DEV IS OUT"

              -
                -
              • - Changes: -
                  -
                • - Atmos system GREATLY OPTIMIZED! -
                • -
                • - Brand new station layout! -
                • -
                • - Robust chemical interaction system! -
                • -
                • - HOLY FUCK PLAYING THIS GAME ISN'T LIKE TRODDING THROUGH MOLASSES ANYMORE -
                • -
                • - Feature: If two players collide with "Help" intent, they swap positions. -
                • -
                -
              • -
              - -
              Tuesday, February 23, 2010
              -
                -
              • - OH NO STRANGLING GOT NERFED: Insta-strangling (hopefully) removed. Victim no longer instantly loses consciousness. -
              • -
              - -
              Sunday, February 21, 2010
              -
                -
              • - Cloning Machine: The Geneticist spilled coffee on the Genetics Machine's revival module and it was too costly to replace! -
                  -
                • - Clones may or may not have horrible genetic defects. -
                • -
                -
              • -
              - -
              Thursday, February 18, 2010
              -
                -
              • - New feature: Obesity from overeating in a short period of time. -
              • -
              - -
              Sunday, February 14, 2010
              -
                -
              • - New feature: Station destruction cinematic if the crew loses in AI Malfunction or Nuclear Emergency. -
              • -
              • - New Position: Tourist -
                  -
                • - Centcom has entered the lucrative business of space tourism! Enjoy an event-filled vacation on the station, and try not to get killed. -
                • -
                • - Guest accounts are now restricted to selecting Tourist in Character Setup. -
                • -
                -
              • -
              - -
              Friday, February 5, 2010
              -
                -
              • - AI: Added 30 second cooldown to prevent spamming lockdowns. -
              • -
              - -
              Wednesday, February 2, 2010
              -
                -
              • - Feature: Character preview in Character Setup! -
              • -
              - -
              Tuesday, February 2, 2010
              -
                -
              • - New item: Drinking glasses that you can fill with water. -
              • -
              • - Feature: Sounds now pan in stereo depending on your position from the source. -
              • -
              - -
              Saturday, December 5, 2009
              -
                -
              • - Traitor tweak: Agent cards can now be forged into a fake ID. -
              • -
              - -
              Friday, December 4, 2009
              -
                -
              • - Supply Dock 2.0: The Supply Dock has been redesigned and now features conveyer belts! Amazing! -
              • -
              • - New uniforms: The Research Director, Chief Engineer, and the research jobs have new uniforms. The Head of Security has a cool new hat which happens to be his most prized possession. -
              • -
              • - Merged research: The first act of the Research Director is to merge Toxins and Chemistry into a single Chemical Lab. Hooray! -
              • -
              • - Robot tweak: You can now observe robots using the observe command. -
              • -
              • - Stamps: The heads now have stamps to stamp papers with, for whatever reason. -
              • -
              - -
              Monday, November 30, 2009
              -
                -
              • - Supply Shuttle 1.0: Now you can order new supplies using Cargo Bay north of the autolathe. -
              • -
              • - New containers: The game now features a variety of crates to hold all sorts of imaginary space supplies. -
              • -
              • - New position: Quartermaster -
                  -
                • - A master of supplies. Manages the cargo bay by taking shipments and distributing them to the crew. -
                • -
                -
              • -
              • - New position: Research Director -
                  -
                • - The head of the SS13 research department. He directs research and makes sure that the research crew are working. -
                • -
                -
              • -
              • - New position: Chief Engineer -
                  -
                • - Boss of all the engineers. Makes sure the engine is loaded and that the station has the necessary amount of power to run. -
                • -
                -
              • -
              • - New robot: Securibot -
                  -
                • - Automatically stuns and handcuffs criminals listed in the security records. It's also really goddamn slow. -
                • -
                -
              • -
              • - New jumpsuits: Engineers and Atmos Techs have new jumpsuits to distinguish between them easier. -
              • -
              - -
              Friday, November 27, 2009
              -
                -
              • - Monkey AI 2.0: Monkeys will now get angry, going after random human targets with the ability to wield weapons, throw random objects, open doors, and break through glass/grilles. They're basically terminators. -
              • -
              • - New gamemode: Monkey Survival -
                  -
                • - Survive a horde of angry monkeys busting through the station's airvents and rampaging through the station for 25 minutes. -
                • -
                -
              • -
              • - New robots: Cleanbot and Floorbot -
                  -
                • - Cleanbots automatically clean up messes and Floorbots repair floors. -
                • -
                -
              • -
              • - New spell: Mindblast -
                  -
                • - Causes brain damage, progressively causing other players to become even more retarded. -
                • -
                -
              • -
              • - Alien Races -
                  -
                • - Wizards may randomly spawn as illithids, who gain Mind Blast for free, and nuke agents may randomly spawn as lizardmen. -
                • -
                -
              • -
              • - Station shields: The station now has a toggleable forcefield that can only be destroyed by meteors or bombs. Takes a lot of station power to use. -
              • -
              • - Traitor scaling: Number of traitors/wizards/agents now scales to number of players. -
              • -
              • - New food item: Donk pockets -
                  -
                • - Delicious and microwavable, gives a bigger health boost for traitors. -
                • -
                -
              • -
              • - Cigarettes: Now you can fulfill your horrible nicotine cravings. The detective starts with a zippo lighter and pack of cigarettes. Other packs can be be obtained via vending machines. -
              • -
              • - Warning signs: The station is now filled with various warning signs and such. -
              • -
              • - Updated graphics: Many, many objects have had their graphics updated including pipes, windows, tables, and closets. HUD graphics have been updated to be easier to understand. -
              • -
              • - Lighting fixes: New turf is now correctly lit instead of being completely dark. -
              • -
              • - Meteor fixes: The code and graphics for meteors has been fixed so the meteor gametype is more playable, sort of. -
              • -
              • - Escape shuttle fix: The shuttle can now be called in Revolution and Malfunction, but the shuttle will be recalled before it arrives. This way players can no longer call the shuttle to figure out the game mode during secret. -
              • -
              • - Changelog updated: New changelog entry for Thanksgiving thanks to Haruhi who will probably update the changelog from now on after almost a month of neglect. -
              • -
              - -
              Monday, November 3, 2009
              -
                -
              • - Bug fix: Made most pop-up windows respect the close button. -
              • -
              - -
              Sunday, October 25, 2009
              -
                -
              • - Randomized naming: Names for Central Command and Syndicate are now randomized. -
              • -
              - -
              Saturday, October 24, 2009
              -
                -
              • - Bug fix: PDAs had their code cleaned up. Notice any problems? Report them. -
              • -
              • - New syndicate item: Detomatix Cartridge, allows remote detonation of PDAs (rather weak explosion)! -
              • -
              • - Feature: Remotely detonating PDAs has a chance of failure depending on the PDA target, a critical failure will result in the detonation of your own PDA. -
              • -
              - -
              Monday, October 19, 2009
              -
                -
              • - Gibbing update: Gibbing stuff has been rewritten, robots now gib nicer. -
              • -
              • - LIGHTING!!!: The station now has dynamic lighting and associated items. -
              • -
              - -
              Friday, October 16, 2009
              -
                -
              • - Poo v1.0~: This has caused many ragequits. -
              • -
              • - Flushable toilets: You can now use toilets to place your vile, disgusting and irreprehensible excretions (you disgusting children). Just be careful what you flush! -
              • -
              -
              Monday, October 12, 2009
              -
                -
              • - Feature: Emergency oxygen bottles can be clipped to your belt now. -
              • -
              • - Clothing update: Bedsheets are now wearable. -
              • -
              • - Updated HUD: A few minor tweaks to the inventory panel. Things might not be exactly where you're used to them being. -
              • -
              -
              Monday, September 28, 2009
              -
                -
              • - New position: Chef -
                  -
                • - Maintains the Cafeteria, has access to Kitchen and Freezer, Food creation will be in shortly. -
                • -
                -
              • -
              • - Food update: Food items now heal Brute/Burn damage. The amount recovered varies between items. -
              • -
              -
              Saturday, August 29, 2009
              -
                -
              • - AI laws update: Nanotrasen has updated its AI laws to better reflect how they wish AIs to - operate their stations. -
              • -
              • - Traitor item change: E-mag renamed to Cryptographic Sequencer. -
              • -
              - -
              Friday, July 31, 2009
              -
                -
              • I&#39;m really sorry everyone I just HAD to add a gib all verb.
              • -
              • Decided to add the creation of bombs to bombers list
              • -
              • Made the new bombing list EVEN BETTER!!!
              • -
              • Fixed a bug with admin jumping AND the traitor death message
              • -
              • Oops, fixed a bug that returned the right click pm thing thinking the admin was - muted.
              • -
              • Made a new improved way of tracking who bombs shit.
              • -
              • More formatting shit.
              • -
              • Fixed up some mute code and made it so that if a player is muted they cannot PM - us.
              • -
              • Adminhelps now logged in the admin file not ooc
              • -
              • Changed the way admin reviving is dealt with. (It was coded kind of weirdly - before)
              • -
              • Added a few areas to the observe teleport. Fixed some adminjump things. Modified - the paths of some areas.
              • -
              • You can now ban people who have logged out and admins can now jump to people - using the player panel.
              • -
              • Added in jump to key coded in a much better way than showtime originally did it.
              • -
              • Fixed magical wind when laying pipes. They start out empty!!
              • -
              • Made blink safer. Fixed the crew-quarters to ai sattelite teleport problem.
              • -
              • Forgot the message again. Added an emp spell. thanks copy&amp;paste.
              • -
              • OH MY GOD I HAVE RUINED ASAY
              • -
              • Added electronic items to the pipe dispenser
              • -
              • fixed a formatting error with the changelog (I didn&#39;t break it, it was showtime)
              • -
              • Fixed a formatting error
              • -
              • Cleaned up sandbox object spawn code
              • -
              • New and improved admin log so we can keep an eye on these fuckers
              • -
              • Fixed adminjump because I realise most people use it for the right click option
              • -
              • Mushed together jump to mob and jump to key
              • -
              • Fixed a compilation error and made my test room more secure!
              • -
              - -
              Wednesday, July 29th, 2009
              - -

              These are a collection of the updates from the last 6 days. I promise to update - the changelog once a week. Note that this does not include all the changes in - the past 6 days.

              - -
                -
              • Multitools can now be used to measure the power in cables.
              • -
              • Fixed a bug where the canister message would repeat and spam the user when - attackby analyzer. Fixed an admin formatting error.
              • -
              • Replaced all range checks with a in_range proc. pretty good chance I broke - something or everything.
              • -
              • Mutations use bitfields
              • -
              • Fixed a bug with my traitor panel.
              • -
              • Fixed the turrets, ruined Pantaloons map (test map). Did some things with - turrets and added a few areas.
              • -
              • Some stuff in here you know the usual shit. Bugfixes, formatting etc.
              • -
              • Stunbaton nerf.
              • -
              • Tempban longer than 1 year -&gt; permaban.
              • -
              • Turfs &gt; spawnable.
              • -
              • Shaking someone now slowly removes paralysis, stuns and the &#39;weakened&#39; stuff.
              • -
              • CTF flags now check if someone has them equipped every 20 seconds, if they are - not then they delete themselves and respawn.
              • -
              • Fixed the r-wall-welder-message-thing.
              • -
              • Change to the CTF code, flag captures can now only happen if your team has their - flag in the starting position.
              • -
              • Pruning my test room.
              • -
              • Instead of the red and green team its now the American and Irish teams!
              • -
              • BACKUP BACKUP TELL ME WHAT YOU GONNA DO NOW Changed the monkey name code. Re-did - my antimatter engine code so it actually puts out power now
              • -
              • dumb as fuck change, whoever did that, it already spawn ()&#39;s inside the proc - code, whoever did that, you are a faggot and should read code before you modify - it
              • -
              • Fixed a bug that gave everyone modify ticker variables you silly sausage.
              • -
              • Sorted the AIs track list.
              • -
              • Constructable filter inlets and filter controls.
              • -
              • Added in admin messages for when someone is banned.
              • -
              • Bannana and honk honk.
              • -
              - -
              Saturday, June 27th, 2009
              - -
                -
              • Pipe construction now works completely. //Nannek
              • -
              • Many many other things that never gets recorded in the changelog!!
              • -
              - -
              Saturday, June 27th, 2009
              -
                -
              • The Michael Jackson Memorial Changelog Update
              • -
              • Pipe filters adjusted for more ideal environmentals //Pantaloons
              • -
              • Added in job tracking //Showtime
              • -
              • Crew Manifest and Security Records now automagically update when someone joins //Nannek
              • -
              • Fixed a bug where sometimes you get a screwdriver stuck in your hand //Pantaloons
              • -
              • Flamethrowers can now be disassembled //Pantaloons
              • -
              • OBJECTION! Added suits and briefcases //stuntwaffle
              • -
              • Added automatic brig lockers //Nannek
              • -
              • Added brig door control authorization and redid brig layout //Nannek
              • -
              • Emergency toolboxes now have radios and flashlights, and mechanical toolboxes now have crowbars //Pantaloons
              • -
              • New whisper system //lallander
              • -
              • Some more gay fixes //everybody
              • -
              • Some really cool fixes //everybody
              • -
              • Really boring code cleanup //Pantaloons
              • -
              • ~~In Loving Memory of MJ~~ Sham on!
              • -
              - -
              Friday, June 12th, 2009
              -
                -
              • Looking back through the SVN commit log, I spy...
              • -
              • Keelin doing some more performance enhancements
              • -
              • Fixed one person being all 3 revs at once (hopefully)
              • -
              • Some gay fixes
              • -
              • New admin system installed
              • -
              • Fixed a bug where mass drivers could be used to crash the server
              • -
              • Various pipe changes and fixes
              • -
              - -
              Wednesday, June 3rd, 2009
              -
                -
              • Death commando deathmatch mode added.
              • -
              - -
              Monday, June 1st, 2009
              -
                -
              • Ghosts can no longer wander from space into the dread blackness that lies beyond.
              • -
              • Those other losers probably did a bunch of other stuff since May 6th but they don't comment their revisions so fuck 'em.
              • -
              - - -
              Wednesday, May 6th, 2009
              -
                -
              • Crematorium
              • -
              • Goon? button makes all your dreams come true.
              • -
              • Restructured medbay
              • -
              -
              Monday, May 4th, 2009
              -
                -
              • Does anyone update this anymore?
              • -
              • New atmos computer promises to make atmos easier
              • -
              • Autolathe
              • -
              • Couple of map changes
              • -
              • Some computer code reorganised.
              • -
              • I'm pretty sure theres a couple things
              • -
              -
              Saturday, April 18th, 2009
              -
                -
              • Weld an open closet (only the normal kind), gayes.
              • -
              • Chaplin has a higher chance of hearing the dead.
              • -
              • New traitor objective
              • -
              • Power traitor objective removed
              • -
              • New job system implemented for latecomers.
              • -
              • Head of Research quits forever and ever, is replaced by Head of Security (who gets his own office)
              • -
              - -
              Fri, April 10, 2009
              -
                -
              • Admins are now notified when the traitor is dead.
              • -
              • Unprison verb (again, for admins).
              • -
              - -
              Wed&Thu, April 8&9, 2009
              -
                -
              • Medical redone, doctors do your jobs! (Tell me what you think of this - compared to the old one)
              • -
              • Clickable tracking for the AI
              • -
              • Only the heads can launch the shuttle early now. Or an emag.
              • -
              - -
              Mon&Tue, April 6&7, 2009
              -
                -
              • Sounds. Turn on your speakers & sound downloads.
              • -
              • Scan something with blood on it detective.
              • -
              - -
              Sunday, April 5, 2009
              -
                -
              • A large icon for the headset, no reason it should be so small.
              • -
              - -
              Saturday, April 4, 2009
              -
                -
              • Emergency closets now spawn an 'emergency gas mask' which are just recolored gas masks, no other difference other than making it obvious where the gas mask came from.
              • -
              - -
              Wednesday, April 1, 2009
              -
                -
              • Constructable rocket launchers: 10 rods, 10 metal, 5 thermite and heated plasma from the prototype.
              • -
              • Emergency closets have randomized contents now.
              • -
              • Fixed a bug where someone who was jobbaned from being Captain could still be picked randomly
              • -
              - -
              Friday, March 27, 2009
              -
                -
              • Fixed a bug where monkeys couldn't be stunned.
              • -
              • Change mode votes before game starts delays the game.
              • -
              - -
              Thursday, March 26, 2009
              -
                -
              • The brig is now pimped out with special new gadgets.
              • -
              • Upgraded the admin traitor menu.
              • -
              - -
              Tuesday, March 24, 2009
              -
                -
              • GALOSHES!
              • -
              • A certain item will now protect you from stun batons, tasers and stungloves when worn.
              • -
              - -
              Monday, March 23, 2009 (EXPERIMENTAL)
              -
                -
              • Say / radio / death talk systems recoded, hopefully improving it.
              • -
              • Announcements of late joiners are now done by the AI if it's alive :-)
              • -
              - -
              Monday, March 23, 2009
              -
                -
              • Random station names.
              • -
              • Changes to the message stylesheet.
              • -
              • Admin messages in OOC will now be colored red.
              • -
              - -
              Saturday, March 21, 2009
              -
                -
              • Added a command to list your medals.
              • -
              • ETA no longer shows when it doesn't matter.
              • -
              • Nerfed the ability to spam shuttle restabalization.
              • -
              • Fixed the 'Ow My Balls!' medal to only apply from brute damage rather than both brute and burn damage.
              • -
              - -
              Thursday, March 19, 2009
              -
                -
              • Job banning.
              • -
              • Genetic Researcher renamed to Geneticist.
              • -
              • Toxins Researcher renamed to Scientist.
              • -
              • Help reformatted.
              • -
              • Fixed a bug where combining bruise packs or ointments resulted in an incorrectly combined amount.
              • -
              • Renamed memory and add memory commands to Notes and Add Note.
              • -
              - -
              Tuesday, March 17, 2009
              -
                -
              • Medals! MEDALS!
              • -
              • Trimmed the excessively long changelog.
              • -
              - -
              Saturday, March 14, 2009
              -
                -
              • Janitor job complete! Report any bugs to adminhelp
              • -
              - -
              Saturday, March 7, 2009
              -
                -
              • Wizard now needs his staff for spells
              • -
              • Be careful with APCs now okay?!
              • -
              • Fixed Memory and made it more efficient in the code
              • -
              • Crowbars now open apcs, not screwdrivers. They do something else entirely
              • -
              • Hackable APCs
              • -
              • When APCs are emagged they now stay unlocked
              • -
              • Re-did a shit tonne of admin stuff
              • -
              • New admin system is pretty much finished
              • -
              • FINALLY backpacks can now be looked in while on the ground.
              • -
              - -
              Tuesday, February 24, 2009
              -
                -
              • Ghosts no longer able to open secret doors
              • -
              • Suicide vests now work as armor
              • -
              • Blood no longer comes out of the guy if you pull him due to lag
              • -
              • Admin panel has been touched up to include html tables
              • -
              • Mines now added, only spawnable right now however
              • -
              • Fixed the syndicate nuclear victory bug
              • -
              • Wizard now spawns with wizard outfit which he must wear to cast spells
              • -
              • Blood bug fixes
              • -
              • Fixed a retarded bug that meant I didn't have the power to kick admins
              • -
              • THUNDERDOME!
              • -
              • Several new facial hair options and a bitchin' mohawk
              • -
              • Blood by Culka
              • -
              • Nuke disk now spawns in ALL game modes so that during secret rounds the syndicate now have the element of surprise!
              • -
              - -
              Saturday, February 22, 2009
              -
                -
              • Implemented unstable's "observer" mode
              • -
              • Halerina's wizard mode
              • -
              • Non-interesting stuff
              • -
              • Began addition to the new admin system - right now only available to coders for testing
              • -
              • Admins can now click on the multikeying offenders name to pm them, instead of hunting for them in the pm list
              • -
              • Halerina's chemistry system
              • -
              • You can now deathgasp without being dead, hopefully so people can fake their own deaths.
              • -
              • Redid Medlab
              • -
              • New chemist job
              • -
              - -
              Thursday, February 19, 2009
              -
                -
              • New DNA system. 200th Revision special.
              • -
              • Various bugfixes
              • -
              • Maze
              • -
              - -
              Monday, February 17, 2009
              -
                -
              • Added a new game mode into rotation.
              • -
              • Added an AI satellite
              • -
              • Lockdowns can be disabled with the communications console
              • -
              • Prison shuttle can be called on the comm console, but only if its enabled by admins first
              • -
              • When you slip into space you'll have a 50% chance of going to z=4 instead of z=3
              • -
              - -
              Friday, February 13, 2009
              -
                -
              • Fixed Cakehat
              • -
              • Dead people can now see all turfs, mobs and objs not in their line of sight.
              • -
              • Modified the map slightly
              • -
              • Stungloves can now be "made"
              • -
              • Flashes can now have their bulbs burning out.
              • -
              • Batons can now be turned on and off for different effects. They also now have 10 uses before they need to be recharged.
              • -
              - -
              Tuesday, February 10, 2009
              -
                -
              • Fixed all the autoclose bugs
              • -
              • Due to it being myself and Keelin's 100th revision we have added a super-secret special item. Don't ask because we won't tell! Figure it out!
              • -
              - -
              Sunday, February 8, 2009
              -
                -
              • Modified doors in engineering so that they do not autoclose - Autoclose now handled by a variable
              • -
              • Fixed toxin researcher spawn bug
              • -
              • Changed the "You hear a faint voice" message.
              • -
              • Gave the host new commands to disable admin jumping, admin reviving and admin item spawning
              • -
              • Fixed some airlock autoclose bugs
              • -
              • Changed some doors to not autoclose.
              • -
              • Nerfed the toolbox down.
              • -
              - -
              Friday, February 6, 2009
              -
                -
              • Doors now close after 15 seconds
              • -
              • Fixed some p cool bugs
              • -
              • Cakehat
              • -
              • Added another suit
              • -
              • Walls now take 5 seconds to build
              • -
              • Added sam0rz, thesoldierlljk and kelson's revolution gamemode. Thanks guys!
              • -
              - -
              Thursday, February 5, 2009
              -
                -
              • Fixed a couple of bugs
              • -
              • Improved bar ;)
              • -
              • Beer acts like pills and syringes
              • -
              - -
              Tuesday, February 3, 2009
              -
                -
              • Added 'Make AI' Option for Admins
              • -
              • Added dissolving pills in beer (cyanide and sleeping pills)
              • -
              • Modified engine AGAIN, but personally I love it now
              • -
              - -
              Monday, February 2, 2009
              -
                -
              • Moved bar due to popular demand
              • -
              • Captains room is now a security checkpoint
              • -
              • Assistants now have access to maint tunnels again
              • -
              • Courtroom
              • -
              • Engine has been redone slightly to make it easier to load
              • -
              • Nerfed beer a lot more
              • -
              - -
              Saturday, January 31, 2009
              -
                -
              • Added a bartender job + Bar
              • -
              • Captains panic room
              • -
              • Voice changer traitor item
              • -
              • Bartender suit
              • -
              • Made taking a table apart take longer
              • -
              • Balanced beer a bit more.
              • -
              • Assistants can no longer open external air locks and maint tunnels, sorry guys. Get a job you bums.
              • -
              • Engineers CAN access external air locks and maint tunnels.
              • -
              • Fixed traitor AI bug
              • -
              - -
              Thursday, January 29, 2009
              -
                -
              • Added traitor menu for admins - The ability to turn people into "traitors" as well as keep track of their objectives.
              • -
              • Implemented Keelins revive system - Primary Admins can now revive people.
              • -
              • Moved and redid security to prevent clusterfucks and everyone just crowding around security.
              • -
              • Redid the brig to make it bigger and so that people can break others more easily out since it isn't right in security.
              • -
              • Moved and redid captains quarters/heads quarters. Captains made much smaller and heads is now more of a meeting room.
              • -
              • Added Stungloves and an axe - right now only admin spawnable.
              • -
              • Implemented Persh's adminjump back in - admins can now jump to set locations.
              • -
              • Added a feature that if someone logs off their character moves around and says things - Change what they say from the config/names/loggedsay.txt file.
              • -
              • Added in adminwho verb - tells the user if there are any admins on and who they are.
              • -
              - -
              Saturday, January 10, 2009
              -
                -
              • Freedom implant has been changed so that it will have a random emote associated with it to activate it rather than always chuckle.
              • -
              • There is now a pinpointer tool for use in Nuclear Emergency. It works similar to the existing locator, in that it will detect the presence of nuclear disks and in what direction it is.
              • -
              • The nuke being detonated in Nuclear Emergency should now properly end the game.
              • -
              • Spacesuits now cause you to move slower when not in space.
              • -
              • Syndicate in Nuclear Emergency now have syndicate-themed spacesuits.
              • -
              • Blob mode should properly end now.
              • -
              - -
              Wednesday, January 7, 2009
              -
                -
              • Syndicate Uplink has been changed up, allowing traitor more freedom in his ability to be... traitorus.
              • -
              • Syndicate Uplink can now spawn a ammo-357, syndicate card, energy sword, or timer bomb.
              • -
              • Fixed an issue where Syndicate Uplink looked different than a normal radio.
              • -
              - -
              Monday, January 5, 2009
              -
                -
              • You can choose to be a nudist now.
              • -
              • Facial hair!
              • -
              • Added constructable flamethrowers.
              • -
              • Redid internal naming scheme for human/uniform sprites.
              • -
              • Helmet visors are now translucent.
              • -
              • Held item graphics corrected for basically everything, internally only uses one dmi file instead of two.
              • -
              • Config settings reorganized for.. organization.
              • -
              • Seperated male and female names.
              • -
              • Females have pink underwear.
              • -
              • Guests can no longer save/load profiles, as this just created useless profiles that weren't used again.
              • + +

                01 April 2015

                +

                ACCount updated:

                +
                  +
                • You can now replace test range firing pins with any other pin.
                • +
                • The plasma cutter is now a gun that comes in the mining vending machine. Use it to mine fast but expensively.
                • +
                • It's also cheaper in RnD, and there's an advanced version of it there too. Yay!
                • +
                • Ripleys and Firefighters have been buffed significantly. Go wild.
                • +
                • Precious mesons are cheaper in RnD.
                • +
                • Now you can mine bluespace crystals. They are extremely rare.
                • +
                • You can make remote bombs out of gibtonite. Figure out how.
                • +
                • Gibtonite is a bit less stable. Do not hit it with anything heavy.
                • +
                +

                Fayrik updated:

                +
                  +
                • Added a bowman headset for death squads and ERTs.
                • +
                • Deathsquad radio channel is now called the Centcom radio channel.
                • +
                • Fixed the admin buttons not spawning ERT Medic gear properly.
                • +
                • Expanded Emergency Shuttle dock to allow for more round end griff.
                • +
                • All 7 ERT members now spawn geared, not just the first 4.
                • +
                • Spawning an ERT or Deathsquad no longer stops a second ERT or Deathsquad from being spawned.
                • +
                • Pulse rifles now have 80 shots, not 40.
                • +
                • Deathsquads now use loyalty pinned Pulse rifles.
                • +
                +

                Gun Hog updated:

                +
                  +
                • NTSL has been updated! It can now read and modify verbs (says, yells, asks)! You can set your own using $say, $yell, $ask, and $exclaim variables in your scripts.
                • +
                • NTSL can now also modify the font, italics, and bolding of radio messages in four ways! In a script, place $loud, $wacky, $emphasis, and/or $robot in a vector to $filters.
                • +
                +

                Jordie0608 updated:

                +
                  +
                • The admin's old player panel has been removed and the new one has taken it's name.
                • +
                +

                NikNakFlak updated:

                +
                  +
                • Adds the ability to shave corgis.
                • +
                +

                Sawu updated:

                +
                  +
                • Spraycans for Revheads: instant use crayons that can be used on walls.
                • +
                • Spraycans can be used as ghetto pepper spray and leave their target with colored faces that can be removed with space cleaner.
                • +
                +

                Xhuis updated:

                +
                  +
                • Area ambience and ship ambience are now separate toggles. If you want one type of ambience but not the other, you only need to switch off the type you don't want.
                • +
                • Added cigar cases. They come in three flavors and can be bought from cigarette machines.
                • +
                + +

                30 March 2015

                +

                AnturK updated:

                +
                  +
                • Added new wizard spell : Lightning Bolt
                • +
                +

                Dannno updated:

                +
                  +
                • Added a hammer and gavel. Court is now in session.
                • +
                +

                Iamgoofball updated:

                +
                  +
                • You can now *flip.
                • +
                • Objects now spin when thrown. Please report any oddities.
                • +
                +

                RemieRichards updated:

                +
                  +
                • Monkeys can now wear ANY mask, not just ones they had specific icons for.
                • +
                • Monkeys can now wear HATS, That's right, Monkey Hats!
                • +
                +

                Szunti updated:

                +
                  +
                • Mining mobs vision adapted to darkness of the asteroids.
                • +
                +

                phil235 updated:

                +
                  +
                • All slimes are now simple animals instead of carbon life forms.
                • +
                • Remove crit status from brain and slimes.
                • +
                • Simple animal aliens can now see in the dark like regular aliens.
                • +
                +

                pudl updated:

                +
                  +
                • The detective's forensic scanner has been resprited.
                • +
                • So has the cargo tagger.
                • +
                • And the RPED.
                • +
                +

                tedward1337 updated:

                +
                  +
                • Admins now have a button to use Bluespace Artillery at their will.
                • +
                +

                xxalpha updated:

                +
                  +
                • You will no longer be slowed down by anything if you use a jetpack in zero gravity.
                • +
                • Engineering hardsuits come with an inbuilt jetpack. Requires an internals tank in suit storage and is slower than a normal jetpack.
                • +
                + +

                25 March 2015

                +

                AnturK updated:

                +
                  +
                • Added an arrow graffiti, made arrow and body graffiti face the same way as the user
                • +
                • New changeling ability : Last Resort : Explode and infect corpses if the situation looks grim
                • +
                +

                Fayrik updated:

                +
                  +
                • Deleted the toy crossbow that wasn't a gun, but flung projectiles like a gun. Why was that even a thing?
                • +
                • Added an abstract new type of reusable ammo.
                • +
                • Added three new foam force guns and a foam force crossbow, all of which take the new foam dart ammo.
                • +
                • Added foam force dart boxes to the autolathe, and two crates orderable from cargo containing the new guns.
                • +
                • Added two new donksoft guns, the syndicate's own brand of Foam Force. Available in all good uplinks!
                • +
                • New crossbow can be won as an arcade prize.
                • +
                • All kinds of ammo containers can now be used to rapidly collect live ammunition on the ground.
                • +
                • Speedloaders and traditional ammo boxes can now be restocked.
                • +
                • Security bots now react to being shot.
                • +
                +

                MrPerson updated:

                +
                  +
                • Added a preference toggle for hearing instruments play as suggested by PKPenguin321.
                • +
                +

                Pennwick updated:

                +
                  +
                • Added a buildable Chem Master, board is in Circuit Imprinter.
                • +
                +

                Wjohnston updated:

                +
                  +
                • Finally fixed the gulag shuttle missing a redemption console.
                • +
                + +

                24 March 2015

                +

                Cheridan updated:

                +
                  +
                • Adds pet collars. Use them to rename pets; Can also be worn if you're a weirdo.
                • +
                • Adds the matyr objective to be dead at the round's end. You can get this objective if you have no others that would need you to survive.
                • +
                +

                Dannno updated:

                +
                  +
                • GAR glasses return. Yes, I got permission. Just who the hell do you think I am?
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Summon Events has been reworked to be a bit less manic.
                • +
                • Summon Events will turn off if the wizard and all apprentices die. Any lingering effects are not reversed however.
                • +
                • Improving Summon Events will reduce the average time between events, but not the minimum time between events.
                • +
                • Departmental Uprising has been made admin only for being an RP heavy event that creates antags in an enviroment that doesn't really support that.
                • +
                +

                Mandurrrh updated:

                +
                  +
                • Added a cyborg upgrade module for the satchel of holding.
                • +
                +

                Miauw updated:

                +
                  +
                • Added a fancy white dress and a jester outfit. Sprites by Nienhaus.
                • +
                +

                Steelpoint, RemieRichards, and Gun Hog updated:

                +
                  +
                • Nanotrasen Security has authorized modifications to the standard issue helmets for officers. These helmets are now issued with a mounted camera, automatically synced to your assigned station's camera network. In addition, the helmets also include mounting points for the Seclite model of flashlights, found in your station's security vendors.
                • +
                • Nanotrasen Security helmets are designed for easy repairs in the field. Officers may remove their Seclite attachment with any screwdriver, and the camera's assembly may be pried off with a crowbar. The assembly is also compatible with analyzer upgrades for scanning through the station's structure, and EMP shielding is possible by applying a sheet of plasma.
                • +
                + +

                22 March 2015

                +

                Iamgoofball updated:

                +
                  +
                • Fixes Morphine not knocking you out.
                • +
                • Hotline no longer exists. Poppies now have Saline-Glucose Solution.
                • +
                • Muriatic Acid, Caustic Soda, and Hydrogen Chloride have been removed, along with the secondary meth recipe. The original recipe still works.
                • +
                • Foam now produces more bang for your buck when mixed. Your foam grenades should be larger now to account for the slowdown.
                • +
                • Adds Stabilizing Agent. This chemical will prevent the effects of certain reactions from taking place in the container it is in, such as Smoke Powder and Flash Powder, allowing you to get the raw forms of the powders for heating up later.
                • +
                • Smoke Powder and Flash Powder can now be mixed along with Stabilizing Agent in order to produce a powder version of the effects. You can then heat these powders to 374K to get the effects.
                • +
                • Mixing them without Stabilizing Agent gives the default effects.
                • +
                • Adds Sonic Powder, a chemical that deafens and stuns within 5 tiles of the reaction. It also follows the reaction rules of the other 2 powders.
                • +
                • Liquid Dark Matter and Sorium also now work like the powders do.
                • +
                • CLF3 now has a higher chance of making plating tiles, and the burn damage caused by it scales based on your fire stacks.
                • +
                • Adds Pyrosium. This reagent heats up mobs by 30 degrees every 3 seconds if it has oxygen to react with. Useful for when you want a delayed mix inside of someone.
                • +
                • Adds Cryostylane. This reagent does the opposite of Pyrosium, but it still requires oxygen to react with.
                • +
                • Adds Phlogiston. This reagent ignites you and gives you a single fire stack every 3 seconds. Burn damage from this chemical scales up with the fire stacks you have. Counter this with literally any chemical that purges other chems.
                • +
                • Smoke now transfers reagents to the mob, and applies touch reactions on them. This means smoke can run out of reagents, no more infinite acid smoke. It also only works on mobs, use the new Reagent Foam(tm) for your hellfoam needs. I suggest a CLF3 Fluoroacid Black Powder mix.
                • +
                • Added a derelict medibot to the code, will place a few on the derelict when this PR is merged as to prevent mapping conflicts.
                • +
                • Adds Initropidril. 33% chance to hit with 5-25 TOX damage every 3 seconds, and a 5-10% chance to either stun, cause lots of oxygen damage, or cause your heart to stop. Get it from the traitor Poison Kit.
                • +
                • Adds Pancuronium. Paralyses you after 30 seconds, with a 7% chance to cause 3-5 loss of breath. Get it from the traitor Poison Kit.
                • +
                • Adds Sodium Thiopental. Knocks you out after 30 seconds, and destroys your stamina. Get it from the traitor Poison Kit.
                • +
                • Adds Sulfonal. +1 TOX per 3 seconds, knocks you out in 66 seconds. Mix it with Acetone, Diethylamine, and Sulfur.
                • +
                • Adds Amantin. On the last second it is in you, it hits you with a stack of TOX damage based on how long it's been in you. Get it from the traitor Poison Kit.
                • +
                • Adds Lipolicide. +1 TOX unless you have nutriment in you. Mix it with Mercury, Diethylamine, and Ephedrine.
                • +
                • Adds Coiine. +2 TOX and +5 loss of breath every 3 seconds. Get it from the traitor Poison Kit.
                • +
                • Adds Curare. +1 TOX, +1 OXY, paralyzes after 33 seconds. Get it from the traitor Poison Kit.
                • +
                • Adds a reagent_deleted() proc to reagents for effects upon the last time it processes in you, like with Amantin.
                • +
                • Neurotoxin required temperature for mixing has been set to 674K, it was 370K for some reason.
                • +
                +

                xxalpha updated:

                +
                  +
                • Added Cybernetic Implants: Security HUD, Medical HUD, X-Ray, Thermals, Anti-Drop, Anti-Stun. All implants are researchable and producible by R&D and Robotics.
                • +
                + +

                21 March 2015

                +

                Chocobro updated:

                +
                  +
                • Checkered skirts can be adjusted to be worn shorter.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Player controller uplifted mobs will be attacked by gold core mobs of different species.
                • +
                +

                Jordie0608 updated:

                +
                  +
                • The backup shuttle will no longer be called if the emergency shuttle is coming.
                • +
                +

                MMMiracles updated:

                +
                  +
                • Adds 3 new bear-based foods.
                • +
                • Beary Pie -1 Plain Pie, 1 berry, 1 bear steak
                • +
                • Filet Migrawr - 5u of Manlydorf, 1 bear steak, 1 lighter(not used in crafting)
                • +
                • Bearger - 1 bun, 1 bear steak
                • +
                +

                Phil235 updated:

                +
                  +
                • Silicons are no longer blinded by welding.
                • +
                +

                Xhuis updated:

                +
                  +
                • A new software update has given the Orion Trail game a cheat code menu. While you can't access this normally (that's for a later software update), maybe an emag could do it...?
                • +
                +

                Zelacks updated:

                +
                  +
                • The chemical implant action button should now correctly inject all chemicals when pressed.
                • +
                +

                pudl updated:

                +
                  +
                • Adds normal security headsets to security lockers.
                • +
                +

                xxalpha updated:

                +
                  +
                • Fixed cables on catwalks being destroyed when creating plating floor.
                • +
                • Portable machines can be mounted on any turf that could hold a cable.
                • +
                + +

                19 March 2015

                +

                Dannno updated:

                +
                  +
                • Adds a few cosmetic glasses to the autodrobe and clothing vendors.
                • +
                +

                Gun Hog updated:

                +
                  +
                • Explosion relics will now destroy themselves in their own explosion.
                • +
                • The Experimentor can no longer re-roll the function of an already scanned relic.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Ghosts are now asked if they want to become a sentient slime.
                • +
                +

                JJRcop updated:

                +
                  +
                • Adds the :rollie: and :ambrosia: emojis.
                • +
                +

                MMMiracles updated:

                +
                  +
                • ERT suits now use helmet toggling like hardsuits.
                • +
                +

                Menshin updated:

                +
                  +
                • Fixed mechs ballistic guns not firing bullets.
                • +
                +

                Xhuis updated:

                +
                  +
                • Allows alt-clicking to adjust breath masks, flip caps, and unlock/lock closets.
                • +
                +

                xxalpha updated:

                +
                  +
                • Fixed jetpack trail animation.
                • +
                + +

                17 March 2015

                +

                CandyClownTG updated:

                +
                  +
                • Syndicate cigarettes' non-healing Doctor's Delight replaced with Omnizine.
                • +
                +

                Fayrik updated:

                +
                  +
                • Expanded the Centcom area for better station-Centcom interaction.
                • +
                +

                Iamgoofball updated:

                +
                  +
                • Acid blobs are less likely to melt equipment.
                • +
                +

                MMMiracles updated:

                +
                  +
                • Adds the ability to spike bears on a meatspike for their pelt. Yields 5 meat instead of the usual 3 from a knife.
                • +
                +

                Thunder12345 updated:

                +
                  +
                • Two new shotgun shells. Ion shell, shotgun version of ion rifle, built with 1 x techshell, 1 x ultra microlaser, 1 x ansible crystal.
                • +
                • Laser slug, regular laser shot in a shotgun shell, build with 1 x techshell, 1 x high power microlaser, 1 x advanced capacitor.
                • +
                • Improvised shotgun shells can now be constructed with 1 x grenade casing, 1 x metal sheet, 1 x cable, 10 units welding fuel. Can be packed with a further 5 units of gunpowder to turn it into a potentially lethal diceroll.
                • +
                • FRAG-12 shotgun shells are no longer duds, now explosive.
                • +
                +

                phil235 updated:

                +
                  +
                • Makes the message when you're attacked slightly bigger for better visibility.
                • +
                + +

                15 March 2015

                +

                Ahammer18 updated:

                +
                  +
                • Adds ass photocopying for drones
                • +
                +

                Gun Hog updated:

                +
                  +
                • Nanotrasen has approved distribution of Loyalty Implant Firing Pin designs. They can be fabricated with sufficient research into weapons technology. If inserted into a firearm, it will only fire upon successful interface with a user's Loyalty Implant.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Utilizing a new magic mirror found in the den, wizards can now customize their appearance to a high degree before descending upon the station.
                • +
                +

                Miauw updated:

                +
                  +
                • Firing delay for tasers has been shortened.
                • +
                +

                RemieRichards updated:

                +
                  +
                • Beginning major rework and refactor of messy Ninja code.
                • +
                • Roleplay based, kill deathsquad and kill alien queen objectives removed.
                • +
                • Kamikaze mode removed.
                • +
                • AIs and pAIs can no longer be integrated into a ninja suit.
                • +
                • Energy blade power replaced by an energy katana that can be dropped but is more deadly.
                • +
                • Energy katana can emag any object it hits.
                • +
                +

                phil235 updated:

                +
                  +
                • Bookbags have a new sprite and can hold bibles.
                • +
                • Adds egg yolk reagent. You can break eggs in open reagent containers or blend them to get egg yolk.
                • +
                • Changes how doughs are made. 10water+15flour chem reaction for dough. 15flour+15eggyolk+5sugar for cake batter (or alternatively soymilk instead of the eggyolk for vegans).
                • +
                • Change customizable snack max volume to 60 and buffs nutriment amount in doughs.
                • +
                • Doors can damage mechs by crushing them now.
                • +
                • The tablecrafting window now also shows partial recipes in grey, if there's some of the things required for the recipe on the table. Recipe requirements are now also shown next to the names of recipes listed in the window.
                • +
                + +

                11 March 2015

                +

                MMMiracles updated:

                +
                  +
                • Adds a new vending machine, the Liberation Station. Consult your local patriot for more details.
                • +
                • Adds a patriotic jumpsuit and freedom sheets. Consult your local patriot for more details.
                • +
                +

                Miauw updated:

                +
                  +
                • Added a toggle for ghosts that allows them to hear radio chatter without having to hover near intercoms/over people with headsets.
                • +
                + +

                10 March 2015

                +

                Ahammer18 updated:

                +
                  +
                • Adds the Slimecake and Slimecake slice.
                • +
                +

                AnturK updated:

                +
                  +
                • Non-player Alien Queens/Drones now spread weeds. Can also lay eggs when enabled.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Nanotrasen scientists have recently reported mutations within the pink family line of slimes. Station scientists are encouraged to investigate.
                • +
                +

                Paprika updated:

                +
                  +
                • Reworked reskinning and renaming guns, and added the ability to reskin the bartender's shotgun. Click it with an empty active hand to reskin. Warning, you can only do this once! Rename by hitting it with a pen. This also works with miner shotguns now.
                • +
                +

                RemieRichards updated:

                +
                  +
                • Embedding from explosions has been tweaked, they will always throw objects fast enough to get embedded if they can.
                • +
                • Damage taken when embedded spears and throwing stars fall out has been increased.
                • +
                • Damage dealt by objects getting embedded has been decreased.
                • +
                +

                Sometinyprick updated:

                +
                  +
                • The Horsemask spell has now been altered, instead of just a horse mask it will now choose between three animal masks including the horse mask.
                • +
                +

                Xhuis updated:

                +
                  +
                • You can now burn papers using lighters, welders, or matches. Useful for secret messages.
                • +
                +

                phil235 updated:

                +
                  +
                • Airlocks with their safety on no longer closes on dense objects (like mechs or tables).
                • +
                • Aliens, slimes, monkeys and ventcrawler mobs can now climb into disposal. Cyborgs and very large mobs can no longer climb into it.
                • +
                • Stuffing another mob in a disposal unit is now only done via grabbing.
                • +
                • Coffee now causes jittering only when you overdose on it.
                • +
                • Turrets now target occupied mechs and don't target drones.
                • +
                + +

                08 March 2015

                +

                Dannno updated:

                +
                  +
                • Added more barsigns, sprites by yours truly.
                • +
                +

                Ikarrus updated:

                +
                  +
                • Gang mode has been reworked to functionality without some of it's features.
                • +
                • Gang membership visibility, conversion pens and weapons are removed until fixed.
                • +
                • Gang bosses have been given uplinks to use in the meantime.
                • +
                • Chance of deconversion from repeated head trauma has been increased.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • The skeleton and zombie hordes have been quelled, at least for the time being.
                • +
                +

                Mandurrrh updated:

                +
                  +
                • Added the ability to stop table climbers by clicking the table if present.
                • +
                +

                Paprika updated:

                +
                  +
                • Labcoats and uniforms no longer use action buttons and object verbs to adjust their aesthetic style. Alt click them to adjust.
                • +
                • Alt click a PDA with an ID inside to eject the ID.
                • +
                • Renamed 'remove ID' verb to 'eject ID' so it's not immediately next to 'remove pen'.
                • +
                • Rebalanced the flash protection of thermal scanners. Syndicate thermals have the same flash weakness as the meson scanners they come disguised as.
                • +
                +

                Xhuis updated:

                +
                  +
                • Adds the Adminbus and Coderbus bar signs.
                • +
                • Added a mining satchel of holding to R&D that can hold an infinite amount of ores. It requires gold and uranium, in addition to a decently high bluespace and materials research.
                • +
                +

                phil235 updated:

                +
                  +
                • Harvesting meat from dead simple animals now takes time, has a sound, and can be done with any sharp weapon.
                • +
                +

                pudl updated:

                +
                  +
                • Updated the sprite for Fire extinguishers.
                • +
                +

                xxalpha updated:

                +
                  +
                • Disposable lighters now come in more colors of the rainbow.
                • +
                + +

                05 March 2015

                +

                Jordie0608 updated:

                +
                  +
                • Server hosts can set a config option to make an unseen changelog pop up on connection.
                • +
                +

                MrPerson updated:

                +
                  +
                • Expanded the on-screen alerts you get on the top-right of your screen. You can shift-click them to get a description of what's wrong and maybe how to fix it.
                • +
                • Getting buckled to something will show an alert of what you're buckled to. If you're not handcuffed, you can click the alert to unbuckle.
                • +
                +

                RemieRichards updated:

                +
                  +
                • Added Necromantic Stones as a buyable item for wizards
                • +
                • Necromantic Stones can cause up to 3 people to rise up as Skeleton Thralls bound to the wielder of the stone
                • +
                • Skeleton Thralls have a 33% Chance to drop all their gear on the floor and autoequip a more fitting thematic uniform
                • +
                • Allows the blob to reroll it's chemical for a cost.
                • +
                +

                phil235 updated:

                +
                  +
                • Adds a lot of new plants to botany (red beet, parsnip, snap corn, blumpkin, rice, oat, vanilla, steel cap, mimana, blue cherries, holy melon, geranium, lily, sweet potato) and two new reagents (vanilla, rice).
                • +
                • Fixes taser stun duration being longer due to jitter animation.
                • +
                + +

                03 March 2015

                +

                Delimusca updated:

                +
                  +
                • Improvised heating! use lighters or other heat sources to heat beakers, bottles, etc.
                • +
                +

                Gun Hog updated:

                +
                  +
                • We have purged the xenomicrobes contamination in the E.X.P.E.R.I-MENTOR using sulfuric acid. Take care not to be exposed should it leak!
                • +
                +

                Iamgoofball updated:

                +
                  +
                • Colorful reagent can be washed off with water now.
                • +
                • Oxygen blob deals breath loss damage.
                • +
                • Radioactive blobs irradiate for more.
                • +
                +

                Ikarrus updated:

                +
                  +
                • Antagonist roles are now restricted by player client age.
                • +
                +

                Jordie0608 updated:

                +
                  +
                • The server's current local time is now displayed in status panel in ISO date format (YYYY-MM-DD hh:mm)
                • +
                +

                Mandurrrh updated:

                +
                  +
                • Added the ability to see pda messages as a ghost with GHOSTWHISPER toggled on.
                • +
                • Removed the degrees symbol from Kelvin units on atmos meters because not gramatically correct
                • +
                +

                RemieRichards updated:

                +
                  +
                • Drones now see either Static, Black icons or Roguelike style lettering instead of actual mob icons
                • +
                • Icons for the above, from VG's SkowronX
                • +
                • Many icon procs for the above, from VG's ComicIronic
                • +
                • Drones moved into their own folder
                • +
                • Drone.dm replaced with seperate files for each functionality
                • +
                • Drones now have 2 skins to choose from, Maintenance Drone (Current drones) and Repair Drone (A modified VG/Bay sprite)
                • +
                • Much more support for alternate skins on drones
                • +
                +

                Xhuis updated:

                +
                  +
                • Added four new emoji: :1997:, :rune:, :blob:, and :onisoma:.
                • +
                +

                xxalpha updated:

                +
                  +
                • Added a surgery table and tools to the Nuke Ops ship.
                • +
                • Added Engineering Scanner Goggles to Engivend, Engineering Secure Closets and Research.
                • +
                • Doubled T-ray scanner range.
                • +
                + +

                01 March 2015

                +

                Dannno updated:

                +
                  +
                • Resprited all owl items. Wings are now a seperate toggleable suit item.
                • +
                • Added The Griffin costume, arch nemesis of The Owl. Found in the autodrobe.
                • +
                • Added admin-only Owl hardsuit. Only for the most vengeful souls.
                • +
                • Added Owl and Griffin merchandise; The Nest barsign, Owl/Griffin toys and posters.
                • +
                +

                Iamgoofball updated:

                +
                  +
                • Fixes the recipe for meth.
                • +
                • Fixes sorium blobs flinging themselves.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • On servers where malfunction, wizard, and blob rounds do not end with the death of the primary antagonist, new antagonists from traitor and changeling derivative modes will now sometimes spawn. This depends on the state of the station, the state of the crew, and the server's gamemode probabilities. Server owners should consider revising their configuration choices
                • +
                • If you would like to opt out of this midround chance, a new toggle to disable it can be found in your preferences
                • +
                +

                Mandurrh updated:

                +
                  +
                • The barsign can be emagged and EMP'd; EMP'd signs can be repaired with a screwdriver and cables.
                • +
                +

                Menshin updated:

                +
                  +
                • Fixed the protolathe not requiring displayed amount of materials to make items.
                • +
                +

                MrStonedOne updated:

                +
                  +
                • Adds a Panic Bunker!
                • +
                • This will prevent any player who has never connected before from connecting. (Admins are exempt)
                • +
                • Can be enabled per-round by any admin with +SERVER or for longer periods via the config.
                • +
                • Also adds some other config options relating to new joins, server operators should consult their configuration file for more details.
                • +
                • Fixes toggle darkness vision hiding other ghosts.
                • +
                • Admins fucking around with ghost icon and icon states in VV are going to want to change the icon and icon state of the ghostimage var of the ghost to change the image shown to ghosts who have darkness disabled. doing this via mass edit is not yet supported.
                • +
                • New ghost verb, toggle ghost vision. Switches the ghost's invisibility vision between that of a ghost and that of a human. So ghosts can hide other ghosts and in general get the old behaviour of toggle darkness.
                • +
                +

                Paprika updated:

                +
                  +
                • Messages with two exclamation marks will be yelled in bold.
                • +
                +

                Phil235 updated:

                +
                  +
                • Cultists can't summon narsie until their sacrifice objective is complete.
                • +
                +

                Razharas updated:

                +
                  +
                • Vines now can grow on any tiles that is not dense, including space, shuttles, centcomm tiles and so on.
                • +
                • Added 'Bluespace' mutation to vines, makes them be able to grow through absolutely everything.
                • +
                • Added 'Space Proofing' mutation to vines, after they grow if the tile under them is space it will become special vinetile, which is just resprited regular floor, if the vine on the vinetile dies the vineturf changes back to space.
                • +
                • Made vines spreading speed depend on the seed's production, can be both slower and faster than current.
                • +
                • Made vines mutation chance be 1/10th of the potency of the seed it is spawned from.
                • +
                • Special chemicals added to vine seeds durning their growth can increase/decrease their potency and productivity.
                • +
                • Special chemicals now can remove good, bad or neutral mutations from vine seeds while they are growing, cultivating actually helpful vines is now possible.
                • +
                • Plant analyzers now show the vine seeds mutations.
                • +
                • Buffed numbers in some of the more useless mutations.
                • +
                +

                Steelpoint updated:

                +
                  +
                • Reworked and expanded areas of the derelict station. Featuring a repairable gravity generator, turret filled computer core and functional solars.
                • +
                +

                Vekter updated:

                +
                  +
                • Chemists, Geneticists, Virologists, Scientists, and Botanists will now spawn with alternative backpacks and satchels in their lockers. Try them out!
                • +
                + +

                25 February 2015

                +

                Cheridan updated:

                +
                  +
                • Foam now carries reagents, similarly to smoke. Have fun with that.
                • +
                +

                Dannno updated:

                +
                  +
                • Altered the sprite for security hardsuits. The old sprite is now a HoS specific hardsuit.
                • +
                +

                Delimusca updated:

                +
                  +
                • Turret's guns replaced by a heavy energy cannon.
                • +
                • Firing a heavy weapon sindlehandedly can make it recoil out of your hands.
                • +
                +

                Gun Hog updated:

                +
                  +
                • Nanotrasen has released new design specifications for the prototype combination of standard night-vision equipment and Optical Meson Scanners. We believe that this will address the concerns many Shaft Miners have concerning visiblity in a potentially unsafe environment.
                • +
                • Fixed malf AI's turret upgrade power not working.
                • +
                +

                Iamgoofball updated:

                +
                  +
                • You can now go into hyperglycaemic shock from having over 200u of Sugar in your body at once. Treat it with Insulin.
                • +
                • Insulin has been added. Use it to treat hyperglycaemic shock. You can get it from medical vendors and the Cargo medicine supplies crate.
                • +
                • Medvendor contents have been reduced from medical having everything they need at roundstart level, and toxin bottles are now back in the vendor.
                • +
                • Medical Analyzers now have 2 modes, Medical Scan and Chemical Scan. You can swap between modes by clicking on it in hand.
                • +
                • Medical scan is default behavior. Chemical scan shows all the reagents inside the mob at the time, along with any addictions the mob has. It will show if a reagent is overdosing.
                • +
                • Tobacco and Space Tobacco have Nicotine in them now.
                • +
                • Adds Methamphetamine to the game. Mix it with Ephedrine, Iodine, Phosphorous, and Hydrogen at 374K.
                • +
                • Or, look around in Maintenance for Muriatic Acid, Caustic Soda, and Hydrogen Chloride. Recipes are also available for these.
                • +
                • Adds Saltpetre. Mix it with Potassium, Nitrogen, and Oxygen.
                • +
                • Adds Bath Salts, mix it with Bad Food, Saltpetre, Nutriment, Space Cleaner, Universal Enzyme, Tea, and Mercury at 374K.
                • +
                • Adds Aranesp. Mix it with Epinephrine, Atropine, and Morphine.
                • +
                • Adds Hotline. Get it from Poppy plants.
                • +
                • Strange Reagent revival was changed. It now won't gib but won't do anything above a 100 BRUTE or BURN threshold.
                • +
                • Carpet now applies carpet tiles to whatever flooring it hits. Mix it with Space Drugs and Blood.
                • +
                • Colorful Reagent colors have been improved drastically and now are more varied and nicer looking.
                • +
                • Corn Starch has been added. Get it from juicing Corn.
                • +
                • Corn Syrup has been added. Mix it with Corn Starch and S-Acid.
                • +
                • Corgium has been added. Mix it with Nutriment, Colorful Reagent, Strange Reagent, and Blood at 374K.
                • +
                • Adds Quantum Hair Dye. Mix it with Colorful Reagent, Radium, and Space Drugs.
                • +
                • Adds Barber's Aid. Mix it with Carpet, Radium, and Space Drugs.
                • +
                • Adds Concentrated Barber's Aid. Mix it with Barber's Aid and Unstable Mutagen.
                • +
                • Re-adds Chlorine Trifluoride. Mix it with Chlorine and Fluorine at 424K.
                • +
                • Adds Black Powder. Mix it with Saltpetre, Charcoal, and Sulfur. Ignite it for an explosion at 474K
                • +
                • Finally nerfs Salglu Solution. It's no longer tricord 2.0.
                • +
                • Salt recipe is now Water + Sodium + Chlorine due to problems with recipe mixing.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • A new event has been added to the wizards summon events spell that gives the game a distictively more roleplaying flair.
                • +
                • Optional population cap functionality has been added to the game. By default all caps are disabled. Server owners should refer to config.txt to see their list of options.
                • +
                • Medical borgs now have access to a deployable onboard roller bed. Should the bed become lost, any roller bed may be used in its place.
                • +
                +

                Mandurrh updated:

                +
                  +
                • Added interchangeable bar signs
                • +
                • Added bays fountain machines for the bar. Soda and Beer. Allowing bartenders to have easier access to mix drinks and making more to make their job a little less boring
                • +
                • Added color tiles to the light up floor tiles.
                • +
                • Added metal, glass, and cable to bar back room at round start for bartender to make colored light dance floors.
                • +
                • Fixes clown cuffing both mobs and two pairs of cuffs appearing.
                • +
                • Fixes clown not being able to use his stamp as antag so no meta.
                • +
                • Fixes sleepypen bug. Instead of only dispensing 50u it dispenses the full 55u.
                • +
                • Fixed silicons not being able to pass through holotape.
                • +
                +

                Menshin updated:

                +
                  +
                • Getting into regenerative statis now puts the changeling into unconscious state.
                • +
                • Fixed changeling 'Revive' not working.
                • +
                +

                Paprika updated:

                +
                  +
                • Added an assistant cap, unlimited by default.
                • +
                • Batons now slowly lose power when left on.
                • +
                • Batons, flashbangs and handcuffs can be seen when stored in a belt.
                • +
                • Computers now emit light when they're on or bluescreened.
                • +
                • Added hotkeys for OOC and custom emotes. Press O and M when in hotkey mode. Remember not to ick in ock!
                • +
                • Added megaphones for the HoS, HoP, and QM.
                • +
                • Goliath tentacles no longer stun you if you cross them. They will only stun you if you don't move away from them in time. They can do up to 15 damage now, in addition to stunning you.
                • +
                • Changed a lot of mining gear and prices. The resonator can now have up to 3 fields active at once, but the fields need to detonate before they crush rocks. Tweak the time before the fields detonate with a screwdriver and upgrade the amount of fields at once with sheets of diamond.
                • +
                • Overhauled straight mining tools. The sonic jackhammer is now the wall-crushing tool, the diamond drill simply mines really fast. Borgs can now get upgraded with the diamond mining tool with only a single sheet of diamond and an upgrade module, they don't need illegal tech modules.
                • +
                • Mining drills and the sonic jackhammer now draw power when they drill down mineral walls. They can be recharged using any type of weapon recharger or by replacing the cell using a screwdriver on them.
                • +
                • Space now emits a dim light to adjacent tiles.
                • +
                • Syndicate shotguns now start with buckshot instead of stunslugs.
                • +
                • Fixed welding tools using fuel when being switched on.
                • +
                • Added a firing delay between shooting electrodes.
                • +
                • Changed the stun revolver from a high-capacity taser to a disabler with extra capacity.
                • +
                • Added a unique stun pistol for the head of security. It is a double capacity taser with no shot delay and a loyalty pin, though it lacks a disable mode.
                • +
                +

                RandomMarine updated:

                +
                  +
                • New brute treatment kit crate for cargo.
                • +
                +

                RemieRichards updated:

                +
                  +
                • Explosions will now blow (throw) items away from the epicenter
                • +
                • Sharp items can now embedd themselves in a human's limbs
                • +
                • Embedded items have a chance to do some damage (based on w_class) each life() call
                • +
                • Embedded items have a chance to fall out doing some more damage, but removing the object
                • +
                • Adds a surgery to remove Embdedded objects
                • +
                • Adds throwing stars as an uplink item (100% chance to embedd)
                • +
                • Items placed on tables now center to exactly where you clicked.
                • +
                +

                Steelpoint updated:

                +
                  +
                • SS13's Head of Security has received a new attempted 1:1 recreation of a Old Earth weapon, the Captain's Antique Laser Gun.
                • +
                • This gun features three firing modes, Tase, Laser and Disable. However this gun lacks the capability to recharge in the field, and is extreamly expensive.
                • +
                • INTERCEPTED TRANSMISSION REPORT: The Syndicate have expressed a interest in this new weapon, and have instructed field agents to do whatever they can to steal this weapon.
                • +
                +

                TheVekter updated:

                +
                  +
                • Box's vault nuke has been replaced with immobile Nuclear Self-Destruct Mechanism.
                • +
                • Vault's new green floor tiles turn flashing red when Self-Destruct is activated.
                • +
                +

                Xhuis updated:

                +
                  +
                • Syndicate contacts have discovered new ways to manipulate alarms. Malfunctioning AIs, as well as normal emags, can now disable the safeties on air and fire alarms.
                • +
                • When one emags an air alarm, its safeties are disabled, giving it the Flood environmental type, which disables scrubbers as well as vent pressure checks. Burn, pigs, burn!
                • +
                • When one emags a fire alarm, its thermal sesnsors are disabled. This will prevent automatic alerts due to the alarm being unable to recognize high temperatures.
                • +
                • Malf AIs gain two new modules, both of which disable ALL air alarm safeties and thermal sensors. These are separate abilities.
                • +
                • Air and fire alarms have notifications on their interface about the current safety status. One only has to glance at one to see that it's been tampered with.
                • +
                +

                phil235 updated:

                +
                  +
                • Fixed arcade machines being usable from inside lockers to dupe rewards.
                • +
                • Silicons can no longer be grabbed by anyone. No more silicon choking
                • +
                • Custom food can now be renamed using a pen.
                • +
                +

                xxalpha updated:

                +
                  +
                • Added new graffiti: cyka, prolizard, antilizard
                • +
                + +

                18 February 2015

                +

                Dannno updated:

                +
                  +
                • Added proper slurring when drunk.
                • +
                +

                Deantwo updated:

                +
                  +
                • Updated the ChemMaster's UI to the station default along with some functionality improvements.
                • +
                • You can now spam the 'Ready' button as much as you want without it setting you 'Not ready' again.
                • +
                +

                Dorsidwarf updated:

                +
                  +
                • Added the ability to call Central Command for the nuclear authentication codes. This requires a captain-level ID and admin approval, and warns the crew.
                • +
                +

                Gun Hog updated:

                +
                  +
                • E.X.P.E.R.I-MENTOR meteor storm event replaced with a fireball that targets random nearby mob.
                • +
                • EMP event range reduced.
                • +
                • Self-duplicating relics limited to 10 copies.
                • +
                • Nanotrasen has released a firmware patch for the Ore Redemption Machine, which now allows the station AI, cyborgs, and maintenance drones to smelt raw resources without the need of a valid identification card installed.
                • +
                • Nanotrasen scientists have completed their designs for a lightweight, compacted anti-armor ion weapon, without loss of efficiency. In theory, such a weapon could easily fit within standard issue backpacks and satchels. With sufficient research into weapons technology and materials, Nanotrasen believes a working prototype can be fabricated.
                • +
                +

                Iamgoofball updated:

                +
                  +
                • Buffs the shit out of Styptic Powder and Silver Sulf, they're now viable to use. Synthflesh is still good for general treatment, but overall Styptic and Silver Sulf deal with their respective damage types way better now.
                • +
                • Improves kinetic accelerator's mining strength.
                • +
                • The kinetic accelerator's cooldown can be decreased by tweaking the thermal exchanger with a screwdriver.
                • +
                • The range can be increased with plasma sheets
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Medical borgs have been given full surgery tools which they can't fail a procedure with.
                • +
                • Medical borgs also have rechargeable gauze.
                • +
                • Borg hypo's omnizine replaced with salbutamol, salglu and charcoal.
                • +
                • Slime and Jelly mutant races now actually contain slime jelly. While playing as these races you will naturally produce it internally from your nutrition, so be sure to stay well fed as losing too much is dangerous to your health! Beware of chems that clear toxins, as they can be rapidly fatal to you!
                • +
                +

                Jordie0608 updated:

                +
                  +
                • Veil render and rifts changed to spawn with vars, rifts can be closed with a nullrod.
                • +
                +

                Lo6a4evskiy updated:

                +
                  +
                • General record now contains a species field and a photo of the crewmember. Security record consoles allow you to update it with a photo taken in-game (with detective's camera, for example, or AI's).
                • +
                • Expanded functionality of security HUDs to display rank and photos.
                • +
                • Medical HUDs allow user to change others' mental and physical status. Currently has no gameplay effect other than changing records.
                • +
                • Medical HUDs can perform an examination at the distance, similrarly to how attacking yourself with help intent works. Has limited ability to detect suffocation and toxin damage.
                • +
                +

                Menshin updated:

                +
                  +
                • Solar control computers remade with NanoUI
                • +
                +

                Paprika updated:

                +
                  +
                • Added rnd requirements to borg upgrades and tweaked the prices.
                • +
                • Changed some clothing around. Added a turtleneck for the HoS. Removed 'civillian' armor and gave normal armor to the bartender and HoP. Added flash protection to the HoS' eyepatch, it functions like sunglasses now. Additionally, added the ability to store ammo on your shoulder holster as detective.
                • +
                • Removed the defib steal objective. Added a compact defibrillator for the CMO that equips to the belt. Removed defibrillators from RND. Removed the requirement for people to not wear armor to revive them with defibs. Doubled defib timers (10 minutes before permadeath).
                • +
                • Flashlights can be attached to pulse carbines.
                • +
                • Miners spawn with a brute patch.
                • +
                • Mining station's sleeper has been replaced with an IV drip and more medkits.
                • +
                • Added hard-light holotape. This holographic police or engineering hazard tape will block entry if you're running, so you need to walk to pass it. This is to help people not sprint into breaches or crime scenes.
                • +
                • Fixed being able to sling the strapless improvised shotgun on your back.
                • +
                • Reverted the price of the ebow to 12tc and removed the range limit of its bolts.
                • +
                +

                Sometinyprick updated:

                +
                  +
                • Novely pig mask prize from arcade machines, can be toggled to make you squeal like a pig.
                • +
                +

                Steelpoint updated:

                +
                  +
                • Security Cyborgs Advance Taser have been replaced with a Disabler.
                • +
                +

                TZK13 updated:

                +
                  +
                • Adds plaid skirts and new schoolgirl outfits to the autodrobe
                • +
                • Novaflowers apply a firestack for every 20 potency they have.
                • +
                • You can now juice whole watermelons instead of slices.
                • +
                • Advancements in the field of virology by Nanotrasen's finest have uncovered three new disease symptoms for further research:
                • +
                • Ocular Restoration heals any eye trauma experienced by the infected patient.
                • +
                • Revitiligo increases the pigmentation levels of the infected patient's skin.
                • +
                • Spontaneous Combustion ignites the infected patient without warning.
                • +
                +

                Vekter updated:

                +
                  +
                • Added shot glasses! Can be obtained from the Booze-o-Mat.
                • +
                • A new premium vodka has been added to the game, hidden in a special spot.
                • +
                • Due to constant attempts to break into the station's safe, the contents have been relocated and have not been replaced. We promise. There is absolutely NO reason to break into the vault's safe anymore.
                • +
                • Each shot glass now holds 15 units, and the drinker takes all 15 in one gulp instead of 5 at a time. This means you can mix certain shots (namely, the B-52 and Toxins Special) in a shot glass. Both drinks have been made exclusive to shot glasses and will no longer show their specific sprite in a normal glass.
                • +
                • Added new sprites for most alcohol in shot glasses. Everything else will show up as 'shot of... what?'.
                • +
                • Fixed shot glasses turning into regular glasses when filled.
                • +
                • Fixed tequila bottle sprites not showing up.
                • +
                • Fixed a typo!
                • +
                • Roboticists will now start with a full toolbelt instead of a toolbox.
                • +
                +

                drovidi updated:

                +
                  +
                • Blobs who burst in space will be given a 30 second warning before dying.
                • +
                • If a blob does die from bursting in space, a new crewmember will be infected.
                • +
                • If there are no blobs or infected crew left after bursting, the round will now end properly, rather than becoming endless.
                • +
                +

                phil235 updated:

                +
                  +
                • You no longer need to cut the syphon wire to deconstruct air alarms.
                • +
                • Cooking overhaul: microwave recipes are converted to tablecraft recipes.
                • +
                • Added customizable food (burger,sandwich,spaghettis,soup,salad,cake,bread,kebab,pie) with specific color depending on ingredients used.
                • +
                • Added new food ingredients (dough, flat dough, cake batter, pie dough, doughslice, bun, raw pastry base, pastry base, raw cutlet, cutlet, pizzabread). Dough is made by mixing egg and flour, then transformed by using knife, rollingpin, milk and microwave. Meat is now sliceable into rawcutlets (specific color for each meat type).
                • +
                • Changed food recipes a bit (replacing stuff with new food ingredients).
                • +
                • Repurposed the microwave to actually cook certain food items(no reagents required), renamed it microwave oven and added a power setting to it.
                • +
                • Bowl is no longer trash but a beaker-like container that is used in salad&soup recipes. Also, milk and soymilk cartons as well as flour packs are now condiment bottles.
                • +
                • Changed the hunger system a bit, sugar becomes a normal reagent again. Faster nutrition drain is now handled by a variable in junk food. Also added a slight buff to vitamin.
                • +
                +

                xxalpha updated:

                +
                  +
                • You now hear the voice of the chosen deity in prayers.
                • +
                + +

                05 February 2015

                +

                Danno updated:

                +
                  +
                • Added suicide messages for all gun types and various different items.
                • +
                +

                Paprika updated:

                +
                  +
                • Compacted ERT room by removing equipment rooms, filler rooms and pre-spawned mechs.
                • +
                • Rebalanced Pulse weapons to have more shots, faster charge, EMP immunity and modified sprites.
                • +
                • ERT spawns fully equipped with no-slowdown suits.
                • +
                • Added blood and bleeding. If you take enough brute damage on a certain limb, you will start bleeding, and will need to patch your bleeding with gauze from medbay. You can replace lost blood by eating nutritious food and waiting for your heart to reproduce it, or by getting a blood transfusion from an IV drip.
                • +
                +

                Vekter updated:

                +
                  +
                • Added a toy red button to the Arcade cabinets.
                • +
                + +

                04 February 2015

                +

                Danno updated:

                +
                  +
                • Fixes dufflebag's storage capacity.
                • +
                +

                Deantwo updated:

                +
                  +
                • Removes duplicate fluorine from Chemical Dispenser.
                • +
                • Added GoonChems to the Portable Chem Dispenser.
                • +
                • Added cancel buttons to the ChemMaster and CondiMaster when making bottles, pills, etc.
                • +
                +

                Delimusca updated:

                +
                  +
                • Added new foam armblade toy to arcade prizes.
                • +
                • Slimes can drag themselves to mobs to start feeding.
                • +
                • Attacking another slime will pull them off their victim if they're feeding or steal some of their mass.
                • +
                +

                GunHog updated:

                +
                  +
                • Five new silicon emotes: *buzz2, *chime, *honk, *sad and *warn
                • +
                +

                Incoming5643 updated:

                +
                  +
                • New contraband crate containing fifty butterflies.
                • +
                • Shuttle will now always arrive, but won't launch in rounds where it would've been previously auto-recalled.
                • +
                +

                Menshin updated:

                +
                  +
                • Fixed being unable to place cables on plating.
                • +
                +

                Paprika updated:

                +
                  +
                • Helmets for hardsuits are stored inside the suit and cannot be equipped without the suit on.
                • +
                • EVA space suits are now lighter for faster movement but lack flash or welding protection, don't hide your identity and have less radiation protection.
                • +
                +

                Perakp updated:

                +
                  +
                • Tweaked slowdown due to coldness when in in low gravity.
                • +
                +

                Steelpoint updated:

                +
                  +
                • ERT base equipment rooms are now access restriction to each role.
                • +
                • ERT commander spawns wearing hardsuit for identification.
                • +
                • Two boxes of metal foam grenades added for ERT engineers.
                • +
                • Deep space sensors recently detected the derelict remains of the NTSS Omen, a outdated Medical Frigate thought lost, within close proximity to Space Station 13.
                • +
                • If any SS13 EVA Personal are able to locate this ship, they will be able to pilot it back to the station for recovery. The ship may also have cordinates for other locations.
                • +
                +

                Xhuis updated:

                +
                  +
                • Changelings now have the ability Strained Muscles, which lets them move at inhuman speeds at the cost of rapid stamina damage. It costs one evolution points=.
                • +
                • Changelings now have the ability Augmented Eyesight, which lets them see creatures through walls and see in the dark. It costs two evolution points.
                • +
                • After many failed raids, the Syndicate have finally done something about poor communications between cyborgs and operatives.
                • +
                • Syndicate Cyborgs now come equipped with operative pinpointers, which will automatically point to the nearest nuclear operative.
                • +
                • Handheld crew monitors can now be stored in medical belts.
                • +
                + +

                26 January 2015

                +

                Dannno updated:

                +
                  +
                • Added fannypacks of multiple colors, 3 of which are accessible from the clothing vendor.
                • +
                • Added dufflebags to multiple lockers throughout the station and to the syndicate shuttle, more storage slots at the cost of move speed.
                • +
                +

                Deantwo updated:

                +
                  +
                • Fixed some bugs in PDA's NTRC Chatroom feature.
                • +
                • You can now use PaperBBCode/PenCode when writing news articles on the newscaster.
                • +
                • PDA's Notekeeper now uses PaperBBCode/PenCode rather than HTML.
                • +
                • Scanning paper with your PDA is now more informative.
                • +
                +

                Firecage updated:

                +
                  +
                • We, at NanoTrasen, have recently upgraded the Kitchen Spikes to include the skin of both Monkeys and Aliens when you harvest their flesh!
                • +
                • In other news, a station in a nearby Sector has been making monkey and xeno costumes. We are not sure why.
                • +
                • Added the ability to reform mineral floortiles back into sheets/bars
                • +
                +

                Ikarrus updated:

                +
                  +
                • Added a new config option MINIMAL_ACCESS_THRESHOLD that can be used to automatically give players more access during low-population rounds. See game_options.txt for more information.
                • +
                +

                Lo6a4evskiy updated:

                +
                  +
                • The kitchen now contains a brand new food cart. It can store foods and liquids, mix them into cocktails and dispense where needed. Food delivery has never been easier!
                • +
                +

                fleure updated:

                +
                  +
                • Added briefcase filled with cash to uplink.
                • +
                + +

                21 January 2015

                +

                Iamgoofball updated:

                +
                  +
                • Addiction and Overdosing has been added to Chemistry.
                • +
                • Deal with Overdosing by purging the ODing chemical with Calomel or Penetic Acid.
                • +
                • Deal with Addiction by either going cold turkey on it and weathering the effects, eventually causing the addiction to subside, or by taking more of the addictive substance to keep the effects down.
                • +
                • Cryoxadane was heavily buffed. Clonex was removed as it was merged into Cryox.
                • +
                • Fixes a shitton of stuff adding Morphine instead of Ephedrine. Sorry, miners.
                • +
                • Fixes instances of Alkysine not being replaced with Mannitol. Same with Ryetalyn being replaced with Mutadone.
                • +
                • Adds Itching Powder. Welding Fuel and Ammonia.
                • +
                • Adds Antihol. Ethanol and Charcoal.
                • +
                • Adds Triple Citrus. Lemon Juice, Lime Juice, and Orange Juice.
                • +
                • Adds Colorful Reagent. Stable Plasma, Triple Citrus,
                • +
                • Morphine replaces sleep toxin now, not Hyperzine. Hyperzine is replaced by Ephedrine now.
                • +
                • Cryotubes will now autoeject you once you are fully healed.
                • +
                • Synthflesh damage values have been fixed.
                • +
                • Charcoal has been buffed to heal 3 TOX damage a cycle and it purges other chemicals faster.
                • +
                • Saline-Glucose Solution has been buffed. It now has a 50% chance per cycle to heal 3 BRUTE and BURN per tick.
                • +
                • Fixes Strange Reagent revivals dying right after being revived. They'll still need immediate medical treatment, however.
                • +
                • Cryoxadone has had a recipe change. It is now Stable Plasma, Acetone, and Unstable Mutagen.
                • +
                • Adds 9 new Disabilites and 2 new genetic powers.
                • +
                • You can now heat donk pockets once more. Rejoice.
                • +
                + +

                17 January 2015

                +

                Cheridan updated:

                +
                  +
                • The lockbox system for R&D has been replaced with a firing pin system. Ask the Warden or HoS for firing pins to allow normal operation of your firearms. Want to test them out at the range? Try printing a test-range pin.
                • +
                • Nuke Ops have used this new technology to lock some of their heavier weapons so that crew cannot use them. These implants replace their explosive implants.
                • +
                • Explosive implants are now purchasable in the uplink, and are much stronger.
                • +
                +

                Firecage updated:

                +
                  +
                • NanoTrasen would like to report that our Mechanical Engineers at NTEngiCore has recently upgraded the capacity of the machines known as Biogenerators. They are now have the added ability to create various belts, and also weed/plantkillers and cartons of milk and cream!
                • +
                +

                Iamgoofball (Look Ma, I didn't mispell my name this time!) updated:

                +
                  +
                • Replaced Star Trek chemistry with Goon Chemistry.
                • +
                • Emergency Medic's Handbook Tip 1: Styptic Powder = BRUTE, Silver Sulfadiazine = BURN, Charcoal = TOX, Salbutamol = OXY
                • +
                • In recipes that used removed reagents, they have been replaced with their goon chemistry counterpart:
                • +
                • Hyperzine -> Morphine
                • +
                • Inaprovaline -> Epinephrine
                • +
                • Kelotane -> Saline-Glucose Solution
                • +
                • Bicardine -> Saline-Glucose Solution
                • +
                • Dermaline -> Saline-Glucose Solution
                • +
                • Dexalin -> Salbutamol
                • +
                • Dexalin Plus -> Salbutamol
                • +
                • Tricordrazine -> Omnizine
                • +
                • Anti-Toxin -> Charcoal
                • +
                • Hydronalin -> Penetic Acid
                • +
                • Arithrazine -> Penetic Acid
                • +
                • Imidazoline -> Oculine
                • +
                • Mannitol -> Alkysine
                • +
                • Ryetalyn -> Mutadone
                • +
                +

                Steelpoint updated:

                +
                  +
                • The Emergency Response Team project has been activated by Nanotrasen. The ERT are a seven man squad that can be deployed by the Central Command (admins) to attempt to rescue the station from a overwhelming threat, or to render assistance in dealing with a significant problem.
                • +
                • The ERT consists of a single Commander, two Security Officers, two Engineering Officers and two Medical Officers. The Commander is preselected when the squad is being spawned in, however the remaining six officers are free to select their role in the squad.
                • +
                • Two new Pulse gun variants have been added to the game. They are the Pulse Carbine and the Pulse Pistol, both have significantly smaller ammunition capacities than the Pulse Rifle, however they both can fit in a backpack and the pistol can fit in a pocket. All ERT Officers have a Pulse Pistol for a sidearm, however the Security Officers get a Pulse Carbine as their primary longarm.
                • +
                +

                phil235 updated:

                +
                  +
                • The two library consoles are now real buildable computers. The circuitboard design is available in R&D.
                • +
                +

                sawu-tg updated:

                +
                  +
                • Added a new R&D machine, to replace the telescience room. The Machine is based on RND and various interdepartmental creations.
                • +
                + +

                14 January 2015

                +

                Boggart updated:

                +
                  +
                • Adds a new spell: Repulse. (Cooldown: 40 seconds, 15 at max upgrade.) Repulse throws objects and creatures within 5 tiles of the caster away and stuns them for a very short time. Distance thrown decreases as the distance from the caster increases. (Max of 5, minimum of 3) Casting it while standing over someone will stun them for longer and cause brute damage but will not throw them.
                • +
                • Fixes the Exosuit Control Console having the unpowered icon state while powered.
                • +
                +

                Iamgootball updated:

                +
                  +
                • Added Goonstation Chemistry!
                • +
                • Adds 14 medicines!
                • +
                • Adds 3 pyrotechnics!
                • +
                • Adds 3 drugs with actual gameplay uses!
                • +
                • Adds 7 toxins!
                • +
                • Adds a Chemical Heater for some new recipes!
                • +
                • Adds Chemical Patches for touch based applications!
                • +
                • Adds a Traitor Poison Kit!
                • +
                • Adds Morphine Medipens!
                • +
                • Adds Morphine bottles to Cargo!
                • +
                + +

                13 January 2015

                +

                Dannno updated:

                +
                  +
                • Adds a bandanas in basic colors to lockers in the station. Activate in hand to switch between head and mask modes.
                • +
                • Adds more colored jumpsuits to the mixed locker.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Player controlled slimes can now change their face to match AI controlled slimes in kawaiiness. use *help as a slime to find the commands.
                • +
                +

                Studley updated:

                +
                  +
                • Fixed NTSL division.
                • +
                • Added new NTSL math functions, including 'sin,' 'cos,' 'asin,' 'acos,' and 'log.'
                • +
                + +

                06 January 2015

                +

                JJRcop, Nienhaus updated:

                +
                  +
                • Adds emoji to OOC chat.
                • +
                + +

                27 December 2014

                +

                Dorsidwarf updated:

                +
                  +
                • Buffs the RCD from 30 matter units to max capacity 100, and increases the value of matter charges.
                • +
                +

                Paprika updated:

                +
                  +
                • Taser electrodes now have a limited range of 7 tiles.
                • +
                • The energy gun's stun mode has been replaced with a disable mode.
                • +
                • Security officers will now spawn with tasers.
                • +
                • Disabler beams now go through windows and grilles.
                • +
                • Disabler beams now do 33 stamina damage instead of 34. This means they will only stun in 3 hits on someone that is NOT 100% healthy.
                • +
                • Most ballistic projectiles now do stamina damage in addition to their regular damage instead of stunning. This includes nuke op c-20r .45, detective's revolver, etc. They are effectively better than disablers at stunning people, as well as do significant damage, but they do not stun instantly like a taser. Expect engagements to be a little longer and plan ahead for this.
                • +
                • Added an advanced taser for RnD, the child of a disabler and a taser.
                • +
                +

                Tkdrg updated:

                +
                  +
                • Envoys from the distant Viji sector have brought an exotic contraption to Centcomm. Known as the 'supermatter shard', it is a fragment of an ancient alien artifact, believed to have a dangerous and mysterious power. This precious item has been made available to the Space Station 13 cargo team for the cost of a hundred supply points, as a reward for all your hard work. Good luck, and stay safe.
                • +
                + +

                16 December 2014

                +

                Paprika updated:

                +
                  +
                • Reverted security armor and helmets to the old (Grey) style of armor/helmets.
                • +
                • Updated security jumpsuits to be more consistent across officer/warden/HoS ranks.
                • +
                • Added tactical armor and helmets to replace the largely unused bulletproof vest in the armory. This armor has higher bullet and explosive resistance than normal armor, and uses the previous, more military armor/helmet sprites.
                • +
                • Added a new injury doll to go along with the overall health meter. This will show you locational overall limb-based burn/brute damage, so you can tell the difference between being hurt with burn/brute or toxins/stamina damage which does not affect limbs.
                • +
                • Reverted some UI icons back to their older status and changed some around for readability. Thank you for your feedback.
                • +
                +

                Thunder12345 updated:

                +
                  +
                • Added a new FRAG-12 explosive shotgun shell, built from the unloaded technological shell, using tablecrafting. Constructed using an unloaded technological tech shell, 5 units sulphuric acid, 5 units polytrinic acid, 5 units glycerol, and requires a screwdriver.
                • +
                +

                phil235 updated:

                +
                  +
                • Change to the hunger system. Eating food with sugar temporarily worsens your nutrition drop rate. Eating too much sugar too quickly can make you temporarily unable to eat any sugary food. The nutritional effect of sugar depends on how hungry you are thus it cannot easily raise your nutrition level past well fed and is best used when hungry. Lots of sugar can make you a bit jittery at times. Eating food with vitamin (new reagent) counteracts the sugar effects and can give you temporary buffs when well fed: it lowers your chance to catch or spread viruses,it makes your body's metabolism more efficient that is it keeps healing reagents longer and evacuate toxins faster and reduces damage from radioactive reagents. Your metabolism gets less efficient if starving.
                • +
                • New hunger hud icons: starving, hungry, fed, well fed, full, fat.
                • +
                • Snack vending machine get a chef compartment that can be loaded with non-sugary food by hand or with a tray by anyone with kitchen access (unless you hack the machine with multitool or emag). The food can be unloaded by anyone, like regular snacks
                • +
                • Cargo can get a nutritious pizza crate order for 60 points.
                • +
                • Grown and cooked food gets some vitamin while junk food gets less nutriment and more sugar (only hot donkpocket and ramen don't have sugar)
                • +
                • The number of junk food in snack vending machine is lowered from 6 to 5 of each type.
                • +
                • Fixing not being able to load an omelette du fromage on a tray.
                • +
                + +

                10 December 2014

                +

                GunHog updated:

                +
                  +
                • The Nanotrasen firmware update 12.05.14 for station AI units and cyborgs includes a patch to give them more control of their automatic speech systems. They can now chose if the message should be broadcasted with their installed radio system and any frequency available to them.
                • +
                +

                Paprika updated:

                +
                  +
                • Added winter coats and winter boots! Bundle up for space xmas! They are available in most wardrobe and job lockers, and come with special features depending on the flavor!
                • +
                +

                Paprka updated:

                +
                  +
                • Added a new weapon for syndicate boarding parties, the C-90gl assault rifle. This is a hybrid rifle with a lot of damage and a grenade launcher alt-fire. Purchasable for 18TC.
                • +
                • Suppressors can now be used on the protolathe SMG and the C-20r.
                • +
                +

                RemieRichards updated:

                +
                  +
                • Sounds in areas of low pressure (E.g. Space) are now much quieter
                • +
                • To hear someone in space you need to be stood next to them, or use a radio
                • +
                +

                as334 updated:

                +
                  +
                • We've discovered that slimes react to the introduction of certain chemicals in their bloodstream. Inaprovaline will decrease the slimes children's chance of mutation, and plasma will increase it.
                • +
                • We've also found that feeding slimes solid plasma bars makes them more friendly to humans.
                • +
                + +

                08 December 2014

                +

                Ergovisavi updated:

                +
                  +
                • Allows using replica pods to clone people without bodies.
                • +
                • Speeds up replica pod cloning by adjusting the starting production.
                • +
                • Replica pods will now only create plantmen.
                • +
                • Mixing blood in a beaker or taking it from a suicide victim will render the blood sample useless for replica pod cloning.
                • +
                +

                GunHog updated:

                +
                  +
                • Our top Nanotrasen scientists have provided improved specifications for the Rapid Part Exchange Device (RPED)! The RPED can now contain up to 50 machine components, and will display identical items in groups when using its inventory interface.
                • +
                • Note that power cells, being somewhat bulkier, will take up more space inside the device. In addition, the RPED's firmware has also been updated to assist in new machine construction! Simply have the required components inside the RPED, apply it to an unfinished machine with its circuit board installed, and watch as the device does the work!
                • +
                • Nanotrasen has updated station bound Artificial Intelligence unit design specifications to include an integrated subspace radio transmitter. The transmitter is programmed with all standard department channels, along with the inclusion of its private channel. It is accessed using :o in its interface.
                • +
                • {INCOMING CLASSIFIED SYNDICATE TRANSMISSION} Greetings, agents! Today, we are happy to announce that we have successfully reverse engineered the new subspace radio found in Nanotrasen's newest AI build plans. We have included our encryption data into these transmitters.
                • +
                • Should you be deployed with one of our agent AIs, it may be wise of you to purchase a syndicate encryption key in order to contact it securely. Remember, as with other agents, a Syndicate AI is unlikely to be of the same Syndicate corporation as you, and you both may actually have conflicting mission parameters! Your channel, as always, is accessed using :t.
                • +
                +

                Paprika updated:

                +
                  +
                • Changed the way flashbang protection works. Currently, only security helmets (and their derivatives like swat and riot), and some hardsuit helmets have flashbang protection. Ear items like earmuffs and 'bowman' headsets (alternative, larger headsets) have flashbang protection as well, so you're able to go hatless as security. The Head of security, warden, and detective's hat do NOT have flashbang protection, but their earpieces do, from the 'noise' part of the flashbang. The flashbang blind still works as before, with only sunglasses/hardsuit helmets protecting you.
                • +
                +

                Tkdrg updated:

                +
                  +
                • Security/Medical HUDs are now more responsive and less laggy.
                • +
                + +

                10 November 2014

                +

                Menshin updated:

                +
                  +
                • Made meteors more threatening.
                • +
                • Added three levels of danger for meteors waves, randomly selected at spawn.
                • +
                • Rumors speaks of a very rare meteor of unfathomable destructive power in the station sector...
                • +
                • Fixed infinigib/infinisplosion with meteors bumping
                • +
                +

                Paprika updated:

                +
                  +
                • Added a defibrillator and an EMT alternative clothing set for medical doctors. These defibrillators will revive the recently deceased as long as their souls are occupying their body and they have no outstanding brute or burn damage.
                • +
                • Yes, they actually work now too!
                • +
                • Added a way for you to unload a round from a chambered projectile weapon. Simply click on the gun when it is in your active hand, and assuming it does not have a magazine, the round should pop out. You can also use this system to unload individual bullets from magazines and ammo boxes, like the detective's speed loaders.
                • +
                • Added glass tables and overhauled how table construction works. Now, you need to build a frame using either metal rods or wood planks, and then place a top on the table.
                • +
                • Table disassembly now works in two stages. Screwdriver the table to remove the top of it and reduce it back to a frame. Frames can be moved around and walked on top of. Wrench the table to deconstruct it entirely back to parts, this is quicker and much more like the old fashion. To deconstruct a frame, use your wrench on it as well.
                • +
                • Fixed traitor med analyzers not fitting in medical belts.
                • +
                • Fixed syringe box bug with medipens.
                • +
                +

                Perakp updated:

                +
                  +
                • Added escape-with-identity objective for changelings. Completion requires the changeling to have transformed to their target and be wearing an ID card with the target's name on it when escaping.
                • +
                • Changelings can absorb someone even if they have that DNA in storage already.
                • +
                • Fixes hivemind being lost when readapting evolutions.
                • +
                + +

                04 November 2014

                +

                MrPerson updated:

                +
                  +
                • You can use AI Upload modules directly on unslaved cyborgs to change their laws. The panel has to be open to do this.
                • +
                • Newly emagged cyborgs get locked down for a little bit until their new laws get shown. Thank you to whoever bitched about it on singulo and filed an issue report.
                • +
                +

                Paprika updated:

                +
                  +
                • Added an advanced mop for the janitor, available at the protolathe. This hi-tech custodian's friend can mop twice as many floor tiles before needing to return to your bucket!
                • +
                • Moved holosign projector to the protolathe and gave the janitor back his old signs. We hope you've enjoyed this free trial run of holo projectors, but our budget is tight!
                • +
                • Added a holosign projector to the janitor module for cyborgs.
                • +
                • Soap now has a delay. Different soap types clean faster than others, but expect to put it some extra arm work if you want to clean that fresh blood!
                • +
                +

                Tkdrg updated:

                +
                  +
                • Cutting-edge research in a secret Nanotransen lab has shed light into a revolutionary form of communication technology known as 'chat'. The Personal Data Assistants of Space Station 13 have been deployed with an experimental module implementing this system, dubbed 'Nanotransen Relay Chat'.
                • +
                + +

                30 October 2014

                +

                MrPerson updated:

                +
                  +
                • Everything now drifts in space, not just mobs.
                • +
                • Mechs are just as maneuverable in space as you are.
                • +
                • Being weightless now slows movement down by a lot rather than speeding it up. Having your hands full will make movement even slower.
                • +
                • Removed spaceslipping (You slipped! x1000)
                • +
                • Jetpacking after being thrown out a mass driver will cancel out your momentum.
                • +
                • Shooting a gun causes you to drift the other direction like spraying a fire extinguisher.
                • +
                + +

                27 October 2014

                +

                Jordie0608 updated:

                +
                  +
                • Adds a new riot shotgun that spawns in the armory with a 6 round beanbag magazine.
                • +
                +

                Paprika updated:

                +
                  +
                • Allowed constructs to assist in activating most runes. This includes conversion runes, but does NOT include sacrifice runes.
                • +
                +

                Patchouli Knowledge updated:

                +
                  +
                • In light of the recent achievements of the Robotics department, new and upgraded exosuit schematics were delivered to SS13 exosuit fabricators as a reward from CentComm.
                • +
                • The countless hours of research, experimentation, and reverse-engineering of wrecks from the mining asteroid have paid off - the Research team has rediscovered the process of building Phazon exosuits. The parts and circuits have been made available in the exofabs and circuit printers.
                • +
                • The APLU Ripley exosuit has been refitted with asteroid mining purposes in mind. Its armour plating will provide extra protection against the brute attacks of asteroid alien lifeforms, and protect from gibtonite explosions. The leg servos were upgraded to provide faster movement.
                • +
                • There are rumours of some creative shaft miners discovering a way of augmenting APLU Ripley armour with trophy hides from goliath-type asteroid lifeforms...
                • +
                • Durand construction routines now require a phasic scanner module and a super capacitor to match Nanotrasen industry standards.
                • +
                • The exosuit fire extinguisher now boasts a larger watertank, and carries a whole 1000u of water from the old 200u tank.
                • +
                • The exosuit plasma converter's efficiency has been vastly improved upon, and its fuel consumption has been cut to nearly half of old values.
                • +
                • The exosuit sleeper interiors have been refitted to allow the occupants more freedom of movement, and the occupants may now eject at will.
                • +
                • The armour plating on all exosuits was fitted with reactive deflection systems specifically designed to stop the rapid slashes from xenomorph threats.
                • +
                • The Firefighter Ripley chassis were moved from the Exosuit Equipment section to the APLU Ripley section of exofab menus.
                • +
                • Fixed some minor errors in the exosuit construction message syntax.
                • +
                +

                phil235 updated:

                +
                  +
                • Closets and its children can no longer contain a near infinite amount of mobs. Large mobs (juggernaut, alien emperess, etc) don't fit, small mobs (mouse, parrot, etc) still fit in the same amounts, human-sized mob (humanoid, slime, etc) fit but maximum two per body bag and three for all the other closets. Adding bluespace body bag to R&D, it can hold even large mobs and in large amounts.
                • +
                • girders and displaced girders now have a chance to let projectiles pass through them.
                • +
                + +

                19 October 2014

                +

                Donkie updated:

                +
                  +
                • Major atmos update. Featuring pretty colors.
                • +
                • 4-Way pipes added.
                • +
                • Flip-able trinary devices (mixer & filter).
                • +
                • Pipe-painter added.
                • +
                • Most pipe icons have been tweaked with shading and such, making it look more uniform.
                • +
                +

                JJRcop updated:

                +
                  +
                • We have managed to re-discover drones! We improved the formula, and have discovered some cool stuff.
                • +
                • After some fiddling with audio recognition, drones can now understand human speech!
                • +
                • One of our engineers found some screws that were loose on some of our tests, screwing them in made the drone work as if it had just come out of robotics.
                • +
                • We had to sacrifice some of our NT brand EMP protection for the speech recognition, so drones can get damaged if subjected to heavy EMP radiation.
                • +
                • We figured out why drones couldn't remove objects from people, that has been fixed.
                • +
                • The standby light wasn't wired correctly.
                • +
                • Unfortunately one of our assistants tasked with transporting the only copy of the data dropped the flash drive in hot water because he wanted to see what "drone tea" tasted like. You'll have to discover them again.
                • +
                +

                Lo6a4evskiy updated:

                +
                  +
                • Station's crayons have been upgraded to the version 2.0 of ColourOS. Don't worry, they are still edible!
                • +
                • Many items now have different stripping delays. Heavier and more armored pieces of clothing are generally more difficult to take off someone else, while smaller items can be very easy to snatch. Pockets always take 4 seconds to search.
                • +
                +

                MrPerson updated:

                +
                  +
                • Implants are activated via action buttons on the top-left corner of your screen instead of emotes.
                • +
                +

                Paprika updated:

                +
                  +
                • Added zipties for security and nuke operatives. These have a slightly shorter 'breakout time' as normal cuffs but they are destroyed upon removal. Beepsky and sec borgs now use these by default.
                • +
                • Changed the medical HUD in the tactical medkit to a night vision HUD so syndicate medics don't stumble around in the dark. Can be purchased through nuke op uplinks.
                • +
                • Added a second roboticist roundstart job slot.
                • +
                • Added a third cargo tech job slot.
                • +
                +

                Paprka updated:

                +
                  +
                • Added undershirts as a companion piece to jumpsuit adjusting. Beat the heat with a comfortable tank top, or warm up with an extra layer tshirt!
                • +
                • Added a holographic sign projector to replace the old wet floor signs to encourage janitors to warn crewmembers about wet floors.
                • +
                +

                optimumtact updated:

                +
                  +
                • Medical supplies crate now comes with a boxy full of bodybags
                • +
                + +

                09 October 2014

                +

                Paprika updated:

                +
                  +
                • Added inaprovaline MediPens (autoinjectors) that replace inaprovaline syringes in medkits. Like the syringes, these can be used to stabilize patients in critical condition, but require no medical knowledge to use.
                • +
                • Reverted the mining scanner to a manual scanner.
                • +
                • Buffed the rate of the automatic scanner
                • +
                • Added automatic scanner to equipment locker. It is also available by redeeming your voucher.
                • +
                • Reduced the size of the mining jetpack, it now fits in backpacks. The capacity of the jetpack has been reduced as well, however.
                • +
                • Removed resonator from the mining voucher redemption. Replaced it with a mining drill.
                • +
                + +

                08 October 2014

                +

                Cheridan updated:

                +
                  +
                • Flash mechanics changed. Instead of a knockdown effect, it will cause a stumbling effect. This includes area-flashing. The five-use-per-minute limit has been removed. "Synthetic" flashes made in the protolathe are now normal flashes.
                • +
                +

                Paprika updated:

                +
                  +
                • Added a unique shield to the Head of Security's locker. It can be concealed for storage and activated like an energy shield, but functions like a riot shield. It can also be made at the protolathe.
                • +
                • Marauders: Due to your extremely great performance as of late, we've added a new equipment room on Mother Base. A lot of your equipment has been moved to this room, as well as some new equipment added onto the Mothership. Take some extra time to familiarize yourself with the placement of your equipment.
                • +
                • Oh, and you guys have a space carp now too.
                • +
                +

                Paprka updated:

                +
                  +
                • Added jumpsuit adjusting. Adjust your jumpsuit to wear it more casually by rolling it down or roll up those sleeves of that hard-worn suit!
                • +
                • Added a grey alternative to the detective's suit and trenchcoat. Now you can be a noir private investigator!
                • +
                +

                Tkdrg updated:

                +
                  +
                • Space Station 13 has been deployed with transit tube dispensers. Central Command encourages the engineering department to repair and improve the station using this equipment.
                • +
                + +

                05 October 2014

                +

                Ikarrus updated:

                +
                  +
                • Character Save Files will now remember Species.
                • +
                +

                Miauw updated:

                +
                  +
                • Added 5 new maintenance ambience tracks, made by Cuboos.
                • +
                +

                Nobalm updated:

                +
                  +
                • Added examine descriptions to twenty eight objects.
                • +
                +

                Paprika updated:

                +
                  +
                • Due to some payroll cuts affecting lower-rank crewmembers on Space Station 13, we fear that uprisings and mutinies might be approaching. To help defend themselves, all heads of staff have been given telescopic batons to subdue attackers should they be assaulted during their shifts.
                • +
                • Toolbelts can now hold pocket fire extinguishers. Atmos technicians spawn with them.
                • +
                • Remember those magboots we had our undercover agents steal? Well we've finally reverse engineered them. Not only that, but we gave them a killer paintjob. They will be available in the uplinks of boarding parties we send out to Nanoscum stations.
                • +
                • Listen up, Marauders! The Syndicate has recently cut back funding into boarding parties. What this means is no more hi-tech fancy energy guns. We've had to roll back to using cheaper, mass-produced automatic shotguns. But in retrospect, could this possibly be a downgrade?
                • +
                + +

                01 October 2014

                +

                Cheridan updated:

                +
                  +
                • Experienced smugglers have joined the Syndicate, adding their bag of tricks to its arsenal. These tricky pouches can even fit under floor plating!
                • +
                +

                Ikarrus updated:

                +
                  +
                • Adds Gang War, a new game mode still in alpha development.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • After a public relations nightmare in which a horde of 30 assistants violently dismembered a sole commander the revolution has begin tactically reducing their initial presence in stations with weak command structures. Should stations add to their command staff during the shift additional support will be provided.
                • +
                • Too many instances of crying CMOs hiding in the lockers of supposedly secured revoulutionary stations has lead to a crackdown on revolutionary policy. You must kill or maroon the entire command staff, reguardless of when they reached the station.
                • +
                • Intense study into the underlying structure of the universe has revealed that everything fits into a massive SPACECUBE. This deep and profound understanding has led to a revolution in cross surface movement, and spacemen can look forward to a new era of predictable and safe space-fairing.
                • +
                +

                Jordie0608 updated:

                +
                  +
                • Grille damage update, humans now deal less unarmed damage to grilles while projectiles and items deal more.
                • +
                • Replaced all AI turrets with porta-turrets that work on LoS instead of area and can be individually configured
                • +
                +

                Miauw updated:

                +
                  +
                • Voice changer masks work again.
                • +
                • You can now turn voice changer masks on/off by activating them in your hand. They start off.
                • +
                • The amount of TC has been increased to 20, but the price of all items has been doubled as well
                • +
                • With this, the prices of stealthy items has been slightly decreased and the prices of offensive items slightly increased (+/- 1 TC)
                • +
                +

                RemieRichards, JJRcop updated:

                +
                  +
                • New research in the area of robotics has been published on Maintenance Drones, little robots capable of repairing and improving the station. Unfortunately, all of the research was lost in a catastrophic server fire, but we know they can be made, so it's up to you to re-discover them! Make us proud!
                • +
                +

                phil235 updated:

                +
                  +
                • Changed all clone damage descriptions to use the term cellular damage to avoid confusion with genetics mutations.
                • +
                + +

                07 September 2014

                +

                Gun Hog updated:

                +
                  +
                • Nanotrasen scientists have released a Heads-Up-Display (HUD) upgrade for all AI and Cyborg units! Cyborgs have had Medical sensors and a Security datacore integrated into their core hardware, replacing the need for a specific module.
                • +
                • AI units are now able to have suit sensor or crew manifest data displayed to them as a visual overlay. When viewing a crewmember with suit sensors set to health monitoring, the health status will be available on the AI's medical HUD. In Security mode, the role as printed on the crewmember's ID card will be displayed to the AI.
                • +
                • Any entity with a Security or Medical HUD active will recieve appropriate messages on that HUD.
                • +
                +

                Ikarrus updated:

                +
                  +
                • Server Operators: A new config option added to restrict command and security roles to humans only. Simply add/uncomment the line ENFORCE_HUMAN_AUTHORITY in game_options.txt. +
                • Players: To check if this config option is enabled on your server, type show-server-revision into the game's command line. +
                • +
                + +

                06 September 2014

                +

                Ikarrus updated:

                +
                  +
                • Tampered supply ordering consoles can now order dangerous devices directly from the Syndicate for 140 points. +
                • Securitrons line units will no longer arrest individuals without IDs if their faces can still be read. +
                • Enemy Agent ID cards have been reported to be able to interfere with the new securitron threat assessment protocols. +
                • The Syndicate base has been modified to only allow the leading operative to launch the mission. +
                • +
                + +

                04 September 2014

                +

                KyrahAbattoir updated:

                +
                  +
                • All the objects found in BoxStation maintenance are now randomized at round start.
                • +
                + +

                03 September 2014

                +

                Ikarrus updated:

                +
                  +
                • Cost of Syndicate bombs increased to 6 telecrystals.
                • +
                +

                JStheguy updated:

                +
                  +
                • Many new posters have been added.
                • +
                + +

                01 September 2014

                +

                ChuckTheSheep updated:

                +
                  +
                • Adds preference toggle for intent selection mode. +
                • Direct selection mode switches intent to the one you click on instead of cycling them. +
                • +
                +

                Ikarrus updated:

                +
                  +
                • New more detailed End-Round report. +
                • Removes count of readied players from the lobby. +
                • +
                +

                Jordie0608 updated:

                +
                  +
                • Virology has been made the right color.
                • +
                +

                Miauw updated:

                +
                  +
                • A major rework of saycode has been completed which fixes numerous issues and improves functionality. +
                • Please report any issues you notice with any form of messaging to Github. +
                • +
                + +

                31 August 2014

                +

                Tokiko1 updated:

                +
                  +
                • Adds two new hairstyles.
                • +
                + +

                30 August 2014

                +

                Ikarrus updated:

                +
                  +
                • Space Station 13 has been authorized to requisition additional Tank Transfer Valves from Centcom's supply lines for the price of 60 cargo points.
                • +
                + +

                26 August 2014

                +

                Ikarrus updated:

                +
                  +
                • Security forces are advised to increase scrutiny on personnel coming into contact with AI systems. Central Intelligence suggests that Syndicate terrorist groups may have begun targeting our AIs for destruction. +
                • For the preservation of corporate assets, Central Command would like to remind all personnel that evacuating during an emergency is mandatory. We suspect that terrorist groups may be attempting to abduct marooned personnel who failed to evacuate. +
                • R&D; teams have released an urgent update to station teleporters. To eliminate the risk of mutation, personnel are advised to calibrate teleporters before attempting each use. +
                • Centcom has approved the Captain's request to rig the display case in his office with a burglar alarm. Any attempts to breach the case will now trigger a lockdown of the room. +
                • **Crew Monitoring Consoles now scan their own Z-level for suit sensors, instead of only scanning Z1. +
                • **Server operators can now set a name for the station in config.txt. Simply add the line "STATIONNAME Space Station 13" anywhere on the text file. Replace Space Station 13 with your station name of choice.
                • + +
                + +

                24 August 2014

                +

                Ikarrus updated:

                +
                  +
                • Ore redemption machine can now smelt plasteel.
                • +
                • Mulebot speed has been vastly increased by up to 300%.
                • +
                • Removed exploit where false walls can be used to cheese the AI chamber's defenses
                • +
                + +

                21 August 2014

                +

                Ikarrus updated:

                +
                  +
                • The Phase Shift ability will no longer instantly gib players. Instead, they will be knocked out for a few seconds. Mechas will be damaged.
                • +
                • The Phase Slayer ability will no longer instantly gib players. Instead, they will be dealt 190 brute damage. Mechas will be dealt 380 damage.
                • +
                • ADMIN: Most Event buttons have been moved out of the secrets menu and into a Fun verb.
                • +
                + +

                20 August 2014

                +

                Cheridan updated:

                +
                  +
                • Three new shotgun shells added. Create them by first researching and printing an unloaded tech shell in R&D.; Afterwards, use table-crafting with the appropriate components with a screwdriver in-hand.
                • +
                • Meteorshot: tech shell + compressed matter cartridge + micro(or better) manipulator.
                • +
                • Pulse Slug: tech shell + advanced capacitor + ultra micro-laser.
                • +
                • Dragonsbreath: tech shell + 5 phosphorus (in container).
                • +
                • Incendiary rounds now leave a blazing trail as they pass. This includes existing incendiary rounds, new dragonsbreath rounds, and the Dark Gygax's carbine.
                • +
                + +

                19 August 2014

                +

                Ikarrus updated:

                +
                  +
                • Brig cells and labour shuttle have both been modified to send a message to all security HUDs whenever a prisoner is automatically released.
                • +
                • The labour camp has been outfitted with a points checking console to allow prisoners to check their quota progress easier and better explain the punishment system of forced labour.
                • +
                + +

                18 August 2014

                +

                Iamgoofball updated:

                +
                  +
                • 25 new hairstyles have been added.
                • +
                + +

                15 August 2014

                +

                AndroidSFV updated:

                +
                  +
                • AI photography has been extended to Cyborgs. While connected to an AI, all images taken by cyborgs will be placed in the AI's album, and viewable by the AI and all linked Cyborgs.
                • +
                • When a Cyborgs AI link wire is pulsed, the images from the Cyborgs image album will be synced with the AI it gets linked to.
                • +
                • Additonally, Cyborgs are able to attach images to newscaster feeds from whichever album is active for them. Cyborgs are also mobile, inefficent, printers of same images.
                • +
                + +

                11 August 2014

                +

                Ikarrus updated:

                +
                  +
                • Boxstation map updates:
                  + -Singularity engine walled from from outer space
                  + -Shutters for exterior Science windows
                  + -Teleporter board moved from RD's Office to Tech Storage now that the teleporter can be built again.
                  + -Security Deputy Armbands (red) added in HoS's Office



                • +
                + +

                14 July 2014

                +

                Miauw updated:

                +
                  +
                • Added a new mutetoxin that can be made with 2 parts uranium, 1 part water and 1 part carbon and makes people mute temporarily
                • +
                • Parapens were renamed to sleepy pens and now contain 30 units of mutetoxin and 30 units of sleeptoxin instead of zombie powder.
                • +
                • C4 can no longer be attached to mobs
                • +
                • Reduced the C4 price to 1 telecrystal
                • +
                + +

                07 July 2014

                +

                Firecage updated:

                +
                  +
                • We, of the NanoTrasen Botany Research and Developent(NTBiRD), has some important announcements for the Botany staff aboard our stations.
                • +
                • We have recently released a new strain of Tower Cap. It is now ensured that more planks can be gotten from a single log.
                • +
                • Head Scientist [REDACTED] is thanked for his new strain of Blood Tomatoes. It now not only contains human blood, but also chunks of human flesh!
                • +
                • We have also discovered a way to replant the infamous Cash Trees and 'Eggy' plants. We can now harvest their seeds, and the rare products are inside the fruit shells.
                • +
                • Similar to the new variety of Tower Caps, it is now possible to get various grass flooring depending on the plants potency!
                • +
                • Our sponsors at Knitters United has released a new variation of the botany Aprons and Coveralls which can hold a larger variety of items!
                • +
                • A new version of Plant-B-Gone was produced which are much more lethal to plants, yet at the same time has no extra effect on humans besides the norm.
                • +
                +

                Paprika updated:

                +
                  +
                • Tweaked mining turf mineral droprates and reverted mesons to the 'old' style of mesons. This means mesons will be more powerful with the added side effect of not being able to track individual light sources through walls and such. This is a trial run; please provide feedback on the /tg/ codebase section of the forum.
                • +
                + +

                05 July 2014

                +

                Firecage updated:

                +
                  +
                • Recently the Space Wizard Federation, with some assistance from the Cult of Nar-Sie, recently created a new strain of Killer Tomato. A sad side effect is this effected all current and future Killer Tomato strains in our galaxy. They are now reported to be extremely violent and deadly, use extreme caution when growing them.
                • +
                + +

                03 July 2014

                +

                Miauw updated:

                +
                  +
                • Added slot machines to the game
                • +
                • Slot machines use reels. All prizes except for jackpots can be won on all lines
                • +
                • Simply put a coin into one of the slot machines in the bar to play!
                • +
                • Slot machines can also be emagged.
                • +
                + +

                01 July 2014

                +

                Ikarrus updated:

                +
                  +
                • Central Security has approved an upgrade of the Securitron line of bots to a newer model.
                  Highlights include:
                • +
                • -An optional setting to arrest personnel without an ID on their uniform (Disabled by default)
                • +
                • -An optional setting to notify security personnel of arrests made by the bots (Enabled by default)
                • +
                • -A new "Weapon Permit" access type that securitrons will use during their weapons checks. Personnel who work with weapons will be granted this access by default. More can be assigned by the station's Head of Security and Personnel.
                • +
                • -A more robust threat assessment algorithm to improve accuracy in identifying perpetrators.
                • +
                +

                Rolan7 updated:

                +
                  +
                • Fixed the irrigation system in hydroponics trays. Adding at least 30 units of liquid at once will activate the system, sharing the reagents between all connected trays.
                • +
                • Changelings can communicate while muzzled or monkified, and being monkified doesn't stop them from taking human form.
                • +
                • Mimes cannot break the laws of physics unless their vow is currently active.
                • +
                • Trays are rewritten to make sense and so borgs can actually use them. Service borgs can discretely carry small objects, hint hint. The items are easily knocked out of them.
                • +
                + +

                20 June 2014

                +

                Ikarrus updated:

                +
                  +
                • The 'Enter Exosuit' button was removed, and you have to click + drag yourself to enter a mech now; like you would to enter a disposal unit.
                • +
                + +

                12 June 2014

                +

                Cheridan updated:

                +
                  +
                • NT Baton Refurbishment Team Notice: 'Released' option in security records has been renamed 'Discharged', and the sechud icon has been changed from a blue R to a blue D. Stop beating your released prisoners, dummies; they're not revheads.
                • +
                • NT Entertainment Department Notice: The prize-dispensing mechanism in Arcade machines has been replaced with a cheaper gas-powered motor. It should still be completely safe, unless an electromagnetic field disrupts it!
                • +
                • Pill code tweaked. In short, you can feed monkeys pills now #whoa
                • +
                • Casings ejected from guns will now spin #wow
                • +
                + +

                11 June 2014

                +

                Ikarrus updated:

                +
                  +
                • Having the nuke disk will no longer prevent you from using teleporters or crossing Z-levels. Leaving the Z-level however, will cause you to "suddenly lose" the disk.
                • +
                • Pulled objects will now transition with you when as you transition Z-levels in space.
                • +
                +

                Malkevin updated:

                +
                  +
                • IEDs have been removed
                • +
                • Firebombs have been added
                • +
                • They're as reliable and predictable as you would expect from a soda can filled with flammable liquid and set off with an igniter contected to two wires. Use your branes and take proper precautions or leave a charred corpse, your choice.
                • +
                + +

                06 June 2014

                +

                Gun Hog updated:

                +
                  +
                • Nanotrasen programmers have discovered a potential exploit involving a station's door control networks. With sufficient computing power, the network can be overloaded and forced to open or bolt closed every airlock at once.
                • +
                • Some reports suggest that the latest firmware update to Artifical Intelligence units installed in our research stations may contain this exploit. Any such reports that state that should a station's AI unit "malfunction", it may gain the ability to use this exploit to initate a full lockdown of the station. Fire locks and blast doors are also said to be affected.
                • +
                • Nanotrasen officially holds no validity to these reports. We recommend that station personnel disregard such rumors.
                • +
                + +

                20 May 2014

                +

                Kelenius updated:

                +
                  +
                • After the years of extensive research into xenobiology, and thousands of scientists lost to the slimes, we have finally come to admitting that we don't have a clue how they work. However, we've outlined a few facts, and managed to breed a new kind of slimes. They will be shipped to your station on the next shifts. They are different from the ones they are used to in the following:
                • +
                • Their sight is better than ever, and now they can finally see things that are not directly pressed against them. Beware, they may appear more aggressive!
                • +
                • Their memory has also become better, and they will now remember the people who fed them. As a note, you need to grab the monkey or push it to show to the slime that you're giving it to them, and it's not just walking in.
                • +
                • Apparently, not only we have studied them, but they have also studied us. Slimes are now capable of showing various emotions, mimicking humans. Use it to your advantage.
                • +
                • There are reports of slimes showing signs of sentience. Further research is recommended.
                • +
                • One of the reported signs is their speech capability. Following facts have been gathered: they talk more often to those who feed them; they react to being called by the number, but 'slimes' is also acceptable; they are able to understand being ordered to "follow" someone, or "stop". There is a report of slime releasing the assistant after a scientist shouted at it, and then calmly ordered a slime to stop.
                • +
                • We've made a modification of a health scanner that is intended for the use on slimes. Two are available in the smartfridge.
                • +
                • We've come to a conclusion that 5 units of reagents are not necessary to cause slime core reactions. You actually only need one unit.
                • +
                + +

                16 May 2014

                +

                Menshin updated:

                +
                  +
                • Thanks to Nanotrasen's Engineering division, the power wiring of the station has a received a complete overhaul. Here are the highlight features in CentCom report :
                • +
                • Improved way of connecting cables! Finished is the time of "laying and praying" : it's now as simple as matching ends of each cables!
                • +
                • Unified way of connecting machines! Every machine is connected to a powernet through a node cable (it still needs to be anchored to receive power though)!
                • +
                • Lots of improvements in the cable cutting tools! Gone is the odd magic of physically separated yet still connected powernets (contact CentCom Engineering Division if you still witness something weird, we always have a fired form ready for our engineers!)!
                • +
                • Any conscientious (or not!) Station Engineer may check an "updated wiring manual" at http://www.tgstation13.org/wiki/Wiring#Power_Wires
                • +
                + +

                14 May 2014

                +

                Ikarrus updated:

                +
                  +
                • A recent breakthrough in Plasma Materials Research has led to the development of a stronger, tougher plasteel alloy that can better resist extreme heat by up to four times. A live demonstration preformed during a press conference earlier today showed a segment of reinforced wall resisting an attack by Thermite. The corporation also announces that upgrades to its existing stations is already underway.
                • +
                + +

                07 May 2014

                +

                /Tg/station Code Management updated:

                +
                  +
                • /tg/station code is now under a feature freeze until 7th June 2014. This means that the team will be concentrating on fixing bugs instead of adding features for the month.
                • +
                • You can find more information about the feature freeze here. http://tgstation13.org/phpBB/viewtopic.php?f=5&t;=273
                • +
                • This is the perfect time to report bugs, which you can do so here. https://github.com/tgstation/-tg-station/issues/new
                • +
                + +

                30 April 2014

                +

                Giacom updated:

                +
                  +
                • You now have to give a reason when calling the shuttle.
                • +
                • The amount of time it takes for the shuttle to refuel is now a config option, the default is 20 minutes.
                • +
                +

                Gun Hog updated:

                +
                  +
                • Certain Enemies of the Corporation have discovered a critical exploit in the firmware of several NanoTrasen robots that could prevent the safe shutdown of units corrupted by illegally modified ID cards, dubbed "cryptographic sequencers".
                • +
                • NanoTrasen requires more research into this exploit, this we have issued a patch for AI and Cyborg software to simulate this malfunction. All NanoTrasen AI units are advised to only allow testing in a safe and contained environment. The robot can safely be reset at any time.
                • +
                • Unfortunately, a robot corrupted by a "cryptographic sequencer" still cannot be reset by any means. NanoTrasen urges regular maintenance and care of all robots to reduce replacement costs.
                • +
                + +

                27 April 2014

                +

                Ikarrus updated:

                +
                  +
                • NT Human Resources announces an expansion of the List of Company-Approved Hair Styles, as well as more relaxed gender restrictions on hair styles. Check with your local company-sponsored hairstylist to learn more.
                • +
                + +

                22 April 2014

                +

                Ikarrus updated:

                +
                  +
                • ID Computers have been modified to allow Station Heads of Staff to assign and remove access to their departments, as well as stripping the rank of their subordinates. An ID computer on the bridge is available for the use of this function.
                • +
                + +

                20 April 2014

                +

                Gun Hog updated:

                +
                  +
                • NanoTrasen Emergency Alerts System 4.20 has been released!
                • +
                • In the unfortunate event of any nuclear device being armed, the station will enter Code Delta Alert.
                • +
                • Should the nuclear explosive be disarmed, the station shall automatically return to its previous alert level.
                • +
                +

                Steelpoint updated:

                +
                  +
                • A new AI satellite has been constructed in orbit of Space Station 13!
                • +
                • The satellite exclusivly is used to hold a station's Artifical Intelligence Unit, the satellite contains a miried of turret and motion sensor defences. A transit tube network is used to connect the satellite to the station, with the transit room being located at engineering south of the engi escape pod.
                • +
                • The AI Upload however has been moved north slightly and is connected directly to the bridge.
                • +
                • In addition, the Gravity Generator has been relocated from its prior position in engineering to the location of the old AI Upload, increasing its defence and logical positioning.
                • +
                + +

                11 April 2014

                +

                Jordie0608 updated:

                +
                  +
                • Wood planks can be used to make wood walls and airlocks; flammability not included
                • +
                + +

                10 April 2014

                +

                Ikarrus updated:

                +
                  +
                • Centcom officials have announced a new initiative to combat misuse of their Emergency Shuttle Service. After the shuttle has been recalled several times over the course of a simple work shift, Centcom will attempt to trace the signal origin and pinpoint its source for station authorities.
                • +
                +

                Steelpoint updated:

                +
                  +
                • Thanks to Nanotrasen's Construction division, the brig has recieved a overhaul in its design, to increase ease of movement, including an addition of a "prisioner release room" and a insanity ward. +
                • Furthing concerns among commentators, Nanotrasen has shipped out additional security equipment to station armories, including Riot Shotguns, tear gas grenades, additional securitron units and military grade Ion Guns.
                • +
                • JaniCo have released a new unique product called the JaniBelt. Capable of holding most standard issue janitorial equipment, designed to help relive inventory management among station janitors. In addition the Jani Water Tank has had its reserve of space clenaer increased to 500 units, up from 250. +
                • +
                +

                Validsalad updated:

                +
                  +
                • After concerns raised by security personel, new armor has been shipped that covers the lower waist and readjusts the helmet for comfort. +
                • In addition, the aging riot shield has been replaced with a newer, more modern, apperance. +
                • +
                + +

                03 April 2014

                +

                Ikarrus updated:

                +
                  +
                • NT AI Firmware version 2554.03.03 includes a function to notify the master AI when a new cyborg is slaved to it.
                • +
                + +

                02 April 2014

                +

                Giacom updated:

                +
                  +
                • The syndicate suit is now black and red, because black and red is the new red.
                • +
                • The Ruskie DJ Station now has a classy orange syndicate suit.
                • +
                • Security officers have been equipped with the latest in flashlight technology. You can find a SecLite™ inside your security belt or you can dispense one from the security vending machine.
                • +
                +

                Ikarrus updated:

                +
                  +
                • Nanotrasen Construction division is pleased to announce the unveiling of our brand now state-of-the-art Space Station 13 v2.1.3!
                • +
                • -Scaffolding used during construction has been repurposed as an expanded maintenance around the station.
                • +
                • -Our new Emergency Transport Shuttle Mk III will be making its debut in times of crisis. Includes a new cargo area and extra security features.
                • +
                • -A redesigned brig will give security staff more peace of mind with added security features
                • +
                • -The armory is now protected by a motion alarm.
                • +
                • -The armory is now equipped with security hardsuits.
                • +
                • -A new command EVA wing has been added to EVA Storage. Station heads of staff will be able to make use of suits stored here.
                • +
                • -A new garden area will be made publicly available for everyone to enjoy some R&R; during their company-endorsed break periods
                • +
                • -A new Testing Lab has been added to the Science department for any field experiments that need to be run.
                • +
                • -The mining ore redemption machine has been relocated to the Cargo Delivery Office.
                • +
                + +

                01 April 2014

                +

                Aranclanos updated:

                +
                  +
                • Made combat more hardcore for hardcore players like myself. This won't be reverted.
                • +
                +

                Malkevin updated:

                +
                  +
                • Sacrifice Cult - Cult has received a massive overhaul to how it works, focusing more on cult magics and sacrificing unbelievers. The most important changes are:
                • +
                • Cult starts off with alot more cultists, 6 cultists below 30 players and 9 above. The HoP can no longer be a round start cultist but is still convertible.
                • +
                • Cultists start off with join, blood, self, and hell. These give the runes for the sacrifice and convert.
                • +
                • Conversions now require three cultists and converts no longer reward words, words must be obtained via sacrifice
                • +
                • Sacrificing players traps their soul in a soul stone, which can then be used on construct shells to implant the souls in them
                • +
                • Constructs shells can be summoned via a new rune (travel, hell, technology)
                • +
                • You can no longer convert the sacrifice target, you dozzy sods
                • +
                • Summoning Narsie when she is not an objective leads to !!FUN!!
                • +
                • Wrote a system to allow all mob types to be sacrificable - currently only corgis are in the new system
                • +
                • Holy Water will DECONVERT cultists, takes around 35 units and two minutes to succeed
                • +
                • Hitting a mob that contains holy water with a tome will convert that water to unholy water, conversely hitting any container that contains unholy water with a bible as chaplain will 'cleanse' the taint.
                • +
                • Unholy water works like a combination synaptazine and hyperzine, with a twist of branes dimarge and highly toxic to non-cultists
                • +
                • Cargo can order a religious supplies crate - it contains flasks of holy water, candles, bibles, and robes
                • +
                • Cultists have an innate ability to communicate to the cult hive mind (BE AWARE - THIS DOES A LOT OF DAMAGE), cultists can also communicate via the new Commune option on their tomes (the Read Tome option has been shifted under the Notes option)
                • +
                +

                MrStonedOne updated:

                +
                  +
                • Cyborgs can now use AI click shortcuts. (shift click on doors opens them, etc) (including ones added below)
                • +
                • New silicon click shortcut: Control+Shift click on doors toggles access override
                • +
                • New silicon click shortcut: Control click turret controls toggles them on or off
                • +
                • New silicon click shortcut: Alt click turret controls toggles lethal on or off
                • +
                • Cyborg hotkey mode was massively improved. 1-3 selects module slot, q unloads the active module. use hotkeys-help as cyborg for more details
                • +
                + +

                31 March 2014

                +

                Ikarrus updated:

                +
                  +
                • Fabricator blueprints for cyborg parts have been updated to allow the debugging of cyborg units prior to boot. To access the debug functions, assemble a cyborg following standard procedure. Before inserting the MMI, use a multitool on the cyborg body. Note that this update also moves designation data into the system files, so pens can no longer be used to name cyborgs.
                • +
                +

                MrPerson updated:

                +
                  +
                • Toggling the "Play admin midis" preference will pause any playing songs. Turning midis back on will resume the current song where it was.
                • +
                • If there's a song playing while you have midis off and you then turn midis on, the current song will begin playing for you.
                • +
                + +

                28 March 2014

                +

                Aranclanos updated:

                +
                  +
                • Workaround with the click cooldowns. They should feel faster. If you think that anything needs a click cooldown (like grilles, mobs, etc.), report it to me.
                • +
                • Here's the fun part, this is a test for the community and players, I hope I won't be forced to revert this and hear "this is why we can't have nice things". Again, if anything needs a cooldown, report it to me. Have fun with your fast clicks spessmans.
                • +
                + +

                25 March 2014

                +

                Ikarrus updated:

                +
                  +
                • A security review committee has approved an update to all Nanotrasen airlocks to better resist cryptographic attacks by the enemy. +

                  *New internal wiring will be able to withstand attacks, and panel wires will no longer be left unusable after an attack. +
                  *Welding tools will now be able to cut through welded doors that have been damaged. +
                  *Damaged electronics are now removable and replaceable following standard procedure. +
                  *Airlocks will not be able to operate autonomously until the electronics are replaced.




                • +
                • Hair sprites have been given a visual lift. For best results, set your hair color to be slightly lighter than how you want it to look. For dark hair, do not use a color darker than 80% black.
                • +
                + +

                22 March 2014

                +

                Giacom updated:

                +
                  +
                • Gravity is no longer beamed from CentCom! Instead, there is a new gravity generator stored near Engineering that will provide all the gravity for the station and the z level. Please remember that it gives off a lot of radiation when charging or discharging, so wear protection.
                • +
                +

                MrPerson updated:

                +
                  +
                • Added a new, somewhat experimental system to delete things. This is basically a port from /vg/, so big thanks to N3X15.
                • +
                • As a result, bombs and the singularity should be much less laggy.
                • +
                • There may be issues with "phantom objects" or other problems with stuff that's suppoed to be deleted or objects interacting with deleted objects. Please report any issues right away!
                • +
                + +

                18 March 2014

                +

                Ikarrus updated:

                +
                  +
                • NT R&D; releases AI Patch version 2553.03.18, allowing all Nanotrasen AIs to override the access restrictions on any airlock in case of emergency. Nanotrasen airlocks have been outfitted with new amber warning lights that will flash while the override is active. Should maintenance teams need to restore an airlock's restrictions without using the AI, pulsing the airlock's ID Scanner wire will reset the override.
                • +
                + +

                17 March 2014

                +

                Giacom updated:

                +
                  +
                • Machines that had their programming overriden by a Malf AI will no longer attack loyal cyborgs; they will still attack cyborgs that aren't loyal, to the Malf AI that hacked them, though.
                • +
                • You only require a single unit of blood to mutate viruses now, instead of 5.
                • +
                + +

                15 March 2014

                +

                Steelpoint/Validsalad updated:

                +
                  +
                • After a review with CentCom, all Security Officers will now begin their shifts with a Stun Baton in their backpack. To avoid inflating costs however all Stun Batons have been removed from Security lockers except from the brig.
                • +
                • After decades of usage CentCom has replaced the Security Uniform, Armor, Belt and Helmet with a newer, more modern design.
                • +
                + +

                14 March 2014

                +

                Ikarrus and Nienhaus updated:

                +
                  +
                • Nanotrasen Corporate announced a revised dress codes primarily affecting senior station officers. A new uniform for Heads of Personnel will be shipped out to all NT Research stations.
                • +
                + +

                12 March 2014

                +

                Yota updated:

                +
                  +
                • Cameras finally capture the direction you are facing.
                • +
                + +

                10 March 2014

                +

                Giacom updated:

                +
                  +
                • Added two new plasma-level disease symptoms, both are very !FUN! and mess with your genetics.
                • +
                • Virologists now only need to use a single unit of virus food, mutagen or plasma to generate symptoms. You can take a single unit by using a dropper, if you change its unit transfer amount in the object tab..
                • +
                • Changed the map to make it harder to escape from the Labor Camp. Please continue using it.
                • +
                +

                Miauw updated:

                +
                  +
                • Added changeling space suits. These allow changelings to survive in space, internals are not needed. They do not provide any sort of armor and slow your chemical regeneration.
                • +
                + +

                05 March 2014

                +

                Various Coders updated:

                +
                  +
                • Ling rounds are LINGIER, with more lings at a given population.
                • +
                • Many simple animals can ventcrawl, including bats. All ventcrawlers must alt-click to ventcrawl.
                • +
                • An automatic tool to store and replace machine parts has been added to R&D.;
                • +
                • Most stuns have been reduced in from 10+ to 5 ticks in duration.
                • +
                • Shoes no longer speed you up.
                • +
                • Added fancy suits, which can be orderd from cargo.
                • +
                • Added sombrerors and ponches.
                • +
                • Cuff application time reduced 25%.
                • +
                • Fire will cause fuel tanks to explode.
                • +
                • Mutagen has been reduced in lethality. Notably, 15 units are not guaranteed to crit the recipient.
                • +
                + +

                03 March 2014

                +

                ChuckTheSheep updated:

                +
                  +
                • Round-end report now shows what items were bought with a traitor's or nuke-op's uplink and how many TCs they used.
                • +
                +

                Drovidi Corv updated:

                +
                  +
                • Facehuggers no longer die to zero-force weapons or projectiles like lasertag beams.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Improved blob spores to zombify people adjacent to their tile.
                • +
                • If a hand-teleporter is activated while you're stuck inside a wall, you'll automatically go through it.
                • +
                +

                Miauw updated:

                +
                  +
                • Decreased the lethality of Mutagen in small doses.
                • +
                +

                TZK13 updated:

                +
                  +
                • Added Incendiary Shotgun Shells made at a hacked autolathe; Xenos be wary.
                • +
                +
                +

                26 February 2014

                +

                Incoming5643 updated:

                +
                  +
                • Color blending Kitty Ears have returned.
                • +
                +
                + +
                +

                25 February 2014

                +

                Incoming5643 updated:

                +
                  +
                • Colored burgers! Include a crayon in your microwave when cooking a burger and it'll come out just like a Pretty Pattie.
                • +
                • Fixed AI's being able to interact with syndicate bombs.
                • +
                +
                + +
                +

                24 February 2014

                +

                Ergovisavi updated:

                +
                  +
                • Added Advanced Mesons and Night Vision Goggles to the protolathe.
                • +
                +

                Giacom updated:

                +
                  +
                • Silicons no longer stop statues from moving when in sight.
                • +
                • Increased the health of statues.
                • +
                • Increased the range of statues's blinding spell.
                • +
                +

                HornyGranny updated:

                +
                  +
                • Reduced the range of Violin notes.
                • +
                • Low quality violin midi-notes replaced with better .ogg-notes.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Improved the Syndicate Implant bundle to contain freedom, uplink, EMP, adrenalin and explosive implanters.
                • +
                • Added Mineral Storeroom to R&D; containing the Ore Redemption Machine; no more assistants stealing your ore in the open hallway.
                • +
                +

                Miauw updated:

                +
                  +
                • No more removing cursed horseheads.
                • +
                +

                Razharas updated:

                +
                  +
                • Fixed infinite telecrystal exploit.
                • +
                +
                + +
                +

                19 February 2014

                +

                Aranclanos updated:

                +
                  +
                • Removed the chance of your hat falling off when slipping.
                • +
                +

                Perakp updated:

                +
                  +
                • Added Iatot's cyborg module selection transformation animations.
                • +
                +
                + +
                +

                17 February 2014

                +

                Ergovisavi updated:

                +
                  +
                • Mining has been significantly overhauled. Hostile alien life has infested the western asteroid! Miners are given an equipment voucher to obtain equipment to protect themselves. There is a spare voucher in the HoP's office, should someone want to switch to mining mid-shift.
                • +
                • The Ore Redemption Machine has been added just outside of the Science wing. Ore goes in, Sheets go out, and points are tallied on the machine. Insert your ID to claim points, then spend them on goods at Mining Equipment Lockers. You require specific accesses to tell the Ore Redemption Machine to unload its sheets.
                • +
                • Should you not care for being eaten alive by horrible alien life, it is suggested you stick to the eastern asteroid, where there is no hostile alien life... though the yield on ore is not as good.
                • +
                • Most ore is no longer visible to the naked eye. You must ping it with a Mining Scanner (Available in all mining lockers) to locate nearby ore. Make sure to equip your mesons first, or it won't be visible.
                • +
                • Mesons no longer remove the darkness overlay, you must properly light your environment. Hull breaches to space can still be clearly seen, and it will still protect you from the singulo.
                • +
                • Mineral spawn rates have been significantly tweaked to cut down on unnecessary inflation of mineral economy.
                • +
                • The asteroid no longer has ambient power on the entire asteroid. AI's who go onto asteroid turf will slowly die of power loss. Mining outposts are unaffected.
                • +
                • Fixed an issue where projectiles shot by simple mobs or thrown would hit multiple times.
                • +
                +

                Fleure updated:

                +
                  +
                • Reduced chicken crates to contain 1-3 chicks, down from 3-6
                • +
                • Increased fertile chicken egg chance from 10% to 25%
                • +
                +

                Yota updated:

                +
                  +
                • Photograph now rendered crisp and clean, so that we may enjoy them in their 0.009 megapixel goodness.
                • +
                • Cameras should now capture more of what they should, and less of what they shouldn't.
                • +
                +
                + +
                +

                09 February 2014

                +

                ADamDirtyApe updated:

                +
                  +
                • The lawyer now spawns with a Sec Headset.
                • +
                +

                Demas updated:

                +
                  +
                • Banana peel size is based on banana size.
                • +
                • Bigger peels slip for longer than smaller ones. Remember that produce size is based on potency.
                • +
                • The clown now spawns with a decent sized banana.
                • +
                • The banana mortar now shoots 65 potency peels.
                • +
                +

                Giacom updated:

                +
                  +
                • A new hostile statue mob, can only move when not being observed, tends to break lights and cause blindness.
                • +
                +

                Incoming5643 updated:

                +
                  +
                • Blob Zombies! When a Blob Spore moves over a dead human body it will infect the body and revive it as a more powerful varient of the spore. Has double the health and deals more damage.
                • +
                +

                Neerti updated:

                +
                  +
                • Sec Belts can now hold Stun Batons.
                • +
                • Stun Batons now only take 10 seconds to fully recharge.
                • +
                +

                Razharas updated:

                +
                  +
                • Tables can now be used as an alternative way to craft makeshift items. Simply click-drag table to yourself bring up a list.
                • +
                +

                adrix89 updated:

                +
                  +
                • Spray Bottles can no longer wet up to three tiles with water.
                • +
                • Spray Bottles have a third higher release volume that wets a single tile.
                • +
                • Water slip times are reduced to the same stun times as soap.
                • +
                +
                + +
                +

                08 February 2014

                +

                MrPerson updated:

                +
                  +
                • Added a NanoUI for the SMES
                • +
                +

                Razharas updated:

                +
                  +
                • Adds more constructible and deconstructable machines!
                • +
                • Added constructible miniature chemical dispensers, upgradable
                • +
                • Sleepers are now constructible and upgradable, open and close like DNA scanners, and don't require a console
                • +
                • Cryogenic tubes are now constructible and upgradable, open and close like DNA scanners, and you can set the cryogenic tube's pipe's direction by opening its panel and wrenching it to connect it to piping
                • +
                • Telescience pads are now constructible and upgradable
                • +
                • Telescience consoles are now constructible
                • +
                • Telescience tweaked (you can save data on GPS units now)
                • +
                • Teleporters are now constructible and upgradable, the console has a new interface and you can lock onto places saved to GPS units in telescience
                • +
                • Teleporters start unconnected. You need to manually reconnect the console, station and hub by opening the panel of the station and applying wire cutters to it.
                • +
                • Biogenerators are now constructible and upgradable
                • +
                • Atmospherics heaters and freezers are now constructible and upgradable and can be rotated with a wrench when their panel is open to connect them to pipes. Screw the board to switch between heater and freezer.
                • +
                • Mech chargers are now constructible and upgradable
                • +
                • Microwaves are now constructible and upgradable
                • +
                • All kitchen machinery can now be wrenched free
                • +
                • SMES are now constructible
                • +
                • Dragging a human's sprite to a cryogenic tube or sleeper will put them inside and activate it if it's cryo
                • +
                • Constructible newscasters, their frames are made with autolathes
                • +
                • Constructible pandemics
                • +
                • Constructible power turbines and their computers
                • +
                • Constructible power compressors
                • +
                • Constructible vending machines. Screw the board to switch vendor type.
                • +
                • Constructible hydroponics trays
                • +
                • Sprites for all this
                • +
                • This update will have unforeseen bugs, please report those you find at https://github.com/tgstation/-tg-station/issues/new if you want them fixed.
                • +
                • As usual, machines are deconstructed by screwing open their panels and crowbarring them. While constructing machines, examining them will tell you what parts you're missing.
                • +
                +
                + +
                +

                05 February 2014

                +

                Yota updated:

                +
                  +
                • Handling a couple flashlights will no longer transform you into the sun. Each light source will have deminishing returns.
                • +
                • Inserting lights into containers should no longer dim other lights.
                • +
                +
                + +
                +

                03 February 2014

                +

                Demas updated:

                +
                  +
                • Changes windoor and newscaster attack messages to be consistent with windows and grilles. This removes the distracting boldness from windoor attack messages, which is reserved for userdanger, and names the attacker.
                • +
                • Thrown items now play a sound effect on impact. The volume of the sound is based on the item's throwforce and/or weight class.
                • +
                • The fireaxe now has 15 throwforce, when previously it had only 1. But why would you throw it away, anyway?
                • +
                • Projectiles now play a sound upon impact. The volume of the sound depends on the damage done by the projectile. Damageless projectiles such as electrodes have static volumes. Practise laser and laser tag beams have no impact sound.
                • +
                • Added sear.ogg for the impacts of damaging beams such as lasers. It may be replaced if player feedback proves negative.
                • +
                +
                + +
                +

                02 February 2014

                +

                Demas updated:

                +
                  +
                • Attack sounds for all melee weapons! No more silent attacks.
                • +
                • The volume of an object's attack sound is based on the weapon's force and/or weight class.
                • +
                • Welders, lighters, matches, cigarettes, energy swords and energy axes have different attack sounds based on whether they're on or off.
                • +
                • Weapons that do no damage play a tap sound. The exceptions are the bike horn, which still honks, and the banhammer, which plays adminhelp.ogg. Surely nothing can go wrong with that last one.
                • +
                • When you tap someone with an object, the message now uses "tapped" or "patted" instead of attacked. The horn still uses HONKED, and the banhammer still uses BANNED.
                • +
                • You won't get the "armour has blocked an attack" message for harmless attacks anymore.
                • +
                • Adds 5 force to the lighter when it's lit. Same as when you accidentally burn yourself lighting it.
                • +
                • Removes boldness from item attack messages on non-human mobs. The attack is bolded for a player controlling a non-human mob. Now your eyes won't jump to the chat when it's Pun Pun who's being brutalised.
                • +
                • Blood will no longer come out of non-human mobs if the attack is harmless.
                • +
                • Adds a period at the end of the catatonic human examine message. That's been bugging me for years.
                • +
                • The activation and deactivation sounds of toy swords, energy swords and energy shields are slightly quieter. Energy swords and shields are now slightly louder than toys.
                • +
                • You can no longer light things with burnt matches.
                • +
                • Match, cigarette and lighter attack verbs, forces and damage types change based on whether the object is lit or not.
                • +
                • Fixes a bug with the energy blade that kept it at weight class 5 after it was deactivated. Who cares, it disappears upon deactivation.
                • +
                • Changes the welder out of fuel message slightly to be less fragmented.
                • +
                • Removes dead air from a lot of weapon sound effects to make them more responsive. In other words, the fire extinguisher attack sound will play a lot sooner after you attack than before.
                • +
                • Equalised the peak volumes of most weapon sounds to be -0.1dB in an attempt to make volumes based on force more consistent across different sounds.
                • +
                +
                + +
                +

                01 February 2014

                +

                Ergovisavi updated:

                +
                  +
                • Walking Mushrooms will now attack and eat each other! They're all a little unique from each other, no two shrooms are exactly alike, and a better quality harvest means stronger Walking Shrooms. Pit them against each other for your entertainment.
                • +
                • Each mushroom will have a different colored cap to identify them. When mushrooms eat each other, they get stronger. The resulting mushroom will drop more slices when cut down to harvest, and will have better quality slices.
                • +
                • Don't hurt them yourself, though, or you'll bruise them, and mushrooms won't get stronger from eating a bruised mushroom. If your mushroom faints, feed it a mushroom such as a plump helmet to get it back on its feet. It will slowly regenerate to full health eventually.
                • +
                +
                + +
                +

                30 January 2014

                +

                Balrog updated:

                +
                  +
                • Syndicate Playing Cards can now be found on the Syndicate Mothership and purchased from uplinks for 1 telecrystal.
                • +
                • Syndicate Playing Cards are lethal weapons both in melee and when thrown, but make the user's true allegiance to the Syndicate obvious.
                • +
                • Sprites are courtesy of Nienhaus.
                • +
                +

                Demas updated:

                +
                  +
                • Adds thud sounds to falling over
                • +
                • Known bug: Thuds play when cloning initialises or someone is put into cryo. This will be fixed.
                • +
                +

                Ergovisavi updated:

                +
                  +
                • Gibtonite, the explosive ore, can now be found on the asteroid. It's very hard to tell between it and diamonds, at first glance.
                • +
                • Gibtonite deposits will blow up after a countdown when you attempt to mine it, but you can stop it with an analyzer at any time. It makes for a good mining explosive.
                • +
                • The closer you were to the explosion when you analyze the Gibtonite deposit, the better the Gibtonite you can get from it.
                • +
                • Once extracted, it must be struck with a pickaxe or drill to activate it, where it will go through its countdown again to explode!
                • +
                • Explosives will no longer destroy the ore inside of asteroid walls or lying on the floor.
                • +
                +

                Miauw updated:

                +
                  +
                • Adds changeling arm blades that cost 20 chems and do 25 brute damage.
                • +
                • Arm blades can pry open unpowered doors, replace surgical saws in brain removal, slice tables and smash computers.
                • +
                +

                MrPerson updated:

                +
                  +
                • Mobs now lie down via turning icons rather than preturned sprites.
                • +
                • You can lie down facing up or down and the turn can be 90 degrees clockwise or counterclockwise.
                • +
                • Resting will always make you lie to the right so you look good on beds.
                • +
                • Please report any bugs you find with this system.
                • +
                +

                Petethegoat updated:

                +
                  +
                • Increased the walk speed. Legcuffed speed is unaffected, and is still suffering.
                • +
                • Sped up alien drones, they are now the same speed as sentinels.
                • +
                +
                + +
                +

                28 January 2014

                +

                Demas updated:

                +
                  +
                • Adds thud sounds to falling over
                • +
                • Known bug: Thuds play when cloning initialises or someone is put into cryo. This will be fixed.
                • +
                +
                + +
                +

                26 January 2014

                +

                Balrog updated:

                +
                  +
                • Syndicate Playing Cards can now be found on the Syndicate Mothership and purchased from uplinks for 1 telecrystal.
                • +
                • Syndicate Playing Cards are lethal weapons both in melee and when thrown, but make the user's true allegiance to the Syndicate obvious.
                • +
                • Sprites are courtesy of Nienhaus.
                • +
                +
                + +
                +

                25 January 2014

                +

                Miauw updated:

                +
                  +
                • Adds changeling arm blades that cost 20 chems and do 25 brute damage.
                • +
                • Arm blades can pry open unpowered doors, replace surgical saws in brain removal, slice tables and smash computers.
                • +
                +
                + +
                +

                24 January 2014

                +

                Ergovisavi updated:

                +
                  +
                • Gibtonite, the explosive ore, can now be found on the asteroid. It's very hard to tell between it and diamonds, at first glance.
                • +
                • Gibtonite deposits will blow up after a countdown when you attempt to mine it, but you can stop it with an analyzer at any time. It makes for a good mining explosive.
                • +
                • The closer you were to the explosion when you analyze the Gibtonite deposit, the better the Gibtonite you can get from it.
                • +
                • Once extracted, it must be struck with a pickaxe or drill to activate it, where it will go through its countdown again to explode!
                • +
                • Explosives will no longer destroy the ore inside of asteroid walls or lying on the floor.
                • +
                +
                + +
                +

                21 January 2014

                +

                MrPerson updated:

                +
                  +
                • Mobs now lie down via turning icons rather than preturned sprites.
                • +
                • You can lie down facing up or down and the turn can be 90 degrees clockwise or counterclockwise.
                • +
                • Resting will always make you lie to the right so you look good on beds.
                • +
                • Please report any bugs you find with this system.
                • +
                +
                + +
                +

                19 January 2014

                +

                KazeEspada updated:

                +
                  +
                • The water cooler is now stocked with paper cups. You can refill the cups by putting paper in it.
                • +
                +

                Rolan7 updated:

                +
                  +
                • You can now sell mutant seeds, from hydroponics, to Centcom via the supply shuttle.
                • +
                • Fixes powersinks causing APCs to stop automatically recharging.
                • +
                +
                + +
                +

                17 January 2014

                +

                ManeaterMildred updated:

                +
                  +
                • Changed the way the Gygax worked. It now has less defense and shot deflection, but is faster and have less battery drain per step.
                • +
                • Nerfed the Carbine's brute damage and renamed it to FNX-99 "Hades" Carbine.
                • +
                • Nerfed the Gygax defense from 300 to 250.
                  + Nerfed the Gygax projectile deflection chance from 15 to 5.
                  + Buffed the Gygax speed from 3 to 2, making it faster.
                  + Reduced the battery use when moving from 5 to 3.
                  +
                  + The Mech Ion Rifle now has a faster cooldown, from 40 to 20.
                  + Nerfed the carbine's Brute Damage from 20 to 5.
                  +
                  + Please post feedback to these changes on the forums. +







                • +
                +
                + +
                +

                15 January 2014

                +

                Dumpdavidson updated:

                +
                  +
                • EMPs affect the equipment of a human again.
                • +
                • EMP flashlight now recharges over time and its description no longer reveals the illegal nature of the device.
                • +
                • EMP implant now has two uses.
                  EMP kit now contains two grenades.
                • +
                +
                + +
                +

                14 January 2014

                +

                Fleure updated:

                +
                  +
                • Added spider butchery.
                • +
                • Added spider meat, legs, and edible eggs.
                • +
                • Added new spider related meals.
                • +
                +

                Giacom updated:

                +
                  +
                • Because of an emagged cyborg's explosion, the MMI will die with it.
                • +
                • The self-respiration symptom will now properly keep you from dying of oxygen loss.
                • +
                • The Stimulant symptom's activation chance was increased so you had a constant flow of hyperzine.
                • +
                +

                Incoming updated:

                +
                  +
                • A new training bomb has been added to the armoury, which will allow you to train your wire cutting skills to disarm real syndicate bombs.
                • +
                +

                ManeaterMildred updated:

                +
                  +
                • Updated research designs.
                • +
                • The Protolathe can now build a Ion Rifle.
                • +
                • The Exosuit Fabricator can now build a Mech Ion Rifle, a Mech Carbine and a Mech-Mounted Missile Rack.
                • +
                +

                SirBayer updated:

                +
                  +
                • Armor now reduces damage by the protection percentage, instead of randomly deciding to half or full block damage by those percentages.
                • +
                • Shotguns, with buckshot shells, will fire a spread of pellets at your target, like a real shotgun blast.
                • +
                +
                + +
                +

                12 January 2014

                +

                VistaPOWA updated:

                +
                  +
                • Added Syndicate Cyborgs.
                • +
                • They can be ordered for 25 telecrystals by Nuclear Operatives. A ghost / observer with "Be Operative" ticked in their game options will be chosen to control it.
                • +
                • Their loadout is: Crowbar, Flash, Emag, Esword, Ebow, Laser Rifle. Each weapon costs 100 charge to fire, except the Esword, which has a 500 charge hitcost. Each borg is equipped with a 25k cell by default.
                • +
                • Syndicate borgs can hear the binary channel, but they won't show up on the Robotics Control computer or be visible to the AI. Their lawset is the standard emag one.
                • +
                • Added two cyborg recharging stations to the Syndicate Shuttle.
                • +
                +
                + +
                +

                11 January 2014

                +

                Cheridan updated:

                +
                  +
                • You can now upgrade laser pointers with micro laser parts. It will increase the chance of blinding people.
                • +
                +

                Errorage updated:

                +
                  +
                • Cyborg modules now use a new UI, which is much quicker than a menu.
                • +
                +

                Giacom updated:

                +
                  +
                • The game will now have all background operations disabled, this will result in smoother gameplay but may result in some spike lags, before being set back to normal. The gradually increasing lag should now be gone.
                • +
                • You can now emag the crusher, in disposals, to remove the safety. You can use a screwdriver to reset it to the default factory settings.
                • +
                • The toxin compensation symptom will stop giving you toxin damage while at full health.
                • +
                +

                JJRCop updated:

                +
                  +
                • The new nuke toy can now be found in your local arcade machine.
                • +
                +
                + +
                +

                10 January 2014

                +

                ChuckTheSheep updated:

                +
                  +
                • Morgue Trays can detect players in their bodies and will now change colour depending on a few things. Red = Dead body with no player inside. Orange = No body but items. Green = A dead body with a player inside.
                • +
                +

                Giacom updated:

                +
                  +
                • You can whisper while in critical, but you will immediately die afterwards DRAMATICALLY. The closer you are to death, the less you can say.
                • +
                • The wizard won't spawn so much smoke after they blink.
                • +
                • The detective's forensic scanner was upgraded so that it can now scan from afar.
                • +
                • Made the ghost's follow button less buggy. Please make an issue report if it still bugs out.
                • +
                +

                Rumia29, Nienhaus updated:

                +
                  +
                • A new alt RD uniform spawns in his locker.
                • +
                +
                + +
                +

                07 January 2014

                +

                Fleure updated:

                +
                  +
                • Janitor now spawns with a service headset.
                • +
                • Backpack watertank slowdown decreased.
                • +
                +
                + +
                +

                04 January 2014

                +

                Razharas updated:

                +
                  +
                • Constructable machines now depend on R&D; parts!
                • +
                • DNA scanner: Laser quality lessens irradiation. Manipulator quality drastically improves precision (9x for best part) and scanner quality allows you to scan suicides/ling husks, with the best part enabling the cloner's autoprocess button, making it scan people in the scanner automatically.
                • +
                • Clone pod: Manipulator quality improves the speed of cloning. Scanning module quality affects with how much health people will be ejected, will they get negative mutation/no mutations/clean of all mutations/random good mutation, at best quality will enable clone console's autoprocess button and will try to clone all the dead people in records automatically, together with best DNA scanner parts cloning console will be able to work in full automatic regime autoscanning people and autocloning them.
                • +
                • Borg recharger: Capacitors' quality and powercell max charge affect the speed at which borgs recharge. Manipulator quality allows borg to be slowly repaired while inside the recharges, best manipulator allows even fire damage to be slowly repaired.
                • +
                • Portable power generators: Capacitors' quality produce more power. Better lasers consume less fuel and reduce heat production PACMAN with best parts can keep whole station powered with about sheet of plamsa per minute (approximately, wasn't potent enough to test).
                • +
                • Autolathe: Better manipulator reduces the production time and lowers the cost of things(they will also have less m_amt and g_amt to prevent production of infinity metal), stacks' cant be reduced in cost, because thatll make production of infinity metal/glass easy
                • +
                • Protolathe: Manipulators quality affects the cost of things(they will also have less m_amt and g_amt to prevent production of infinity metal), best manipulators reduces the cost 5 times (!)
                • +
                • Circuit imprinter: Manipulator quality affects the cost, best manipulator reduce cost(acid insluded) 4 times, i.e. 20 boards per 100 units of acid
                • +
                • Destructive analyzer: Better parts allow items with less reliability in. Redone how reliability is handled, you now see item reliability in the deconstruction menu and deconstructing items that has same or one point less research type level will rise the reliability of all known designs that has one or more research type requirements as the deconstructed item. Designs of the same type raise in reliability more. Critically broken things rise reliability of the design drastically. Whole reliability system is not used a lot but now at least on the R&D; part it finally matters.
                • +
                +
                + +
                +

                02 January 2014

                +

                Demas updated:

                +
                  +
                • Added different colours to departmental radio frequencies. Now you'll be able to filter out or pay attention to each frequency a lot easier.
                • +
                +

                Fleure updated:

                +
                  +
                • Fixed bruisepacks and ointments not working.
                • +
                • Fixed injecting/stabbing mouth or eyes when only thick suit worn.
                • +
                +
                + +
                +

                01 January 2014

                +

                Errorage updated:

                +
                  +
                • The damage overlay for humans starts a little later than before. It used to start at 10 points of fire + brute damage, it now starts at 35.
                • +
                +
                + +
                +

                31 December 2013

                +

                Fleure updated:

                +
                  +
                • Paper no longer appears with words on it when blank input written or photocopied
                • +
                • Vending machine speaker toggle button now works
                • +
                • Building arcade machines works again
                • +
                +
                + +
                +

                27 December 2013

                +

                Giacom updated:

                +
                  +
                • Light explosions will no longer gib dead bodies anymore. C4 will function the same and gib anything attached.
                • +
                • Syringe gun projectiles will now display a message when shots are deflected by space suits, biosuits and etc...
                • +
                • You can now move out of sleepers by moving, again.
                • +
                +

                Miauw updated:

                +
                  +
                • Monkeys now have a resist button on their HUD.
                • +
                +
                + +
                +

                21 December 2013

                +

                Bobylein updated:

                +
                  +
                • Labcoats can now store bottles, beakers, pills, pill bottles and paper.
                • +
                +

                Giacom updated:

                +
                  +
                • The Labor Camp has been changed based on feedback. Ore boxes added, internals added, safety pickaxes/shovels (might revert if unliked).
                • +
                • Labor Camp prisoners, who have earned enough points for freedom, will now have to be alone in the shuttle to move it and to open the middle door; this is in order to prevent free'd prisoners from releasing their comrades.
                • +
                • Monkeys no longer walk away when being pulled or grabbed.
                • +
                • Anti-breach shield generators have a greater range, and will cover more exposed space tiles.
                • +
                +

                Jordie updated:

                +
                  +
                • Blowing up borgs from the Robotics Console will now actually make them explode. Emagged Cyborgs will explode even more.
                • +
                +

                Nienhaus updated:

                +
                  +
                • Added more poster.
                • +
                +

                Perakp updated:

                +
                  +
                • You can no longer slip while laying down.
                • +
                +

                RobRichards, Validsalad updated:

                +
                  +
                • Added new sprites for the sec-hailer, SWAT gear and riot armour.
                • +
                +
                + +
                +

                18 December 2013

                +

                Adrinus updated:

                +
                  +
                • Playing cards, just like the real thing! Play poker, blackjack, go fish, the limits are limitless! Recommended to use space cash as the poker chips for now
                • +
                +

                Giacom updated:

                +
                  +
                • THE REALISM: Injectors such as syringes, parapens and hypos will not penetrate coveralls with thick material, this includes space suits, biosuits, bombsuits, and their head slot equivalent. Injectors now also consider where you aim, if you aim for the head it will try to use it through the head slot, otherwise it will aim for the body. To clarify, if you are wearing a space helmet and a doctor tries to inject you while aiming at your head, it will protect you until they aim for the unprotected body; same story for wearing a space suit and not a helmet.
                • +
                • The syndicate shuttle has been heavily upgraded with a brand new technology which allows the blast doors to the entrance of the shuttle to AUTOMATICALLY close. Whoa. This brand new technology will help keep out those snoopy crew members.
                • +
                • Lowered the cooldown of creating virus cultures to 5 seconds. You now only need to mix one unit of synaptizine, in a blood full of an advance virus, to get it to remove a random symptom.
                • +
                +

                Incoming updated:

                +
                  +
                • Rounds will no longer end when the wizard dies and there are still apprentices or traitors/survivors..
                • +
                +

                JJRcop updated:

                +
                  +
                • Transit tube tweaks. You can now put someone into a transit tube pod using grabs and you can empty a transit tube pod by clicking on it.
                • +
                +

                Jordie0608 updated:

                +
                  +
                • Replaced the digital valves in atmospherics with pumps.
                • +
                +
                + +
                +

                14 December 2013

                +

                Incoming updated:

                +
                  +
                • Magic Mania! Powerful new magical tools and skills for wizard and crew alike!
                • +
                • Beware the new Summon Magic spell, which will grant the crew access to magical tools and spells and cause some to misuse it!
                • +
                • One Time Spellbooks that can be spawned during summon magic that can teach a low level magic skill to anyone! Beware the effects of reading pre-owned books, the magical rights management is potent! +
                • Magical Wands that can be spawned during Summon Magic! They come in a variety of effects that mimic both classical wizard spells and all new ones. They come precharged but lack the means to refill them once their magical energy is depleted... Fire efficently!
                • +
                • Be aware of the new Charge spell, which can take normally useless spent wands and give them new life! This mysterious effect has been found to wear down the overall magical potency of wands over time however. Beyond wands the clever magical user can find ways to use this spell on other things that may benefit from a magical charge...
                • +
                • The Staff of Resurrection, which holds intense healing magics able to defeat death itself! Look out for this invaluable magical tool during castings of Summon Magic.
                • +
                • Be on the lookout for a new apprentice! This noble mage is a different beast from most wizards, trained in the arts of defending and healing. Too bad he still works for the wizard!
                • +
                +
                + +
                +

                09 December 2013

                +

                Giacom updated:

                +
                  +
                • New colourful ghost sprites for BYOND members. Sprites by anonus.
                • +
                +
                + +
                +

                08 December 2013

                +

                Rolan7 updated:

                +
                  +
                • Leather gloves can be used to removes lights.
                • +
                • Plant, ore, and trash bags have a new option to pick up all items of single type
                • +
                • Creating astroturf now works like sandstone, converting all the grass at once.
                • +
                • Uranium and radium can be used instead of mutagen. 10 can mutate species, 5 or 2 mutate traits. Highly toxic.
                • +
                • Plants require a little light to live. Mushroom require even less (2 units vs 4) and take less damage.
                • +
                +
                + +
                +

                05 December 2013

                +

                Razharas updated:

                +
                  +
                • Reworked how ling stings are done, now when you click a sting in the changeling tab it becomes current active sting, the icon of that sting appears under the chem counter, alt+clicking anyone will sting them with current sting, clicking the icon of the sting will unset it.
                • +
                • Monkeys have ling chem counter and active sting icons in their UI.
                • +
                • Going monkey -> human will try to equip the human with everything on the ground below it.
                • +
                +
                + +
                +

                02 December 2013

                +

                Giacom updated:

                +
                  +
                • A less annoying virology system! From now on, you can only get low level virus symptoms from virus food, medium level virus symptoms from unstable mutagen and high level virus symptoms from liquid plasma. You can find a list of symptoms, and which chemicals are required to get them, here: http://wiki.ss13.eu/index.php/Infections#Symptoms_Table
                • +
                • The virologist starts with a bottle of plasma in his smart fridge.
                • +
                • Made it so you cannot accidentally click in the gaps between chem masters.
                • +
                +
                + +
                +

                01 December 2013

                +

                cookingboy3 updated:

                +
                  +
                • Added three new buttons to the sandbox panel.
                • +
                • Removed canister menu, replaced it with buttons.
                • +
                • Players can no longer spawn "dangerous" canisters in sandbox, such as Plasma, N20, CO2, and Nitrogen.
                • +
                +
                + +
                +

                30 November 2013

                +

                Yota updated:

                +
                  +
                • The identification console will now require that ID and job names follow the same restrictions as player names.
                • +
                • NTSL scripts and parrots should now handle apostrophes and such properly. It&#39;s about time.
                • +
                • NTSL scripts now have a better sense of time.
                • +
                +
                + +
                +

                28 November 2013

                +

                Malkevin updated:

                +
                  +
                • Made the suit storage on the Captain's Tunic more useful than just a place to store your e-o2 tank. You can now store the nuke disk, stamps, medal box, flashes and melee weapons (mainly intended for the Chain of Command), and of course - smoking paraphernalia
                • +
                +
                + +
                +

                27 November 2013

                +

                RobRichards updated:

                +
                  +
                • Nanotrasen surgeons are now certified to perform Limb replacements, The robotic parts used in construction of Nanotrasen Cyborgs are the only parts authorized for crew augmentation, these replacement limbs can be repaired with standard welding tools and cables.
                • +
                +
                + +
                +

                17 November 2013

                +

                Laharl Montgommery updated:

                +
                  +
                • AI can now anchor and unanchor itself. In short, it means the AI can be dragged, if it wants to.
                • +
                +
                + +
                +

                29 September 2013

                +

                RobRichards updated:

                +
                  +
                • Nanotrasen Cyborg Upgrades:
                  +Standard issue Engineering cyborgs now come equipped with replacement floor tiles which they can replenish at recharge stations.
                • +
                +
                + + +
                +

                28 September 2013

                +

                Ergovisavi updated:

                +
                  +
                • Mobs can now be lit on fire. Wearing a full firesuit (or similar) will protect you. Extinguishers, Showers, Space, Cryo, Resisting, being splashed with water can all extinguish you. Being splashed with fuel/ethanol/plasma makes you more flammable. Water makes you less flammable.
                • +
                +
                + + +
                +

                26 September 2013

                +

                Cheridan updated:

                +
                  +
                • Nanotrasen Anomaly Primer:
                  + Unstable anomalies have been spotted in your region of space. These anomalies can be hazardous and destructive, though our initial encounters with these space oddities has discovered a method of neutralization. Method follows.
                  +

                  Step 1. Upon confirmation of an anomaly sighting, report its location. Early detection is key.
                  + Step 2. Using an atmospheric analyzer at short range, determine the frequency that the anomaly's core is fluctuating at.
                  + Step 3. Send a signal through the frequency using a radio signaller. Note that non-specialized signaller devices may possibly lack the frequency range needed.

                  + With the anomaly neutralized and the station brought out of danger, inspect the area for any remnants of the anomaly. Properly researched, we believe these events could provide vast amounts of valuable data.
                  + Did you find this report helpful?
                • +
                +
                + + + +
                +

                21 September 2013

                +

                Malkevin updated:

                +
                  +
                • Due to complaints about Security not announcing themselves before making arrests NT has now issued it's Sec team with loud hailer integrated gas masks, found in their standard equipment lockers. Users can adjust the mask's level of aggression with a screwdriver.
                • +
                • The sprites could be shaded better. Think you can do better? Post your submissions here.
                • +
                +
                + + +
                +

                19 September 2013

                +

                Malkevin updated:

                +
                  +
                • Juggernaut's ablative armor has been adjusted. They have a greater chance to reflect lasers however on reflection they take half damage instead of no damage, basically this adjustment means you should be able to kill a Juggernaut with two laser guns instead of four! Also their reflection spread has been greatly widened, enjoy the lightshow
                • +
                • Cargo can now order exile implants.
                • +
                • Checking a collector's last power output via analyzers has been moved to multitools, because that actually made sense (betcha didn't know this existed, I know I didn't)
                • +
                • Analyzers can now be used to check the gas level of the tank in a loaded radiation collector (yay no more crowbars), you can also use them on pipes to check gas levels (yay no more pipe meters)
                • +
                +
                + +
                + + +

                17 September 2013

                +

                SuperSayu updated:

                +
                  +
                • You can no longer strip people through windows and windoors
                • +
                • You can open doors by hand even if there is a firedoor in the way, making firedoor+airlock no longer an unbeatable combination
                • +
                • Ghosts can now click on anything to examine it, or double click to jump to a turf. Double clicking a mob, bot, or (heaven forbid) singularity/Nar-Sie will let you follow it. Double clicking your own corpse re-enters it.
                • +
                • AI can double click a mob to follow it, as well as double clicking turfs to jump.
                • +
                • Ventcrawling mobs can alt-click a vent to start ventcrawling.
                • +
                • Telekinesis is now part of the click system. You can click on buttons, items, etc, without having a telekinetic throw in hand; the throw will appear when you click on something you can move (with your mind).
                • +
                +
                + + +
                +

                13 September 2013

                +

                JJRcop updated:

                +
                  +
                • We at Nanotrasen would like to assure you that we know the pain of waiting five minutes for the emergency shuttle to be dispatched in a high-alert situation due to our confirmation-of-distress policy. Therefore, we have amended our confirmation-of-distress policy so that, in the event of a red alert, the distress confirmation period is shortened to three minutes and we will hurry in preparing the shuttle for transit. This totals to 6 minutes, in hope that it will give our very expensive equipment a better chance of recovery.
                • +
                +
                + + +
                +

                3 September 2013

                +

                Cael_Aislinn updated:

                +
                  +
                • Terbs Fun Week Day 5: Chef gets a Nanotrasen-issued icecream machine with four pre-approved icecream flavours and two official cone types.
                • +
                +
                + + +
                +

                2 September 2013

                +

                Cael_Aislinn updated:

                +
                  +
                • Terbs Fun Week Day 4: Humans, aliens and cyborgs now show speech bubbles when they talk.
                • +
                +
                + + +
                +

                1 September 2013

                +

                Cael_Aislinn updated:

                +
                  +
                • Terbs Fun Week Day 3: Detective can reskin his gun to one of five variants: Leopard Spots, Gold Trim, Black Panther, Peacemaker and the Original.
                • +
                +
                + + +
                +

                12 September 2013

                +

                AndroidSFV updated:

                +
                  +
                • AI Photography: AIs now have two new verbs, Take Picture and View Picture. The pictures the AI takes are centered on the AI's eyeobj. You can use these pictures on a newscaster, and print them at a photocopier. +
                +
                + + +
                +

                31 August 2013

                +

                Cael_Aislinn updated:

                +
                  +
                • Terbs Fun Week Day 2: RD, lawyers and librarians now spawn with a laser pointer. Don't point them in anyone's eyes!
                • +
                +
                + + +
                +

                30 August 2013

                +

                Cael_Aislinn updated:

                +
                  +
                • Terbs Fun Week Day 1: Added ghost chilis as a mutation of chili plants. Be careful, they're one of the hottest foods in the galaxy!
                • +
                +
                + + +
                +

                21 August 2013

                +

                Dumpdavidson updated:

                +
                  +
                • Replaced the EMP grenades from the uplink with an EMP kit. The kit contains a grenade, an implant and a flashlight with 5 uses that can EMP any object or mob in melee range.
                • +
                +
                + + +
                +

                18 August 2013

                +

                Delicious updated:

                +
                  +
                • Made time and date consistent across medical and security records, mecha logs and detective scanner reports
                • +
                • Added date to PDA
                • +
                +
                + + +
                +

                13 August 2013

                +

                Giacom updated:

                +
                  +
                • Malf AIs now have a new power which will spawn a "borging machine". This machine will turn living humans into loyal cyborgs which the AI can use to take over the station with. The AI will limit themselves by using this ability, such as no shunting, and the machine will have a long cooldown usage.
                • +
                +
                + + + + + +
                +

                12 August 2013

                +

                Giacom updated:

                +
                  +
                • Changed the blob balance to make the blob start strong but grow slower, resulting in rounds where the blob doesn't instantly get killed off if found out and doesn't immediately dominate after being left alone long enough. AIs no longer have to quarantine the station.
                • +
                +
                + + +
                +

                10 August 2013

                +

                Malkevin updated:

                +
                  +
                • Cargo Overhaul: Phase 1
                • +
                • Ported Bay's cargo computer categoy system
                • +
                • Crates have been tweaked significantly. Crates have been reduced to single item types where possible, namely with expensive crates such as weapons and armor. A total of 28 new crates have been added, including chemical and tracking implants, and raw materials can also be bought from cargo for a significant number of points (subject to change)
                • +
                • This was a pretty large edit of repetitive data, so no doubt I've made a mistake or two. Please report any bugs to the usual place
                • +
                +
                + + +
                +

                6 August 2013

                +

                Giacom updated:

                +
                  +
                • NTSL no longer allows you to use a function within another function parameter. This was changed to help prevent server crashes; if your working script no longer compiles this is why.
                • +
                    +
                + + +
                +

                5 August 2013

                +

                Kaze Espada updated:

                +
                  +
                • Nanotrasen has recentely had to change its provider of alcoholic beverages to a provider of lower quality. Cases of the old ailment known as alcohol poisoning have returned. Bar goers are to be weary of this new condition.
                • +
                +
                + + +
                +

                4 August 2013

                +

                Giacom updated:

                +
                  +
                • Nanotrasen has re-arranged the station blueprint designs to have non-essential APCs moved to the maintenance hallways. Non-essential rooms that aren't connected to a maintenance hallway will have their APC remain. Station Engineers will now have easy access to a room's APC without needing access themselves. Nanotrasen also wishes to remind you that you should not sabotage these easy to access APCs to cause distractions or to lockdown someone in a location. Thank you for reading.
                • +
                +
                + + +
                +

                31 July 2013

                +

                Ricotez updated:

                +
                  +
                • Atmospherics now has its own hardsuit. Instead of radiation protection it offers fire protection.
                • +
                +
                + + +
                +

                21 July 2013

                +

                Malkevin updated:

                +
                  +
                • Cultists now start with two words each, and the starting talisman no longer damages you when you use it
                • +
                +
                + + + +
                +

                21 July 2013

                +

                Cheridan updated:

                +
                  +
                • Instead of a level-up system where it is possible to acquire all the skills, each skill now costs 1 point, and you can pick up to 5.Husking people, instead of giving you more XP to buy skills, now gives you a skill reset, allowing you to pick new ones.
                • +
                • DNA Extract Sting is now free, and is your main mode of acquiring DNA. You can hold up to 5 DNAs, and as you acquire more, the oldest one is removed. If you're currently using the oldest DNA strand, you will be required to transform before gaining more.
                • +
                • New abilities! An UI indicator for chemical storage! Fun!
                • +
                +
                + + +
                +

                16 July 2013

                +

                Malkevin updated:

                +
                  +
                • Summary of my recent changes: Added a muzzle and a box of Prisoner ID cards to the perma wing, RnD can make a new combined gas mask with welding visor, added some atmos analyzers to atmospherics, air alarm circuit boards have their own sprites, package wrapped objects will now loop back round to the mail chute instead of auto-rerouting to disposals, and the detective and captain have access to securitrons through their PDA cartridges.
                • +
                +
                + +
                +

                15 July 2013

                +

                Giacom updated:

                +
                  +
                • A new item has been added to the syndicate catalog. The AI detector is a device disguised as a multitool; it is not only able to be used as a real multitool but when it detects an AI looking at it, or it's holder, it will turn red to indicate to the holder that he should cease supiscious activities. A great and cheap, to produce, tool for undercover operations involving an AI as the security system.
                • +
                +
                + + +
                +

                7 July 2013

                +

                Giacom updated:

                +
                  +
                • Revamped blob mode and the blob random event to spawn a player controlled overmind that can expand the blob and upgrade pieces that perform particular functions. This will use resources which the core can slowly generate or you can place blob pieces that will give you more resources.
                • +
                +
                + +
                +

                27 June 2013

                +

                Ikarrus updated:

                +
                  +
                • Nanotrasen R&D released a new firmware patch for their station AIs. Included among the changes is the new ability for AIs to interact with fire doors. R&D officials state they feel giving station AIs more influence could only lead to good things.
                • +
                +
                + +
                +

                16 June 2013

                +

                Khub updated:

                +
                  +
                • Job preferences menu now not only allows you to left-click the level (i.e. [Medium]) to raise it, but also to right-click it to lower it. That means you don't have to cycle through all the levels to get rid of a [Low].
                • +
                +
                + + +
                +

                15 June 2013

                +

                Carnie updated:

                +
                  +
                • DNA-scanner pods (DNA-modifier + cloning), now open and close in a similar fashion to closets. This means you click on them to open/close them. This change was to fix a number of issues, like items being lost in the scanner-pods.
                • +
                • As a side-effect, borgs can now clone humans. No harm can become a dead human, so they are not necessarily lawbound to clone them, and such tasks are probably best left to qualified genetics staff.
                • +
                +

                Petethegoat updated:

                +
                  +
                • Updated chemical grenades. The build process is much the same, except they require an igniter-X assembly instead of a single assembly item. You can also just use a cable coil to get regular grenade behaviour.
                • +
                +
                + +
                +

                9 June 2013

                +

                Ikarrus updated:

                +
                  +
                • Server operators may now allow latejoiners to become antagonists. Check game_options.txt for more information.
                • +
                • Server operators may now set how traitors and changelings scale to population. Check game_options.txt for more information.
                • +
                +
                + +
                +

                6 June 2013

                +

                Giacom updated:

                +
                  +
                • Emptying someone's pockets won't display a message. In theory you can now pickpocket!
                • +
                +
                + + +
                +

                4 June 2013

                +

                Dumpdavidson updated:

                +
                  +
                • Headsets can no longer broadcast into a channel that is disabled.
                  Headsets now have a button to turn off the power instead of the speaker. This button disables all communication functions.
                  EMPs now force affected radios off for about 20 seconds.
                • +
                +
                + +
                +

                2 June 2013

                +

                Ikarrus updated:

                +
                  +
                • To reduce costs of security equipment, mounted flashers have been adjusted to use common handheld flashes as their flashbulbs. Although these flashbulbs are more prone to burnout, they can easily be replaced with wirecutters.
                • +
                +
                + +
                +

                25 May 2013

                +

                Ikarrus updated:

                +
                  +
                • CentCom announced some minor restructuring within Space Station 13's command structure. Most notable of these changes is the removal of the Head of Personnel's access to the security radio channel. CentCom officials have stated the intention was to make the HoP's role more specialized and less partial towards security.
                • +
                +
                + +
                +

                14 May 2013

                +

                Ikarrus updated:

                +
                  +
                • Nanotrasen seeks to further cut operating costs on experimental cyborg units. +
                  -Cyborg chassis will now be made from a cheaper but less durable design. +
                  -RCDs found on engineering models have been replaced with a smaller model to make room for a metal rods module. +
                  -Cyborg arms will no longer be long enough to allow for self-repairs. +
                  NOTE: A cyborg's individual modules have been found to become non-operational should the unit sustain too much structural damage.
                • +
                +
                + +
                +

                11 May 2013

                +

                Malkevin updated:

                +
                  +
                • SecHuds now check for valid clearance before allowing you to change someone's arrest status. There is only one way to bypass the ID check, and its not the usual way.
                • +
                + +
                + +
                +

                7 May 2013

                +

                Ikarrus updated:

                +
                  +
                • As a part of the most recent round of budget cuts, toolboxes will now be made with a cheaper but heavier alloy. HR reminds employees to avoid being struck with toolboxes, as toolbox-related injuries are not covered under the company's standard health plan.
                • +
                +
                + +
                +

                5 May 2013

                +

                Rolan7 updated:

                +
                  +
                • Cargo manifests from CentComm may contain errors. Stamp them DENIED for refunds. Doesn't apply to secure or large crates. Check the orders console for CentComm feedback.
                • +
                +
                + +
                +

                2 May 2013

                +

                Malkevin updated:

                +
                  +
                • You can now weld four floor tiles together to make a metal sheet
                • +
                • The All-In-One Grinder can now grind Metal, Plasteel, Glass, Reinforced Glass, and Wood sheets
                • +
                • Made grey backpacks slightly less ugly
                • +
                +
                + +
                +

                30 April 2013

                +

                Ikarrus updated:

                +
                  +
                • Researchers have discovered that glass shards are, in fact, dangerous due to their typically sharp nature. Our internal Safety Committee advises that glass shards only be handled while using Nanotrasen-approved hand-protective equipment.
                • +
                +
                + +
                +

                24 April 2013

                +

                Carnie updated:

                +
                  +
                • DNA reworked: All SE blocks are randomised. DNA-modifier emitter strength affects the size of the change in the hex-character hit. Emitter duration makes it more likely to hit the character you click on. Almost all DNA-modifier functions are on one screen.
                  Balancing -will- be off a bit. Is getting halk to hard/easy? Please report bugs/balancing issues/concerns here: http://forums.nanotrasen.com/viewtopic.php?f=15&t=13083 <3
                • +
                +
                + +
                +

                26 April 2013

                +

                Aranclanos updated:

                +
                  +
                • Exosuit fabricators will now need to be manually updated
                • +
                +

                Ikarrus updated:

                +
                  +
                • Commanding Officers of Nanotrasen Stations have been issued a box of medals to be awarded to crew members who display exemplary conduct.
                • +
                +
                + + +
                +

                23 April 2013

                +

                Malkevin updated:

                +
                  +
                • Replaced the captain's run of the mill armored vest with his very own unique vest. Offers slightly better bullet protection.
                • +
                +
                + + +
                +

                22 April 2013

                +

                Malkevin updated:

                +
                  +
                • Overhauled the thermal insulation system
                • +
                • All clothing that protected from one side of the thermal spectrum now protects from the other.
                • +
                • Armor (although most don't have full coverage) protects between 160 to 600 kelvin
                • +
                • Firesuits protect between 60 to 30,000 kelvin (Note: Hotspot damage still exists)
                • +
                • CE's hardsuit got its firesuit level protection back
                • +
                • Bomb suits function as ghetto riot gear
                • +
                +
                + +
                +

                22 April 2013

                +

                Cheridan updated:

                +
                  +
                • Stungloves removed 5eva.
                • +
                • Don't rage yet. Makeshift stunprods(similar in function to stungloves) and spears are now craftable. Make them by using a rod on cable restraits, then adding something.
                • +
                • Stun batons/prods now work off power cells, which can be removed and replaced! Use a screwdriver to remove the battery.
                • +
                +
                + + +
                +

                17 April 2013

                +

                Giacom updated:

                +
                  +
                • If the configuration option is enabled, AIs and or Cyborgs will not be able to communicate vocally. This means they cannot talk normally and need to use alternative methods to do so
                • +
                +
                + + +
                +

                10 April 2013

                +

                Cheridan updated:

                +
                  +
                • You can now condense capsaicin into pepper spray with chemistry.
                • +
                • Pepper spray made slightly more effective.
                • +
                • Teargas grenades can be obtained in Weapons and Riot crates.
                • +
                • Riot crate comes with 2 sets of gear instead of 3, made cheaper. Beanbag crate removed entirely. Just make more at the autolathe instead. Bureaucracy crate cheaper, now has film roll.
                • +
                • NT bluespace engineers have ironed-out that little issue with the teleporter occasionally malfunctioning and dropping users into deep space. Please note, however, that bluespace teleporters are still sensitive experimental technology, and should be Test Fired before use to ensure proper function.
                • +
                +
                + + +
                +

                9 April 2013

                +

                Ikarrus updated:

                +
                  +
                • Liquid Plasma have been found to have strange and unexpected results on virion cultures. The executive chief science officer urges virologists to explore the possibilities this new discovery could bring.
                • +
                • After years or research, our scientists have engineered this cutting-edge technology born from the science of shaving. The Electric Space-Razor 5000! It uses moisturizers to refuel your face while you shave with not three, not four, but FIVE lazer-precise inner blades for maximum comfort.
                • +
                +
                + +
                +

                4 April 2013

                +

                Cheridan updated:

                +
                  +
                • When an AI shunts into an APC, the pinpointer will begin tracking it. When the AI returns to its core, the pinpointer will go back to locating the nuke disc.
                • +
                • New sechud icons for sec officers/HoS, medical doctors, and loyalty implants.
                • +
                +
                + +
                +

                28 March 2013

                +

                Carnie updated:

                +
                  +
                • Empty character slots in your preferences screen will now randomize. So they won't initialise as bald, diaper-clad, white-guys.
                • +
                • Reworked the savefile versioning/updating code. Your preferences data is less likely to be lost.
                • +
                +
                + +
                +

                14 March 2013

                +

                Major_sephiroth updated:

                +
                  +
                • You can now light cigarettes with other cigarettes, and candles. And cigars. Light a cigar with a candle! It's possible now!
                • +
                +
                + +
                +

                13 March 2013

                +

                Elo001 updated:

                +
                  +
                • You can now open and close job slots for some jobs at any IDcomputer. There is a cooldown when opening or closing a position.
                • +
                +
                + +
                +

                8 March 2013

                +

                Kor updated:

                +
                  +
                • You can now construct/destroy bookcases. This is super exciting and game changing.
                • +
                +
                + +
                +

                6 March 2013

                +

                Petethegoat updated:

                +
                  +
                • Petethegoat says, "Added a new feature involvi-GLORF"
                • +
                • Overhauled how grabs work. There aren't any interesting mechanical differences yet, but they should be a lot more effective already. You don't have to double click them anymore. Report any bugs with grabbing directly to me, or on the issue tracker.
                • +
                +
                + +
                +

                24 February 2013

                +

                Ikarrus updated:

                +
                  +
                • AI has been moved back to the center of the station. Telecoms has been moved to engineering.
                • +
                +

                Faerdan updated:

                +
                  +
                • Competely new UI overhaul! Most user interface have been converted.
                • +
                + +
                +

                22 February 2013

                +

                Petethegoat updated:

                +
                  +
                • Added cavity implant surgery.
                • +
                • Additionally, surgery must now be performed with help intent. Some procedures have also been updated. + As always, check the wiki for details.
                • +
                +
                + +
                +

                18 February 2013

                +

                Ikarrus updated:

                +
                  +
                • The AI has been moved to Research Division, and Telecomms has been moved into the former AI chamber. Affected areas: Telecoms Satellite, Research Division South & Command Sector.
                • +
                +

                Incoming updated:

                +
                  +
                • Added three new types of surgery- lipoplasty, plastic surgery, and gender reassignment.
                • +
                +

                Kor updated:

                +
                  +
                • The RD has lost access to telecomms, and basic engineers have gained it.
                • +
                +
                + +
                +

                14 February 2013

                +

                Petethegoat updated:

                +
                  +
                • Updated surgery: you now initiate surgery with surgical drapes or a bedsheet. Most procedures have changed, check the wiki for details. Currently it's pretty boring, but this paves the way for exciting new stuff- new procedures are very simple to add. Report any bugs directly to me, or on the issue tracker.
                • +
                +
                + +
                +

                13 February 2013

                +

                Giacom updated:

                +
                  +
                • There are now hackable wires for the PA computer. To open the interface, click on it while the wires are exposed/panel is open. All but one wire will do something interesting, see if you can figure it out. You can also attach signallers to the wires so have fun remotely releasing the singularity.
                • +
                • New staff of animation icon by Teh Wolf!
                • +
                • You can now hack plastic explosives (C4)!
                • +
                • You can now use NTSL to send signals! With the function signal(frequency, code) you can create some clever ways to trigger a bomb. NTSL also has two new additions; return in the global scope will now stop the remaining code from executing and NTSL now has "elseif"s, huzzah!
                • +
                +

                Kor "I'm quitting I swear" Phaeron updated:

                +
                  +
                • You've been asking for it for years, it's finally here. Wizards can spend points to buy apprentices.
                • +
                • A new wizard artefact, the scrying orb.
                • +
                • The spellbook now has descriptions of spells/items visible BEFORE you purchase them.
                • +
                +

                Petethegoat updated:

                +
                  +
                • Traitors with the station blueprints steal objective can now use a photo of the blueprints instead!
                • +
                +
                + +
                +

                11 February 2013

                +

                SuperSayu updated:

                +
                  +
                • Signallers, prox sensors, mouse traps and infrared beams can now be attacheed to grenades to create a variety of mines.
                • +
                • A slime core can be placed in a large grenade in place of a beaker. When the grenade goes off, the chemicals from the second container will be transfered to the slime core, triggering the usual reaction.
                • +
                +
                + +
                +

                10 Feburary 2012

                +

                Ikarrus updated:

                +
                  +
                • Implants can now be surgically removed. Hint: They're inside the skull.
                • +
                +
                + +
                +

                07 February 2012

                +

                Giacom updated:

                +
                  +
                • The return of the Nanotrasen Scripting Language! (NTSL) If you haven't heard of NTSL, it is a scripting language within a game for telecomms. Yes, you can create scripts to interact with the radio! For more information, head here: http://wiki.nanotrasen.com/index.php?title=NT_Script But before you do, if you are not an antag, do not create bad scripts which hinders communication.
                • +
                • Cameras, mulebots, APCs, radios and cyborgs can have signallers attached to their wires, like airlocks!
                • +
                • Cameras have non-randomized wires and the power wire when pulsed will now toggle the camera on and off.
                • +
                • Cyborgs have a new wire, the lockdown wire! It will disable/enable the lockdown of a Cyborg when pulsed.
                • +
                • The traffic control computer (or more commonly known as the computer which lets you add NTSL scripts) will now have a user log, which will log all user activity. Other users can then view that log and see who has been uploading naughty scripts!
                • +
                • NTSL has two new functions! time() and timestamp(format) will help you increase the range of types of scripts you can make, especially time(); since you can then make the scripts know the different between each execution by storing the results in memory.
                • +
                • Two new advance disease symptoms! Their names are "Longevity" and "Anti-Bodies Metabolism". Have fun experimenting with them!
                • +
                +
                + + +
                +

                31 January 2013

                +

                Kor "Even in death I still code" Phaeron updated:

                +
                  +
                • Four new slime types with their own reactions and two new reactions for old slimes.
                • +
                • Put a suit of reactive teleport armour back in the RD's office.
                • +
                • Chemistry now has two dispensers (with half charge each) so both chemists can actually work at the same time.
                • +
                +
                + + +
                +

                27 January 2013

                +

                Ikarrus updated:

                +
                  +
                • Security frequency chatter now appears in cyan (Similar to how command is gold)
                +

                Cheridan updated:

                +
                  +
                • The plant bags in Hydroponics lockers have been replaced with upgraded models with seed-extraction tech. Activate with via right-click menu or Objects verb tab.
                • +
                • Obtaining grass tiles works a bit different now: grass is harvested as a normal plant item, clicking on it in-hand produces the tile.
                • +
                +
                + +
                +

                26 January 2013

                +

                Pete updated:

                +
                  +
                • Added hugging and kicking. I also updated the text styles for clicking on humans in most intents, but they should be pretty much the same.
                • +
                +
                + +
                +

                25 January 2013

                +

                Errorage updated:

                +
                  +
                • All the equipment you spawn with will now contain your fingerprints, giving the detective more ability to tell where items came from and if a crewmember has changed clothing.
                • +
                +
                  +
                • Better explosions: Explosion spreading will now be determined by walls, airlocks and poddoors and not just a flat circle.
                • +
                +
                + +
                +

                20 January 2013

                +

                Cheridan updated:

                +
                  +
                • Chickens will now lay a certain number of eggs after being fed wheat, rather than just laying them whenever they felt like it. No more chickensplosions.
                • +
                +
                + +
                +

                16 January 2013

                +
                  +
                • Department Security can now be runned: +
                  Department Security decentralizes security by assigning each officer to a different department. They will be given the radio channel and access to their assigned department along with a security post. The brig has been remapped to be smaller to accomodate this change. +
                  To run DeptSec: Before compiling, server operators must
                  tick jobs.dm (in WorkInProgress/Sigyn/Department Sec) and use map 2.1.1 instead of 2.1.0.
                • +
                +
                + +
                +

                /tg/station 13 Presents

                +

                Directed by S0ldi3rKr4s0

                +

                & produced by Petethegoat

                +
                  +
                • Curse of the Horseman.
                • +
                +
                + +
                +

                12 January 2013

                +

                Cael Aislinn updated:

                +
                  +
                • Spiders which will breed and spread through vents. Different classes of vents. AI controlled only at the moment.
                • +
                • Farm animals! Cows, goats and chickens are now available. You can order them at Cargo Bay.
                • +
                +

                Giacom updated:

                +
                  +
                • Staff of animation mimics will no longer care whether you are holding the staff or not, they will never attack their creator.
                • +
                • Brainrot will only need alkysine to be cured.
                • +
                • New spider infestation event based on Cael's spiders. The announcement will be the same as alien infestations.
                • +
                +
                + +
                +

                11 January 2013

                +

                Giacom updated:

                +
                  +
                • Plasma (Air) will give the breather the plasma reagent, for a toxic effect, instead of just straight damage.
                • +
                • The agent card will now work inside PDAs/Wallets; meaning the AI won't be able to track you.
                • +
                +
                + +
                +

                09 January 2013

                +

                Malkevin updated:

                +
                  +
                • The owl mask now functions as a gasmask for increased crimestopping power.
                • +
                +
                  +
                • Adds the missing icons for the arrest statuses of Parolled and Released, as well as a little blinking icon for chemical implants.
                • +
                + + +
                +

                08 January 2013

                +

                Cael Aislinn & WJohnston updated:

                +
                  +
                • Many new icons for aliens (death, sleeping, unconscious, neurotox, thrown/impregnated facehugger etc)
                • +
                • Alien larva can now be removed by dangerous and unnecessary surgery (and actually chestburst if they aren't).
                • +
                • Alien larva now have sprites to represent their growth: bloody at 0%, pale at 25% and 75% the normal deep red.
                • +
                • New icon overlays for representing alien embryo progression.
                • +
                + +
                +

                07 January 2013

                +

                Kor updated:

                +
                  +
                • Four new slime types with their own extract reactions have been added. Sprites this time were created by Reisyn, SuperElement, and LePinkyFace.
                • +
                + +
                +

                02 January 2013

                +

                Kor updated:

                +
                  +
                • Slime breeding! There are now 13 varities of slime, each with its own extract reaction (inject five units of plasma). Some of these reactions are reused from the old cores, some are new. As to breeding, each colour of slime has a series of other slimes it may mutate into when it reproduces.
                • +
                +

                Giacom updated:

                +
                  +
                • You can now use wallets as IDs and equip them in your ID slot.
                • +
                • Firesuits are once again effective at protecting you from heat. The flames themselves will still hurt you, even with a firesuit. The damage protection is much better with a firesuit though.
                • +
                • Engineering starts with a PACMAN generator for jump starting the singularity if the power runs out of the SMES. 30 plasma spawns in Secure Storage inside the crate, to use as fuel for the generator.
                • +
                + +
                +

                31 December 2012

                +

                Giacom updated:

                +
                  +
                • Simple animals (Corgis, Cats, Constructs, Mice, Etc...) can now pull people.
                • +
                • You can quickly stop pulling on someone by pulling them, while they're already being pulled by you. For example, CTRL+Click on something you are pulling to quickly stop pulling it.
                • +
                + +
                +

                30 December 2012

                +

                Giacom updated:

                +
                  +
                • Emitters now require to be wired in order to work. When there is not enough power it will stop shooting until there is enough power, meaning you do not have to turn it back on, just get the power flowing.
                • +
                • You can order shield generators from cargo. Teleporter access is required to open the crate.
                • +
                +

                Ikarrus updated:

                +
                  +
                • Map: Reorganized the Command Sector. The Captain has his own private quarters in addition to his office.
                • +
                + +
                +

                26 December 2012

                +

                Ikarrus updated:

                +
                  +
                • An agent card is now required to use doors and controls on the Syndicate Shuttle (Nuke).
                • +
                • Scanning gas tanks is now a PDA-cart function. Only Atmos and Science PDA carts have this function. Have fun mislabelling gas tanks!
                • +
                + +
                +

                23 December 2012

                +

                Giacom updated:

                +
                  +
                • The syndicate Military PDA will not show up on possible PDAs to message anymore, even when the receive/signal is turned on. You can still send messages and people can still reply to you.
                • +
                • You can now sell processed plasma for supply points. The conversion rate is 2 plasma sheets for 1 point. You must put the plasma in a crate for it to count.
                • +
                • The mecha toy prize promotion has officially ended. You can no longer redeem all 11 action mecha figures for a real mech. New toy redeeming promotions in the future will be considered.
                • +
                + +
                +

                21 December 2012

                +

                Ikarrus updated:

                +
                  +
                • You can now use . to speak over headset department channels in addition to the : and # characters.
                • +
                + +
                +

                19 December 2012

                +

                Nodrak updated:

                +
                  +
                • You can now use # to speak over headset department channels. For example say "#e Hello" will say "Hello" over the engineering channel. say ":e Hello" will still work as it always has.
                • +
                + +
                +

                16 December 2012

                +

                Giacom updated:

                +
                  +
                • You can now create your own solar arrays! Order the solar pack crate and you'll receive 21 solar assemblies, 1 electronic which you can put into an assembly to make it a solar tracker and finally the solar computer circuit board. You will get more detailed instructions in the crate, on a piece of paper. Engineering will also start with this crate to help repair destroyed solar arrays.
                • +
                +

                Petethegoat updated:

                +
                  +
                • Added a new option to the key authorisation devices. It removes the maintenance access requirement from all doors. It's irreversible, so only use it in an emergency!
                • +
                + +
                +

                15 December 2012

                +

                Ikarrus updated:

                +
                  +
                • Swapped the locations of the Library and Chapel. Thanks to killerz104 for the remap.
                • +
                • Partial remap of atmos. Monitoring and Refill stations are now the same room.
                • +
                • Toxins Mixing should be working properly again.
                • +
                + +
                +

                12 December 2012

                +

                Ikarrus updated:

                +
                  +
                • Robotics is now a full Science department.
                • +
                • Completely remapped Research Division, Robotics, Medbay, and the Library.
                • +
                • Partially remapped Cargo Bay, Mining Dock, Engineering, and Atmospherics.
                • +
                • Changed the access of the HoS and HoP. For a list, refer to their respective wiki pages.
                • +
                +

                Errorage updated:

                +
                  +
                • Miners now have to go through cargo to reach the Mining Dock.
                • +
                +

                Petethegoat updated:

                +
                  +
                • The Detective's revolver no longer cares about how cool you look. It now spawns in his locker.
                • +
                • Added new inhands for most energy weapons, by Flashkirby!
                • +
                +

                Giacom updated:

                +
                  +
                • Disintegrate (EI NATH) will leave behind the brain of the victim. Possible productive uses include: trophies, looking awesome as you gib someone and only their brain remains, people to talk to when you get an MMI, pocket brains, a way to get back into the game if the wizard didn't grab your brain and stuffed it into his bag.
                • +
                + +
                +

                07 December 2012

                +

                Giacom updated:

                +
                  +
                • The detective's scanner was upgraded, it can now scan for reagents in items and living beings. Potential uses include, scanning dead bodies for leftover poison or scanning items to see if they have been spiked.
                • +
                • You can now attach photos to newscaster news feeds and wanted posters.
                • +
                • You can now emag buttons to remove access from them.
                • +
                • The CentComm. Report has been changed so it no longer names potential antagonists. It will just announce the potential round type instead.
                • +
                + +
                +

                05 December 2012

                +

                Cheridan updated:

                +
                  +
                • Agent cards have been upgraded with microscanners, allowing operatives in the field to copy access levels off of other ID cards.
                • +
                +

                Ikarrus updated:

                +
                  +
                • The Chief Medical Officer, Research Director, Chief Engineer, and Lawyers now have basic Brig access (corridor only)
                • +
                • Merged Mining and Cargo radio channels into the Supply Radio. To use the supply channel, use :u
                • +
                • Mining Dock remapped to be more compact and closer to cargo.
                • +
                +

                Giacom updated:

                +
                  +
                • The wizard's fireball spell is once again dumbfire. It will fire in the direction of the wizard instead of having to choose from a list of targets and then home in on them.
                • +
                +
                + +
                +

                02 December 2012

                +

                Giacom updated:

                +
                  +
                • Added a new artefact called the "Staff of Animation". You can get it in the Wizard's Spellbook. It will animate objects and items, but not machines, to fight for you. The animated objects will not attack the bearer of the staff which animates them, meaning if you lose your staff, or if it gets stolen, your minions will turn on you.
                • +
                +
                + +
                +

                30 November 2012

                +

                Petethegoat updated:

                +
                  +
                • Janitor has recieved a slightly upgrade mop bucket. The old one is still there too.
                • +
                +

                Ikarrus updated:

                +
                  +
                • Swapped the locations of the Vault and Tech Storage.
                • +
                • Cargo Techs, Miners, and Roboticists no longer start with gloves. They are still available from their lockers.
                • +
                +
                + +
                +

                28 November 2012

                +

                Kor updated:

                +
                  +
                • Slimes have replaced roros (finally)! Right now they are functionally identical, but massive expansion of slimes and xenobio is planned. Sprites are by Cheridan.
                • +
                +
                + +
                +

                25 November 2012

                +

                Giacom updated:

                +
                  +
                • Added new very high level symptoms which are only obtainable in the virus crate. Virus crate will also come with mutagen.
                • +
                +

                Petethegoat updated:

                +
                  +
                • Removed clown planet! It'll return shortly in away mission form.
                • +
                +

                Ikarrus updated:

                +
                  +
                • Added Gateway access. Only the RD, HoP, and Captain start with this.
                • +
                • New access levels in the brig:
                  -Brig access now opens the front doors of the brig, as well as other lower-risk security areas.
                  -Security access allows you into the break room and equipment lockers.
                  -Holding Cells allows you to use brig timers and lets you in the Prison Wing.
                  -The Detective no longer has Security Equipment access.
                • +
                • Significantly increased max cloneloss penalty for fresh clones to 40%.
                • +
                +
                + +
                +

                23 November 2012

                +

                Giacom updated:

                +
                  +
                • Simplified detective stuff. The high-res scanner is gone and instead the detective's normal scanner will instantly report all fingerprints, dna and cloth fibers in full. This was needed because the system took too long to work with and disencouraged detectives. Not only that, it made detectives less of a threat for antagonists and made possible scenerios, such as framing someone by changing fingerprints with someone else, impratical. To replace the computer, the detective will have a full medical computer with access to it. Not only that, but his useless filing cabinet will be replaced with an empty one for serious investigators. Along with this, are fingerprint cards and built-in PDA scanning, as all of security had access to it which was really the detective's thing. The new scanner will also log every finding and you can print them out as a report by clicking the scanner while it is in your active hand.
                • +
                • You can toggle the pressure of your sprayer by clicking on it while it is in your active hand. With pressure, the sprayer will spray 10 units on the floor, otherwise it sprays 5. You'll need to turn pressure on to spray water on the floor and make it slippery.
                • +
                • AIs in intellicards can no longer move their camera. This will limit them in ability but without making creating and carding an AI to have as a personel door opener impossible.
                • +
                • Telecommunication Busses can now be set to change the frequency of a signal. (Allowing you to say.. set the command channel to broadcast to the common channel).
                • +
                • Telecommunication was changed to be more effecient. Because of this, Relays don't need a broadcaster or a receiver and you can setup a relay on it's own. You can still disable sending and or receiving from the relay's interface.
                • +
                +

                Zelacks updated:

                +
                  +
                • Plant Analysers now work on seed bags.
                • +
                +
                + +
                +

                21 November 2012

                +

                Petethegoat updated:

                +
                  +
                • The nuke shuttle can now travel at will, and to any location. When travelling from syndicate space to the station, (and vice versa), it will travel through hyperspace.
                • +
                +

                Carn updated:

                +
                  +
                • Changed savefile structure. There's a bunch of unused files left lying around so old savefiles will be purged. Sorry for the inconvenience. Many preferences have been moved to the Preferences verb tab. Everything in that tab is persistent between rounds (it updates your savefile, so even DCing won't reset them). Enjoy x
                • +
                +

                Phol updated:

                +
                  +
                • Added female sprites for most mutant races.
                • +
                +

                Cheridan updated:

                +
                  +
                • SSU manufacturers have issued a product recall! It seems old models shipped with faulty wiring, causing them to short-circuit.
                • +
                +
                + +
                +

                20 November 2012

                +

                Kor updated:

                +
                  +
                • Added Exile Implants to the Gateway room. Someone implanted with an Exile Implant will be able to enter the away mission, but unable to return from it. Not only can they be used for getting rid of dangerous criminals, but revs/stationheads count as dead while on the away mission, and traitor/changeling/wizard assassination targets count as dead if they're on the away mission at round end, allowing for those objectives to be completed peacefully.
                • +
                • Added medical hardsuits, sprited by Majorsephiroth. Two of them spawn in EVA. Their most unique/medical oriented feature is being able to hold a medkit in the suit storage slot, allowing you to easily access medicine while keeping your hands free.
                • +
                +
                + +
                +

                19 November 2012

                +

                Giacom updated:

                +
                  +
                • Malf AIs can only shunt to APCs from their core. Meaning their core needs to be alive before they can shunt to another APC. Malf AIs can start a takeover inside an APC now.
                • +
                • When taking damage, the next sequence of the overlay will show for a bit before reverting to the overlay you should have. This allows you to know you are taking damage without having to check the text screen.
                • +
                +
                + +
                +

                18 November 2012

                +

                Petethegoat updated:

                +
                  +
                • Ported over BS12 style cameras. They now take a photo of a 3x3 area!
                • +
                • Catatonic people (those that have ghosted while alive) now count as dead for assasinate objectives.
                • +
                +
                + +
                +

                17 November 2012

                +

                Donkie updated:

                +
                  +
                • You can now deconstruct and construct Air Alarms and Fire Alarms. Read wiki on howto.
                • +
                +

                Giacom updated:

                +
                  +
                • Medical Cyborgs no longer lose the reagents in their hypospray when switching modes.
                • +
                • Spaceacillin will now help stop the spread of diseases.
                • +
                • You can once again make floors slippery by spraying water. This was done by increasing the amount the sprayer uses, which is from 5 to 10. You can also empty your sprayer's contents onto the floor with a verb in the Object tab.
                • +
                +
                + +
                +

                16 November 2012

                +

                Kor updated:

                +
                  +
                • Fixed the syndicate teleporter door, making teleport assaults possible. It will once again open when you open the outter door.
                • +
                +
                + + +
                +

                15 November 2012

                +

                Giacom updated:

                +
                  +
                • You can now name your advance diseases! You can't name already known diseases though.
                • +
                • Chemical implants can now hold 50 units instead of 10 units.
                • +
                +
                + +
                +

                13 November 2012

                +

                Giacom updated:

                +
                  +
                • More work to advance diseases. Please report any bugs to the bug tracker, I have tried everything that I can on my own but I'll need lots of people playing to fix the more minor bugs. You can find a guide to making your own diseases here: LINK!
                • +
                • Reduced the cost to use Hive Absorb from 40 to 20. This is to help encourage people to use this power more and to use team work.
                • +
                • New symptom added! See if you can find it.
                • +
                • You can now remove symptoms from a disease using synaptizine.
                • +
                • Kor: You can once again debrain changelings. They won't make anyone half-lings though, and you won't be able to tell if the body of a debrained changeling is a changeling by putting a player brain in there.
                • +
                +

                Nodrak updated:

                +
                  +
                • Wizards can no longer cast spells when muzzled. It iss now actually possible to capture a live wizard without constantly injecting them with chloral.
                • +
                • You can no longer take bags of holding or mechs to the clown planet.
                • +
                +
                + +
                +

                11 November 2012

                +

                Carn updated:

                +
                  +
                • Admin-ranks changes
                  + Lots of changes. This is just a brief summary of the most recent changes; still working on proper documentation.
                  + All admins have access to view-vars, player-panel(for individual mobs), game panel and secrets panel. Most of the things on those pages have their own rights requirements. For instance, you can only use event orientated secrets in the secret panel if you have FUN rights. Debug secrets if you have DEBUG rights. etc.
                  + Spawn xeno and toggle gravity procs were moved into the secrets panel (fun).
                  + This may help with understanding which flags do what. Unfortuanately it's still somewhat vague.
                  + If you have any problems, feel free to PM me at irc.rizon.net #coderbus. I go by the username carn or carnie. +
                • +
                +
                + +
                +

                11 November 2012

                +

                Kor updated:

                +
                  +
                • New cyborg upgrade available for production that requires illegal and combat tech
                • +
                • Summon Guns has a new gun type created by Ausops. It also lets the user know when its been cast now to prevent people trying to buy it multiple times
                • +
                • Grilles are no longer immortal in regards to solid projectiles, you can now shoot out windows.
                • +
                +
                + +
                +

                09 November 2012

                +

                Giacom updated:

                +
                  +
                • Cyborgs can now ping and beep! (Say "*beep" and "*ping") Thanks to Rahlzel for the proposal.
                • +
                • HULKS WILL NOW TALK IN ALL CAPS AND WILL RANDOMLY SAY HULK THINGS. Thanks to Brotemis for the proposal.
                • +
                • Sorry for the inconveniences with advance diseases. They are working much better now!
                • +
                • An improved APC sprite by TankNut!
                • +
                +
                + + +
                +

                05 November 2012

                +

                Giacom updated:

                +
                  +
                  +
                • AIs can now tweak with a bot's setting like a human who unlocked the bot.
                • +
                +
                + +
                +

                05 November 2012

                +

                Errorage updated:

                +
                  +
                • Being in an area with extremely low pressure will now deal some damage, if you're not protected.
                • +
                • Space suits and the captain's armor now protect against pressure damage
                • +
                • Slightly lowered all environment damage intakes (temperature, oxygen deprevation) to make up for low pressure damage.
                • +
                • Pressure protection finally works properly. Items that protect from pressure (firesuits, space suits, fire helmets, ...) will now properly protect. The pressure damage indicator will update properly based on the pressure effects on you. Black (low) and red (high) mean you are taking damage.
                • +
                • Slightly slowed down the speed at which your body temperature changes if you are in a very hot or very cold area. The speed at which you recover from an abnormal body temperature remains the same.
                • +
                +
                + + +
                +

                03 November 2012

                +

                TankNut updated:

                +
                  +
                • New APC sprite.
                • +
                • New Wraith sprite and jaunting animation.
                • +
                +
                + +
                +

                03 November 2012

                +

                WJohnston updated:

                +
                  +
                • New Ablative Armor sprite.
                • +
                +
                + +
                +

                03 November 2012

                +

                Giacom updated:

                +
                  +
                  +
                • Airborne diseases will not spread through walls now.
                • +
                • Reduced queen healing rate to 5. The maximum health will be enough.
                • +
                • Aliens can now clear hatched eggs by clicking on them.
                • +
                +
                + +
                +

                02 November 2012

                +

                Errorage updated:

                +
                  +
                • You can once again travel to the station, derelict, satellite and mining z-levels through space. You will also never loop into the same level on transition - So if you are exiting the derelict z-level, you will enter one of the other z-levels.
                • +
                +
                + + +
                +

                01 November 2012

                +

                Giacom updated:

                +
                  +
                • Aliens now take x2 as much damage from fire based weaponary, instead of x1.5.
                • +
                • Doors are now weaker than walls; so normal weapons can destroy them much more easily.
                • +
                +
                + +
                +

                31 October 2012

                +

                Giacom updated:

                +
                  +
                • Advance evolving diseases! Virology can now create, mutate and mix advance diseases together. I replaced the two bottles of blood in Virology with the advance disease. I'll write a wiki article soon enough. Here's a tip: Putting mutagen or virus food (a mixture of milk, water and oxygen) in blood with an existing disease will mutate it to gain symptoms. It can potentially lose old symptoms in the process, so keep backups!
                • +
                +
                + +
                +

                28 October 2012

                +

                Errorage updated:

                +
                  +
                • You can now set your character's age up to 85. This used to be 45.
                • +
                +
                + +
                +

                27 October 2012

                +

                Petethegoat updated:

                +
                  +
                • Mousetraps are now assemblies.
                • +
                • Added a new crate for cargo to order.
                • + +
                +
                + +
                +

                27 October 2012

                +

                Petethegoat updated:

                +
                  +
                • Player Weekend begins!
                • +
                • Added a camera and hand labeler to art storage.
                • +
                • Added a medical records cabinet to the Detective's office.
                • +
                • Added a safe to the vault. Who'll be the first to crack it?
                • +
                +

                Nodrak updated:

                +
                  +
                • The CE has a new pet!
                • +
                +
                + +
                +

                25 October 2012

                +

                Flashkirby99 updated:

                +
                  +
                • Added 18 new hairstyles!
                • +
                +
                + +
                +

                24 October 2012

                +

                Giacom updated:

                +
                  +
                • Throwing eggs will result in the reagents of the egg reacting to the target. (Which can be a turf, object or mob) This creates possibilities like chloral eggs, lube eggs, and many more.
                • +
                • Aliens can now acid walls and floors! Not R-Walls though.
                • +
                • Facehugger throw range reduced to 5, so aim at humans that are 2 tiles apart from the edge of your screen.
                • +
                • Making eggs is a little more expensive but secreting resin is cheaper. (Both cost 75 now)
                • +
                • Aliens no longer have a random duration of stunning humans, it's a constant value now of the lower based value.
                • +
                • Acid is less random and will be more reliable. Don't bother aciding stuff more than once, as it will waste plasma.
                • +
                • You can now target non-dense items (such as facehuggers) with a gun.
                • +
                • You can now shoot canisters, computers and windoors to break them.
                • +
                +
                + +
                +

                18 October 2012

                +

                Giacom updated:

                +
                  +
                • As an AI, you can type in the "track with camera" command and get a list of names to show up there. This also works with "list camera" verb. Remember to use space to auto-fill.
                • +
                • Welding goggles have been added. They are like welding helmets but they are for the glasses equipment slot. Science and the assembly line are given a pair.
                • +
                • Thanks to WJohnston for the welding goggle icons.
                • +
                • Small change to the Assembly Line. Instead of six normal flashes, the Assembly Line will instead have two normal flashes and eight synthetic flashes. Synthetic flashes only work once but are designed to be used in construction of Cyborgs.
                • +
                • Nar-Sie put on a few pounds. Thanks HornyGranny.
                • +
                +
                + +
                +

                16 October 2012

                +

                Giacom updated:

                +
                  +
                • New changeling powers!
                • +
                • Hive Channel/Hive Absorb. Allows you to share your DNA with other changelings, very expensive chemical wise to absorb (download), not so much to channel (upload)! You cannot achieve your objective by sharing DNA.
                • +
                • Mimic Voice! You can form your voice of a name you enter. You won't look like them but when you talk, people will hear the name of who you selected. While you're mimicing, you can't regenerate chemicals.
                • +
                • Extract DNA! A power that allows you to silently sting someone and take their DNA! Meaning you do not have to absorb someone to become them. Extracting their DNA doesn't count towards completing your objectives.
                • +
                • You can now get flares from red emergency toolboxes. Has a 50% chance of a flash-light or a flare spawning.
                • +
                • Flare icon by Ausops!
                • +
                • Thanks to RavingManiac (Smoke Carter), Roros now lay eggs which can grow into baby roros or be used for cooking recipes. Scientists will need to expose the egg to plasma for it to hatch; while it is orange (grown).
                • +
                • A new icon for the map spawned x-ray cameras. Icon by Krutchen.
                • +
                +
                + +
                +

                13 October 2012

                +

                Giacom updated:

                +
                  +
                • Facehuggers have a new animation, thanks to Sly.
                • +
                • Firelocks, glass-less airlocks and walls will stop heat.
                • +
                • Fires are now more deadly, especially the flames.
                • +
                • Fires will now break windows.
                • +
                +
                + +
                +

                10 October 2012

                +

                Giacom updated:

                +
                  +
                • Larva grow a little bit faster when on weeds or when breathing in plasma.
                • +
                +
                + +
                +

                8 October 2012

                +

                Giacom updated:

                +
                  +
                • Thanks to Skasi. Atmospherics has been changed to be made simpler and spawn with the new atmos features, such as the heaters.
                • +
                • Radio headsets can only be heard by people wearing them on their ear slot. This will let us do more fun stuff with headsets, such as a traitor encryption key which can listen to all the channels, but not talk in them.
                • +
                +
              +

              Kor updated:

              +
                +
              • A pen no longer spawns in your pocket. Instead, each PDA will spawn with a pen already in it.
              • +
              + + +
              +

              5 October 2012

              +

              Giacom updated:

              +
                +
              • Aliens can now be harmed by fire. They now also take double fire damage, meaning flame based weaponry is very effective.
              • +
              • Buffed alien facehuggers and eggs. Facehuggers don't go idle anymore, and they attach to anyone who walks past them. Eggs do the same; fully grown eggs will open to potential hosts. If you are still in the range of them, the facehugger inside will leap out and hug you. Removed "activate facehuggers", since it's useless now. Emote "roar" if you want to roar now.
              • +
              • There can be only one living queen at a time, if the queen dies then a drone can take her place as a princess.
              • +
              • Buffed queen regeneration a bit, so it's not the same as her underlings. It's also more important because there can only be one queen at a time.
              • +
              • Aliens don't slip in space anymore.
              • +
              • Hulks don't paralyze aliens anymore, they instead slow them down to a slow crawl. It is very effective for punching aliens out of weeds, so it can't regenerate it's health.
              • +
              • New egg opening and opened egg icons by WJohnston.
              • +
              +

              Aranclanos updated:

              +
                +
              • A buncha crud nobody cares about lol Added a light to the airlock wiring interface to show the status of the timing.
              • +
              • You can't fill sprays without being next to the dispenser.
              • +
              • Simple animals no longer freeze to death in places with normal temperature.
              • +
              • Mechs no longer freeze on the spot when they are using the Energy Relay on powerless areas.
              • +
              • Improvements to showers, they now clean gear on beltslot, back, ears and eyes. Showers only clean visible gear.
              • +
              • Replica pods works again! But you can't make potato people without a key or clone people who ghosted alive (Catatonic).
              • +
              • Engiborgs can deconstruct airlocks with their RCDs once again.
              • +
              • You can construct airlocks while standing on another airlock with RCDs.
              • +
              +
              + +
              +

              3 October 2012

              +

              Agouri updated:

              + +
              + +
              +

              1 October 2012

              +

              Cheridan updated:

              +
                +
              • Wizards have a new artifact added to their spellbooks.
              • +
              +
              + + +
              +

              30 September 2012

              +

              Numbers updated:

              +
                +
              • Readded Volume Pumps - now they work as intended and are constructable
              • +
              • Readded Passive Gates - now they work as intended and are constructable
              • +
              • Readded Heat Exchangers - now they work as intended and are constructable
              • +
              • Added Heater - to warm up gasses to 300C
              • +
              • Pipe dispensers can produce the readded pieces.
              • +
              • New graphics for all of the above - courtesy by Ausops.
              • +
              +
              + +
              +

              30 September 2012

              +

              Giacom updated:

              +
                +
              • Airlocks now use the Environmental power channel, since they are airlocks after-all. Meaning, when power is low the airlocks will still work until the environmental channel on the APC is turned off. This applies to all the door control buttons too. Pipe meters now use the environmental power channel. If you have any comments have this change, please let me know in the feedback section of the forums.
              • +
              +
              + +
              +

              26 September 2012

              +

              Carnwennan updated:

              +
                +
              • Added new hotkeys. Type hotkeys-help for details or see the drop-down help menu at the top of the game window.
              • +
              +

              Aranclanos updated:

              +
                +
              • Mechs are once again spaceproof!
              • +
              • The YouTool machine is now all access
              • +
              • Cutting tower caps in hand no longer deletes the wood, and planks now auto stack
              • +
              +
              + +
              +

              25 September 2012

              +

              Donkie updated:

              +
                +
              • Reworked the Piano, now really optimized and new interface!
              • +
              +
              + +
              +

              24 September 2012

              +

              Petethegoat updated:

              +
                +
              • Hopefully fixed the stop midis button. It should now stop any midis that are currently playing.
              • +
              +
              + +
              +

              23 September 2012

              +

              Petethegoat updated:

              +
                +
              • Fixed an exploit which would allow the janitor to magically mop floors.
              • +
              • Added lipstick~ It's not available on station, as Nanotrasen has deemed it contraband.
              • +
              • If you encounter any issues with computers, notify an admin, or ask for assistance on #coderbus, on irc.rizon.net.
              • +
              +

              Donkie updated:

              +
                +
              • Updated the Package Tagger with new interface!
              • +
              • You can now dispense, remove and retag sort junctions properly!
              • +
              +
              + +
              +

              17 September 2012

              +

              Cheridan updated:

              +
                +
              • Metroids have been replaced with Rorobeasts. Roros are strange latex-based lifeforms that hate light, fun, and gloves.
              • +
              +
              + +
              +

              17 September 2012

              +

              Carn updated:

              +
                +
              • F5 is now a hotkey for adminghosting. F8 toggles ghost-like invisibility for admins.
              • +
              • Catatonia makes you fall down. Admins appear braindead when admin-ghosting.
              • +
              • "Set-observe"/"Set-play" renamed and merged into "Aghost".
              • +
              • "Lay down/Get up" renamed to "Rest"
              • +
              • Closets can't be sold on the supply shuttle anymore
              • +
              • Fixed all dat light
              • +
              +
              + +
              +

              13 September 2012

              +

              Carn updated:

              +
                +
              • New Hotkeys (Trial period). Details can be found in the help menu or via the hotkeys-help verb. It's all client-side. It shouldn't intefere with regular controls (except ctrl+A, ctrl+S, ctrl+D and ctrl+W).
              • +
              +
              + +
              +

              10 September 2012

              +

              Giacom updated:

              +
                +
              • AIs can double click on mobs to instantly start tracking them.
              • +
              +
              + +
              +

              Important note for server hosts!

              +

              Important note for server hosts!:

              +
                +
              • The file /code/defines/hub.dm was moved into /code/hub.dm. To get your server back on the hub, open /code/hub.dm and set the hub variables again. Sorry for the inconvenience.
              • +
              +
              +
              +

              8 September 2012

              +

              Carn updated:

              +
                +
              • Added an additional check to stop changelings sharing powers/becomming un-absorbable/etc by absorbing eachother and then rejuvinating from death.
              • +
              • Cloaked Aliens are now slightly easier to see, so they should avoid strongly lit areas when possible. They can still lay down to become even stealthier though. Let me know what you think, it's only a minor sprite change.
              • +
              +
              + +
              +

              6 September 2012

              +

              Cheridan updated:

              +
                +
              • -Changes flour from an item to a container-held reagent. All recipes have been updated to use 5 units of reagent flour for every item required previously. This has a few advantages: The 16(!) sacks of flour previously in the kitchen cabinet have been condensed to an equivalent 3 sacks. Beer is now brewable with universal enzyme, and converting lots of wheat into flour should be less tedious. Also, flour grenades, etc. Because of this, flour is now obtained from the all-in-one blender rather than the processor, and spaghetti noodles are made with 5 units of flour in the microwave.
              • +
              +
              + +
              +

              6 September 2012

              +

              Giacom updated:

              +
                +
              • Removed cameras from bots (NOT BORGS). They weren't working well with freelook and I felt that since they weren't used at all, they wouldn't be missed.
              • +
              +
              + +
              +

              3 September 2012

              +

              Giacom updated:

              +
                +
              • Cameras has changed quite a bit. They are no longer created from grenade canisters, instead you make them from an autolathe. The construction and deconstruction for them has also changed, so look it up or experiment it with yourself to see how to setup the cameras now. Cameras also get wires, like airlocks and APCs. There's two duds, a focus wire, a power wire, an alarm wire and a light wire. Protip: You can see which one is the alarm wire by pulsing it.
              • +
              • Added a red phone and placed it in the Cyborg Station. Sprite by Pewtershmitz! You'll also find an AI restorer there, replacing the computer frame.
              • +
              • Cameras aren't all X-ray anymore. The AI won't be able to see what room you are in if there's no normal camera inside that room or if there's no X-ray camera nearby..
              • +
              • Cameras get upgrades! Currently there's X-ray, EMP-Proof and Motion. You'll find the EMP-Proof and Motion cameras in the normal places (Singularity Pen & EVA), the new X-ray cameras can be found in the Dormitory and Bathrooms, plus some extra ones to invade your privacy. See if you can smash them all.
              • +
              • Alien Larva can bite simple animals (see: Ian, Runtime, Mice) to kill them and gain a small amount of growing points.
              • +
              • Space travel was tweaked to be more random when changing Z levels. This will stop people and items from being stuck in an infinite loop, as they will eventually hit something to make them stop.
              • +
              +
              + +
              +

              31 August 2012

              +

              Agouri updated:

              +
                +
              • Overhauled newscasters. No visual additions but the thing is much more robust and everything works as intended. Wanted issues are fixed. Admins, check out Access News Network under Fun.
              • +
              +
              + +
              +

              30 August 2012

              +

              Giacom updated:

              +
                +
              • You can now create an EMP Pulse. Like an explosion, it is the mixing of two reagents that trigger this to happen. I will tell you the first required reagent. Uranium. Have fun!
              • +
              • I have made most chemicals need 3-5 or more chemicals in order to react to a turf. For instance, you need at least 5 units of thermite splashed on a wall for it to burn down."
              • +
              • The EMP kit, that you can buy through the uplink, has two more grenades in them now. Making the box full of EMP grenades!
              • +
              • Changed the EMP grenade's range to be much bigger.
              • +
              +
              + +
              +

              29 August 2012

              +

              Nodrak updated:

              +
                +
              • Mice now work with the admin player panel. Admins can now turn players into mice with the 'Animalize' button in the player panel!
              • +
              • Space bear AI no longer runs when a player is controlling it. Admins can now turn players into space bears with the 'Animalize' button in the player panel!
              • +
              • The holodeck beach program once again has a beach.
              • +
              • The nuke op shuttle floor was pressure-washed a few days ago. We have since re-painted it with nanotrasen blood. Sorry for any confusion.
              • +
              +
              + +
              +

              28 August 2012

              +

              Giacom updated:

              +
                +
              • You can now toggle the bolt light of airlocks. An extra wire, that controls the airlock's bolt light, has been added.
              • +
              • Aliens can now tell who is and who isn't infected. They get a special facehugger icon that appears over mobs that have been impregnated.
              • +
              • Cameras have temporary X-Ray for the time being.
              • +
              +
              + +
              +

              August 26, 2012

              +

              Nodrak updated:

              +
                +
              • Admins now have an 'Animalize' button on a mob's player panel. This button allows admins to turn players into simple animals.
                There are a few exceptions. Mice, Parrots, Bears and Space Worms all have issues that, until fixed, prevent me from allowing players those transformations.
              • +
              +

              August 25, 2012

              +

              Carnwennan updated:

              +
                +
              • New lighting. It should look and feel the same as the old lighting whilst being less taxing on the server. Space has a minimum brightness (IC starlight) and areas that do not use dynamic lighting default to a lighting level of 4, so they aren't dark, but they aren't superbright. Replacing turfs should preserve dynamic lighting. Singulo/bombs should cause a lot less lighting-related lag. There are some minor known issues, see the commit log for details.
              • +
              • Admins can now access most controller datums with the "Debug Controller" verb. Time to break all the things!
              • +
              • Supply shuttle now uses a controller datum. This means admins can see/edit supply orders etc.
              • +
              • Changeling fakedeath can be initiated after death again. Next time you want something reverted, just ask rather than being obnoxious.
              • +
              +

              Giacom updated:

              +
                +
              • AIs can now look around like a ghost with the exception that they cannot see what cameras cannot see. Meaning if you're in maintenance, and there's no cameras near you, the AI will not know what you are doing. This also means there's no X-Ray vision cameras anymore.
              • +
              • AIs can add links to Telecommunication Machines. Added some cameras for areas that should have it but instead relied on cameras nearby for vision.
              • +
              • Choking has been changed. You have to stand still while lethally choking someone. It takes time to get into that lethal choke. When you are lethaling choking someone, they are still concious until the lack of oxygen knocks them out.
              • +
              +

              trubble_bass updated:

              +
                +
              • Nerfed the Neurotoxin drink, it is now less effective than a stunbaton. But more effective than a Beepsky Smash.
              • +
              • Updated descriptions on various cocktails to be more accurate or more relevant to the drink itself.
              • +
              +
              + +
              +

              August 24, 2012

              +

              Sieve updated:

              +
                +
              • Floorbots now actually pull up tiles when emagged
              • +
              • All helper bots (excluding MULEs) have an access panel and maint panel, access being for behavior and maint for internal work
              • +
              • To open the maint panel, the access panel needs to be unlocked, then you use a screwdriver. There you can emag/repair it to your heart's content. (Emagging the access panel will also unlock it permanently)
              • +
              • Helper bots are now repaired by using a welder when their maint panel is open
              • +
              +
              + +
              +

              August 23, 2012

              +

              Nodrak updated:

              +
                +
              • In-hand sprites once again update correctly when equipping items.
              • +
              +
              + + +
              +

              August 16, 2012

              +

              Errorage updated:

              +
                +
              • Changes were made to how heating and cooling of humans works.
              • +
              • You must wear both a space suit and space helmet to be protected from space! Likewise you must wear a firesuit and a fire helmet to be protected from fire! Fire helmets are red and white hardhats, found in all fire closets.
              • +
              • Fire suits now only protect from heat and space suits only protect from cold, so make your choice count.
              • +
              +
              + + +
              +

              August 14, 2012

              +

              Sieve updated:

              +
                +
              • DNA modifiers can be used if there is no occupant, primarily to handle the buffer.
              • +
              • Ion Rifles are only effected by max severity EMPs, so AOE from its own shot won't effect it
              • +
              • Pepper Spray fits on Sec belts again
              • +
              +
              + +
              +

              August 11, 2012

              +

              Sieve updated:

              +
                +
              • Turrets now properly fire at simple_animals.
              • +
              • Borgs, AIs, and brains/MMIs can be sacrificed by cultists.
              • +
              • Grenades now automatically set throw on again.
              • +
              +
              + +
              +

              August 6, 2012

              +

              Dingus updated:

              +
                +
              • Library has been redesigned. It's a whole lot more classy now.
              • +
              • Significant changes to Medbay. CMO's office is more centralized, genetics has a new exit into cryogenics, and a new break room has been installed
              • +
              +
              + +
              +

              August 4, 2012

              +

              Icarus updated:

              +
                +
              • Changes to Med-Sci south and surrounding maintenance areas. Virology is more isolated and Science gets a new Misc. Research Lab.
              • +
              • Atmos techs get construction access now to do their little projects in.
              • +
              • Transformation Stings now work on living humans.
              • +
              +
              + +
              +

              August 2, 2012

              +

              Errorage updated:

              +
                +
              • Gas masks now protect you from reagent smoke clouds
              • +
              • Changed the 'black overlay' you get when paralyzed, blind or in critical condition to include a small circle around you.
              • +
              • Dramatically lowered the amount of damage you get per breath while in critical condition. Critical condition now lasts for about 5 minutes if nothing is causing you any additional harm. This in combination with the new black image overlay is an attempt at making doctors more willing to help.
              • +
              +

              Icarus updated:

              +
                +
              • Borgs now have flashlights to allow them to see in lightless areas
              • +
              • Changes to Medbay: The sleeper and storage rooms have been swapped around. Hopefully this leads to more healing and less looting.
              • +
              +
              + +
              +

              August 1, 2012

              +

              Sieve updated:

              +
                +
              • Borgs can now have an encryption key installed into their internal radios. Simply ID, open the panel, and use the key to insert it (Screwdriver to remove)
              • +
              • Due to that as well, borgs have a 'Toggle Broadcast Mode' button for their radios, which changes the broadcast type between station-bounced (Non-reliant on TComms), and subspace (Required for department channels)
              • +
              • Also changed the binary chat for consistency, now for the prefix is ':b' for everyone, not just one for humans and one for borgs/AIs/pAIs
              • +
              • Based on feedback, Nuke Op pinpointers now automagically change between shuttle and disk mode when the nuke is armed or disarmed.
              • +
              +
              + +
              +

              01-August-2012

              +

              Carn updated:

              +
                +
              • Please update your BYOND clients! Ideally everybody should be running the latest version of byond (v496). People who fail to update to at least version 494 within a month's time may find themself unable to connect. Currently our code has no restrictions at all, which is rather bad. By getting the user-base to keep their clients up-to-date we can make use of newer BYOND features reliably.
              • +
              +

              Giacom updated:

              +
                +
              • I've made some adjustments to the Fireball spell. I've changed it to shoot in the player's facing direction instead of you having to pick a name from a list. It will explode upon contact of a person, if it hits an obstacle or if it shoots for too long. To make up for the fireball not being able to go diagonal I've shortened the cooldown to 10 seconds. It still can hurt you badly and knock you down if you shoot it at a wall. Lastly, it now lights up so it'll show up in dark rooms easily.
              • +
              +
              + +
              +

              31 July 2012

              +

              Giacom updated:

              +
                +
              • Removed passive throwing. You need at least an aggressive hold of the mob before you can throw them.
              • +
              • New map changes by Ikarrus. AI Upload Foyer is now Secure Tech Access, and the outer door only requires Bridge access. Attached to it are two new rooms: The messaging server room and the communications relay. The comms relay room runs off its own SMES unit like the AI, so it won't be affected by powersinks
              • +
              +
              + + +
              +

              29 July 2012

              +

              Giacom updated:

              +
                +
              • All radios now only work in their Z level. This means that the CommSat has a few more additions to work with this change. There is now a new Telecomms Machine called the Relay which allows information to travel across Z levels. It it then linked to a new machine called the Hub, which will receive information from the Relays and send it to the buses. Because every Z level needs these relays, which are linked up with Receivers/Broadcasters, every Z level will get one. There is one in the station, in the RD's office, one in Telecomms as always, one in the Ruskie station which is turned off and hidden from the HUB's linked list. The last one is in Mining but the location for it has not been decided yet.
              • +
              • PDAs now need to be in a Z level with a functioning Relay/Comms Network in order to send messages. It will also send uncompressed (scrambled) messages like you would with the ordinary voice messages.
              • +
              • Added some of WJohnston's sprites. Added a new mining borg sprite, Added a new high tech security airlock, Added the new telecomm sprites for Relays. Hubs were given old Bus sprites.
              • +
              +
              + +
              +

              29 July 2012

              +

              Errorage updated:

              +
                +
              • You can now use crayons to color eggs
              • +
              • Mice have invaded the station!
              • +
              +
              + +
              +

              26 July 2012

              +

              Giacom updated:

              +
                +
              • Added a new mushroom for Hydroponics, the Reishi Mushroom! It is obtained like any other mushroom and it has relaxing properties.
              • +
              +
              + +
              +

              July 25, 2012: The day of updates!

              +

              Nodrak updated:

              +
                +
              • Attacking mobs with items will now give new messages. Instead of "Monkeyman was attacked in the head with a wrench by Nodrak." it will read "Monkeyman was bashed in the head with a wrench by Nodrak." Diffrent items have diffrent verbs and some have multiple verbs.
              • +
              • Cultists can now read what words a rune was made with by examining the rune. Due to an error in the code, this was not possible before.
              • +
              • Clowns no longer have practice lasers or staves of change blow up in their face due to clumsyness.
              • +
              • Engineering cyborgs can now actually repair a cut AI wire in APCs.
              • +
              • I've removed a ton of pointless checks and redundant loops from metroid's which have been causing lag due to how often they get called. If metroids are behaving strangly ping me in #coderbus
              • +
              +

              Sieve updated:

              +
                +
              • Made a 'default' save slot (D), and whenever you connect it automatically selects the default slot to load from, but manually selecting a different slot will allow you to play on that one before it returns to default.
              • +
              • Added the ability to name your save slots with the '*'. Names can be up to 16 characters and contain letters, numbers, and basic symbols
              • +
              • The preview icon on the preference screen now takes into account any job you have set on high, and dresses up the icon accordingly. If assistant is set to 'yes', or AI/Cyborg are on high it will put the icon in a grey suit (So you can still customize).
              • +
              • Nuke Ops get a new pinpointer, changing modes with the verb will switch between pointing to the disk, and pointing to the shuttle. Also provides a notification when you leave the station z-level
              • +
              • Reworked how MMI Life() was done, now they will never lose consciousness, and many less things affect them now(Like deafening/blindness from explosions). However, they are vulnerable to EMPs, but all damage is temporary.
              • +
              • Clowns will no longer be killed trying to use holo eswords
              • +
              • Major tweaking to try and optimize many operations on the game's backend. Hopefully, this will reduce a large amount of lag by steamlining CPU-intensive operations, but at the same time there was so much changed that there is no real way for a small group to test everything. If anyone spots a bug involving being unable to 'find' mobs, characters, whatever, then put it on the issue tracker or at the very least let #coderbus know. We can't fix shit unless we know about it.
              • +
              +

              Icarus updated:

              +
                +
              • Players not buckled in when the emergency shuttle/pod starts moving get will get knocked down.
              • +
              • Added a YouTool vending machine to primary tool storage.
              • +
              +
              + +
              +

              24 July 2012

              +

              Errorage updated:

              +
                +
              • Both the chef and bartender have access to the bar area so both can serve if the other is incompetent or does not exist. Bartender's shotgun and shaker were moved to his back room and the booze-o-mat is now ID restricted to the bartender.
              • +
              • Added powercells into vending machines in engineering
              • +
              • Gave two beartraps to the janitor for pest control purposes................
              • +
              +
              + +
              +

              22 July 2012

              +

              Errorage updated:

              +
                +
              • Mech toys can now be redeemed at the quartermaster's for a great reward! If you collect the full set of 11 toys you should put them in a crate and send them to centcom via the supply shuttle.
              • +
              • Supply shuttle arrival time reduced to 2 minutes
              • +
              • Hopefully fixed the toy haul problem which made it possible to get a million toys from arcade machines.
              • +
              +

              Giacom updated:

              +
                +
              • You can now make newlines with your PDA notes.
              • +
              • You can now research and build the Light Replacer.
              • +
              • You can now store donuts in the donut box. The next donut you pull out will be the last one you put in.
              • +
              • APCs will auto-turn on if there is enough power in the grid, even if the powercell is below 30%. The APC needs to be charged for a long enough time before it starts turning equipment on, to avoid spazzing out. If you have any problems with it, such as equipment turning off and on repeatedly then please make an issue report with a screenshot of the APC.
              • +
              +
              + +
              +

              18 July 2012

              +

              Giacom updated:

              +
                +
              • Added the Light Replacer. This is a device that can auto replace lights that are broken, missing or burnt. Currently it is found in the Janitor's closet and Janitor Borgs can equip it. You can refill it with glass, or if you're a Cyborg, just recharge. It is emaggable and will replace lights with rigged lights. The light's explosion was nerfed to help balance it and it is very noticable when you are holding an emagged Light Replacer.
              • +
              • The Janitor's equipment locator, on their PDA, will now tell you the direction of the equipment.
              • +
              +
              + +
              +

              17 July 2012

              +

              Icarus updated:

              +
                +
              • Added department satchels
              • +
              • Added Captain's Backpack and Satchel
              • +
              • Added three new hairstyles by Sly: Gelled, Flat Top, and Pigtails. Hair list has also been sorted by grouping similar styles.
              • +
              +

              Giacom updated:

              +
                +
              • Added a new wire for Cyborgs. See if you can figure out what it does.
              • +
              • You can now fill any container with a sink. You can change the amount to fill, from sinks, by setting your container's transfer amount.
              • +
              +
              + +
              +

              14 July 2012

              +

              Carn updated:

              +
                +
              • All living mobs can now ghost whenever they want. Essentially making the suicide verb obsolete. If you ghost whilst still alive however, you may not re-enter your body for the rest of the round.
              • +
              • Humans can no longer suicide whilst restrained (this is purely to prevent meta whilst I finish up the new FUN suicides)
              • +
              • Fixed dem evidence bags. Fixed metroids getting at it like rabbits. Fixed stuff like welding masks not hiding your face. Bunch of other things
              • +
              +

              Willox and Messycakes updated:

              +
                +
              • pAI Emoticons! Allows each pAI to set their screen to display an array of faces! Click on 'Screen Display' in the pAI OS for a list.
              • +
              +
              + + +
              +

              Saturday July 14th 2012

              +

              Giacom updated:

              +
                +
              • Added Russian Revolvers. This is a special Revolver that can only hold a single bullet randomly in it's chamber. This will allow you to play Russian Roulette with your fellow crew members! You can use it like a normal gun but you will need to cycle through the chamber slots until you hit the bullet. Only admin spawnable.
              • +
              +
              + +
              +

              Friday July 13th 2012

              +

              Carn updated:

              +
                +
              • Added FLOORLENGTH HAIR. YEESSSSSSSS!!!! :3 If you like it say thanks to Ausops for fixing it up. Credits to Powerfulstation for the original sprite.
              • +
              +

              Giacom updated:

              +
                +
              • Save Slots! You can now have separate save slots for different character setups, with a customizable maximum of 3 slots per account. If you are wondering, you will not lose your old saved setup.
              • +
              • The character setup screen was updated to look nicer and to fit on the screen.
              • +
              +

              Icarus updated:

              +
                +
              • Added new Dwarf and Very Long hairstyles. Dwarf hair and beard by SuperCrayon.
              • +
              +
              + +
              +

              Thursday July 12th 2012

              +

              Giacom updated:

              +
                +
              • pAI gets a better PDA that can actually receive messages from people. They can also instantly reply like everybody else now and they can toggle their receiver/signaller/ringer.
              • +
              • You can show the AI the notes on your PDA by holding it up to a camera. When you show up a paper/pda to the camera the AI can now click on your name to go to you, if you're near a camera. People who are Unknown will not have a link; which would've allowed the AI to track them.
              • +
              • Made the" common server" and the "preset right receiver" listen for frequencies 144.1 to 148.9. This will allow people to use different frequencies to talk to eachother without bothering the common channel. It will also allow Revs and Cultists to work with each other; everything is still logged though so it still has risks.
              • +
              • Increased the maximum frequency limit for handheld radios and intercoms. It will give you the option to just use station bounced radios on a higher frequency so that anyone with a headset can't simply tune in.
              • +
              • Created an All-In-One Grinder that is suppose to replace the blender, juicer and reagent grinder all together. Meaning any department that has a juicer, blender and grinder will instead get this. It will help people be more independent from Chemistry by recycling foods and plants.
              • +
              +
              + +
              +

              Wednesday July 11th 2012

              +

              Nodrak, Cheridan and Icarus updated:

              +
                +
              • Added a couple of Emergency Shield Projectors to Engineering secure storage. +

                Note: Credit goes to Barhardar for the original code and functionality. +

                These devices can be used to quickly create an air-tight seal across a hull breach until repairs can been made. +

                Wrench them in place and activate them near a hull breach. The shield should extend to all space tiles in range. +

                They can be (un)locked by engineering IDs and can also be emagged and otherwise malfunction. As they can not be constructed, you can repair damage and malfunctions by opening the panel with a screwdriver and replacing the wires with a cable coil

              • +
              +

              Giacom updated:

              +
                +
              • Chemistry update: Pills can now be ground up in reagent grinders. You can now put custom amounts of reagent into things using chemmasters. Can now load pill-bottles into chemmasters for mass pill-production.
              • +
              +

              Carn updated:

              +
                +
              • Clicks on inventory slots with items in now act like a click on the thing in that slot. So clicking smaller things (like pens) is easier and you can remove clothing that borks/goes invisible. Please continure to report those kinds of bug though please. Thanks x
              • +
              • Can no longer interact with your inventory with a mech.
              • +
              +

              Errorage updated:

              +
                +
              • You can now only adminhelp once every 2 minutes so please provide all necesary information in one adminhelp instead of 5! Also reply to admins in PM-s and not additional adminhelps.
              • +
              +
              + +
              +

              Saturday July 7th, 2012

              +

              Icarus updated:

              +
                +
              • A basketball simulation is now available at the holodeck. Credit to Sly and Ausops for the sprites.
              • +
              +
              + +
              +

              Friday July 6th, 2012

              +

              Giacom updated:

              +
                +
              • Bottles can now be broken over people's heads! To do this, you must have the harm intent on and you must be targeting the person's head. This change affects alcoholic bottles only. It does not change pill bottles or chemistry bottles. Helmets help protect you from damage and the regents of the bottles will splash over the victim.
              • +
              • AI's now have access to a PDA. Note: It is not PDA-bomb-able
              • +
              • Health analyzers and medical PDAs now give a time of death when used on corpses.
              • +
              +
              + +
              +

              Thursday July 5th, 2012

              +

              Carn updated:

              +
                +
              • Alien larva now chestburst even after their host has died.
              • +
              • Aliens can now slap facehuggers onto faces so they can infect mobs which lay down (or those stuck to nests).
              • +
              • Aliens can now slash security cameras to deactivate them.
              • +
              +
              + +
              +

              Wednesday July 4th, 2012

              +

              39kk9t & Carn updated:

              +
                +
              • Added alien nests. They're basically beds made of thick sticky resin which aliums can 'stick' (buckle) people to for sexytimes
              • +
              • Weed nodes are no longer dense.
              • +
              • Queens can secrete resin for walls/nests/membranes
              • +
              • Various bugfixes
              • +
              +
              + +
              +

              Saturday June 30th, 2012

              +

              Icarus updated:

              +
                +
              • Added Petethegoat's basic mirrors to the map. They allow you to change hairstyles.
              • +
              • Remapped Bar, Theatre, and Hydroponics. Bar and Kitchen are now more integrated with each other.
              • +
              +
              + +
              +

              Thursday, June 28th

              +

              Nodrak updated:

              +
                +
              • I'm currently working on cleaning up and moving around a large portion of the mob code. These changes do not directly affect players; HOWEVER, bugs, oversights or simple mistakes may cause problems for players. While I have tested as much as I can, there may be some lingering bugs I have missed.

                This part of the mob code cleanup mainly focuses on damage variables and procs. So if you suspect something related to taking, dealing or examining damage is not working as intended please fill out an issue report here and be as detailed as possible. This includes what you were doing, steps to reproduce the problem, who you were doing it to, what you were using ect... Thank you.

              • +
              +

              Carn updated:

              +
                +
              • Alien hunters will now cloak when using the 'stalk' movement intent. Whilst cloaked they will use up their plasma reserves. They can however cloak as long as they like. Using the Lay-down verb will allow them to remain cloaked without depleting their plasma reserves, hence allowing them to lay ambushes for unsuspecting prey. Should a hunter attack anybody, or get knocked down in any way, they will become visible. Hopefully this allows for more strategic/stealthy gameplay from aliens. On the other hand, I may have totally screwed the balance so feedback/other-ideas are welcome.
              • +
              • Removed the invisibility verb from the alien hunter caste.
              • +
              +
              + +
              +

              Wednesday, June 27th

              +

              Errorage updated:

              +
                +
              • Fixed the bug which prevented you from editing book's titles and authors with a pen. Also fixed the bug which prevented you from ordering a book by it's SS13ID.
              • +
              • Added the F12 hotkey which hides most of the UI. Currently only works for humans.
              • +
              +

              Donkie updated:

              +
                +
              • Pizza boxes! Fully stackable, tagable (with pen), and everythingelse-able. Fantastic icons by supercrayon!
                Created with a sheet of cardboard.
              • +
              +
              + +
              +

              Tuesday, June 26th

              +

              Errorage updated:

              +
                +
              • Changeling parasting now only weakens for 10 game ticks. It no longer silences your target.
              • +
              +
              + +
              +

              Saturday, June 23rd

              +

              Donkie updated:

              +
                +
              • Reworked job randomizing system. Should be more fair.
              • +
              • List of players are now randomized before given antag, this means that declaring as fast as possible doesn't mean shit anymore!
              • +
              +

              Carn updated:

              +
                +
              • Putting a blindfold on a human with lightly damaged eyes will speed up the healing process. Similar with earmuffs.
              • +
              • More overlay bug fixes. Most of it to do with little robots and construction stuff. Bugs go here if you have 2 minutes http://nanotrasen.com/phpBB3/viewtopic.php?f=15&t=9077
              • +
              • Weakening is instant! That means if you stunbaton somebody they're gonna fall-down immediately.
              • +
              +

              Icarus updated:

              +
                +
              • Medical storage now requires Surgery access (Medical Doctors only)
              • +
              +
              + +
              +

              Wednesday, June 20th

              +

              Nodrak updated:

              +
                +
              • AIs, Borgs are no longer able to be cultists or revolutionaries as their objectives completely contradict their laws. They can still be subverted of course.
              • +
              • pAI's no longer keep cult or rev icons.
              • +

              Cheridan updated:

              +
                +
              • -Both Ambrosia forms have had their reagent contents modified to prevent going over the 50-unit cap at high potencies. Ambrosia Deus now contains space drugs instead of poison.
              • +
              • -Drinking milk removes capsaicin from your body. REALISM!
              • +
              • -Frost oil hurts less upon consumption.
              • +
              • -Liquid plasma can be converted into solid plasma sheets, by mixing 20 plasma, 5 iron, and 5 frost oil.
              • +
              • -Added effects for holy water injection on hydroponics plants.
              • +
              +
              + +
              +

              Monday, June 18th

              +

              Giacom updated:

              +
                +
              • Fix for special characters on paper and from announcements
              • +
              +

              Sieve updated:

              +
                +
              • Various small bugfixes, check the commit log for full details
              • +
              • Fixed falsewalls not working
              • +
              • Made Lasertag ED-209's and turrets much more useful, including making their emag function more fitting
              • +
              • Added Mesons to the EngiVend to make up for how many lockers were removed
              • +
              • New Item: Sheet Snatcher. Right now only borgs have them, but they can hold up to 500 sheets of minerals (Of any combination), and auto-stacks them to boot. Used just like the mining satchels, meaning minerborgs can actually deliver metal
              • +
              • Mech drills can mine sand, and the diamond gets a much larger volume
              • +
              • If a borg has the satchel in its modules (Doesn't have to be the active one), it will auto-magically pick up any ores it walks over.
              • +
              • Bumping an asteroid wall with a pickaxe/drill in your hand makes you auto-magically start drilling the wall, making mining much less tedious (humans and borgs)(Also, gustavg's idea)
              • +
              +

              Icarus updated:

              +
                +
              • New afro hairstyles. Big Afro by Intigracy.
              • +
              +
              + +
              +

              Friday, June 15th

              +

              Carnwennan updated:

              +
                +
              • First update for update_icons stuffs:
                Fixed husking and fatties
                Fixed floor tiles still appearing in hand when laying them
                Fixed runtimes with fatties.
              • +
              • Fixes for pre-existing bugs:
                Fixed being unable to put belts & backpacks on other people
                nodamage (godmode) now prevents all organ damage. It does not stop healing however.
                Nerd stuff...
              • + +
              +

              Errorage updated:

              +
                +
              • Greatly reduced the amount of damage high pressure does.
              • +
              • Fire suits, firefighting helmets (red harhats) and the chief engineer's white hardhat now protect against high pressure.
              • +
              +

              Icarus updated:

              +
                +
              • Ported over ponytail sprites from Baystation
              • +
              +
              + +
              +

              Thursday, June 14th

              +

              Carn updated:

              +
                +
              • FEAR NOT! You can now store a pen in your pda. (aka Best commit all commits)
              • +
              +
              + +
              +

              Wednesday, June 13th

              +

              Carn updated:

              +
                +
              • Massive Mob-Icon Overhaul:
                A large amount of the mob code has been replaced. The systems replaced were causing immense performance issues so the following are very necessary optimisations.
                However, there is a downside: SS13 code is the equivilant of monkeys on typewriters. Despite weeks of constant coding/testing there -will- be things I've missed. The kinds of bugs I'm expecting are overlays not updating and/or in rare cases things not appearing in your hud. Most of these issues can be worked around simply by dropping the item and picking it back up. If all else fails ask an admin to regenerate your icons through view-vars.
                Please report any bugs to me on #coderbus IRC or make an issue on the tracker as a matter of urgency. I will fix them ASAP.
                Also a massive thankyou to Nodrak, Erro, Pete and Willox. :)
              • +
              • Massive rewrite of the overlays system (particularly for humans). Stuff is cached and only updates when necessary. In effect this means faster updates, less overheads/lag, and less reliance on the game-ticker.
              • +
              • Numerous bugfixes and tweaks for damage-procs and damage-overlays for humans. They should now be almost seamless, use very little overhead and update instantly.
              • +
              • TK grab can now be cancelled using the throw hotkey. (so now it toggles on/off like it used to).
              • +
              • Added verbs to the view-var drop-down list: "Regenerate Icons" will fix a mob's hud/overlays. "Set Mutantrace" will change a mob's mutantrace.
              • +
              • Damage icons were split up. They kinda look a bit crap so spriters feel free to replace them. Templates are provided in dam_human.dmi
              • +
              • More to come...
              +

              Cheridan updated:

              +
                +
              • -Added Lezowski's overalls to hydroponic supply closets. 50% chance per closet for them to replace the apron. -Removed some covers tags from things that made no sense to have them.
              • +
              +
              + +
              +

              Monday, June 11th

              +

              Donkie updated:

              +
                +
              • Fixed being able to lock yourself in or out of a locker using the verb.
              • +
              +

              Xerux updated:

              +
                +
              • Added lightfixture creating.
              • +
              +

              Errorage updated:

              +
                +
              • You can now use the resist verb or UI button when welded or locked in a closet. Takes 2 minutes to get out tho (Same as getting out of handcuffs or unbuckling yourself)
              • +
              • Making single-pane windows will now make the window in the direction you're facing. If a window already exists in that direction it will make it 90 degrees to your left and so on.
              • +
              +
              + +
              +

              Sunday, June 10th

              +

              Agouri updated:

              +
                +
              • Cyborgs and AIs can now use the newscaster. It was a mistake on my part, forgetting to finish that part of them.
              • +
              +
              + +
              +

              Saturday, June 9th

              +

              Errorage updated:

              +
                +
              • You can now make restraints from cable. It takes 15 lengths of cable to make a pair of restraints, they are applied the same way as handcuffs and have the same effects. It however only takes 30s to remove them by using the resist verb or button. You can also remove them from someone by using wirecutters on the handcuffed person.
              • +
              • Added four new cable colors: pink, orange, cyan and white. Engineer belts spawn with yellow, red or orange cables while toolboxes and tool closets spawn with all 8 colors.
              • +
              +
              + +
              +

              Thursday, June 7st

              +

              Icarus updated:

              +
                +
              • Added a second ZIS suit to engineering.
              • +
              • Remapped CE office and surrounding areas.
              • +
              +
              + +
              +

              Wednesday, June 6th

              +

              Sieve updated:

              +
                +
              • Radiation now works properly, watch out for that Singularity!
              • +
              • Disposals are no longer the loudest machines in existence.
              • +
              • Building portable turrets with lasertag guns now makes them fire lasertag bolts based on team, and they will automatically target and prioritize people wearing opposing team gear.
              • +
              • The same can be done for ED-209s, simply using a lasertag vest and gun (same color) where you would use a security vest and taser in construction.
              • +
              • Added mineral walls and powered mineral door construction. More information can be found in the commit thread, but basically they are built the same way others are, apply mineral to girder for a mineral wall, mineral to airlock assembly for a powered mineral door.
              • +
              • Commit Thread
              • +
              • Throw hotkey (end) now works with TK.
              • +
              • Swap hands hotkey (page up) now cycles through borg modules.
              • +
              +

              Nodrak updated:

              +
                +
              • Cargo's 'shuttle: station' and 'shuttle: dock' has been changed to 'shuttle: station' and 'shuttle: away' to help avoid confusion.
              • +
              +

              Icarus updated:

              +
                +
              • Maintenance shafts changed around. Renamed, less windows, more turns, and expanded in a few areas.
              • +
              +

              Neek updated:

              +
                +
              • You can now add chemicals into cigarettes by injecting them directly or dipping individual cigarettes into a beaker. You can inject into cigarette packs directly to affect multiple cigarettes at once.
              • +
              +

              Willox updated:

              +
                +
              • You can now click individual blocks/subblocks in the genetics console instead of having to scroll through the blocks with a forward/back button!
              • +
              +
              + +
              +

              Sunday, June 3rd

              +

              Donkie updated:

              +
                +
              • You can now Drag-Drop disposal pipes and machinery into the dispenser, in order to remove them.
              • +
              • You must now use wrench before welding a pipe to the ground
              • +
              • You can no longer remove a trunk untill the machinery ontop is unwelded and unwrenched
              • +
              • You are now forced to eject the disposal bin before unwelding it.
              • +
              +
              + +
              +

              Friday, June 1st

              +

              SkyMarshal updated:

              +
                +
              • Readded fingerprints and detective work, after lots of debugging and optimization.
              • +
              • Any PDA with access to the Security Records can now, by the normal forensic scanner function, store data the same way as the detective's scanner. Scanning the PDA in the detective's computer will copy all scanned data into the database.
              • +
              • If something goes wrong, please contact SkyMarshal on the #bs12 channel on irc.sorcery.net
              • +
              +
              + +
              +

              Friday, June 1st

              +

              Nodrak updated:

              +
                +
              • Windoor's are now constructable! Steps are found here.
              • +
              +
              + +
              +

              Tuesday, May 29th

              +

              Nodrak updated:

              +
                +
              • Glass Doors are now breakable.
              • +
              • Added more treasures to the secret mining room.
              • +
              • Changed mineral lockers from secret mining rooms: Instead of giving you two stacks of everything, you get stacks of ore based on rarity
              • +
              +

              Icarus updated:

              +
                +
              • Moved Engineering and Bridge deliveries.
              • +
              +
              + +
              +

              This 28th day of May, in the year of our Lord, Two Thousand Twelve

              +

              Cheridan updated:

              +
                +
              • -Adjusted balaclavas and added luchador masks. Wearing luchador masks give you latin charisma. They replace the boxing gloves in the fitness room. Boxing gloves are still available in the holodeck. -Fake moustache tweaked and given new sprites.
              • +
              +
              + +
              +

              Monday, May 28th

              +

              Donkie updated:

              +
                +
              • You can now dispense Disposal Bins, Outlets and Chutes from the disposal dispenser. These are movable and you can attach them above open trunks with a wrench, then weld them to attach them completely. You can remove Bins by turning off their pump, then screwdriver, then weld, then wrench. Same with outlet and chute except for the pump part.
              • +
              +
              + +
              +

              Saturday, May 26th

              +

              Icarus updated:

              +
                +
              • Ported over Flashkirby99's RIG suit sprites from Bay12
              • +
              • Department PDA Carts moved out of lockers and into head offices.
              • +
              +
              + +
              +

              Wednesday, May 23rd

              +

              Cheridan updated:

              +
                +
              • -Reverted default UI sprites to Erro's old-style UI. Config options for UI color styles coming soon. -Driest Martinis will no longer be invisible. -Braincakes are now sliceable.
              • +
              • -Medical borg overhaul. Instead of a dozen random pills and syringes, they get a hypospray that can switch between auto-replenishing tricordrazine, inprovaline, and spaceacillin.
              • +
              +

              Errorage updated:

              +
                +
              • Some of the more pressing issues with the new user interface were addressed. These include the health indicator being too far up, the open inventory taking a lot of space, hotkey buttons not being removable and suit storage not being accessible enough.
              • +
              • A toggle-hotkey-buttons verb was added to the OOC tab, which hides the pull, drop and throw buttons for people who prefer to use hotkeys and never use the buttons.
              • +
              • Added a character setup option which allows you to pick between the Midnight, Orange and Old iconsets for the user interface.
              • +
              +
              + +
              +

              Tuesday, May 22nd

              +

              Icarus updated:

              +
                +
              • RIG helmets can now be used as flashlights, just like hardhats. Credit to Sly for the sprites.
              • +
              • HoP's office has been remapped and made into his private office. Conference Room can now be accessed from the main hall.
              • +
              +
              + +
              +

              Sunday, May 20th

              +

              Errorage updated:

              +
                +
              • The new user interface is here. If anything is broken or something should be done differently please post feedback on the forum. Spriters are encouraged to make new sprites for the UI.
              • +
              • When you receive a PDA message, the content is displayed to you if the PDA is located somewhere on your person (so not in your backpack). You will also get a reply button there. This will hopefully make PDA communication easier.
              • +
              • New hotkeys! delete is the now the 'stop dragging' hotkey, insert is the 'cycle intents' hotkey.
              • +
              +
              + +
              +

              Saturday, May 19th

              +

              Doohl updated:

              +
                +
              • You can now swap hands by clicking with your middle mouse button (you have to click on a visible object though, that's the catch).
              • +
              • Tweaked the DNA modifier consoles a little bit so that it's much easier to see individual blocks instead of one jumbled mess of hexadecimal.
              • +
              • You can now properly emag AI turret controls and commsat turret controls.
              • +
              +

              Invisty updated:

              +
                +
              • Brand new ending animations!
              • +
              +
              + +
              +

              Friday, May 18th

              +

              Errorage updated:

              +
                +
              • Removed hat storage, which was useless.
              • +
              • Implanting someone now takes 5 seconds, both people need to remain still. Implanting yourself remains instant.
              • +
              • Wallets once again spawn in the cabinets in the dormitory
              • +
              • Wallets now fit in pockets
              • +
              +
              + +
              +

              Thursday, May 17th

              +

              Icarus updated:

              +
                +
              • Individual dorms now have a button inside that bolts/unbolts the door
              • +
              • New sprites for Cargo, HoP, and Captain's lockers
              • +
              • More department-specific door sprites. Most noticable changes in medsci and supply departments.
              • +
              +
              + +
              +

              Tuesday, May 15th

              +

              Icarus updated:

              +
                +
              • Added WJohnston's scrubs to Medical Doctor lockers. Comes in blue, green, and purple.
              • +
              • Added two new syndicate bundles
              • +
              • Reduced cost of thermals to 3 telecrystals (formerly 4)
              • +
              • Singularity Beacons are now spawned from a smaller, portable device.
              • +
              • CMO and QM jumpsuits made more unique.
              • +
              +
              + +
              +

              Monday, May 14th

              +

              Icarus updated:

              +
                +
              • Reinforced table parts are now made by using four metal rods on regular table parts. No plasteel involved.
              • +
              • Beakers, small and large can now be made/recycled in autolathes.
              • +
              +

              Nodrak updated:

              +
                +
              • Added a 'random item' button to traitor uplinks. You can potentially get ANY item that shows up on the traitor item list, provided you have enough crystals for it.
              • +
              +
              + +
              +

              Friday, May 11th

              +

              Icarus updated:

              +
                +
              • New design for security. This should be the last time it sees major changes for a while.
              • +
              • Added a new construction area. What could it be for?
              • +
              +

              Petethegoat updated:

              +
                +
              • Readded the RD's genetics access.
              • +
              • RD is still without chemistry access, but I'm going to review this decision in a week and see if R&D is useless due to lack of acid.
              • +
              • Added Flashkirby99's SMES sprites!
              • +
              +

              Invisty updated:

              +
                +
              • Sexy new warpspace (or whatever) tiles.
              • +
              +

              Important changes below!

              +
              + +
              +

              Thursday, May 10th

              +

              Sieve updated:

              +
                +
              • Reverted dismemberment, the recent gun changes, and Tarajans. Before you shit up the forums, read this:
              • +
              • Dismemberment was ported from Bay12, but only halfway, and there were several problems with it. I know many people really liked it, but as it stood it did not fit the playstyle here at all. This had to be removed, there is work on a more fitting system, but this had to be taken out first regardless, and the longer people beat around the bush the worse the situation got.
              • +
              • The gun change was made for no real reason and was pretty problematic, so reverting that should mean there are a lot less 'accidental suicides.'
              • +
              • Tarjans were reverted by request as well, and since keeping them working after removing dismemberment would be a stupid amount of work.
              • +
              +
              + +
              +

              Sunday, May 6th

              +

              Cheridan updated:

              +
                +
              • -New booze sprites for the drinks that were removed! Re-enabled the recipes for the removed drinks. Get cracking, bartenders. -You now need 10 sheets of metal instead of 2 to make a gas canister, people can't FILL ENTIRE ROOMS WITH THEM.
              • +
              • -Emergency Toolboxes now contain smaller, lighter fire extinguishers that actually fit inside them! +
              +
              + +
              +

              Saturday, May 5th

              +

              Petethegoat updated:

              +
                +
              • RD get the fuck out of chemistry and genetics
              • +
              • CHEMISTS, DON'T BE DICKS TO RESEARCH, GIVE THEM ACID YOU TIGHT FUCKS
              • +
              +

              Icarus updated:

              +
                +
              • Updates to Sec, including a stationary scrubber for the prison area.
              • +
              • Swapped around cryogenics and the patient rooms in medbay.
              • +
              +
              + +
              +

              Friday, May 4th

              +

              Cheridan updated:

              +
                +
              • -Added fat jumpsuit sprites for orange, pink, yellow, owl, security, and warden jumpsuits.
              • +
              • -Somatoray is hopefully more useful and less buggy when used on trays. -Botanists now have morgue access, because of their ability to clone via replica pods. Try not to get this removed like all your other access, okay hippies?
              • +
              +
              + +
              +

              Thursday, May 3rd

              +

              Ikarrus updated:

              +
                +
              • Updated genetics, medbay and security.
              • +
              +

              Petethegoat updated:

              + +
              + +
              +

              Tuesday, May 1st

              +

              PolymorphBlue updated:

              +
                +
              • Adds BS12 dismemberment!
              • +
              • Adds greater changeling for 30 points
              • +
              +
              + + +
              +

              Monday, April 20th

              +

              Erro updated:

              +
                +
              • Added a verb to the PDA which you can use to remove an ID in it. If your active hand is empy, it puts it there otherwise it puts it on the floor under you.
              • +
              +
              + +
              +

              Wednesday, April 27th

              +

              Cheridan updated:

              +
                +
              • -New sprites for lemons, oranges, and walking mushroom critters. -Added Invisty's new blob sprites.
              • +
              • -Added a new chemical: lipozine, a weight loss drug. Made with sodium chloride, ethanol, and radium.
              • +
              +
              + +
              +

              Wednesday, April 25th

              +

              Ikarrus & Flazeo updated:

              +
                +
              • New layout for Security, including a prison area instead of permacells.
              • +
              • New layout for the library, bar, and botany.
              • +
              • Medbay and R&D now have three-tile halls.
              • +
              +

              Scroll down for more commits! There's a bunch of new shit.

              +
              + +
              +

              Tuesday, April 24th

              +

              PolymorphBlue updated:

              +
                +
              • Fakedeath changelings can no longer have their brains cut out.
              • +
              • Rev checkwin changed to fire every five ticks (from twenty) and actually use the right objective type so revs being off station counts as success.
              • +
              +

              Sieve updated:

              +
                +
              • Powercells now have unique icons for cell types
              • +
              • Implemented mech construction sprite by WJohnston for the Ripley, Firefighter, Gygax, and Durand
              • +
              • Durand construction is reversible
              • +
              • Power Cells can now be made in Mechfabs, provided the proper research level has been achieved
              • +
              • Added a new item, the Synthetic Flash. Works just like a normal flash, except they can only withstand one use, but can be produced in the Mechfab(To replace the need for normal flashes)
              • +
              • Added a new type of gloves, ones that are cheap copies of the coveted Insulated Gloves, but be warned, quality control wasn't too thorough
              • +
              • Added a new Cyborg Upgrade, a jetpack for use by Miner Cyborgs. Can be refilled on any air canister
              • +
              • Miner Cyborgs now have a Diamond Drill equivalent along with an upgraded Ore Satchel
              • +
              • Mechfabs no longer brick if there are parts in the quene on sync
              • +
              • MMIs can be built in the Mechfabs again
              • +
              • Crabs are no longer immortal, and are now especially vulnerable to wirecutters
              • +
              • Bibles printed in the library now retain the religion's deity
              • +
              • Added Construction Sprites for the Ripley, Firefighter, Gygax, and Durand by WJohnston
              • +
              • Added Particle Accelerator sprites by Invisity
              • +
              • Added Power Cell, Synthetic Flash, Robot Upgrades, and made some modifications to the PA sprites
              • +
              +

              Petethegoat updated:

              +
                +
              • Added Invisty's field generator sprites.
              • +
              +
              + +
              +

              April 1-22, 2012

              +

              Cheridan updated:

              +
                +
              • CATCHING UP ON MY CHANGELOG. Some of this has been in for a while: -Added carved pumpkins and corncob pipes. -Added mutations for ambrosia and lemon trees. -Added more wood items for tower cap wood construction. -Added soil to plant seeds in. Make it by crushing up sandstone. Soil does not have indicators like trays do! Watch your plants carefully!
              • +
              • -The biogenerator is now more robust. It can dispense fertilizer in batches, and make simple leather items. -RnD can create a new tool for botanists: The floral somatoray. Has two modes. Use it on your plants to induce mutations or boost yield.
              • +
              • -Added plump helmet biscuits, mushroom soup, pumpkin pie and slices, chawanmushi, and beet soup recipes for the chef to make.
              • +
              • -Added transparency to biohelmets. -Normalized grass harvests. -Changed the name of "Generic Weeds". -Blenders can now be filled directly from plant bags. -Added low chance for a species mutation whenever a plant's stats mutate. -You now get more descriptive messages when applying mutagen to plant trays. -Removed sugarcane seeds from the vending machine. Added the sugarcane seeds to the seeds crate.
              • +
              +
              + +
              +

              Sunday, April 22nd

              +

              Petethegoat updated:

              +
                +
              • New gasmask sprites. Removed emergency gasmasks, so there's only one type now.
              • +
              • New shotgun sprites by Khodoque!
              • +
              • The barman's double-barrel actually works like a double-barrel instead of a pump-action! Rejoice!
              • +
              • Sneaky barmen may be able to illegally modify their shotgun, if they so choose.
              • +
              • Trimmed the changelog, vastly.
              • +
              +
              + +
              +

              Saturday, April 21st

              +

              Errorage updated:

              +
                +
              • Maintenance door outside of tech storage now requires maintenance OR tech storage access instead of maintenance AND robotics accesses.
              • +
              +
              + +
              +

              Thursday, April 19th

              +

              Carn updated:

              +
                +
              • Rewrote the cinematic system to try and simplify and optimise it. Please report any bugs asap to me or coderbus, thanks.
              • +
              +
              + +
              +

              Tuesday, April 17th

              +

              Kor updated:

              +
                +
              • Engineering jobs now have their PDA spawn in their pocket, and their toolbelt on their belt
              • +
              • The nuke going off on station will now gib everyone on the Z level.
              • +
              • Two more core displays are available for the AI
              • +
              • The Artificer can now build cult floors and walls
              • +
              +
              +

              Friday, April 13th

              +

              Sieve updated:

              +
                +
              • Updated the robotics layout.
              • +
              +

              Petethegoat updated:

              +
                +
              • Nerfed the librarian by removing the r-walls from his cubbyhole thing, fuck WGW readers hiding out in there.
              • +
              +
              +
              +

              Thursday, April 12th

              +

              Agouri updated:

              +
                +
              • Fixed the ability to move while lying down/resting.
              • +
              • Sleep has been fixed and works as intended again. Anaesthetic and toxins can now properly put people to sleep, permanently if you keep the administration stable. Sleeplocs are now viable again. The sleep button and *faint emote work again.
              • +
              +
              +
              +

              Wednesday, April 11th

              +

              PolymorphBlue updated:

              +
                +
              • Droppers are now used at the eyes, and thus, access to the eyes is required to have an effect.
              • +
              +
              + + +
              +

              April 10, the year of our lord 2012

              +

              Agouri updated:

              +
                +
              • CONTRABAND-CON UPDATE: Added posters. I'm sorry to add it seperately with the rest of contraband, but there was a lack of sprites for everything else. Hopefully, people will gain interest and get me some damn sprites this way :3 + As I said, this is an ongoing project of mine. So starting of, we've got...
              • +
              • POSTERS! Posters come in rolled packages that can adhere to any wall or r_wall, if it's uncluttered enough. +

                How they get on-board: The quartermaster can now set the receiver frequency of his supplycomp circuit board. A bit simplistic as of now, will work on it later. Building a supplycomp with a properly set up circuitboard will give access to the Contraband crate. +

                How they're used: Unfold the rolled poster on any wall or r_wall to create the poster. There are currently 17 designs, with the possibility of me adding more. +

                How to get rid of them: You can rip them using your hand... To cleanly extract them and not ruin them for future use, however, you can use a pair of wirecutters. +

                How they're classified: They're contraband, so it's perfectly okay for security officers to confiscate them. Punishment for contraband-providers (or end-users, if you want to go full nazi) is up to the situational commanding officers. + +
              +

              Nodrak updated:

              +
                +
              • Merged 'Game' and 'Lobby' tabs during pre-game into one tab
              • +
              • Added the little red x to the late-join job list
              • +
              • Late-joiners are warned if the shuttle is past the point of recall, and if the shuttle has already left the station
              • +
              • Late-joiners now see how long the round has been going on.
              • +
              • Mining shuttle computer no longer spits out both 'Shuttle has been sent' and 'The shuttle is already moving' every time.
              • +
              + +
              + +
              +

              Monday, April 9th

              +

              Petethegoat updated:

              +
                +
              • TORE OUT DETECTIVE WORK! THIS IS A TEMPORARY PATCH TO SEE IF THIS FIXES THE CRASHING.
              • +
              • DETECTIVE SCANNERS AND EVIDENCE BAGS (AND FINGERPRINTS) ARE GONE.
              • +
              +
              + +
              +

              Sunday, April 8th

              +

              PolymorphBlue updated:

              +
                +
              • Secret little rooms now spawn on the mining asteroid, containing various artifacts.
              • +
              • Added the beginnings of a borg upgrade system. Currently, can be used to reset a borg's module.
              • +
              +
              + +
              +

              2 April 2012

              +

              PolymorphBlue updated:

              +
                +
              • ERP is gone. Hope you enjoyed it while it lasted!
              • +
              +
              + +
              +

              April Fools Day! Get hype!

              +

              Doohl updated:

              +
                +
              • Security officers can modify people's criminal status by simply examining them with a security hud on and clicking a link that will show up as part of the character's description.
              • +
              • Less jobs have maintenance access. The only jobs that will have it now are engineers, atmos techs, cargo techs, heads, and the detective.
              • +
              • Changed Runtime's sprite to look more catlike.
              • +
              • View Variables can now better list associative lists.
              • +
              • Miscellaneous bugfixes for the NT Script IDE.
              • +
              +

              PolymorphBlue updated:

              +
                +
              • Minor bugfixes to borg deathsquad, adds borg deathsquad to potential tensioner (set so high it's never going to happen)
              • +
              • Adds consiterable support for ERP! (If enabled.)
              • +
              • Increases cost for changeling unstun to 45
              • +
              +
              + +
              +

              30 March 2012

              +

              Donkie updated:

              +
                +
              • You can now stick papers back in to paperbins, text will persist.
              • +
              • Added a [field] bbcode tag to the pen writing. Lets your start writing from that point.
              • +
              • Changed fonts a bit for papers to make [sign] stand out more.
              • +
              +

              Doohl updated:

              +
                +
              • Gave pill bottles the ability to scoop up pills like ore satchels scoop ore. (There you go, /vg/ Anon.)
              • +
              • Security Officers and Wardens now start with maintenance acceess.
              • +
              +
              + +
              +

              29 March 2012

              +

              PolymorphBlue updated:

              +
                +
              • Exosuits now provide a message when someone is getting in, someone getting in must remain stationary and unstunned, and getting in takes four seconds.
              • +
              +
              + +
              +

              28 March 2012

              +

              Carn updated:

              +
                +
              • Fixed turrets shooting people that leave the area and the telecomm turret controls.
              • +
              +

              Donkie updated:

              +
                +
              • Updated air alarm's GUI.
              • +
              +
              + +
              +

              27 March 2012

              +

              Nodrak updated:

              +
                +
              • Security borgs now have modified tasers.
              • +
              • Security borgs have gone back to having the same movement speed as all other borgs.
              • +
              +
              + +
              +

              23 March 2012

              +

              Doohl updated:

              +
                +
              • Escape shuttles/pods now spend about 2 minutes in high-speed transit before they reach centcom/recon shuttle. This is a warning: regular after-round shuttle grief is NOT OKAY while the shuttle is still in transit! Save it for when the shuttle gets to centcom! The purpose of this is to give potential antagonists and protagonists a chance to have a final showdown in the shuttle. The round does not end until the shutle comes to a stop and docks. Don't step outside while the shuttle is moving!

                For example; if you are a traitor and have an escape-alone objective and a couple of people manage to squeeze in the shuttle, you have two minutes to kill/toss them out to win. Or you can just chill for the duration and reflect on the round.
              • +
              + +

              Donkieyo updated:

              +
                +
              • A bunch new standard-namespace NTSL functions added! Check them out at the NT Script wiki page!
              • +
              +
              + +
              +

              22 March 2012

              +

              Ricotez updated:

              +
                +
              • Medical Lockers, Security Lockers, Research Lockers, Warden Locker, CMO Locker, and RD locker all have new sprites.
              • +
              • Encryption keys now each have their own invidual sprites.
              • +
              + +

              PolymorphBlue updated:

              +
                +
              • Added a prototype holodeck to fitness!
              • +
              • Assorted tensioner fixes
              • +
              + +
              +

              20 March 2012

              +

              Kor updated:

              +
                +
              • Lasertag vests and guns have been added to fitness.
              • +
              • Art storage has replaced the emergency storage near arrivals. Emergency storage has replaced chem storage (has anyone ever used that?)
              • +
              • Wraiths can now see in the dark
              • +
              +
              + + +
              +

              19 March 2012

              +

              PolymorphBlue updated:

              +
                +
              • Added LSD sting to modular changeling by popular demand.
              • +
              • Silence sting no longer provides a message to the victim.
              • +
              • Tensioner will no longer assign dead people as assassination targets.
              • +
              +
              + +
              +

              18 March 2012

              +

              Quarxink updated:

              +
                +
              • The medical record computers can finally search for DNA and not just name and ID.
              • +
              +
              + +
              +

              14 March 2012

              +

              PolymorphBlue updated:

              +
                +
              • Modular changeling added! Changelings now purchase the powers they want. Balancing still underway, but should be playable.
              • +
              +

              Petethegoat updated:

              +
                +
              • Janitor cyborgs have been massively upgraded. Suffice to say they're pretty ballin' now...
              • +
              +

              Nodrak updated:

              +
                +
              • You can now choose whether to spawn with a backpack, satchel, or nothing. Excess items will spawn in your hands if necessary.
              • +
              • You can now choose what kind of underwear you'd like to wear, per a request.
              • +
              +
              + +
              +

              14 March 2012

              +

              Carn updated:

              +
                +
              • Added 6 female hairstyles -- credits go to Erthilo of Baystation. Added a male hairstyle -- credits go to WJohnston of TG. If you can sprite some unique and decent-looking hair sprites, feel free to PM me on the TG forums.
              • +
              +
                +
              • The way objects appear to be splattered with blood has been rewritten in an effort to fix stupid things happening with icons. It should be called far less frequently now. PLEASE, if you experience crashes that could in anyway be related to blood, report them with DETAILED information. Thanks
              • +
              +
              + +
              +

              13 March 2012

              +

              Nodrak & Carn updated:

              +
                +
              • Fixed the way flashes break. Long story short: They'll never break on first use so rev don't get screwed over. They run out of charge temporarily when spammed but recharge. Spamming them also increases the chance of them breaking a little, so use them sparingly.
              • +
              +

              Doohl updated:

              +
                +
              • Ablative Armor now has a high chance of reflecting energy-based projectiles.
              • +
              • Riot shields were buffed; they now block more attacks and they will prevent their wielder from being pushed (most of the time).
              • +
              +
              + +
              +

              12 March 2012

              +

              PolymorphBlue updated:

              +
                +
              • PDA messages now require an active messaging server to be properly sent.
              • +
              +
              + +
              +

              11 March 2012

              +

              PolymorphBlue updated:

              +
                +
              • The AI can now open doors with shift+click, bolt them with ctrl+click, and shock them with alt+click
              • +
              • Tratior borgs who hack themselves cannot be blown by the robotics console, and can override lockdowns.
              • +
              • Adds a new wire to doors that controls the time delay before they close. If pulsed, they close like a sliding glass door. If cut, they do not close by themselves.
              • +
              • Borgs who have died, ghosts, and are then blown up will now have their ghosts properly transfered to their dropped MMIs.
              • +
              +

              Carnwennan updated:

              +
                +
              • You can now request AI presence at a holopad for immediate private communication with the AI anywhere. AIs can click a quick button to zoom to the holopad.
              • +
              +
              + +
              +

              08 March 2012

              +

              Nodrak and Carnwennan updated:

              +
                +
              • Nodrak: Fixed crayon boxes and stuff getting stuck in pockets.
              • +
              • Nodrak: 'Steal item' objectives will report correctly when wrapped up in paper now.
              • +
              • Carn: fixed the vent in the freezer...poor chef kept suffocating.
              • +
              +
              + +
              +

              02 March 2012

              +

              Carn updated:

              +
                +
              • Fixed a number of issues with mob examining. Including: not being able to see burns unless they were bruised; vast amounts of grammar; and icons. Updated them to use stylesheet classes.
              • +
              • Borgs can no-longer drop their module items on conveyor belts.
              • +
              • Names input into the setup screen are now lower-cased and then have their first letters capitalised. This is to fix problems with BYOND's text-parsing system.
              • +
              • Runtime fix for lighting.
              • +
              • Over the next few commits I will be updating a tonne of item names to fix text-parsing. Please inform me if I've typoed anything.
              • +
              +
              + +
              +

              03 March 2012

              +

              Petethegoat updated:

              +
                +
              • Removed cloakers. Removed Security's thermals. Added disguised thermals as a traitor item.
              • +
              +
              + +
              +

              01 March 2012

              +

              SkyMarshal updated:

              +
                +
              • Tweak/Bugfix for Hallucinations. Much more robust.
              • +
              +
              + +
              +

              01 March 2012

              +

              SkyMarshal updated:

              +
                +
              • Ported BS12 Detective Work System
              • +
              +
              + +
              +

              1 March 2012

              +

              Petethegoat updated:

              +
                +
              • Head revolutionaries no longer spawn with traitor uplinks.
              • +
                  +
                +
              + +
              +

              29 February 2012

              +

              SkyMarshal updated:

              +
                +
              • BS12 Hallucination and Dreaming port
              • +
                  +
                +
              + + +
              +

              29 February 2012

              +

              muskets updated:

              +
                +
              • Integrated BS12's improved uplink code
              • +
              +
              + + +
              +

              26 February 2012

              +

              Doohl updated:

              +
                +
              • The insane crashing has finally been fixed!
              • +
              +
              + +
              +

              25 February 2012

              +

              Doohl updated:

              +
                +
              • Telecommunications has been refined, with many new features and modules implemented.
              • +
              • NTSL (Nanotrasen Scripting Language) is ONLINE! This is a brand new, fully operational scripting language embedded within SS13 itself. The intended purpose is to eventually expand this scripting language to Robotics and possibly other jobs, but for now you may play with the TCS (Traffic Control Systems) implementation of NTSL in the Telecommunications Satellite. Recommended you read the NT Script wiki page for information on how to use the language itself. Other than that, there's not a lot of documentation.
              • +
              • Radio systems have been further optimized, bugfixed, etc. Should be more stable.
              • +
              • Intercoms now require power to work.
              • +
              +
              + + +
              +

              24 February 2012

              +

              PolymorphBlue updated:

              +
                +
              • Headsets are now modular! Use a screwdriver on them to pop out their encrpytion keys, and use a key on one to put it in. A headset can hold two keys. Normal headsets start with 1 key, department headsets with two. The standard chip does nothing, and is not required for listening to the common radio.
              • +
              • Binary translators made into a encrpytion key, and fixed. They now broadcast to AIs properly.
              • +
              +
              + +
              +

              23 February 2012

              +

              PolymorphBlue updated:

              +
                +
              • MMIs/pAIs no longer lip read, and thus can now hear in the dark.
              • +
              • Borg rechargers are no longer Faraday cages, and thus can now receive radio while they're recharging.
              • +
              +

              LastyScratch updated:

              +
                +
              • Glass airlocks now make a different sound than regular airlocks.
              • +
              • in 1997 nanotrasen's first AI malfunctioned
              • +
              • Toggle ambience probably works now! +
              • Runtime is dead. +
              • The Research Director's consoles were moved into the completely empty cage in the back of his office.
              • +
              +
              + + +
              +

              22 February 2012

              +

              PolymorphBlue updated:

              +
                +
              • Changed alt+click to ctrl+click for pulling.
              • +
              +

              Petethegoat updated:

              +
                +
              • New stationary scrubber sprites~
              • +
              • Removed the mint. Coins can still be found and used in vending machines. PACMANs now run off sheets.
              • +
              +

              coolity updated:

              +
                +
              • New sprites for HoS and Captain lockers.
              • +
              • New sprites for the orebox.
              • +
              +
              + +
              +

              21 February 2012

              +

              Petethegoat updated:

              +
                +
              • The jetpacks now display correctly when worn.
              • +
              • Buckling to chairs no longer causes you to drop your weapon
              • +
              +

              Nodrak updated:

              +
                +
              • Ghosts now have a "Jump to Mob" verb.
              • +
              +

              Sieve updated:

              +
                +
              • Mining lanterns work properly once again!.
              • +

                Skaer updated:

                +
                  +
                • The armoury now includes a box of spare Sec cartridges.
                • +
                +
              + +
              +

              19 February 2012

              +

              Petethegoat updated:

              +
                +
              • The jetpacks in EVA have been replaced with CO2 ones, painted a classy black.
              • +
              • Additionally, jetpacks will now run on gases other than oxygen, as you would expect.
              • +
              • Chair overhaul! You shouldn't notice anything different, but if you encounter bugs with chairs or beds, please report those asap.
              • +
              • New electric chair sprites, by myself.
              • +
              • Electric chairs will only electrocute people buckled into them.
              • +
              • Karma should be fixed.
              • +
              +

              KorPhaeron updated:

              +
                +
              • A new construct type: Artificer. It is capable of constructing defenses, repairing fellow constructs, and summoning raw materials to construct further constructs
              • +
              • Simple animals (constructs, Ian, etc) can now see their health in the Status tab
              • +
              • Detective's revolver is non-lethal again. Was fun while it lasted
              • +
              +
              + +
              +

              18 February 2012

              +

              Petethegoat updated:

              +
                +
              • Foam has a reduced range to prevent spamming
              • +
              +

              Sieve updated:

              +
                +
              • Stopped the unholy Radium/Uranium/Carbon smoke that crashed the server. And for anyone that did this, you are a horrible person
              • +
              • Cleanbots clean dirt
              • +
              • Cleanbots automatically patrol on construction
              • +
              • Removed silicate because it is not useful enough for how much lag it caused
              • +
              +
              + +
              +

              16 February 2012

              +

              Smoke Carter updated:

              +
                +
              • Newscasters now alert people of new feeds and wanted-alerts simultaneously.
              • +
              +
              + +
              +

              15 February 2012

              +

              Kor updated:

              +
                +
              • Terrorists Win! Desert Eagles and Riot Shields now spawn on the syndicate shuttle, replacing the c20r
              • +
              • The Detectives gun still uses .38, but they're now fully lethal bullets. Go ahead, make his day.
              • +
              • The Veil Render has been nerfed, the Nar-Sie it spawns will not pull anchored objects. This is a temporary measure, more nerfs/reworking to come
              • +
              +
              + +
              +

              14 February 2012

              +

              Carn updated:

              +
                +
              • Spacevines added to the random events.
              • +
              • The bug where doors kept opening when a borg tried to close them at close range is now fixed.
              • +
              +
              + +
              +

              13 February 2012

              +

              Khodoque updated:

              +
                +
              • Security officers, the warden and the HoS have new jumpsuits.
              • +
              +

              Erro updated:

              +
                +
              • Clicking the internals button on your user interface (The one that shows if you have internals on or not) will now toggle internals even if they are in your pockets. (humans only) - It now works if your internals are on your back, suit storage, belt, hands and pockets.
              • +
              • The public autolathe has been removed. If you want some some stuff from a lathe, go to cargo.
              • +
              +

              Kor updated:

              +
                +
              • A new item, the null rod, protects the one bearing it from cult magic. One starts in the chaplains office, and this replaces the job based immunity he had. The null rod also is capable of dispelling runes upon hitting them (the bible can no longer do this)
              • +
              • Shooting fuel tanks with lasers or bullets now causes them to explode
              • +
              • A construct shell is now waiting to be found in space.
              • +
              • Chaplains can no longer self heal with the bible
              • +
              • Simple animals, including constructs, can now attack mechs and critters
              • +
              + +
              + +
              +

              12 February 2012

              +

              Erro updated:

              +
                +
              • You can no longer attach photos to ID cards. This never worked properly and if anything, it was misleading.
              • +
              • Backpacks can now hold 7 normal sized items (box size) as opposed to 6 normal sized items + 1 small item
              • +
              • Added several fire alarms to areas around the station including the brig, engineering and others
              • +
              • The atmospherics department now has a few hazard vests available for atmos techs to wear if they don't like the fire suit
              • +
              • Roboticist now have engineering + science headsets, virologists now have medsci headsets with medical + science channels
              • +
              • Added some headsets to the jobs that didn't have any extras: roboticist, qm, scientist, virologist and geneticist.
              • +
              • Station engineers now have construction site access (vacent office by arrivals)
              • +
              • Replaced a few airlocks with glass airlocks (detective, autolathe, assistant storage, robotics, checkpoint)
              • +
              • Removed the wall that was blocking the entrance to the theater
              • +
              • Made a small redesign for the HoP's office so that people running towards it from the escape hallway don't run right into the queue, annoying everyong
              • +
              • The engineering, command and security airlocks now glow green when closing instead of red to match all the other airlocks
              • +
              • The disposal units now auto trigger every 30 game ticks, if there is something (or someone) in them. So no more hiding in disposal units!
              • +
              • You can no longer control the disposal unit from within it. You will have to wait for it to trigger itself.
              • +
              • You can no longer strip items off of Ian while dead / a ghost
              • +
              +

              Pete updated:

              +
                +
              • Updated fitness, athletic shorts are now available!
              • +
              +
              + + +
              +

              11 February 2012

              +

              Erro updated:

              +
                +
              • You can now take individual crayons out of the crayon box the same way as from boxes
              • +
              • Clicking a grille with a glass or reinforced glass pane in your hand will glaze the grille from the direction you're looking from (don't forget to fasten the window tho)
              • +
              • When you click somewhere with the intent to interact, you will automaticaly face the item you're trying to interact with. This won't slow you down when running and firing guns behind you.
              • +
              +

              Kor updated:

              +
                +
              • A new passive mob ability: Relentless. Relentless mobs cannot be shoved (though may still swap places with help intent)
              • +
              • Alien Queens, Juggernaut constructs, and Medical Borgs are all Relentless. Maybe the medborg can actually drag people to medbay on time now
              • +
              • Two constructs, the Juggernaut and the Wraith are now available for wizards and cultists to use soul stones with
              • +
              • A new highly destructive artefact, Veil Render, is now available for wizards
              • +
              • A new one time use global spell, Summon Guns, is now available for wizards.
              • +
              • DEEPSTRIKING! There is now a partially constructed teleporter on the nuke shuttle, and for a large sum of telecrystals they may purchase the circuitboard needed to complete it.
              • +
              • The Chaplain is immune to cult stun, blind, deafen, and blood boil
              • +
              +
              + +
              +

              10 February 2012

              +

              Quarxink updated:

              +
                +
              • Added a new toy: Water balloons. They can be filled with any reagent and when thrown apply the reagents to the tile and everything on it.
              • +
              +
              + +
              +

              9 February 2012

              +

              Erro updated:

              +
                +
              • Engineering and security lockers now spawn with their respective backpacks in them so job-changers can look as they should. HoS locker now also contains an armored vest, for the convenience of the HoS who wants to play with one.
              • +
              • Slightly changed the spawn order of items in the CE and HoS lockers to make starting up a hint less tedious.
              • +
              +
              + +
              +

              8 February 2012

              +

              ConstantA updated:

              +
                +
              • Added Exosuit Jetpack
              • +
              • Added Exosuit Nuclear Reactor (runs of normal, everyday uranium, maybe I'll switch it to run on enriched) - requires research (level 3 in Materials, Power Manipulation and Engineering)
              • +
              • Added Ripley construction steps sprites (courtesy of WJohnston - man, you're awesome)
              • +
              • Exosuit Sleeper can now inject occupant with reagents taken from Syringe Gun
              • +
              • Exosuit Cable Layer will now auto-dismantle floors
              • +
              • Exosuit Heavy Lazer cooldown increased, Scattershot now fires medium calibre ammo (less damage)
              • +
              • Exosuit wreckage can be pulled
              • +
              • EMP now drains half of current exosuit cell charge, not half of maximum charge.
              • +
              • Fixed several possible exosuit equipment runtimes
              • +
              • Introduced new markup to changelog. Javascript is extremely slow (in byond embedded browser) for some reason.
              • +
              +
              +
              +

              4 February 2012, World Cancer Day

              +

              Erro updated:

              +
                +
              • Examining humans now works a bit differently. Some external suits and helmets can hide certain pieces of clothing so you don't see them when examining. Glasses are also now displayed when examining.
              • +
              • The job selection screen has been changed a little to hopefully make making changes there easier.
              • +
              +
              +31 January 2012 +
                +
              • Carn updated: +
                  +
                • Grammar & various bug-fixes
                • +
                • Thank-you to everyone who reported spelling/grammar mistakes. I'm still working on it, so if you spot anymore please leave a comment here. There's still lots to fix.
                • +
                • Mining station areas should no longer lose air.
                • +
                +
              + + +30 January 2012( +
                +
              • Sieve updated: +
                  +
                • This stuff is actually already implemented, it just didn't make it to the changelog
                • +
                • Firefighter Mech - A reinforced Ripley that is more resistant to better cope with fires, simply look in the Ripley Contruction manual for instructions.
                • +
                • Mech contruction now has sounds for each step, not just 1/4 of them.
                • +
                • Mech Fabricators are fixed, Manual Sync now works and certain reseach will reduce the time needed to build components.
                • +
                • Added special flaps to the mining station that disallow air-flow, removing the need to shuffle Ore Boxes through the Airlocks.
                • +
                • Each outpost has it's own system for the conveyors so they won't interfere with each other.
                • +
                • Powercell chargers have been buffed so now higher capacity cells are actually useable.
                • +
                • A diamond mech drill has been added. While it isn't any stronger than the standard drill, it is much faster.
                • + +
                +
              + +29 January 2012, got Comp Arch exams on Wednesday :( +
                +
              • Agouri updated: +
                  +
                • UPDATE ON THE UPDATE: Newspapers are now fully working, sorry for that. Some minor icon bugs fixed. Now I'm free to work on the contest prizes :3
                • +
                • Newscasters are now LIVE! Bug reports, suggestions for extra uses, tears etc go here.
                • +
                • What ARE newscasters? Fans of the Transmetropolitan series might find them familiar. Basically, they're terminals connected to a station-wide news network. Users are able to submit channels of their own (one per identified user, with channels allowing feed stories by other people or, if you want the channel to be your very own SpaceJournal, being submit-locked to you), while others are able to read the channels, either through the terminals or a printed newspaper which contains every news-story circulating at the time of printing.
                • +
                • About censorship: You can censor channels and feed stories through Security casters, found in the HoS'es office and the Bridge. Alternatively, if you want a channel to stop operating completely, you can mark it with a D-Notice which will freeze it and make all its messages unreadable for the duration it is in effect. If you've got the access, of course.
                • +
                • Basically I think of the newscaster as nothing more as an additional Roleplaying tool. Grab a newspaper along with your donuts and coffee from the machines, read station rumors when you're manning your desk, be a station adventurer or journalist with your very own network journal!
                • +
                • I would ask for a bit of respect when using the machine, though. I removed all and any channel and story restrictions regarding content, so you might end up seeing channels that violate the rules, Report those to the admins.
                • +
                • Finally, due to the removal of the enforced "Channel" string, it's recommended to name your channels properly ("Station Paranormal Activity Channel" instead of "Station Paranormal Activity", for example") + +
                +
              + +28 January 2012 +
                +
              • BubbleWrap updated: +
                  +
                • Arresting buff!
                • +
                • A person in handcuffs being pulled cannot be bumped out of the way, nor can the person pulling them. They can still push through a crowd (they get bumped back to behind the person being pulled, or pushed ahead depending on intent).
                • +
                +
              + +27 January 2012 +
                +
              • LastyScratch updated: +
                  +
                • Toggle-Ambience now works properly and has been moved from the OOC tab to the Special Verbs tab to be with all the other toggles.
                • +
                +
              • RavingManiac updated: +
                  +
                • The bar now has a "stage" area for performances.
                • +
                +
              • Blaank updated: +
                  +
                • Added a vending machine to atmopherics reception desk that dispenses large +oxygen tanks, plasma tanks, emergency oxegen tanks, extended capacity emergency +oxygen tanks, and breath masks.
                • +
                +
              • Petethegoat updated (for a bunch of other people): +
                  +
                • Lattice is now removed when you create plating or floor (credit Donkieyo).
                • +
                • Monkeys now take damage while in crit (credit Nodrak).
                • +
                • The warden now has his own jacket. (credit Shiftyeyesshady).
                • +
                • Spectacular new dice that will display the proper side when rolled!! (credit TedJustice)
                • +
                • Spectacular new dice that will display the proper side when rolled!! (credit TedJustice)
                • + +
                • Borg RCDs can no longer take down R-walls. (headcoder orders)
                +
              + +19 January 2012 +
                +
              • Petethegoat updated: +
                  +
                • Exciting new pen additions! Get the low-down at the wiki.
                • +
                +
              + +17 January 2012 +
                +
              • Doohl updated: +
                  +
                • Syndicate shuttle now starts with a All-In-One telecommunication machine, which acts as a mini-network for the syndie channel. It intercepts all station radio activity, too, how cool is that?
                • +
                +
              + +15 January 2012 +
                +
              • Doohl updated: +
                  +
                • The radio overhaul 'Telecommunications' is now LIVE. Please submit any opinions/feedback in the forums and check the wiki article on Telecommunications for some more info for the curious.
                • +
                • The AI satellite has been replaced with a communications satellite. You can get there via teleporter or space, just like the AI satellite. I highly recommend not bum-rushing the new satellite, as you may be killed if you don't have access. It's a very secure place.
                • +
                • Once a human's toxicity level reaches a certain point, they begin throwing up. This is a natural, but overall ineffective method of purging toxins from the body.
                • +
                • You can now travel Z-levels in Nuclear Emergency mode (the nuke disk is still bound to the station). This means the nuclear agents can and probably will fly off into space to blow up the comm satellite and shut down communications.
                • +
                +
              + +9 January 2012 +
                +
              • ConstantA updated: +
                  +
                • Reworked exosuit internal atmospherics (the situation when exosuit is set to take air from internal tank, otherwise cabin air = location air): +
                    +
                  • If current cabin presure is lower than &quot;tank output pressure&quot;, the air will be taken from internal tank (if possible), to equalize cabin pressure to &quot;tank output pressure&quot;
                  • +
                  • If current cabin presure is higher than &quot;tank output pressure&quot;, the air will be siphoned from cabin to location until cabin pressure is equal to &quot;tank output pressure&quot;
                  • +
                  • Tank air is not altered in any way even if it's overheated or overpressured - connect exosuit to atmos connector port to vent it
                  • +
                  • &quot;Tank output pressure&quot; can be set through Maintenance window - Initiate maintenance protocol to get the option
                  • +
                  +
                • +
                • Fixed bug that prevented exosuit tank air updates if exosuit was connected to connector port
                • +
                • Combat exosuits melee won't gib dead mobs anymore
                • +
                • QM exosuit circuit crates cost lowered to 30 points
                • +
                • Exosuit plasma converter effectiveness +50%
                • +
                +
              + + +8 January 2012 +
                +
              • Agouri updated: +
                  +
                • I'm back home and resumed work on Newscasters and Contraband.
                • +
                • But I got bored and made cargo softcaps instead. Flippable! Enjoy, now all we need is deliverable pizzas.
                • +
                • Oh, also enjoy some new bodybag functionality and sounds I had ready a while ago, with sprites from Farart. Use a pen to create a visible tag on the bodybag. Wirecutters to cut it off. Also it's no longer weldable because it makes no goddamn sense. +
                +
              + +7 January 2012 +
                +
              • Donkieyo updated: +
                  +
                • You must now repair damaged plating with a welder before placing a floor tile.
                • +
                • You can now relabel canisters if they're under 1kPa.
                • +
                +
              • Polymorph updated: +
                  +
                • Dragging your PDA onto your person from your inventory will bring up the PDA screen.
                • +
                • You can now send emergancy messages to Centcomm (Or, with some.. tampering, the Syndicate.) via a comms console. (This occurs in much the fashion as a prayer.)
                • +
                +
              +3 January 2012 +
                +
              • Erro updated: +
                  +
                • Shift-clicking will now examine whatever you clicked on!
                • +
                +
              • Polymorph updated: +
                  +
                • Alt-clicking will now pull whatever you clicked on!
                • +
                +
              + +1 January 2012 (12 more months until doomsday) +
                +
              • Doohl updated: +
                  +
                • XENOS ARE NOW IMMUNE TO STUNNING! To compensate, stunning via tasers/batons now slows them down significantly.
                • +
                + +
              • Polymorph updated: +
                  +
                • Doors no longer close if they have a mob in the tile. (Generally!) Door safties can now be overriden to close a door with a mob in the tile and injure them severely.
                • +
                + +
              + +29 December 2011 +
                +
              • ConstantA updated: +
                  +
                • Added some new Odysseus parts and tweaked old ones.
                • +
                • Added Exosuit Syringe Gun Module
                • +
                • New Odysseus sprites - courtesy of Veyveyr
                • +
                +
              • Polymorph updated: +
                  +
                • Air Alarms can now be hacked.
                • +
                • Too much of a good thing is just as bad as too little. Pressures over 3000 kPa will do brute damage.
                • +
                +
              + +28 December 2011 +
                +
              • RavingManiac updated: +
                  +
                • Wrapped objects can now be labelled with a pen
                • +
                • Wrapped small packages can be picked up, and are now opened by being used on themselves
                • +
                • Mail office remapped such that packages flushed down disposals end up on a special table
                • +
                • Package wrappers placed in most of the station departments
                • +
                • In short, you can now mail things to other departments by wrapping the object, labelling it with the desired destination using a pen, and flushing it down disposals. At the mail room, the cargo tech will then tag and send the package to the department.
                • +
                +
              + +27 December 2011 +
                +
              • Errorage updated: +
                  +
                • Engineering's been remapped
                • +
                +
              • RavingManiac updated: +
                  +
                • Refrigerators and freezer crates will now preserve meat
                • +
                +
              + +25 December 2011 +
                +
              • ConstantA updated: +
                  +
                • Circuit boards for Odysseus mech can be ordered by QM
                • +
                • Designs for them were added to R&amp;D
                • +
                +
              • +
              • Kor updated: +
                  +
                • Soul Stones Added: Like intellicards for dead or dying humans! Full details are too long for the changelog
                • +
                • A belt full of six soul stones is available as an artefact for the wizard
                • +
                • Cultists can buy soulstones with their supply talisman
                • +
                • The chaplain has a single soulstone on his desk
                • +
                • The reactive teleport armour's test run is over. It no longer spawns in the RD's office.
                • +
                +
              • +
              + + + +24 December 2011 +
                +
              • Rockdtben updated: +
                  +
                • Added sprites for soda can in left and right hands on mob: sodawater, tonic, purple_can, ice_tea_can, energy_drink, thirteen_loko, space_mountain_wind, dr_gibb, starkist, space-up, and lemon-lime.
                • +
                +
              • + +
              + +23 December 2011 +
                +
              • ConstantA updated: +
                  +
                • Mech Fabricators now require robotics ID to operate. Emag removes this restriction.
                • +
                • Added Odysseus Medical Exosuit. Has integrated Medical Hud and ability to mount medical modules.
                • +
                • Added Sleeper Medical module for exosuits. Similar to common sleepers, but no ability to inject reagents.
                • +
                • Added Cable Layer module for exosuits. Load with cable (attack cable with it), activate, walk over dismantled floor.
                • +
                • Added another exosuit internal damage type - short circuit. Short-circuited exosuits will drain powercell charge and power relay won't work.
                • +
                • You should be able to send messages to exosuit operators using Exosuit Control Console
                • +
                • Gygax armour and module capacity nerfed.
                • +
                • Exosuit weapon recharge time raised.
                • +
                • Bugfix: EMP actually drains exosuit cell and damages it
                • +
                +
              • +
              • RavingManiac updated: +
                  +
                • Meat will now spoil within three minutes at temperatures between 0C and 100C.
                • +
                • Rotten meat has the same nutritional value as normal meat, and can be used in +the same recipes. However, it is toxic, and ingesting a badly-prepared big bite +burger can kill you.
                • +
                • Because refrigeration serves a purpose now, the kitchen cold room freezing unit +is turned off by default. Chefs should remember to turn the freezer on at the +start of their shift.
                • +
                +
              • +
              + +21 December 2011 +
                +
              • RavingManiac updated: +
                  +
                • Kitchen cold room is now cooled by a freezing unit. Temperature is about 240K by default, but can be raised to room temperature or lowered to lethal coldness.
                • +
                +
              + +19 December 2011 +
                +
              • Kor updated: +
                  +
                • General/Misc Changes +
                    +
                  • Escape pods no longer go to the horrific gibbing chambers. Rather, they will be picked up by a salvage ship in deep space. (This basically changes nothing mechanics wise, just fluff)
                  • +
                  • An ion rifle now spawns on the nuclear operative shuttle. Maybe this will help with them getting destroyed by sec borgs every round?
                  • +
                  +
                • +
                • Wizard Changes +
                    +
                  • The wizard can now purchase magic artefacts in addition to spells in a subsection of the spellbook.
                  • +
                  • The first (and currently only) new artefact is the Staff of Change, which functions as a self recharging energy weapon with some special effects.
                  • +
                  • The wizard has a new alternative set of robes on his shuttle.
                  • + +
                  +
                • Cult Changes
                    +
                  • Cultists now each start with three words (join, blood, self). No more will you suffer at the hands of cultists who refuse to share words.
                  • +
                  • The starting supply talisman can now be used five times and can now be used to spawn armor and a blade.
                  • +
                  • Replaced the sprites on the cultist robes/hood.
                  • +
                • +
                +
              + +18 December 2011 +
                +
              • Carnwennan updated: +
                  +
                • Thanks to the wonders of modern technology and the Nanotrasen steel press Ian's head has been shaped to fit even more silly hats. The taxpayers will be pleased.
                • +
                +
              • +
              • Doohl updated: +
                  +
                • Vending machines got yet another overhaul! Good lord, when will they stop assfucking those damned vendors??
                • +
                +
              • +
              + +17 December 2011 +
                +
              • Erro updated: +
                  +
                • Your direct supervisors are now displayed when you are assigned a job at round start or late join.
                • +
                +
              • +
              + +14 December 2011 +
                +
              • Erro updated: +
                  +
                • Meteor mode is hopefully deadly again!
                • +
                +
              • +
              • Kor updated: +
                  +
                • Research director has a new toy: Reactive Teleport Armour. Click it in your hand to activate it and try it out!
                • +
                +
              • +
              + +11 December 2011 +
                +
              • NEO updated: +
                  +
                • AIs actually consume power from APCs now
                • +
                • Bigass malf overhaul. tl;dr no more AI sat, instead you have to play whackamole with APCs.
                • +
                +
              • +
              + +10 December 2011 +
                +
              • Doohl updated: +
                  +
                • Title music now plays in the pregame lobby. You can toggle this with a verb in "Special Verbs" if you really want to.
                • +
                • User Interface preferences now properly get transferred when you get cloned.
                • +
                +
              • +
              • Erro updated: +
                  +
                • Escape pods have been added to test the concept.
                • +
                • Escaping alone in a pod does not count towards the escape alone objective, it counts towards the escape alive objective tho. Escape alone only requires you to escape alone on the emergency shuttle, it doesn't require you to ensure all pods are empty. Cult members that escape on the pods do not ocunt towards the cult escaping acolyte number objective. Escaping on a pod is a valid way to survive meteor.
                • +
                +
              • +
              • Polymorph updated: +
                  +
                • Fire is now actually dangerous. Do not touch fire.
                • +
                +
              • + + +
              + +8 December 2011 +
                +
              • Errorage updated: +
                  +
                • Fixed the comms console locking up when you tried to change the alert level.
                • +
                • Added a keycard authentication device, which is used for high-security events. The idea behind it is the same as the two-key thing from submarine movies. You select the event you wish to trigger on one of the devices and then swipe your ID, if someone swipes their ID on one of the other devices within 2 seconds, the event is enacted. These devices are in each of the head's offices and all heads have the access level to confirm an event, it can also be added to cards at the HoP's ID computer. The only event that can currently be enacted is Red alert.
                • +
                +
              • +
              • Kor updated: +
                  +
                • The chef now has a fancy dinner mint in his kitchen. It is only wafer thin!
                • +
                +
              • +
              + +3 December 2011 +
                +
              • Pete & Erro Christmas update: +
                  +
                • Reinforced metal renamed to steel and steel floor tile renamed to metal floor tile to avoid confusion before it even happens.
                • +
                • It is no longer possible to make steel from metal or vice versa or from the autolathe. You can however make metal from the autolathe and still insert steel to increase the metal resource.
                • +
                • To make steel you can now use the mining smelting unit and smelt iron and plasma ore.
                • +
                • The RCD can no longer take down reinforced walls.
                • +
                +
              • +
              • Errorage updated: +
                  +
                • Grass plants in hydro now make grass floor tiles instead of the awkward patches.
                • +
                +
              • +
              • Petethegoat updated: +
                  +
                • Fixed all known vending machine issues.
                • +
                • Fixed a minor visual bug with emagged lockers.
                • +
                • Clarified some of the APC construction/deconstruction messages.
                • +
                +
              • +
              • Numbers updated: +
                  +
                • Potency variations tipped in favour of bigger changes over smaller periods of time.
                • +
                +
              • +
              • PolymorphBlue updated: +
                  +
                • Traitors in the escape shuttle's prison cell will now fail their objective.
                • +
                • Lockers are no longer soundproof! (or flashproof, for that matter)
                • +
                • Headrevs can no longer be borged, revs are dereved when borged. +
                +
              • +
              + +30 November 2011 +
                +
              • Vinyl Scratch updated: +
                  +
                • New ambient sounds for the station and AI Sat.
                • +
                • He's baaaaaack...
                • +
                +
              • +
              + +27 November 2011 +
                +
              • Kor updated: +
                  +
                • Changeling husks are now borgable again (though not clonable) and genome requirements were lowered
                • +
                • De-revved revolutionaries had their message clarified a bit. You remember the person who flashed you, and so can out ONE revhead
                • +
                • Light tubes/bulbs can now be created in the autolathe. Recycle those broken lights!
                • +
                +
              • +
              + +22 November 2011 +
                +
              • Doohl updated: +
                  +
                • The firing range now has a purpose. Go check it out; there's a few surprises!
                • +
                +
              • +
              + +19 November 2011 +
                +
              • Doohl updated: +
                  +
                • Toggling admin midis will now DISABLE THE CURRENT MIDI OH MY GOSH!
                • +
                +
              • +
              • Tobba updated: +
                  +
                • We're looking for feedback on the updated chem dispenser! It no longer dispenses beakers of the reagent, and instead places a variable amount of the reagent into the beaker of your choosing.
                • +
                +
              • +
              • Petethegoat updated: +
                  +
                • Diagonal movement is gone on account of them proving to be a bad idea in practice. A grand experiment nonetheless.
                • +
                +
              • +
              • Kor updated: +
                  +
                • The PALADIN lawset in the AI upload has been replaced with the corporate lawset
                • +
                +
              • +
              + +16 November 2011 +
                +
              • Tobba updated: +
                  +
                • Report any issues with the updated vending machines!
                • +
                +
              • +
              + +16 November 2011 +
                +
              • Petethegoat updated: +
                  +
                • Security, Engineer, Medical, and Janitor borgs no longer get a choice of skin. This is for purposes of quick recognition, and is the first part of a series of upcoming cyborg updates.
                • +
                +
              • +
              + +7 November 2011 +
                +
              • Kor updated: +
                  +
                • Repair bots (mechs) are now adminspawn only
                • +
                • Extra loyalty implants are now orderable via cargo bay (60 points for 4 implants)
                • +
                • Changeling regen stasis now takes two full minutes to use, but can be used while dead. Burning and gibbing are the only way to keep them dead now.
                • +
                +
              • +
              + +29 October 2011 +
                +
              • ConstantA updated: +
                  +
                • Added step and turn sounds for mechs
                • +
                • Added another mecha equipment - plasma converter. Works similar to portable generator. Uses solid plasma as fuel. Can be refueled either by clicking on it with plasma in hand, or directly from mecha - selecting it and clicking on plasma.
                • +
                • Added mecha laser cannon.
                • +
                • Added damage absorption for mechs. Different mechs have different absorption for different types of damage.
                • +
                • Metal foam now blocks air movement.
                • +
                +
              • +
              • Petethegoat updated: +
                  +
                • Fixed sticking C4 to containers.
                • +
                • Rearranged the armoury, and changed the medical treatment room in Sec to an interrogation room.
                • +
                • Mr Fixit has been powered off and returned to the armoury. Deploying him every round is still recommended!
                • +
                +
              • +
              + +29 October 2011 +
                +
              • Petethegoat updated: +
                  +
                • Stunglove overhaul: part one. Stun gloves are now made by wiring a pair of gloves, and then attaching a battery- this shows up on the object sprite, but not on your character. Stungloves use 2500 charge per stun! This means that some low capacity batteries will make useless stungloves. To get your old inconspicous gloves back, simply cut away the wire and battery. Note that insulated gloves lose their insulation when you wire them up! Yet to come: stungloves taking extra damage from shocked doors.
                • +
                • Removed sleepypens! Paralysis pens have been changed to look like normal pens instead of penlights, and have been slightly nerfed. They will paralyse for about fifteen seconds, and cause minor brain damage and dizziness. +
                • Uplink Implants now have five telecrystals instead of four. +
                +
              • +
              • Doohl updated: +
                  +
                • More hairs added and a very long beard.
                • +
                • Finally fixed the request console announcements going AMP AMP AMP all the time when you use punctuation.
                • +
                +
              • +
              + +27 October 2011 +
                +
              • Mport updated: +
                  +
                • New WIP TK system added. To activate your TK click the throw button with an empty hand. This will bring up a tkgrab item. Click on a non-anchored (currently) non mob Object to select it as your "focus". Once a focus is selected so long as you are in range of the focus you can now click somewhere to throw the focus at the target. To quit using TK just drop the tkgrab item.
                • +
                +
              • +
              + +21 October 2011, Tuesday: +
                +
              • Errorage updated: +
                  +
                • Old keyboard hotkey layout option available again! home, end, page down and page up now once again do what they did before by default. To use diagonal movement you will need to use your numpad with NUM LOCK enabled.
                  + The new list of hotkeys is as follows: (Valid as of 21.10.2011) +
                    +
                  • Numpad with Num Lock enabled = movement in wanted direction.
                  • +
                  • Numpad with Num Lock disabled = as it was before. movement north-south-east-west and throw, drop, swap hands, use item on itself.
                  • +
                  • Page up (also numpad 9 with num lock disabled) = swap hands
                  • +
                  • Page down (also numpad 3 with num lock disabled) = use item in hand on itself
                  • +
                  • home (also numpad 7 with num lock disabled) = drop
                  • +
                  • end (also numpad 1 with num lock disabled) = throw
                  • +
                  • CTRL + A = throw
                  • +
                  • CTRL + S = swap hands
                  • +
                  • CTRL + D = drop
                  • +
                  • CTRL + W = use item in hand on itself
                  • +
                  • Numpad divide (/) = throw
                  • +
                  • Numpad multiply (*) = swap hands
                  • +
                  • Numpad subtract (-) = drop
                  • +
                  • Numpad add (+) = use item in hand on itself
                  • +
                  + In short, use Num Lock to swap between the two layouts. +
                • +
                +
              • +
              + +18 October 2011, Tuesday: +
                +
              • Errorage updated: +
                  +
                • You can now move diagonally! To do so, use the numpad. The keybaord has been remapped to make this possible: +
                    +
                  • CTRL + A = throw
                  • +
                  • CTRL + S = swap hands
                  • +
                  • CTRL + D = drop
                  • +
                  • CTRL + W = use item in hand on itself
                  • +
                  • Numpad divide (/) = throw
                  • +
                  • Numpad multiply (*) = swap hands
                  • +
                  • Numpad subtract (-) = drop
                  • +
                  • Numpad add (+) = use item in hand on itself
                  • +
                  +
                • +
                +
              • +
              + +15 October 2011, White Cane Safety Day: +
                +
              • Agouri updated: +
                  +
                • There has been a tidal wave of bugfixes over the last 5 or so days. If you had previously tried something on the station, saw that it was bugged and never tried it again, chances are it got fixed. I don't want you to neglect using stuff because you think it was bugged, which it was at one point, but no longer is. Thanks, happy new semester to everyone~
                • +
                +
              • +
              • Errorage updated: +
                  +
                • When you're unconscious, paralyzed, sleeping, etc. you will still see the same blackness as always, but it will rarely flicker a bit to allow you to see a little of your surroundings.
                • +
                +
              • +
              • Doohl updated: +
                  +
                • New hairstyles! YOU never asked for this!
                • +
                +
              • +
              + +11 October 2011: +
                +
              • ConstantA updated: +
                  +
                • Added radios to exosuits. Setting can be found in 'Electronics' menu.
                • +
                • Exosuit maintenance can be initiated even if it's occupied. The pilot must permit maintenance through 'Permissions &amp; Logging' - 'Permit maintenance protocols'. For combat exosuits it's disabled by default. While in maintenance mode, exosuit can't move or use equipment.
                • +
                +
              • +
              + +8 October 2011: +
                +
              • Doohl updated: +
                  +
                • You can put things on trays and mass-transport them now. To put stuff on trays, simply pick up a tray with items underneath/on top of it and they'll be automatically carried on top of the tray. Be careful not to make a mess~!
                • +
                +
              • +
              • Mport updated: +
                  +
                • Telekenesis now only allows you to pick up objects.
                • +
                • Stun gloves ignore intent.
                • +
                • Moved the loyalty implants to the HoS' locker.
                • +
                • Job system redone, remember to setup your prefs.
                • +
                +
              • +
              + +2 October 2011: +
                +
              • Petethegoat updated: +
                  +
                • Pandemic recharge speed is affected by the number of different types of antibodies in the blood sample. For maximum efficiency, use blood samples with only a single type of antibody. (Blood samples with two types of antibodies will still let the Pandemic recharge slightly faster than it used to.)
                • +
                +
              • +
              • Errorage updated: +
                  +
                • Opening a storage item on your belt will now display the proper number of slots even if the number is different from 7.
                • +
                • Less ponies, gryphons, and Tohou.
                • +
                +
              • +
              + +1 October 2011: +
                +
              • Knognob Lungsparkle updated: +
                  +
                • Xenomorphic aliens can now shape resin membranes (organic windows basically).
                • +
                • The AI can no longer see runes. Instead, they will see blood splatters.
                • +
                +
              • +
              + +28 September 2011: +
                +
              • Rolan7 updated: +
                  +
                • New method for job assignment. Remember to review your preferences. Send all your hate and bug reports to me.
                • +
                +
              • +
              • Doohl updated: +
                  +
                • Putting someone inside a cloning machine's DNA scanner will notify the person that they are about to be cloned. This completely removes the necessity to announce over OOC for someone to get back in their body.
                • +
                +
              • +
              + +22 September 2011, OneWebDay: +
                +
              • Errorage updated: +
                  +
                • Added an additional 9 colors, into which you can color bedsheets, jumpsuits, gloves and shoes at the washing machine.
                • +
                • A new click proc will need to be live-tested. The testing will be announced via OOC and you will get a message when it happens. When testing is going on, the new click proc will be used. If testing is going on and you notice a bug, please report it via adminhelp. If you find yourself unable to perform an action, you can double click (By that I mean spam the shit out of clicking) to use the old proc, which is unchanged and will behave like you're used to. Standard roleplay rules apply during tests, they're not an excuse to murder eachother.
                • +
                +
              • +
              + +20 September 2011, 10 year anniversary of the declaration of the "war on terror": +
                +
              • Errorage updated: +
                  +
                • You can no longer clone people who suicided. I REPEAT! You can no longer clone people who have suicided! So use suiciding more carefully and only if you ACTUALLY want to get out of a round. You can normally clone people who succumbed tho, so don't worry about that.
                • +
                • Washing your hands in the sink will now only wash your hands and gloves. You can wash items if you have them in your hands. Both of these actions are no longer instant tho.
                • +
                +
              • +
              + +18 September 2011, World Water Monitoring Day: +
                +
              • Errorage updated: +
                  +
                • Added the most fun activity in your every-day life. Laundry. Check the dormitory. (Sprites by Hempuli)
                • +
                • You can now change the color of jumpsuits, shoes, gloves and bedsheets using the washing machine.
                • +
                • Some religions (currently Islam, Scientology and Atheism) set your chapel's symbols to the symbols of that religion.
                • +
                • A new old-style cabinet's been added to the detective's office. (Sprite by Hempuli)
                • +
                • Runtime the cat ran away!! And it gets even worse! Mr. Deempisi met a premature end during an excursion to the kitchen's freezer! -- I was ORDERED to do this... don't kill me! :(
                • +
                • Kudzu can now be spawned (Currently admin-only. Will test a bit, balance it properly and add it as a random event) (Original code and sprites donated by I Said No)
                • +
                +
              • +
              • Kor updated: +
                  +
                • Added purple goggles to chemistry (you're welcome Lasty)
                • +
                • Added a third MMI to robotics and monkey cubes to xenobio (dont fucking spam them in the halls)
                • +
                +
              • +
              • Mport updated: +
                  +
                • Turns out tasers and a few other weapons were slightly bugged when it came to checking the internal powercell. Tasers and such gained an extra shot.
                • +
                • Ion Rifle shots lowered to 5 per charge.
                • +
                +
              • +
              + +17 September 2011, Operation Market Garden remembrance day: +
                +
              • Errorage updated: +
                  +
                • You can now insert a coin into vending machines. Some machines (currently only the cigarette vending machine) have special items that you can only get to with a coin. No, hacking will not let you get the items, coin only.
                • +
                +
              • +
              + +14 September 2011: +
                +
              • Lasty updated: +
                  +
                • Runtime now actually spawns in medbay because nobody cared whether it is a tiny smugfaced espeon that explodes violently on death or a spacecat that presumably doesn't.
                • +
                • You can no longer put someone into a sleeper and erase them from existence by climbing into the same sleeper.
                • +
                • Players who haven't entered the game will no longer be able to hear administrators in deadchat.
                • +
                +
              • +
              • Pete updated: +
                  +
                • Added new sprites for the light tube and glasses boxes.
                • +
                • Fixed the bug where syndicate bundles would have an emergency O2 tank and breath mask.
                • +
                +
              • +
              • Mport, SECOND REMOVER OF SUNS updated: +
                  +
                • Singularity absorbtion explosion range lowered and is now dependent on singularity size.
                • +
                • Bag of Holding no longer instakills singularity, and the chance for bombs to destroy a singularity has been changed from 10% to 25%.
                • +
                • Removed THE SUN.
                • +
                • Damage and stun duration from shocked doors has been lowered to account for a larger amount of energy in the powernet.
                • +
                +
              • +
              +13 September 2011: +
                +
              • Errorage updated: +
                  +
                • Healing, attacking or 'gently tapping' Ian will now properly display the name of the attacker to all people.
                • +
                +
              • +
              • Vinyl Scratch updated: +
                  +
                • Runtime!
                • +
                • Mine ambience now fades out at the end so that not only is it less jarring when it ends, but it also loops properly.
                • +
                • Malfunctioning AI's runtime message now has a voice clip to go with it (courtesy of Rosen Ritter)
                • +
                • Every headset will now display the commands for their respective channels upon examine.
                • +
                +
              • +
              • Miss Phaeron updated: +
                  +
                • E-Swords now have a chance to deflect projectiles when active
                • +
                • Taser/Laser shots now drain 40 battery per use from a borg, as opposed to 20
                • +
                • EMPs do 60 damage to borgs now, up from 25. They also stun them momentarily and drain 1k charge
                • +
                • As part of a joint effort between Mport, Falazameer, and myself an Ion Rifle has been added to the armoury
                • +
                • The Mutate spell now gives Laser Vision and Hulk
                • +
                +
              • + +
              + + +11 September 2011: +
                +
              • Errorage updated: +
                  +
                • Ian can now be toolboxed.
                • +
                • Ian can now be healed with bruisepacks, unless he's already dead.
                • +
                • You can now walk over Ian when he's killed.
                • +
                • Ian will chase food, if you leave it on the floor. If he notices it and you pick it up, he'll chase you around, if you stay close enough.
                • +
                +
              • +
              + +10 September 2011: +
                +
              • Errorage updated: +
                  +
                • A new pet on the bridge.
                • +
                • The pet can now be buckled and will no longer escape from closed containers, such as closets or the cloning pods.
                • +
                • Vending machines and request consoles are the first to use the new in-built browser in the upper-right of the user interface. If feedback is positive on these, more machines will be added to this. Hoping that this will eventually reduce the number of popup micromanagement.
                • +
                +
              • +
              • Lasty updated: +
                  +
                • The collectible hats have been removed from the theatre, doomed to rot forever in the hat crates they spawned from. No longer shall you see racks full of "collectible hard hat"!
                • +
                +
              • +
              • TLE updated: +
                  +
                • You can now toggle the message for becoming a pAI on and off in your prefs.
                • +
                +
              • +
              • Mport updated: +
                  +
                • Synaptizine now once again helps you recover from being stunned, however it is now also slightly toxic and may cause a small amount of toxins damage for every tick that it is in your system.
                • +
                • Assembly updating!
                • +
                • Original blob is back, though it still has lava sprites.
                • +
                • The bug where you would spawn on the wizard shuttle for a second at the start of the round should no longer occur.
                • +
                +
              • +
              + +8 September 2011: +
                +
              • Lasty updated: +
                  +
                • Suicide has been changed to biting your tongue off instead of holding your breath, and examining someone who has comitted suicide will give you a message stating that their tongue is missing.
                • +
                • Chemsprayers are now large items instead of small, meaning they can no longer fit in your pocket.
                • +
                +
              • +
              + +6 September 2011 +
                +
              • Greek Rioter updated: +
                  +
                • Removed dumb/badly sprited drinks for barman. This is for you, people that love to play barman. Your job is now non-retarded again. EDIT: FIXED DRINK MIXING
                • +
                • More info here: rev2136
                • +
                +
              • +
              • Lasty updated: +
                  +
                • Fixed the karma exploit! Uhangi can rest in peace knowing his -87 karma ways were not his own.
                • +
                • Added new ambient sound for space and the mines.
                • +
                • Examining an oxygen tank will now tell you if it is about to run out of air. If it is, you will recieve a beep and a message, and if not, then you'll just get the default "this is an oxygentank!" message.
                • +
                +
              • +
              • Rageroro updated: +
                  +
                • Sleeper update! Sleepers can now only inject soporific, dermaline, bicaridine, and dexaline into people with 1% or more health. They can also inject inaprovaline into people with -100% or more health. Nothing can be injected into people who are dead.
                • +
                +
              • +
              • Superxpdude updated: +
                  +
                • Virology is now part of medbay, and as such the RD no longer has Virology access and the CMO and Virologist no longer have Research access.
                • +
                +
              • +
              • Uhangi updated: +
                  +
                • Electropacks, screwdrivers, headsets, radio signalers, and station bounced radios can now be constructed from the autolathe.
                • +
                • Added a courtroom to Centcom.
                • +
                • Fixed electropack bug regarding using screwdrivers on electropacks.
                • +
                +
              • +
              • Microwave updated: +
                  +
                • Nuka Cola re-added.
                • +
                +
              • +
              • Urist McDorf updated: + +
              • +
              • Cheridan updated: +
                  +
                • New sprites for beds and air, plasma and nitrogen tanks (The ones found in maintenance shafts).
                • +
                +
              • +
              +5 September 2011. +
                +
              • Rageroro updated: +
                  +
                • Updates made to the HoP's ID computer. New interface and removing a card will now put it directly in your hand, if it's empty. Using your card on the computer will place it in the appropriate spot. If it has access to edit cards it will put it as the authentication card, otherwise as the card to be modified. If you have two cards with ID computer access first insert the authentication card, then the one you wish to midify, or do it manually like before.
                • +
                • Placed extra fire suits in maintenance (Near eva and mining) and an extra fire closet to atmos.
                • +
                +
              • +
              + +4 September 2011. +
                +
              • Urist McDorf updated: +
                  +
                • People who have been infected by facehuggers can no longer suicide.
                • +
                • Stammering has been reworked.
                • +
                • The chaplain can now no longer discern what ghosts are saying, instead receiving flavour text indicating that ghosts are speaking to him.
                • +
                • Walking Mushroom yield decreased from 4 to 1.
                • +
                • Glowshrooms now only spread on asteroid tiles.
                • +
                • Fixed rev round end message reporting everyone as dead.
                • +
                • Gave the changeling an unfat sting.
                • +
                +
              • Erro updated: +
                  +
                • R'n'D and Gas Storage locations have been swapped in order for R&D to be given a hallway-facing table, just like Chemistry.
                • +
                • Buckling someone to a chair now causes them to face in the same direction as the chair.
                • +
                • Conveyor will now move only 10 items per game cycle. This is to prevent miners from overloading the belts with 2000 pieces of ore and slowing down time by turning it on. Does not apply to mobs on belt.
                • +
                +
              • Doohl updated: +
                  +
                • New escape shuttle!
                • +
                • Metroids get hungry slower, but gain more nutrients from eating.
                • +
                +
              • Kor updated: +
                  +
                • Added Necronomicon bible (credit Joseph Curwen)
                • +
                • Removed Doc Scratch clothing from chaplain? locker and added as a random spawn in the theatre.
                • +
                • Shaft Miners now start with regular oxygen tanks.
                • +
                • Mutate now gives the wizard hulk and OPTIC BLAST instead of hulk and TK.
                • +
                • Spiderman suit is no longer armoured or space worthy.
                • +
                • Crayons
                • +
                +
              • Superxpdude updated: +
                  +
                • New, more appropriate arrivals message.
                • +
                • Shuttle escape doors fixed.
                • +
                • New RIG sprite.
                • +
                +
              • Lasty updated: +
                  +
                • Switched xenomorph weeds to run in the background, hopefully causing them to destroy the server slightly less.
                • +
                +
              • Microwave updated: +
                  +
                • Added sink to hydroponics
                • +
                • Cleaned up autolathe menu
                • +
                • Glasses and cups can now be filled with water from sinks.
                • +
                +
              + +31 August 2011. +
                +
              • Lasty updated: +
                  +
                • The costumes that spawn in the theatre are now randomized.
                • +
                • The kitchen now has a Smartfridge which can be directly loaded from the Botanist? plantbags. No more crates! (credit to Rolan7).
                • +
                • Snappops can now be acquired from the arcade machines. Amaze your friends! (credit to Petethegoat)
                • +
                • Bangindonk.ogg added to random end sounds list.
                • +
                +
              • Urist McDorf updated: +
                  +
                • Players can no longer be randomly assigned to Librarian, Atmospherics Technician, Chaplain, and Lawyer unless all other non-assisstant job slots are full or they have those jobs in their preferences.
                • +
                • Changeling mode now has multiple changelings.
                • +
                +
              • Superpxdude updated: +
                  +
                • The mail system actually works properly now! Probably!
                • +
                • Most hats had their ?eadspace?tag removed, meaning they will no longer substitute for a space helmet in protecting you from the dangers of space.
                • +
                +
              + +28 August 2011. +
                +
              • Doohl updated: +
                  +
                • Chaplains can now select different bible icons when they start. The selection is applied globally and the library's bible printer will print the same bibles.
                • +
                • Joy to the world! The Library's bible-printing function now has a one-minute cooldown. One minute may seem a little extreme, but it is necessary to prevent people from spamming the fuck out of everything with 100,000,000,000,000 carbon-copy bibles.
                • +
                • Tweaked Metroids a bit; they are slightly more aggressive and become hungrier faster. To compensate, they now move slightly slower.
                • +
                +
              + +26 August 2011. +
                +
              • Mport updated: +
                  +
                • Rev:
                • +
                    +
                  • Station Heads or Head Revs who leave z1 will count as dead so long as they are off of the z level.
                  • +
                  • Once a player has been unconverted they may not be reconverted.
                  • +
                  +
                • Cult:
                • +
                    +
                  • Heads other than the Captain and HoS are now able to start as or be converted to a cultist.
                  • +
                  • New Item: Loyalty Implant, which will prevent revving/culting. 4 spawn in the armory.
                  • +
                  • If a rev (not cultist) is injected with one he will unconvert, if a revhead is injected it will display a resist message.
                  • +
                  • Loyalty Implants show up on the SecHud
                  • +
                  • New Machine: Loyalty Implanter - Is on the prison station, shove a guy inside it to implant a loyalty implant. It can implant 5 times before it needs a 10 minute cooldown.
                  • +
                  +
                +
              + +20 August 2011. +
                +
              • Doohl updated: +
                  +
                • The smoke chemistry recipe has been upgraded! You can lace smoke with chemicals that can bathe people or enter their lungs through inhalation. Yes, this means you can make chloral hydrate smoke bombs. No, this doesn't mean you can make napalm smoke or foam smoke.
                • +
                +
              + +16 August 2011. +
                +
              • Superxpdude updated: +
                  +
                • Traitor item bundles: Contains a random selection of traitor gear
                • +
                +
              + +
                +
              • Uhangi updated: +
                  +
                • .38 Special Ammo can now be made from unhacked autolathes
                • +
                +
              + +15 August 2011. +
                +
              • Superxpdude updated: +
                  +
                • NEW MINING STATION, POST YOUR OPINIONS AND BUGS HERE
                • +
                • Added some new awesome mining-related sprites by Petethegoat. All credit for the sprites goes to him.
                • +
                +
              + +9 August 2011. +
                +
              • Mport updated: +
                  +
                • Cyborgs once again have open cover/cell icons.
                • +
                • To override a cyborg's laws you must emag it when the cover is open.
                • +
                • Emags can unlock a cyborgs cover.
                • +
                • Xbow radiation damage has been lowered from 100 to 20 a hit
                • +
                +
              + +5 August 2011. +
                +
              • Mport updated: +
                  +
                • The various assemblies should be working now.
                • +
                • Old style bombs and suicide vests temporarily removed.
                • +
                +
              + +3 August 2011. +
                +
              • Superxpdude updated: +
                  +
                • Virology Airlock changed to no longer cycle air. Should work faster and prevent virologists from suffocating.
                • +
                • Server Room APC is now connected to the power grid.
                • +
                • Stun Batons now start OFF. Make sure to turn them on before hitting people with them.
                • +
                +
              + +2 August 2011. The day the earth stood still. +
                +
              • Agouri updated: +
                  +
                • SSUs now correctly cycle and dump the unlucky occupant when designated to supercycle, when the criteria to do so are met.
                • +
                • Fixed bugshit
                • +
                • You can now make normal martinis again, removed a silly recipe conflict (Thanks, muskets.). Good barmen are urged to blend the good ol' recipes since the new ones have sprites that SUCK ASS JESUS CHRIST
                • +
                +
              + +
                +
              • Doohl updated: +
                  +
                • Speech bubbles: you can toggle them on in the character setup window. Basically, whenever someone around you talks, you see a speech bubble appear above them.
                • +
                • You can no longer create wizarditis and xenomicrobes with metroid cores.
                • +
                • Tweak: Using an exclamation mark as an AI, pAI, or cyborg will not longer get rid of the last exclamation mark.
                • +
                +
              + +30 July 2011. +
                +
              • Superxpdude Updated: +
                  +
                • Engineer and CE space helmets now have built-in lights.
                • +
                +
              • Rockdtben updated: +
                  +
                • Bugfix: Fixed a bug where you could dupe diamonds
                • +
                +
              • Doohl updated: +
                  +
                • New virus: Retrovirus. It basically screws over your DNA.
                • +
                • You can now do CTRL+MOVEMENT to face any direction you want. See those chairs in Medbay and the Escape Wing? You can do CTRL+EAST to actually RP that you're sitting on them. Is this cool or what?! +
                +
              + +29 July 2011. - Day of Forum revival! +
                +
              • Doohl updated: +
                  +
                • Bugfix: Metroids should never "shut down" and just die in a corner when they begin starving. And so, hungry Metroids are a force to be feared.
                • +
                • The Cargo computers now have the ability to cancel pending orders to refund credits. This was put in place so that idiots couldn't waste all the cargo points and run off. However, if the shuttle is en route to the station you won't be able to cancel orders.
                • +
                • Bugfix: the manifest has been fixed! Additionally, the manfiest is now updated realtime; job changes and new arrivals will be automatically updated into the manifest. Joy!
                • +
                • Metroids, when wrestled off of someone's head or beaten off, now get stunned for a few seconds.
                • +
                +
              • Agouri updated: +
                  +
                • I was always bothered by how unprofessional it was of Nanotransen (in before >Nanotransen >professionalism) to just lay expensive spacesuits in racks and just let them be. Well, no more. Introducing...
                • +
                • Suit Storage Units. Rumored to actually be repurposed space radiators, these wondrous machines will store any kind of spacesuit in a clean and sterile environment.
                • +
                • The user can interact with the unit in various ways. You can start a UV cauterisation cycle to disinfect its contents, effectively sterilising and cleaning eveyrthing from the suits/helmets stored inside.
                • +
                • A sneaky yordle can also hide in it, if he so desires, or hack it, or lock it or do a plethora of shady stuff with it. Beware, though, there's plenty of dangerous things you can do with it, both to you and your target.
                • +
                • The Unit's control panel can be accessed by screwdriving it. That's all I'm willing to say, I'd like to let the players find out what each hack option does and doesn't. Will add more stuff later.
                • +
                • Added Command Space suit, Chief Engineer space suit and Chief Medical Officer spacesuit (In a new space that you'll probably notice by yourself) to make it easier for you to look like a special snowflake.
                • +
                • EVA and CMO office modified to accomodate the new suits and SSUs. Look, I'm not a competent mapper, okay? Fuck you too. A mapper is strongly recommended to rearrange my half assed shit.
                • +
                • Soda cans, cigarette packets, cigarettes and cigars as well as bullet casings are now considered trash and can be picked up by the trashbag. Now you can annoy the janitor even more!
                • +
                • Sprite credit goes to Alex Jones, his portfolio can be found here: http://bspbox.com. Thanks a lot, bro.
                • +
                • With the recent forum fuss and all that, I've got a thread to specifically contain rants and bug reports about this update. Click me
                • +
                +
              • Uhangi updated: +
                  +
                • EVA redesigned
                • +
                • An electropack is now available once again on the prison station
                • +
                +
              • Errorage updated: +
                  +
                • Hopefully fixed the derelict 'hotspots'. Derelict medbay has also been fixed.
                • +
                +
              + +4 July - 28 July 2011. +
                + +
              • Trubble Bass updated (committed by Superxpdude): +
                  +
                • Hat crates. Hat Station 13.
                • +
                +
              • Matty: +
                  +
                • Engineers and miners now start off with the new industrial backpack.
                • +
                +
              • Agouri: +
                  +
                • Sleepers are now OP and heal every kind of damage.
                • +
                • Made cloning 30% faster, due to popular demand.
                • +
                +
              • Superxpdude updated: +
                  +
                • Added in the Submachine Gun to R&D.
                • +
                • Syndicate agents now have Mini-Uzis.
                • +
                • Added an exosuit recharged to the mining station.
                • +
                • New labcoats for scientists, virologists, chemists, and genetecists.
                • +
                • Moved the vault and added a bridge meeting room next to the HoP's office.
                • +
                • Deathsquad armor now functions like a space suit.
                • +
                • Added in security jackboots.
                • +
                +
              • Uhangi updated: +
                  +
                • Traitors can now purchase syndicate balloons, which serve no purpose other than to blow your cover. For a limited time only, you can get them at a bargain price - just 10 telecrystals!
                • +
                • Removed security shotguns from the armory. No fun allowed.
                • +
                • Changed some bullet damage stuff.
                • +
                +
              • Microwave updated: +
                  +
                • Monkey boxes:
                • +
                • Contains a score of monkey cubes, which you apply water to create monkies. Nanotrasen provides only the finest technology!
                • +
                • You can order monkey crates from the cargo bay. They contain monkey boxes.
                • +
                • Changed the amount of labels labelers have to 30. 10 was too low and 30 should not be too griefy.
                • +
                • Maximum label text length increased from 10 to 64.
                • +
                • You can no longer label people because they can just peel the labels off. Sorry, clowns!
                • +
                • Jelly dooonnuuuutsss! Happy birthday, officers!
                • +
                • Made xenomeat not give any nutrition.
                • +
                • Added some new reagents.
                • +
                • Made it possible to feed monkies and xenos things, as well as making it possible for them to eat themselves (please don't read that too literally).
                • +
                • Added in synthiflesh.
                • +
                • Plasma is now not used in reactions, instead, is treated as a catalyst that is not used up. This only applies to certain reactions.
                • +
                • Made it possible to grind more things in the chemistry grinder.
                • +
                +
              • Errorage updated: +
                  +
                • Increased environmental damage by a factor of 1.5.
                • +
                • Made firesuits a lot more resistant to heat. Previously, they stopped protecting at around 4,500 degrees. They now go up to around 10,000 (which is also the temperature which the floor starts melting)
                • +
                • Edited the quartermaster's office a bit.
                • +
                • Cargo technicians now have access to a cargo ordering console.
                • +
                • Added different-colored hardhats. The CE gets a white hardhat.
                • + +
                +
              • Rastaf.Zero updated: +
                  +
                • Botanists get a new toy: Biogenerator. Insert biological items, recieve biological items.
                • +
                • Added roller beds, otherwise known as stretchers, to medbay. You can buckle people onto them and pull them.
                • +
                • Added egg-smashing and tomato-smashing decals.
                • +
                +
              • Muskets updated: +
                  +
                • The prepackaged songs (Space Asshole, Cuban Pete, Darkest Honk, etc) have been removed. This doesn't mean admins can't play midis, this just gets rid of a lot of unnecessary download time.
                • +
                +
              • Firecage updated: +
                  +
                • A whole bunch of new drinks and food. Seriously, there's alot!
                • +
                • New weapons such as the shock revolver and large energy crossbow.
                • +
                • Hydroponics can now grow a bunch more stuff. A lot of these new crops have some very interesting mutations.
                • +
                • Added a command for AIs to change their icon.
                • +
                • New costumes to the costume room.
                • +
                +
              • Urist McDorf updated: +
                  +
                • Adding shading to pills.
                • +
                • Detective's office noir look has been removed. The icon operations required to render everything in monochrome was too heavy on the players.
                • +
                • Added in an uplink implant.
                • +
                +
              • Doohl updated: +
                  +
                • A new alien race: Metroids! They are a mostly non-sentient race of jellyfish-like organisms that float in the air and feed on the life energy of other organisms. While most Metroids have never shown signs of self-awareness, they do exhibit signs of basic logic and reasoning skills as well as very sophisticated perception. Nanotrasen has shipped two baby Metroids for the xenobiology department. They should be handled with the utmost care - they are some of the deadliest beings in the known universe!
                • +
                • R&D gets a new toy: the freeze gun. Despite popular belief, this will not literally freeze things!
                • +
                • Every single chemical reagent has been assigned a color.
                • +
                • You can now see beakers fill up with chemicals. You can also observe how the colors mix inside the beakers. Spray bottles also will also show the color of whatever you're spraying.
                • +
                • Added a timestamp to combat logs.
                • +
                • Non-insulated gloves now need to be wrapped in wire in order to be electrified.
                • +
                • You can now shoot at people on the ground by simply clicking on the tile they're on.
                • +
                • Changeling husks are now uncloneable. To clarify: when a changeling sucks out a victim's DNA, the victim is said to become a "husk".
                • +
                +
              + +4 July 2011. +
                +
              • Agouri updated: +
                  + Medical stuff overhaul in preparation of Erro's big Medic update: +
                • Sleepers are now able to heal every kind of damage. Drag your ass to medbay and ask a doctor to get you in one.
                • +
                • A lil' bit faster cloning (about 30% faster) due to popular demand.
                • +
                • Sleepers are now all located in the inner part of medbay, except for the examination room one.
                • +
                • Added Dermaline, burn-healing drug that outpowers kelotane (and kelotane is getting a major nerf, so be sure to know how to get this). Recipe is on the wiki if you need to make it.
                • +
                • Drugs no longer heal or metabolise when injected in dead bodies, fuck. Thank god you guys missed this major bug or we'd have cloning-by-injecting-healing-drugs.
                • +
                • Reminder to coders: Goddamn, update the changelog. +
                +
              • + +
              • Matty updated: +
                  +
                • New engineering backpacks. Enjoy!
                • +
                +
              • +
              + +18 June 2011. +
                +
              • Agouri updated: +
                  +
                • Bugfixes: The reagent grinding now works, allowing the miners to FINALLY bring plasma to the chemistry.
                • +
                • Bugfixes: Pill bottles and clipboards can now be removed from the pockets once placed there.
                • +
                +
              • +
              +
              +7 June 2011. +
                +
              • TLE updated: +
                  +
                • Wiped/suicided pAIs should be eligible for being candidates again (5 minute cooldown between prompts.)
                • +
                • pAIs are now affected by EMP bursts. pAIs hit with a burst will be silenced (no speech or PDA messaging) for two minutes and may have their directives or master modified. A sufficiently powerful EMP burst will have a 20% chance of killing a pAI.
                • +
                +
              • +
              • Neo updated: +
                  +
                • Nar-sie is now a more vengeful eldritch being. When summoned into our world, he first rewards his loyal cultists by chasing down and eating them first, then turns his attention to any remaining humans.
                • +
                +
              • +
              • Darem updated: +
                  +
                • Gun Code Overhaul Phase 1.
                • +
                • Taser guns shoot ONE WHOLE SHOT more then they do now.
                • +
                • Energy Crossbow has a slightly higher shot capacity (still automatically recharges).
                • +
                • Revolvers can either be loaded one shell at a time or all at once with an ammo box.
                • +
                • Shotguns no longer need to be pumped before firing (will change back in phase 2).
                • +
                +
              • +
              • K0000 updated: +
                  +
                • Arcane tome now has a "notes" option. Set English translations for runewords which come up when scribing runes. Attack an arcane tome with another arcane tome to copy your notes to the target tome.
                • +
                • Stun rune buffed considerably. Its a 1-use item so it deserved longer stun time.
                • +
                • Tome text: Added missing word description for stun rune.
                • +
                +
              • +
              +
              + +1 June 2011, Canadian Day Against Homophobia +
                +
              • Noise updated: +
                  +
                • Changed holopad speaking to :h on request.
                • +
                • Ninja fixes, changes, etc. Refer to the code changelog for more info.
                • +
                +
              • +
              • Neo updated: +
                  +
                • Department radio chat now shows with the department name instead of the frequency.
                • +
                +
              • +
              • Veyveyr updated: +
                  +
                • Spent casing sprites + SMG sprite.
                • +
                • Sprites for 1x4 and 1x2 pod doors.
                • +
                +
              • +
              • Errorage updated: +
                  +
                • Fixed twohanded weapon throwing, which left the 'off-hand' object in your other hand.
                • +
                • Doors now hide items under them when closed, mobs are still always above.
                • +
                • Singularity engine emitter is now much quieter.
                • +
                • Mining station redesigned.
                • +
                • Atmospherics' misc gasses tank now starts with N2O.
                • +
                • Hitting the resist button while handcuffed and buckled to something will make you attempt to free yourself. The process is the same as trying to remove handcuffs. When the 2 minutes pass you will be unbuckled but still handcuffed.
                • +
                +
              • +
              • ConstantA updated: +
                  +
                • Added exosuit energy relay equipment. Uses area power (any power channel +available) instead of powercell for movement and actions, recharges powercell.
                • +
                • Exosuits can be renamed. Command is in Permissions & Logging menu.
                • +
                • Lowered construction time for Ripley parts.
                • +
                • Exosuit wreckage can be salvaged for exosuit parts (torso, limbs etc).
                • +
                • Speed-up for mecha.
                • +
                • New malf-AI sprite. (Sprite donated by the D2K5 server)
                • +
                +
              • +
              • Cheridan updated: +
                  +
                • Updated mine floor and wall edge sprites.
                • +
                +
              • +
              • Urist McDorf updated: +
                  +
                • AIs no longer bleed when they reach 0HP (Critical health).
                • +
                • Added 2 more security HUDs to security.
                • +
                • Security HUDs now show if a person has a tracking implant.
                • +
                +
              • +
              • Microwave updated: +
                  +
                • Barman renamed to Bartender.
                • +
                • The amount of drink you get when mixing things in the bar has been rebalanced.
                • +
                • Fixed arrivals maintenance shaft not having air at round start.
                • +
                +
              • +
              • Deuryn updated: +
                  +
                • Meteors now do a bit more damage and they're not stopped by grills.
                • +
                +
              • +
              • TLE updated: +
                  +
                • Added personal AIs (pAI).
                • +
                +
              • +
              +
              + +19 May 2011 +
                +
              • Errorage updated: +
                  +
                • Asteroid floors can be built on by adding tiles
                • +
                • Mining satchels now fit in rig suit storage, on belts and in pockets.
                • +
                • Cables now come in four colors: Red, yellow, green and blue.
                • +
                +
              • + +
              • NEO updated: +
                  +
                • Armour overhaul, phase 3. See rev notes for details.
                • +
                • AI cores should now block movement.
                • +
                • MMIs are now properly buildable with the mecha fabricator.
                • +
                +
              • + +
              • Urist updated: +
                  +
                • Added sandstone and mineral doors. Mineral boors cannot be opened by the AI or NPCs.
                • +
                • Removed Imperium robes from map.
                • +
                • Added the ability to draw letters and graffiti with crayons.
                • +
                • Removed fire axes except for bridge and atmospherics.
                • +
                +
              • + +
              • Veyveyr updated: +
                  +
                • New serviceborg sprite option.
                • +
                • Map changes to robotics; removed borg fabricators and added second exosuit fabricator.
                • +
                • Cyborg parts are now built from exosuit fabricators and benefit from research.
                • +
                • New exosuit fabricator and borg frame sprites.
                • +
                +
              • +
              +
              +14 May 2011, late friday 13 update. +
                +
              • K0000 updated: +
                  +
                • Cult updates:
                • +
                • New rune! Stun rune. When used as rune, briefly stuns everyone around (including cultists). When imbued into a talisman, hit someone to stun and briefly mute them. Spawnable with the starter talisman.
                • +
                • Imbue rune doesnt disappear after succesful invocation, only the source rune.
                • +
                • Chaplain's bible now has 20% chance to convert a cultist (was 10%), and gives a message on success.
                • +
                • Also, wrapping paper is back! Find it in the mailroom.
                • +
                +
              • + +
              • NEO updated: +
                  +
                • Beginning of armor overhaul. Armor now has slightly better defence against melee, and weaker against shots. More coming soon...someday
                • +
                • Cyborgs finally drop their MMI when gibbed like they were supposed to back when I added MMIs. Round- start cyborgs use whatever name you have selected for your character for the brain that gets spawned for them.
                • +
                +
              • + +
              • Darem updated: +
                  +
                • Chemistry update
                • +
                • In containers where there isn't a perfect ratio of reagents, reactions won't consume ALL of the related reagents (so if you mix 10 anti-toxin with 20 inaprovaline, you get 10 tricordrazine and 10 inaprovaline rather then just 10 tricodrazine)
                • +
                • Catalysts: some reactions might need presence of an element, while not directly consuming it.
                • +
                • Reactions changed to use catalysts: all recipes that require Universal Enzyme now require 5 units of the enzyme but the enzyme isn't consumed (So Tofu, Cheese, Moonshine, Wine, Vodka, and Kahlua recipes). +
                +
              • +
              • Errorage updated: +
                  +
                • Smooth tables: Tables now automatically determine which direction and sprite they'll use. They will connect to any adjacent table unless there is a window between them (regular, reinforced, tinted, whichever)
                • +
                +
              • +
              +
              +7 May 2011, Mother's day? +
                +
              • Agouri updated: +
                  +
                • Fireaxes now work. Derp.
                • +
                +
              • + +
              • Erro updated: +
                  +
                • New sprites for thermited walls and girders. Rework of thermited walls. Thermited walls leave a remnant damaged wall, crowbar it to scrap it.
                • +
                • More colors for the lightfloors CANCELLED/POSTPONED
                • +
                +
              • + +
              • Noise updated: +
                  +
                • Codephrases for traitors. More details here
                • +
                • New mech sprite
                • +
                +
              • +
              +
              +6 May 2011, AMURRICA FUCK YEAH day +
                +
              • DANGERCON UPDATE:Agouri and Erro updated(I'm in the DangerCon team now, nyoro~n :3): +
                  +
                • Backpacks removed from all players. It was unrealistic. You can now had to the living quarters to get one from the personal closets there.
                • +
                • Any firearms now send you to critical in 1-2 shots. Doctors need to get the wounded man to surgery to provide good treatment. Guide for bullet removal is up on the wiki.
                • +
                • Brute packs and kelotane removed altogether to encourage use of surgery for heavy injury.
                • +
                • Just kidding
                • +
                • Fireaxe cabinets and Extinguisher wall-mounted closets now added around the station, thank Nanotransen for that.
                • +
                • Because of Nanotransen being Nanotransen, the fire cabinets are electrically operated. AIs can lock them and you can hack them if you want the precious axe inside and it's locked. The axe itself uses an experimental two-handed system, so while it's FUCKING ROBUST you need to wield it to fully unlock its capabilities. Pick up axe and click it in your hand to wield it, click it again or drop to unwield and carry it.You can also use it as a crowbar for cranking doors and firedoors open when wielded, utilising the lever on the back of the blade. And I didn't lie to you. It's fucking robust.
                • +
                • Fireaxe, when wielded, fully takes up your other hand as well. You can't switch hands and the fireaxe itself is unwieldy and won't fit anywhere. +
                • A fireaxe cabinet can also be smashed if you've got a strong enough object in your hand.
                • +
                • EXTINGUISHER CLOSETS, made by dear Erro, can be found in abundance around the station. Click once to open them, again to retrieve the extinguisher, attack with extinguisher to place it back. Limited uses, but we've got plans for our little friend. +
                • Sprite kudos go to: Cheridan for most of them, Khodoque for the fantastic fireaxe head. I merged those two. Also thanks to matty and Arcalane for giving it a shot.
                • +
                • Has the piano got TOO annoying? Try the fire axe...
                • +
                • Oh, and tou can now construct Light floors! To do it: Use wires on glass, then metal on the produced assembly, then place it on an uncovered floor like you would when replacing broken floor tiles. To deconstruct: Crowbar on light floor, use crowbar on produced assembly to remove metal, wirecutters to seperate the wires from the glass. Sprites by delicious Hempuli.
                • +
                • Got something to bitch about? Got a bug to report? Want to create drama? Did the clown destroy your piano while you were playing an amazing space remix of the moonlight sonata? Give it here + +
                +
              • + +
              • Rastaf.Zero updated: +
                  +
                • New uniforms added for captain and chaplain, in their respective lockers. Credits to Farart.
                • +
                +
              • + +
              • Urist McDorf updated: +
                  +
                • Mime and Clown now spawn with Crayons. You can eat those crayons. And use them for other nefarious purposes.
                • +
                • Health Scanners (A new type of Goggles) now spawn in medbay. Use them, doctors!
                • +
                • New Arcade toy.
                • +
                • Glowshrooms! What other lifeform will threaten the welfare of the station now?!
                • +
                • Bananas growable in hydroponics. Also soap is now on-board.
                • +
                • Added new "Lights out!" random event.
                • +
                +
              • +
              • ConstantA updated: +
                  +
                • Mech pilots are now immune to zapping, thank you very much.
                • +
                +
              • +
              +
              + +17 April 2011, World Hemophilia Day +
                +
              • Microwave updated: +
                  +
                • Rabbit ears have a small tail, night vision goggle sprites updated.
                • +
                • Space tea has a nice, calming effect.
                • +
                • Space drugs? Liberty cap... something like that. Microwave, make your changelog entries more understandable!
                • +
                • Brobot merged with Service Borg with a Rapid Service Fabricator.
                • +
                • Arcade machine prizes look and sound realistic once again.
                • +
                • New arcade toy: Syndicate space suit costume, can hold arcade toys in suit storage.
                • +
                • Empty cap gun loaders can be recycled in an autolathe.
                • +
                • Seizure man has laying down sprites now. Update to wizard den.
                • +
                • Mech bay has two more borg chargers.
                • +
                • Beepsky is back!
                • +
                • Detective's office grille has been electrified.
                • +
                • You can now see if someone is wearing an emergency oxygen tank on their belt on the mob itself.
                • +
                • Lexorin - Now deals 3 oxygen damage per tick. Countered with Dexalin or Dexalin Pkus, which remove 2 units of it from your body per tick.
                • +
                • Bilk - Shares the effects of beer and milk. Disgusting!
                • +
                • Sugar - Gives nutrition!
                • +
                • Arithrazine - Now extremely good against radiation damage.
                • +
                • Hyronalin - Stronger radiation removal
                • +
                • Space cleaner spray bottles now contain enough cleaner for 50 uses. Making space cleaner now yields more cleaner.
                • +
                +
              • +
              • Errorage updated: +
                  +
                • Shuttle diagonal sprites now work for any kind of floor.
                • +
                • You can now make plaques from gold. Place them on a wall and engrave an epitaph.
                • +
                • Placed a single wall tile in the AI satellite so you don't have a clear LOS of the AI from the door.
                • +
                • Added arrivals lobby. (Map by Superxpdude, updated by Microwave)
                • +
                • Lattice now connects to the solar shields.
                • +
                • Law office maintenance is now connected with Tech storage maintenance. (Some rewiring dont in the area)
                • +
                • Xenobiology now has it's own access level. (Also fixed xeno pen access and blast doors)
                • +
                • You might soon start to see different airlocks and airlock assemblies around the station. (Sprites donated by Baystation 12)
                • +
                • Chemical storage added, discussion on which chemicals it should store is on the forums. You're welcome to contribute.
                • +
                • Hot fires will now melt floors.
                • +
                • Added a pair of market stalls south of the teleporter. LET THERE BE CLOWNMART!
                • +
                • Screwdrivers and wirecutters can now spawn in different colors.
                • +
                • Electrical toolboxes have a 5% chance of spawning a pair of insulated gloves. A set spawns in tech storage.
                • +
                • Oxygen canisters now spawn in emergency storage, near disposal, in the incinerator and the CE's office.
                • +
                • A plasma canister now spawns in toxins, near the maintenance door.
                • +
                • Wooden tables now look nicer.
                • +
                +
              • +
              • Agouri updated: +
                  +
                • 2001 space suits added to AI Satellite!
                • +
                • New look for the 2001 space suit.
                • +
                • 2001 space suit jetpack added.
                • +
                • Improved TRAYS!
                • +
                +
              • +
              • Noise updated: +
                  +
                • Thermals and mesons no longer give slightly better night vision.
                • +
                • NINJAS! (Too many things to list)
                • +
                • Wizards are no longer trackable by the AI when in their den.
                • +
                • Removed all old notes, except for the last one.
                • +
                • Nuke team now cannot return with their shuttle until the bomb is armed and counting down.
                • +
                • Energy blades can no longer cut through r-walls, walls take 7 seconds to cut through.
                • +
                • Turrets are now destructible. Bash them with stuff when they pop out or (more likely) die trying.
                • +
                • Updated Ripley Mech sprite.
                • +
                +
              • +
              • Neo updated: +
                  +
                • You can now aim guns at body parts, armor and helmets properly protect you from projectiles.
                • +
                • Cat ears now match the hair color of the wearer.
                • +
                • Robots can no longer stick their items onto/into things.
                • +
                • Meson, thermal and x-ray vision are now modules for borgs.
                • +
                • Welding now uses less fuel when on and idle but more when welding.
                • +
                • Hopefully fixed the bug when running into airlocks didn't open them and running into objects didn't push them.
                • +
                +
              • +
              • HAL updated: +
                  +
                • Added air alarm to security checkpoint, added cameras to aux. arrival docks so the AI can see everything.
                • +
                • Added fire alarm, fire locks and air alarm to delivery office.
                • +
                +
              • +
              • ConstantA updated: +
                  +
                • Added mecha DNA-locking. Only the person with matching UE can operate such mechs.
                • +
                • Added two mecha armor booster modules and a repair droid module.
                • +
                • Mech fabricator is now buildable.
                • +
                • Gygax construction is now reversible.
                • +
                +
              • +
              • Rastaf0 and Farart updated: +
                  +
                • Ghosts should now always properly hear people.
                • +
                • Monkeyized people (genetics or jungle fever disease) no longer lose their genetic mutations and diseases.
                • +
                • People who get bitten by monkeys get jungle fever.
                • +
                • Most chemicals should now heal and harm humans properly.
                • +
                • Many new (and updated) recipes for the microwave including Pizza, Meatball Soup, Hot Chili and many more.
                • +
                • Items should no longer spawn under vendomats and microwaves.
                • +
                • Runes are now drawn under doors and tables.
                • +
                • Penlights fit in medical belts.
                • +
                • People will scream if they get cremated while still alive.
                • +
                • Diseases should now properly make you loose health.
                • +
                • Monkeys wearing masks now get acid protection too.
                • +
                • You should probably turn off your stun baton before washing it.
                • +
                • latex loves + short piece of wire + some air from tank = balloon!
                • +
                • Kitchen was expanded, also a new look for the kitchen sink.
                • +
                • New dishware vending machine - dispenses knives, forks, trays and drinking glasses.
                • +
                • Water cooler was added to kitchen.
                • +
                • New uniform - Waiter Outfit. Chef can give it to his assistant.
                • +
                +
              • +
              • Deeaych updated: +
                  +
                • Updated satchel, bananimum, shovel, jackhammer and pick-in-hand sprites.
                • +
                • Many unneeded r-walls removed, detective's office reinforced.
                • +
                • Captain armor now acts as a space suit, added a unique captain's space helmet to captain's quarters.
                • +
                • Golems cannot speak, but should be perfectly spawnable. Also added golem fat sprite.
                • +
                • Security borgs have side sprites.
                • +
                +
              • +
              • Matty406 updated: +
                  +
                • AIs can now feel a little more dorfy.
                • +
                • Many ores, both raw and smelted, look much better.
                • +
                +
              • +
              • Urist_McDorf updated: +
                  +
                • You can now light other people's cigarettes by targeting their mouth with a lighter.
                • +
                +
              • +
              • Veyveyr updated: +
                  +
                • New tool sprites.
                • +
                • New sprites for smooth-lattice.
                • +
                +
              • +
              • Muskets updated: +
                  +
                • Kabobs now return the bar used to make them.
                • +
                +
              • +
              +
              + +2 April 2011, International Children's Book Day +
                +
              • Microwave updated: +
                  +
                • New look for the mining cyborg, jackhammer, kitchen sink.
                • +
                • Singularity is now enclosed again (still airless tho).
                • +
                • Wizard has a new starting area.
                • +
                • Chemists and CMOs now have their own jumpsuits.
                • +
                +
              • +
              • ConstantA updated: +
                  +
                • You can now put Mind-machine-interface (MMI)'d brains into mecha.
                • +
                +
              • +
              • Errorage updated: +
                  +
                • Added smooth lattice.
                • +
                +
              • +
              +
              + +26 March 2011 +
                +
              • Rastaf0 updated: +
                  +
                • Food sprites from Farart
                • +
                • New food: popcorn (corn in microwave), tofuburger (tofu+flour in microwave), carpburger (carp meat+floor in microwave)
                • +
                • Medical belts are finally in medbay (credits belong to errorage, I only added it)
                • +
                • Pill bottles now can fit in containers (boxes, medbelts, etc) and in pockets.
                • +
                • Cutting camera now leaves fingerprints.
                • +
                +
              • +
              • Microwave updated: +
                  +
                • Armor Can hold revolvers, and so can the detective's coat.
                • +
                • Chef's apron is going live, it can carry a knife, and has a slight heat +resistance (only slight don't run into a fire).
                • +
                • Kitty Ears!
                • +
                • Various food nutriment changes.
                • +
                • Added RIGs to the Mine EVA.
                • +
                • Night vision goggles. They have a range of five tiles.
                • +
                • Added Foods: Very Berry Pie, Tofu Pie, Tofu Kebab.
                • +
                • Modified foods: Custard Pie is now banana cream pie.
                • +
                +
              • +
              • ConstantA updated: +
                  +
                • Removed redundand steps from Gygax and HONK construction.
                • +
                • Added some mecha equipment designs to R&D.
                • +
                +
              • +
              +
              + +23 March 2011, World Meteorological Day +
                +
              • Neo updated: +
                  +
                • Fixed PacMan (and affiliates) generator construction.
                • +
                • It is now possible to actually eat omelettes with the fork now, instead of just stabbing yourself (or others) in the eye with it.
                • +
                • Welding masks can now be flipped up or down. Note that when they're up they don't hide your identity or protect you from welding.
                • +
                • Reagent based healing should now work properly.
                • +
                • Revolver has been balanced and made cheaper.
                • +
                • Tasers now effect borgs.
                • +
                • Plastic explosives are now bought in single bricks.
                • +
                • Nuke team slightly buffed and their uplink updated with recently added items.
                • +
                • Player verbs have been reorganized into tabs.
                • +
                • Energy swords now come in blue, green, purple and red.
                • +
                • Cameras are now constructable and dismantlable. (Code donated by Powerful Station 13)
                • +
                • Updated the change network verb for AIs. (Code donated by Powerful Station 13)
                • +
                • Added gold, silver and diamond pickaxes to R&D which mine faster.
                • +
                +
              • +
              • Agouri updated: +
                  +
                • New look for the Request consoles.
                • +
                +
              • +
              • Rastaf0 updated: +
                  +
                • Brig cell timers should now tick closer-to-real seconds.
                • +
                • New look for food, including meat pie, carrot cake, loaded baked potato, omelette, pie, xenopie and others. (some sprites by Farart)
                • +
                • Hearing in lockers now works as intended.
                • +
                • Fixed electronic blink sprite.
                • +
                • Added the 'ghost ears' verb, which allows ghosts to not hear anything but deadcast.
                • +
                +
              • +
              • XSI updated: +
                  +
                • New AI core design.
                • +
                • HoP now has a coffee machine!
                • +
                +
              • +
              • Veyveyr updated: +
                  +
                • Replaced nuke storage with a vault.
                • +
                • Redesigned the mint, moved the public autolathe and n2o storage.
                • +
                • New look for the coin press. (Sprite by Cheridan)
                • +
                +
              • +
              • Errorage updated: +
                  +
                • You can now manually add coins into money bags, also fixed money bag interaction window formatting.
                • +
                • QM no longer has access to the entire mining station to stop him from stealing supplies.
                • +
                • New machine loading sprite for mining machinery. (sprites by Cheridan)
                • +
                • Added a messanging server to the server room. It'll be used for messanging, but ignore it for now.
                • +
                • The delivery office now requires delivery office access. It's also no longer called "Construction Zone"
                • +
                • Almost all the mecha parts now have sprites. (Sprites by Cheridan)
                • +
                • Tinted and frosted glass now look darker.
                • +
                • There are now more money sprites.
                • +
                • Department closets now contain the correct headsets.
                • +
                +
              • +
              • Microwave updated: +
                  +
                • Bicaridine now heals a lot better than before.
                • +
                • Added Diethylamine, Dry Ramen, Hot Ramen, Hell Ramen, Ice, Iced Coffee, Iced Tea, Hot Chocolate. Each with it's own effects.
                • +
                • Re-added pest spray to hydroponics.
                • +
                • Carrots now contain a little imidazoline.
                • +
                • HoS, Warden and Security Officer starting equipment changed.
                • +
                • New crate, which contains armored vests and helmets. Requires security access, costs 20.
                • +
                • Miner lockers now contain meson scanners and mining jumpsuits.
                • +
                • Food crate now contains milk, instead of faggots. Lightbulb crates cost reduced to 5. Riot crates cost reduced to 20. Emergency crate contains 2 med bots instead of floor bots. Hydroponics crate no longer contains weed spray, pest spray. It's latex gloves were replaced with leather ones and an apron.
                • +
                • Added chef's apron (can hold a kitchen knife) and a new service borg sprite.
                • +
                • Autolathe can now construct kitchen knives.
                • +
                • Biosuit and syndicate space suits can now fit into backpacks.
                • +
                • Mime's mask can now be used as a gas mask.
                • +
                • Added welding helmet 'off' sprites.
                • +
                +
              • +
              + +
              + +18 March 2011 +
                +
              • Errorage updated: +
                  +
                • You can now use the me command for emotes! It works the same as say "*custom" set to visible.
                • +
                • There is now a wave emote.
                • +
                • Enjoy your tea!
                • +
                +
              • +
              • Deeaych updated: +
                  +
                • The exam room has some extra prominence and features.
                • +
                • A new costume for the clown or mime to enjoy.
                • +
                • Service Cyborgs can be picked! Shaker, dropper, tray, pen, paper, and DOSH to show their class off. When emagged, the friendly butler-borg is able to serve up a deadly last meal.
                • +
                • It should now be possible to spawn as a cyborg at round start. Spawned cyborgs have a lower battery life than created cyborgs and begin the round in the AI Foyer.
                • +
                +
              • +
              • Rastaf0 updated: +
                  +
                • Fixed an issue with examining several objects in your hands (such as beakers).
                • +
                • Fixed bug with random last name being empty in rare cases.
                • +
                +
              • +
              • hunterluthi updated: +
                  +
                • It is now possible to make 3x3 sets of tables.
                • +
                • Fixed some missplaced grilles/lattices on the port solar.
                • +
                • There is now a breakroom for the station and atmos engineers. It has everything an intelligent young engineer needs. Namely, Cheesy Honkers and arcade games.
                • +
                +
              • +
              +
              +15 March 2011, International Day Against Police Brutality +
                +
              • Errorage updated: +
                  +
                • Autolathe deconstruction fixed.
                • +
                • Atmos Entrance fixed.
                • +
                • AI no longer gibs themselves if they click on the singularity.
                • +
                • Fixed all the issues I knew of about storage items.
                • +
                • Redesigned Assembly line and surrounding maintenance shafts.
                • +
                • Redesigned Tech storage. (Map by Veyveyr)
                • +
                +
              • +
              • TLE updated: +
                  +
                • Forum account activation added. Use the 'Activate Forum Account' verb.
                • +
                +
              • +
              • Neo updated: +
                  +
                • New R&D Item: The 'Bag of holding'. (Sprite by Cheridan)
                • +
                • Getting someone out of the cloner now leaves damage, which can only be fixed in the cryo tube.
                • +
                • New reagent: Clonexadone, for use with the cryo tube.
                • +
                • Fixed using syringes on plants.
                • +
                +
              • +
              • Constanta updated: +
                  +
                • Added queueing to fabricator.
                • +
                +
              • +
              • Rastaf0 updated: +
                  +
                • Air alarms upgraded.
                • +
                • Fixed problem with AI clicking on mulebot.
                • +
                • Airlock controller (as in EVA) now react to commands faster.
                • +
                • Fixed toxins mixing airlocks.
                • +
                +
              • +
              + +6 March 2011 +
                +
              • Neo updated: +
                  +
                • Neo deserves a medal for all the bugfixing he's done! --errorage
                • +
                +
              • +
              • Errorage updated: +
                  +
                • No. I did not code on my birthday!
                • +
                • Windows can now be rotated clockwise and counter clockwise.
                • +
                • Window creating process slightly changed to make it easier.
                • +
                • Fixed the newly made reinforced windows bug where they weren't properly unfastened and unscrewed.
                • +
                • Examination room has a few windows now.
                • +
                • Can you tell I reinstalled Windows?
                • +
                • Robotics has health analyzers.
                • +
                • Bugfixing.
                • +
                +
              • +
              • Deeyach updated: +
                  +
                • Roboticists now spawn with a lab coat and an engineering pda
                • +
                +
              • +
              + +2 March 2011, Wednesday +
                +
              • Errorage updated: +
                  +
                • Mapping updates including Atmospherics department map fixes, CE's office and some lights being added here and there.
                • +
                • Mining once again given to the quartermaster and HoP. The CE has no business with mining.
                • +
                • Removed the overstuffed Atmos/Engineering supply room.
                • +
                • Replaced all 'engineering' doors in mining with maintenance doors as they were causing confusion as to which department mining belongs to.
                • +
                • The incinerator is now maintenance access only.
                • +
                +
              • +
              • Neo updated: +
                  +
                • New look for the advanced energy gun. (Sprite by Cheridan)
                • +
                • Mech fabricator accepts non-standard materials.
                • +
                • Mules accesses fixed, so they can be unlocked once again.
                • +
                • Atmospherics department mapping overhaul. (Map by Hawk_v3)
                • +
                • Added more name options to arcade machines.
                • +
                +
              • +
              • ConstantA updated: +
                  +
                • Added mecha control console and mecha tracking beacons.
                • +
                • Some changes to gygax construction.
                • +
                +
              • +
              • Darem updated: +
                  +
                • R&D minor bugfixes.
                • +
                • AI computer can now be deconstructed (right click and select 'accessinternals').
                • +
                • Server room updated, added server equipment to use with R&D.
                • +
                • Wizard and ghost teleport lists are now in alphabetical order, ghosts can now teleport to the mining station.
                • +
                • Rightclicking and examining a constructable frame now tells you what parts still need to be finished.
                • +
                • Large grenades added to R&D.
                • +
                +
              • +
              • Deeyach updated: +
                  +
                • Mining given to the CE. (Reverted by Errorage)
                • +
                • Clowns can now pick a new name upon entering the game (like wizards previously).
                • +
                +
              • +
              + +24 February 2011, Thursday +
                +
              • Darem updated: +
                  +
                • Lighting code fixed for mining and thermite.
                • +
                • R&D instruction manual added to the R&D lab.
                • +
                • Fixed R&D disk commands not working.
                • +
                • Added portable power generators which run on solid plasma.
                • +
                • You can now set the numer of coins to produce in the mint.
                • +
                • Added two more portable power generators to R&D.
                • +
                +
              • +
              • Deeyach updated: +
                  +
                • New uniform for roboticists
                • +
                +
              • +
              • Neo updated: +
                  +
                • Game speed increased
                • +
                • Mining stacking machine no longer devours stacks larger than 1 sheet (it was only increasing its stock by 1 when given a stacked stack)
                • +
                • Stackable uranium ore added (a better sprite is needed, contributions are welcome)
                • +
                • Made Meteor gamemode actually do stuff
                • +
                • Made a bigger class of meteor
                • +
                • New R&D item: Advanced Energy Gun
                • +
                • Law priority clarified with regards to ion laws and law 0.
                • +
                +
              • + +
              • Veyveyr updated: +
                  +
                • Minor mapping fixes
                • +
                +
              • +
              • Uhangi updated: +
                  +
                • New red bomb suit for security.
                • +
                +
              • + +
              • Errorage updated: +
                  +
                • Slight mapping change to arrival hallway.
                • +
                +
              • +
              + +23 February 2011, Red Army Day +
                +
              • Uhangi updated: +
                  +
                • Antitox and Inaprovaline now mixable via chemistry.
                • +
                • Explosive Ordinance Disosal (EOD) suits added to armory and security.
                • +
                • Large beaker now holds 100 units of chemicals. (code by Slith)
                • +
                +
              • +
              • Rastaf0 updated: +
                  +
                • Secbot interface updated.
                • +
                • Syringe auto-toggels mode when full.
                • +
                • Captain's flask volume increased.
                • +
                +
              • +
              • Neo updated: +
                  +
                • Fixed the 'be syndicate' choice to actually work on nuke rounds.
                • +
                • Syndicates no longer win if they detonate the nuke on their ship.
                • +
                +
              • + +
              • Errorage updated: +
                  +
                • Added cloning manual. (Written by Perapsam)
                • +
                +
              • +
              • K0000 updated: +
                  +
                • Cult mode updates.
                • +
                • You can now read the arcane tome. It contains a simple guide for making runes.
                • +
                • Converting people doesnt give them word knowledge.
                • +
                • Sacrifice monkeys or humans to gain new words.
                • +
                • Total number of rune words set to 10
                • +
                • Some minor bugfixes.
                • +
                +
              • +
              + +20 February 2011, Sunday +
                +
              • Errorage updated: +
                  +
                • Slight updates to processing unit and stacking machine at the mining outpost.
                • +
                • Digging now yields sand, which can be smelted into glass.
                • +
                • Stacking machine can now stack reinforced metal, regular and reinforced glass too.
                • +
                • Engineers now have two copies of the singularity safety manual.
                • +
                +
              • +
              • Neo updated: +
                  +
                • Magboots now have a verb toggle like jumpsuit sensors.
                • +
                • Jumpsuit sensors are now in all jumpsuits, except tactical turtlenecks.
                • +
                • Tweaks to the AI report at round start.
                • +
                • Syndi-cakes now heal traitors/rev heads/etc much more than anyone else.
                • +
                • Containment fields zap once again.
                • +
                • Fire damage meter no longer lies about fire damage.
                • +
                +
              • +
              • Darem updated: +
                  +
                • Mass Spectrometer added to R&D. Load it with a syringe of blood and it will tell you the chemicals in it. Low reliability devices may yield false information.
                • +
                • Not all devices have a 100% reliability now.
                • +
                • Miners now have access to mint foyer and loading area. Only captain has access to the vault.
                • +
                • More stuff can be analyzed in the destructive analyzer, protolathe can produce intelicards.
                • +
                +
              • +
              • Rastaf0 updated: +
                  +
                • Added blast door button to atmospherics.
                • +
                • Toxins timer-igniter assemblies fixed.
                • +
                • Engineering secure storage expanded.
                • +
                • Added singularity telescreen.
                • +
                +
              • +
              + +18 February 2011, Friday +
                +
              • Errorage updated: +
                  +
                • New look for the bio suits. (Biosuit and hood sprites by Cheridan)
                • +
                • New radiation suits added along with radiation hoods and masks. Must wear complete set to get full protection.
                • +
                +
              • +
              • Rastaf0 updated: +
                  +
                • Binary translator cost reduced to 1 telecrystal.
                +
              • +
              • AtomicTroop updated: +
                  +
                • Mail Sorter job added.
                • +
                • Disposal system redone to allow for package transfers. Packages are routed to mail sorter room and then routed to the rest of the station
                • +
                • Disposal area moved. Old disposal area now just an incinerator and a small disposal into space.
                • +
                • New wrapping paper for sending packages.
                • +
                +
              • +
              • Veyveyr updates: +
                  +
                • New machine frame sprite.
                • +
                • Braincase sprites for mechs added. Not actually used, yet.
                • +
                +
              • +
              • Darem updates: +
                  +
                • Research and Development system is LIVE. Scientists can now research new advancements in technology. Not much can be made, right now, but the system is there. Technologies are researched by shoving items into the destructive analyzer. Circuit Imprinter, Destructive Analyzer, and Protolathe are controlled from the R&D console.
                • +
                • Autolathe, Protolathe, Destructive Analyzer, and Circuit Imprinter can now be built, taken apart, and upgraded. The basic frame for all of the above requires 5 metal.
                • +
                +
              • +
              + +15 February 2011, Tuesday +
                +
              • Rastaf0 updated: +
                  +
                • Added radio channels and headsets for miners (:h or :d ("diggers" lol)) and for cargo techs (:h or :q )
                • +
                • Added a personal headsets to HoP and QM.
                • +
                • Aliens now attack bots instead of opening control window.
                • +
                • All bots can be damaged and repaired.
                • +
                • All bots are effected to EMP now.
                • +
                • Atmos now starts with nitrous oxide in storage tank.
                • +
                +
              • +
              • Veyveyr updated: +
                  +
                • New look for the pipe dispenser.
                • +
                +
              • Errorage updated: +
                  +
                • Mining station will now charge properly.
                • +
                • Duffle bags (Money bags) can now be emptied.
                • +
                +
              • +
              + +14 February 2011, Valentine's day +
                +
              • Errorage updated: +
                  +
                • New Job! - Shaft Miners have finally been added and are available to play.
                • +
                • Mining outpost - A new mining outpost has been built, the mining dock on SS13 has been updated.
                • +
                +
              • +
              • ConstantA updated: +
                  +
                • Slight speed up for combat mechs..
                • +
                • Added H.O.N.K construction
                • +
                • Fixed bug with switching intent while in mecha.
                • +
                +
              • +
              + +
              12.02.2011, 01.00 GMT, r1021
              +
                +
              • Added Durand combat exosuit.
              • +
              • Players can modify operation permissions of newly constructed civilian mechs. Click on mech with ID card or PDA with ID inside.
              • +
              • Added robotics access to default mecha maintenance permissions (all mechs) and operation permissions (civilian models only).
              • +
              • Fixed double adminlog message of explosion proc.
              • +
              • Fixed accidental mecha wreckage deletion.
              • +
              • Tweaked mecha internal fire processing.
              • +
              • Added some mecha-related sounds.
              • +
              • Moved GaussRand to helpers.dm and added GaussRandRound helper proc.
              • +
              • Other small changes.
              • +
              + +
              11.02.2011, r1001-1020
              +
                +
              • Headsets upgraded. Shortcuts for channels are: :command :security scie:nce :engineering :medical. Also there is :binary :whisper :traitor and old good :h for your department. +
              • "One Click Queue" added: When you quickly click on two things in a row, it will automatically queue the second click and execute it after the default 'action delay' of 1 second after the first click. Previously you had to spam-click until 1 second had passed. THIS AFFECTS EVERYTHING. NEEDS TESTING. - Skie +
              • EMP effects added for further revisions. - Darem +
              • Goon stuff removed, AI/Bots leave behind hidden fingerprints, firedoors fixed, powersinks eat more, small map changes. - Mport +
              • Big map changes in engineering/robotics/science wing. - errorage +
              • Welder Fixed. Now burns your eyes again. - errorage +
              • 9x9 singularity sprite added. - Skie/Mport +
              • Ghetto surgery added. - Neophyte +
              • Nasty vent pump lag fixed. - Neophyte +
              • Mech gameplay and building updates. - ConstantA +
              + +
              08.02.2011, r999-1000
              +
                +
              • The amount of power the station uses should be lower. +
              • The singularity now changes size based upon how much energy it has +
              • New Machine: Particle Accelerator. +
              • It might need a better name/sprite but when put together properly and turned on it will shoot Accelerated Particles. +
              • The particles irradiate mobs who get in the way and move through solid objects for a short time. +
              • The Particle Accelerator parts are set up by using a Wrench followed by a Cable Coil, then finished with a screwdriver. +
              • When you shoot the Singularity Generator with Accelerated Particles it will spawn a Singularity. +
              • New layout for Engineering, might be changed up slightly in the next few days. +
              + +
              06.02.2011, r979
              +
                +
              • Jesus christ it's a new map what the fuck +
              • Just kidding, it's only minor changes to medbay/mechbay/cybernetics/R&D/toxins/robotics/chapel/theatre +
              • Okay so there's too many changes to list completely, but basically: toxins/R&D/virology/xenobiology are south through medbay; robotics/cybernetics/mechbay are where toxins used to be, the theatre and chapel are above medbay. +
              • Theatre is a new place for the Clown and Mime to play, there are some costumes available, a stage and backstage, and seating for the audience. +
              • R&D and Toxins have been combined together. R&D is still work in progress. You need to head south through medbay to get there. +
              • Medbay has been re-arranged slightly. Honestly, it's nothing, I bet you won't even notice anything different. There's also a new surgery suite, complete with pre-op and post-op rooms. +
              • Virology's been rearranged, but it is mostly in the same place still. +
              • Xenobiology is work in progress. [There's some fun stuff still being coded] +
              • The Chapel is now to the north. You'll probably be thinking something like "Goddamn, this place is fucking huge", but it's actually smaller than the previous chapel. +
              • Robotics and related stuff is also work in progress - however, everything needed for making borgs is there. Note: de-braining will now be done by surgeons in medbay, rather than roboticists in robotics, in case you're wondering where your optable went. +
              • I added color-coded pipes in the areas I was working on. Red pipes run from air siphons and feed into the waste loop; blue pipes run from air vents and pump whatever atmos is set to pump. Yeah, there's TWO pipe networks on the station, who knew? Well, some of you probably did, but I didn't! +
              • Please bring all complaints/ideas/suggestions/bugfixes to: This awesome thread on our forums +
              • This update brought to you by: veyveyr and Rookie, with help from friends! [Now you know who to strangle, please be gentle q_q] +
              + +
              05.02.2011, r968
              +
                +
              • This really needs to be updated more often. +
              • Various map updates have been applied with many more to come. Expect overhauls! +
              • Mining system nearing completion. +
              • Massive overhaul to the electricity systems, the way singularity makes power, and various related functions. +
              • Mime spawns with White Gloves instead of Latex Gloves (apparently there's a difference!) +
              • A new event has been- CLANG! What the fuck was that? +
              • Ion storm laws should be much more interesting. +
              • Security reports should no longer list traitor heads/AIs as possible revs/cultists, nor should nuke operatives ever get named for anything. +
              • Pens are much more versatile and user friendly. +
              • Mech building is rapidly on its way! Ripleys can be built, consult your quartermasters. +
              • Traitors now have several new things they can steal. +
              • Some surgeries coded to accompany the new operating room and surgery tools. +
              • Research and Design is continuing development and should be rolled out shortly. +
              • Various sprites added, tweaked, scrapped and fixed. +
              + +

              Changelog

              +
              05.02.2011, r968
              +
                +
              • This really needs to be updated more often. +
              • Various map updates have been applied with many more to come. Expect overhauls! +
              • Mining system nearing completion. +
              • Massive overhaul to the electricity systems, the way singularity makes power, and various related functions. +
              • Mime spawns with White Gloves instead of Latex Gloves (apparently there's a difference!) +
              • A new event has been- CLANG! What the fuck was that? +
              • Ion storm laws should be much more interesting. +
              • Security reports should no longer list traitor heads/AIs as possible revs/cultists, nor should nuke operatives ever get named for anything. +
              • Pens are much more versatile and user friendly. +
              • Mech building is rapidly on its way! Ripleys can be built, consult your quartermasters. +
              • Traitors now have several new things they can steal. +
              • Some surgeries coded to accompany the new operating room and surgery tools. +
              • Research and Design is continuing development and should be rolled out shortly. +
              • Various sprites added, tweaked, scrapped and fixed. +
              + +
              20.01.2011, r894
              +
                +
              • Pipes can now be removed and re-attached by wrenching them. +
              • Mining system continues to develop. Still unaccessible to players. +
              • Various map changes. Some minor lag causing things were fixed. +
              • Admins have a new tool: They can now give any spell to anyone. Hurray! +
              • Imadolazine now works. Maybe? +
              • Singularity now releases itself if fed too much and will potentially explode. +
              • Magboots now successfully prevent you from getting pulled into the singularity. +
              • Strike teams immune to facehuggers. Why? I dunno. +
              • Many reagent containers are adjustable so you can pour the exact amount you need. +
              • No more emitters working in space, Collectors and collector controllers now ID lockable. +
              • Christmas Contest plaque finally added. It's near the armor/warden's office. +
              • Rocks fall, everyone dies. +
              • All cyborgs and robots can now be named. Just use a pen on the cyborg's frame before the brain is inserted. +
              • Knock spell now unbolts doors as well as opens them. +
              • New cultist runs and other changes. +
              • Added surgery tools for eventual surgery system. +
              • Autolathe and Circuit Printer animations redone. Yay pretty icons. +
              • AI law changes/uploads are now tracked (admin viewable). +
              • Revheads now get a PDA uplink instead of a headset one. +
              • Added a penlight. +
              • Science Research and Development tech tree uploaded. Not really accessible by anyone yet, though. +
              + +
              14.01.2011, r853
              +
                +
              • Changlings Overhauled. Now function kinda like alium (using an internal chemical reserve instead of plasma) with each ability requiring a certain amount of chemicals to activate. Both venoms removed. Several "Dart" abiliites added. They allow the changling to deafen, blind, mute, paralyze, or even transform (dead) targets. +
              • Carp meat now contaminated with Carpotoxin. Anti-toxin negates the poison, however. +
              • New Reagent: Zombie Powder: Puts subjects into a deathlike state (they remain aware, though). Each unit of Zombie Powder requires 5 units of Carpotoxin, Sleeping Toxin, and Copper. +
              • Various alium fixes. +
              • Matches now available from smokes machine. +
              • Megabomb bug fixed. Bombs with timers/signalers won't detonate after the timer/ signaler is removed. +
              • New Disease: Rhumba Beat. Functions like GBS with a few exceptions. Not only available by admindickery. +
              • More mining fixes/changes. +
              • Ghost can now teleport to AI sat, Thunderdome, and Derelict. +
              • New gimmick clothes +
              • Constructing Glass Airlocks now use one sheet of R.Glass. +
              • Windows mow always appear above grills and pipes always above lattices. +
              • Added FireFighter mecha and various mecha fixes. +
              • Deconstructed walls now leave proper floor tiles (that can be pried up like normal) and remember what kind of floor they were before the wall was made. +
              • Carded AIs no longer take damage while in unpowered areas. +
              • Chaplains can now name their religion at round start. +
              • New Recipies: Clown Burger (1 Clown wig, 5 flour), Mime Burger (1 beret, 5 flour), Cuban Carp (1 carp fillet, 1 chili, 5 flour). +
              • Napalm nerfed a bit. Produces ~25% less plasma but it's all concentrated in a single tile (will still spread, though). +
              • Reagent bottles can, once again, be put into grenades. +
              • Various minor map changes. +
              + +
              08.01.2011,8:00PST, r820
              +
                +
              • Holograms (AI controled psudo-mobs) added. Currently only admin spawn. +
              • Pre-spawned pills no longer have randomized image. +
              • Bridge reorganized. +
              • Automated turrets now less dumb. Additionally, turrets won't target people laying down. +
              • Cultists now automatically start known words for add cultist ritual. Also, converted cultists start knowning a word. +
              • Supply ship no longer can transport monkeys. Also, monkeys are no longer orderable from QM. +
              • Meat Crate added to QM. +
              • Corn can now be blended in a blender to produce corn oil which is used to create glycern. +
              • Request Consoles added across the station. Can be used to request things from departments (and slightly easier to notice then the radio). +
              • Centcom reoragnized a fair bit. Not that players care but admins might enjoy it. +
              • There is now a toggable verb that changes whether you'll turn into an alium or not. +
              • Hair sprited modified. +
              • Napalm and Incendiary Grenades both work now. Have fun setting things on fire. +
              • Binary Translater traitor item added. It allows traitors to hear the AI (it functions like a headset). +
              • New Disease: Pierrot's Throat. Enjoy, HONK! +
              • Robotic Transformation (from Robrugers) and Xenomorph Transformation (from xenoburgers) now curable. +
              • Mining added. Only accessible by admins (and those sent by admins). Very much WIP. +
              • Alium Overhaul. Divided into multiple castes with distinct abilities. +
              • New AI Modules: The goody two-shoes P.A.L.A.D.I.N. module and it's evil twin T.Y.R.A.N.T. Only the former actually spawns on the station. +
              • Lizards added. They run away and shit. +
              • PDA overhaul. Doesn't change anything for humans, just makes coders happy. +
              • Firesuits redone to look less like pajamas and instead like firesuits. Fire lockers also added. +
              • New Mecha: H.O.N.K. +
              • Deployable barriers added. Can be locked and unlocked. +
              • Mecha bay (with recharging stations) added. +
              • Bunny Ears, Security Backpacks, Medical Backpacks, Clown Backpacks, and skirt added. +
              • Various wizard changes. New Wizard Spell: Mind Swap. Swap your mind with the targets. Be careful, however: You may lose one of your spells. Wizards are no longer part of the crew and get a random name like the AI does. Wizards can change their known spells with their spellbook but only while on the wizard shuttle. +
              • Circuit Imprinter: Using disks from the various deparments, new circuit boards and AI modules can be produced. +
              • Heat Exchanging pipes added and various pipe/atmos changes. +
              • Ghost/Observer teleport now works 100% of the time. +
              + +
              17.12.2010,11:00GMT
              +
                +
              • You need an agressive grip to table now, as well as being within one tile of the table (to nerf teletabling).
              • +
              • Teleport only runs once at the beginning of the round, hopefully reducing the lag in wizard rounds.
              • +
              • Wizards can't telepot back to their shuttle to afk now.
              • +
              • Someone added it a while ago and forgot to update the changelog - syndies in nuke need to move their shuttle to the station's zlevel first.
              • +
              • Bunch of other stuff.
              • +
              + +
              Sunday, November 21, 3:34 PST
              +
                +
              • Bug fixes, not going into detail. Lots and lots of bug fixes, mostly regarding the new map
              • +
              • CMO has a more obvious lab coat, that looks nicer than the original
              • +
              • CMO also has a stamp now
              • +
              • QM has a denied stamp
              • +
              • Cyborgs got tweaked. Laws only update when checked. Also have wires that can be toyed
              • + with for various effects, including AI sync, and law control +
              • Second construction site similar to the original
              • +
              • Prison station has been tweaked, now includes a lounge, and toilets
              • +
              • Detective's revolver starts with a reasonable number of bullets
              • +
              • Prison teleporter to the courtroom now exists
              • +
              • AI related stuff: More cameras, oh, and bug fixes!
              • +
              • Emergency storage moved
              • +
              • Random AI law changes. New law templates, and variables. Old variables were tweaked and some of the crappy templates were removed
              • +
              • Ghosts can teleport to the derelict now
              • +
              • Respriting of a bunch of stuff as well as graphical fixes
              • +
              • Kitchen is attached to the bar again
              • +
              • General map tweaks and fixes
              • +
              • Wardens fixed for rev rounds
              • +
              • 5-unit pills now work
              • +
              • APCs added and moved
              • +
              • Cola machine added. Contains various flavours of soda. Also added new snacks to the now named snack machine. Water cooler added, just apply an empty glass to it
              • +
              • Salt & Pepper have been added, as part of the food overhaul
              • +
              • More bug fixes. There was a lot
              • +
              + +
              Tuesday, November 16, 00:20 GMT
              +
                +
              • Cruazy Guest's map is now live.
              • +
              • Entire station has been rearranged.
              • +
              • Prison Station added, with Prison Shuttle to transport to and from.
              • +
              • New Job: Warden. Distributes security items.
              • +
              • The new map is still in testing, so please report any bugs or suggestions you have to the forums.
              • +
              • AI Liquid Dispensers, Codename SLIPPER, have been added to the AI core. They dispense cleaning foam twenty times each with a cooldown of ten seconds between uses. Mounted flashes have also been included.
              • +
              • Clown stamp added to clown's backpack.
              • +
              + +
              Sunday, November 14, 18:05
              +
                +
              • Major food/drink code overhaul. Food items heal for more but not instantly. Poison, Drug, and "Heat" effects from food items are also non-instant.
              • +
              • Preperation for one-way containers and condiments.
              • +
              • New Reagents: Nutriment, Ketchup, Soysauce, Salt, Pepper, Capsaicin Oil, Frost Oil, Amatoxin, Psilocybin, Sprinkles
              • +
              • New Food Item: Chaos Donut: 1 Hot Sauce + 1 Cold Sauce + 1 Flour + 1 Egg. Has a variable effect. NOT DEADLY (usually).
              • +
              • New Drug: Ethylredoxrazine: Carbon + Oxygen + Anti-Toxin. Binds strongly with Ethanol.
              • +
              • Tape Recorders added! Now you can actually PROVE someone said something!
              • +
              • Amospherics Overhaul Started: It actually works now. You can also build pipes and create function air and disposal systems!
              • +
              • Walls are now smooth.
              • +
              • Alcohol no longer gets you as wasted or for as long.
              • +
              • QM job split into QM and Cargo Technicians. QM has his own office.
              • +
              • Doors can no longer be disassembled unless powered down and unbolted.
              • +
              • New Job: Chief Medical Officer. Counts as a head of staff and is in charge of medbay. Has his/her own office and coat.
              • +
              • Wizarditis Bottle no longer in virus crate.
              • +
              + +
              Friday, November 5, 19:29
              +
                +
              • The ban appeals URL can now be set in config.txt
              • +
              • Secret mode default probabilities in config.txt made sane
              • +
              • Admin send-to-thunderdome command fixed to no longer send people to the other team's spawn.
              • +
              • Having another disease no longer makes you immune to facehuggers.
              • +
              • Magboots are now available from EVA.
              • +
              • Changeling death timer shortened. Destroying the changeling's body no longer stops the death timer.
              • +
              • Cult mode fixes.
              • +
              • Fixed cyborgs pressing Cancel when choosing AIs.
              • +
              • The play MIDIs setting now carries over when ghosting.
              • +
              • Admins can now see if a cyborg is emagged via the player panel.
              • +
              • PAPERWORK UPDATE v1: Supply crates contain manifest slips, in a later update these will be returnable for supply points. +
              • The Supply Ordering Console (Request computer in the QM lobby) can now print requisition forms for ordering crates. In conjunction with the rubber stamps, these can be used to demonstrate proper authorisation for supply orders.
              • +
              • Rubber stamps now spawn for each head of staff.
              • +
              • The use of DNA Injectors and fueltank detonations are now admin-logged.
              • +
              • Removed old debug code from gib()
              • +
              + +
              Tuesday, November 2, 19:11(GMT)
              +
                +
              • Finished work on the "cult" gamemode. I'll still add features to it later, but it is safe to be put on secret rotation now.
              • +
              • Added an energy cutlass and made a pirate version of the space suit in preparation for a later nuke update.
              • +
              • Changeling now ends 15 minutes after changeling death, unless he's ressurected.
              • +
              • Further fixing of wizarditis teleporting into space.
              • +
              • Fixed the wise beard sprite.
              • +
              • Fixed missing sprite for monkeyburgers.
              • +
              • Fixed Beepsky automatically adding 2 treason points to EVERYONE.
              • + +
              + +
              Thursday, October 28, 19:30(GMT)
              +
                +
              • Sleepers and disposals now require two seconds to climb inside
              • + +
              • Hydroponics crate ordered in QMs doesnt spawn too many items
              • +
              • Replacement lights crate can be ordered in QM.
              • +
              • Added space cleaner and hand labeler to Virology.
              • +
              • Welder fuel tanks now explode when you try to refuel a lit welder.
              • +
              • Made clown's mask work as a gas mask.
              • +
              • 9 new cocktails: Irish Coffee, B-52, Margarita, Long Island Iced Tea, Whiskey Soda, Black Russian, Manhattan, Vodka and Tonic, Gin Fizz. Refer to the wiki for the recipes.
              • +
              • Kitchen update: +
                  +
                • -New Microwave Recipies: Carrot Cake (3 Flour, 3 egg, 1 milk, 1 Carrot), Soylen Viridians (3 flour, 1 soybeans), Eggplant Parmigania (2 cheese, 1 eggplant), and Jelly Donuts (1 flour, 1 egg, 1 Berry Jam), Regular Cake (3 flour, 3 egg, 1 milk), Cheese Cake (3 flour, 3 egg, 1 milk), Meat Pies (1 meat of any kind, 2 flour), Wing Fang Chu (1 soysauce, 1 xeno meat), and Human and Monkey Kabob (2 human or monkey meat, metal rods).
                • +
                • - Ingredients from Processor: Soysauce, Coldsauce, Soylent Green, Berry Jam.
                • +
                • - Sink added to kitchen to clean all the inevitable blood stains and as preperation for future cooking changes.
                • +
                • - The food processor can't be abused to make tons of food now.
                • + +
                +
              • Multiple tweaks to virology and diseases: +
                  +
                • - Added wizarditis disease.
                • +
                • - Spaceacillin no longer heals all viruses.
                • +
                • - Some diseases must be cured with two or more chemicals simultaneously.
                • +
                • - New Virology design including an airlock and quarantine chambers.
                • +
                • - Made vaccine bottles contain 3 portions of vaccine.
                • +
                • - Lots of minor bug fixes.
                • + +
                + +
              + +
              Monday, October 18, 06:24(GMT)
              +
                +
              • Added virology profession with a cosy lab in northwestern part of medbay.
              • +
              • Virology related things, like taking blood samples, making vaccines, splashing contagious blood all over the station and so on.
              • +
              • Added one pathetic disease.
              • +
              • Virus crates are now available from the quartermasters for 20 points.
              • +
              • The DNA console bug (issue #40) was fixed, but I still made the DNA pod to lock itself while mutating someone.
              • +
              • Added icons for unpowered CheMaster and Pandemic computers
              • +
              • Added some sign decals. The icons were already there, but unused for reasons unknown.
              • +
              • Some map-related changes.
              • +
              + +
              Wednesday, October 13, 14:12(GMT)
              +
                +
              • Crawling through vents (alien) now takes time. The farther destination vent is, the more time it takes.
              • +
              • Cryo cell healing ability depends on wound severity. Grave wounds will heal slower. Use proper chemicals to speed up the process.
              • +
              • Added sink to the medbay.
              • +
              • Bugfixes: +
                  +
                • - Some reagents were not metabolized, remaining in mob indefinitely (this includes Space Cola, Cryoxadone and cocktails Kahlua, Irish Cream and The Manly Dorf).
                • +
                • - Fixed placement bug with container contents window. Also, utility belt window now doesn't obscure view.
                • +
                +
              • +
              + +
              Sunday, October 10, 14:25(GMT)
              +
                +
              • Scrubbers in the area can be controlled by air alarms. Air alarm interface must be unlocked with an ID card (minimum access level - atmospheric technician), usable only by humans and AI. Panic syphon drains the air from affected room (simple syphoning does too, but much slower).
              • +
              • Sleeper consoles inject soporific and track the amounts of rejuvination chemicals and sleep toxins in occupants bloodstream.
              • +
              • Flashlights can be used to check if mob is dead, blind or has certain superpower. Aim for the eyes.
              • +
              • Radiation collectors and collector controls can be moved. Secured\unsecured with a wrench.
              • +
              • Air sensors report nitrogen and carbon dioxide in air composition(if set to).
              • +
              • Air Control console in Toxins.
              • +
              • Additional DNA console in genetics
              • +
              • Enough equipment to build another singularity engine can be found in engineering secure storage
              • +
              • Air scrubber, vent and air alarm added to library
              • +
              • Air alarm added to brig
              • +
              • Air scrubbers in Toxins turned on, set to filter toxins
              • +
              • Empty tanks, portable air pumps and similar can be filled with air in Aft Primary Hallway, just connect them to the port. Target pressure is set by Mixed Air Supply console in Atmospherics (defaults to 4000kPa).
              • +
              + +
              Wednesday, October 6, 18:36
              +
                +
              • Fixed the Librarian's suit - its worn iconstate wasn't set.
              • +
              • Fixed some typos.
              • +
              • Monkey crates are now available from the quartermasters for 20 points.
              • +
              • Corpse props removed from zlevel 8 as they were causing issues with admin tools and the communications intercept.
              • +
              • Cleaned up the default config.txt
              • +
              • Added a readme.txt with installation instructions.
              • +
              • Changed the ban appeals link to point to our forums for now - this'll be a config file setting soon.
              • +
              + +
              Tuesday, October 5, 01:41
              +
                +
              • Fixes to various nonworking cocktails.
              • +
              • More map and runtime error fixes.
              • +
              • Nuke operative headsets should be on an unreachable frequency like department headsets.
              • +
              • Another AI Malfunction change: Now once the AI thinks enough APCs have been hacked, it must press a button to start the timer, which alerts the station to its treachery.
              • +
              • Blob reskinned to magma and increased in power.
              • +
              • The HoS now has an armored greatcoat instead of a regular armor vest.
              • +
              • Admin logs now show who killswitched a cyborg.
              • +
              • The roboticist terminal now lets you see which AI a cyborg is linked to.
              • +
              • Malf AIs are no longer treated as inactive for the purpose of law updates and cyborg sync while hacking APCs.
              • +
              • Traitor AIs are now affected by Reset/Purge/Asimov modules, except law 0.
              • +
              • AI core construction sprites updated.
              • +
              • Securitrons removed from the Thunderdome.
              • +
              • An APC now supplies power to the bomb testing area, and has external cabling to supply it in turn.
              • +
              • A new variant freeform module has been added to the AI Upload room.
              • +
              • The changeling's neurotoxic dart has been made more powerful - this will likely be an optional upgrade from a set of choices, akin to wizard spells.
              • +
              • Some gimmick clothes moved to a different object path.
              • +
              • The chameleon jumpsuit should now be more useful - it includes job-specific jumpsuits as well as flat colours.
              • +
              + +
              Wednesday, September 29, 15:40
              +
                +
              • Bartender update! Bartender now has a Booze-O-Mat vending machine dispensing spirits and glasses, which he can use to mix cocktails. Recipes for cocktails are available on the wiki.
              • +
              • The barman also now has a shotgun hidden under a table in the bar. He spawns with beanbag shells and blanks. Lethal ammo is obtainable from hacked autolathes.
              • +
              • Dead AIs can once more be intelicarded, however in order to be restored to functionality they must be repaired using a machine in the RD office.
              • +
              • Silicon-based lifeforms have metal gibs and motor oil instead of blood.
              • +
              • Aliens now have a death message.
              • +
              • Intelicarded AIs can now have their ability to interact with things within their view range reactivated by the person carrying the card.
              • +
              • New AI cores can be constructed from victimsvolunteers.
              • +
              • Verbs tweaked.
              • +
              • Intelicarded AIs can be deleted.
              • +
              • RD office redesigned, and the RD now spawns there.
              • +
              • The AI can now choose to destroy the station on winning a Malf round.
              • +
              • General bugfixes to AIs, constructed AIs and decoy AIs.
              • +
              • Hats no longer prevent choking.
              • +
              • Some extra gimmick costumes are now adminspawnable.
              • +
              • AI health is now displayed on their status tab.
              • +
              • AI upload module now requires you to select which AI to upload laws to in case of multiple AIs.
              • +
              • Cyborgs now choose an AI to sync laws with upon creation.
              • +
              • Law office redesigned.
              • +
              • Roboticists no longer have Engineering access.
              • +
              • More fixes to areas which had infinite power due to having no assosciated APC.
              • +
              • Meatbread slices are no longer infinite.
              • +
              • Malf rounds no longer end if a non-malfunctioning AI is killed.
              • +
              • Cigarettes now have directional sprites.
              • +
              • AI Core circuitboard spawns in the RD office.
              • +
              • AI Satellite now has cameras and properly-wired SMES batteries.
              • +
              • Decoy AIs can no longer be moved.
              • +
              • Several runtime errors have been fixed.
              • +
              • Nuke rounds will now end properly on a station or neutral victory.
              • +
              • Riot shields have been nerfed and are now only available in the armory and in riot crates.
              • +
              • Foam dart crossbows, cap guns and caps can now be won as arcade prizes.
              • +
              • AI Malfunction has been redesigned - the AI must now hack APCs in order to win. More APCs hacked makes the timer tick faster.
              • +
              • Hydroponics now has a MULEbot station.
              • +
              • Changeling mode has been added to the game and is now in testing.
              • +
              • Electrified airlocks should now only zap you once if you bump into them while running.
              • +
              • Chemistry and Toxins access has been removed from Botanists.
              • +
              • General bugfixes and map tweaks.
              • +
              + +
              Sunday, September 26, 17:51
              +
                +
              • Riot shields! One in every security closet, and a few in armory. Also orderable from QM.
              • +
              + +
              Tuesday, September 21, 17:51
              +
                +
              • New experimental UI for humans by Skie. Voice out if it has problems or you don't like it.
              • + ---> YOU CAN CHOOSE UI FROM PREFERENCES <--- +
              • Hydroponics: Now you can inject chemicals into plants with a syringe. Every reagent works differently.
              • +
              • Hydroponics: Added a small hoe for uprooting weeds safely. Botanists now have sissy aprons.
              • +
              • New random station/command names and verbs.
              • +
              • Dead AIs can no longer be intellicarded and the steal AI objective is now working.
              • +
              • Aliens now bleed when you hit them, as well as monkeys.
              • +
              • Hurt people and bodies leave blood behind if dragged around when they are lying. Sprites to be updated soon...
              • +
              • Fixed several run-time errors in the code. Also food doesn't deal damage anylonger in some cases.
              • +
              • Blobs and alien weeds slowed down some. Plant-b-gone buffed some.
              • +
              • Fixed monkeys and aliens not being able to deal damage to humans with items.
              • +
              • Monkeys now slip on wet floor.
              • +
              + +
              Friday, September 17, 23:03
              +
                +
              • The Lawyer now starts in his snazzy new office.
              • +
              • Law Office accesslevel added. Currently, the Lawyer and the HoP begin with this.
              • +
              • Robotics access can now be added or removed from the HoP's computer.
              • +
              • Robotics, the captain's quarters, the cargo lobby and the staff heads office now have APCs and can lose power like the rest of the station.
              • +
              • Toxins mixing room is now a separate area for power and fire alarm purposes, as it already had its own APC.
              • +
              + +
              Thursday, September 16, 20:11
              +
                +
              • Added the Lawyer job.
              • +
              • Doors can now be constructed and deconstructed. This is being playtested, expect the specifics to change.
              • +
              • Fixed certain jobs which were supposed to have stuff spawning in their backpack that just wasn't getting spawned.
              • +
              + +
              Monday, September 13, 13:30
              +
                +
              • Bunch of new announcer sounds added
              • +
              • Minor lag fix implementation in the pipe system
              • +
              • You can now hear ghosts... sometimes
              • +
              • Seed bags and nutrients can now be pocketed
              • +
              + +
              Monday, September 12, 12:48
              +
                +
              • New kitchen stuff: New recipes (Meatbread, Cheese, Omelette Du Fromage, Muffins), new chef's knife and trays (Spawn in the meat locker) and milk (spawns in the fridge). Recipes are as follows: + -Cheese: milk on food processor + -Cheese wedge: Slice the cheese wheel with the chef's knife + -Omelette Du Fromage: 2 eggs 2 cheese wedges + -Muffin: 2 eggs 1 flour 1 carton of milk + -Meatbread: 3 meats (whatever meats) 3 flour 3 cheese. Can be sliced. + Cheese_amount is actually displayed on the microwave.
              • +
              • Profession-special radio channels now have color.
              • +
              • AI card not retardedly lethal anymore, for anyone that didn't notice
              • +
              • HYDROPONICS OVERHAUL, credit goes to Skie and Numbers. Wood doesn't have the entity so the tower caps cannot be harvested. For now.
              • +
              • Bar is now barman-only, access-wise. No more shall the entire station trump inside the bar and choke the monkey.
              • +
              • Prepping ground for Barman update (SPRITE ME SOME GODDAMN BOTTLES)
              • +
              +
              Thursday, September 2, 22:45
              +
                +
              • Ghosts can no longer release the singularity.
              • +
              • Sprites added in preparation for a Hydroponics update
              • +
              • A decoy AI now spawns in the AI core during Malfunction rounds to reduce metagaming.
              • +
              • libmysql.dll added to distribution.
              • +
              • Aircode options restored to default configuration.
              • +
              • AIs properly enter powerloss mode if the APC in their area loses equipment power.
              • +
              • Hydroponics crates added to Hydroponics, containing Weed-B-Gone
              • +
              • Airlock electrification now actually works properly.
              • +
              • Karma database error message updated.
              • +
              • Cyborgs choosing the standard module no longer become invisible except for a pair of glowing red eyes.
              • +
              • Aliens now have a hivemind channel, accessed like departmental radio channels or robot talk with ':a'.
              • +
              • Full donut boxes no longer eat whatever item is used on them and disappear.
              • +
              +
              Monday, August 30, 16:24
              +
                +
              • PDA user interface has been given a graphical overhaul. Please report any problems with it on the issue tracker.
              • +
              • Personal lockers are once again available in the lockerroom
              • +
              • BUGFIX: Xenoburger iconstate was accidentally removed in an earlier revision. This has been fixed.
              • +
              • Some of the default messages have been changed.
              • +
              • Additional sprites added for plants and weeds in preparation for an expansion of Hydroponics.
              • +
              • A schema script is now available for setting up the SQL database.
              • +
              + +
              Sunday, August 29, 05:09
              +
                +
              • The Robotics Crate no longer exists. Quartermasters can now order a MULEbot crate for 20 points, or a Robotics Assembly crate for 10 points. The latter provides 4 flashes, 3 proximity sensors, two 10k charge power cells and an electrical toolbox, and requires a roboticist or a head of staff to open.
              • +
              • Traitor AIs no longer lose their Law 0 in the event of power loss.
              • +
              • Administrators can now toggle the availiabilty of their right-click verbs to prevent accidental usage while playing.
              • +
              • Tool Storage vending machine is now a proper object. (code cleanup)
              • +
              • Buckets are now available from autolathes.
              • +
              • Four generic remote signaller PDA cartridges are now stocked in the Tool Storage vending machine.
              • +
              • AI status display density adjusted.
              • +
              + +
              Thursday, August 26, 21:07
              +
                +
              • Open Source Release Thanks to Mport for releasable singularity code.
              • +
              • Cyborgs redone Thanks again to Mport for this, cyborgs are totally different now.
              • +
              • Engine Monitor PDA app is now Power Monitor PDA app, and actually works.
              • +
              • AI State Laws verb now allows the AI to choose which laws to state, in case of traitor AIs or laws ordering it not to state them. Hopefully this will cut down on 'OMG THE AI IS COPYING AND PASTING' metagaming.
              • +
              • Power Monitor circuitboard isn't mislabeled as Mass Driver Control any more.
              • +
              • Traitor and Rev-head clowns lose the clumsiness gene - this should make trying to flash people in Rev mode less of an exercise in frustration.
              • +
              • A sink has been added to the Fitness room - this lets you wash dirty and bloodstained clothing and equipment.
              • +
              • Blast doors and firedoors no longer open by just bumping into them.
              • +
              • The bar and kitchen now open onto the same seating area. The old cafeteria area is now used as a lockerroom to replace the old one which was displaced by Hydroponics.
              • +
              • The bar now has a space piano with which you can entertain and annoy the crew.
              • +
              • LIBRARY A library has been added to the station in the escape arm in order to educate the crew. The new Librarian job is available to manage it. Crewmembers can request and read books, or write and bind their own books for upload to a persistent database.
              • +
              • The supply of flashbangs available from Security has been reduced to cut down on people constantly flashbanging the escape shuttle.
              • +
              • InteliCards are available in various locations to allow the retrieval of valuable AI personality data in the event of catastrophic station damage.
              • +
              + +
              Friday, August 06, 20:32
              +
                +
              • Hydroponics/Botany Added Credit goes to Skie and the folks over at the independent opensource SS13 branch, this is their code. It's lacking a lot, but it's a great start!
              • +
              • Way more tweaks than I can remember. Shouldn't wait so long between changelog updates.
              • +
              +
              Tuesday, July 13, 22:35
              +
                +
              • Singularity Engine Added Oh God we're all going to die (All credit on this one goes to Mport2004)
              • +
              • 'Purge' AI module added - purges ALL laws (except for law 0). Will probably change this to a Syndicate only item
              • +
              • Cyborgs now spawn with a power cell. Should prevent stupid cyborg deaths (and also pave the way for starting as a cyborg once more bugs are fixed)
              • +
              +
              Saturday, July 10, 15:10
              +
                +
              • Examining a player will now tell you if their client has disconnected.
              • +
              • Examining a brain will now tell you if it's owner is still connected to the game.
              • +
              • Alien Queens can make facehuggers. Facehuggers can make larva. Larva can grow into xenos! Xenos can become queens! The circle of life~
              • +
              • Some powernet bug fixes: Bad list and division by zero.
              • +
              +
              Friday, July 09, 05:16
              +
                +
              • Tweaked crate costs for Quartermaster.
              • +
              • Increased metal available in Robotics.
              • +
              • Added department-specific headsets. Engineering, Medical, Command, and Security all receive special headsets capable of broadcasting on a standard frequency PLUS a secure frequency only available to headsets of the same type. Precede say messages with ":h" to use.
              • +
              + +
              Tuesday, July 06, 19:16
              +
                +
              • Prayer command added.
              • +
              • State Laws command for AI added.
              • +
              • Disabled Lockdown command for AI. Too server heavy.
              • +
              • Crew manifest and various station databases should properly update when late arrivals join the game, now.
              • +
              • Quartermasters will receive 10 points every five minutes. This will probably be nerfed heavily, but we'll give it a shot anyhow.
              • +
              • Fixed a bug with doors/airlocks. (Thanks Mport2004)
              • +
              + +
              Sunday, April 25, 18:53
              + +
            • + New graphics: +
                +
              • + Side Facing Sprites: Player sprites will now face in all directions when moving. Holy shit! +
              • +
              +
            • + +
              Monday 2.0, April 19, 2100
              +
                +
              • + New features: +
                  +
                • + Disposal System: The station now has a fully functional disposal system for throwing away nuclear authentication disks and old, dirty clowns. +
                • +
                • + Breakable Windows: Windows are breakable by projectiles and thrown items (including people), shards hurt your feet. +
                • +
                • + Status Display: Station escape shuttle timers now function as status displays modifiable from the bridge. +
                • +
                • + Space Heater: Space heaters for heating up cold spaces, in space. +
                +
              • +
              • + New items: +
                  +
                • + Welding Mask: Helps engineers shield their eyes when welding. +
                • +
                • + Utility Belt: Function as toolboxes equippable in the belt slot. +
                • +
                • + Mouse Trap: Hurt your feet, especially if you aren't wearing shoes! +
                • +
                • + Power Sink: Traitor item that rapidly drains power. +
                • +
                +
              • +
              • + New graphics: +
                  +
                • + North Facing Sprites: Player sprites will now face north when moving north. +
                • +
                • + Hidden Pipes: Pipes are now hidden underneath floor tiles. +
                • +
                +
              • +
              • + New robot: Medibot +
                  +
                • + Automatically attempts to keep crewmembers alive by injecting them with stuff. +
                • +
                +
              • +
              • + New robot: Mulebot +
                  +
                • + Allows quartermasters to automatically ship crates to different parts of the station. +
                • +
                +
              • +
              + +
              Funday, December 31, 2099
              +

              "FINALLY, DEV IS OUT"

              +
                +
              • + Changes: +
                  +
                • + Atmos system GREATLY OPTIMIZED! +
                • +
                • + Brand new station layout! +
                • +
                • + Robust chemical interaction system! +
                • +
                • + HOLY FUCK PLAYING THIS GAME ISN'T LIKE TRODDING THROUGH MOLASSES ANYMORE +
                • +
                • + Feature: If two players collide with "Help" intent, they swap positions. +
                • +
                +
              • +
              + +
              Tuesday, February 23, 2010
              +
                +
              • + OH NO STRANGLING GOT NERFED: Insta-strangling (hopefully) removed. Victim no longer instantly loses consciousness. +
              • +
              + +
              Sunday, February 21, 2010
              +
                +
              • + Cloning Machine: The Geneticist spilled coffee on the Genetics Machine's revival module and it was too costly to replace! +
                  +
                • + Clones may or may not have horrible genetic defects. +
                • +
                +
              • +
              + +
              Thursday, February 18, 2010
              +
                +
              • + New feature: Obesity from overeating in a short period of time. +
              • +
              + +
              Sunday, February 14, 2010
              +
                +
              • + New feature: Station destruction cinematic if the crew loses in AI Malfunction or Nuclear Emergency. +
              • +
              • + New Position: Tourist +
                  +
                • + Centcom has entered the lucrative business of space tourism! Enjoy an event-filled vacation on the station, and try not to get killed. +
                • +
                • + Guest accounts are now restricted to selecting Tourist in Character Setup. +
                • +
                +
              • +
              + +
              Friday, February 5, 2010
              +
                +
              • + AI: Added 30 second cooldown to prevent spamming lockdowns. +
              • +
              + +
              Wednesday, February 2, 2010
              +
                +
              • + Feature: Character preview in Character Setup! +
              • +
              + +
              Tuesday, February 2, 2010
              +
                +
              • + New item: Drinking glasses that you can fill with water. +
              • +
              • + Feature: Sounds now pan in stereo depending on your position from the source. +
              • +
              + +
              Saturday, December 5, 2009
              +
                +
              • + Traitor tweak: Agent cards can now be forged into a fake ID. +
              • +
              + +
              Friday, December 4, 2009
              +
                +
              • + Supply Dock 2.0: The Supply Dock has been redesigned and now features conveyer belts! Amazing! +
              • +
              • + New uniforms: The Research Director, Chief Engineer, and the research jobs have new uniforms. The Head of Security has a cool new hat which happens to be his most prized possession. +
              • +
              • + Merged research: The first act of the Research Director is to merge Toxins and Chemistry into a single Chemical Lab. Hooray! +
              • +
              • + Robot tweak: You can now observe robots using the observe command. +
              • +
              • + Stamps: The heads now have stamps to stamp papers with, for whatever reason. +
              • +
              + +
              Monday, November 30, 2009
              +
                +
              • + Supply Shuttle 1.0: Now you can order new supplies using Cargo Bay north of the autolathe. +
              • +
              • + New containers: The game now features a variety of crates to hold all sorts of imaginary space supplies. +
              • +
              • + New position: Quartermaster +
                  +
                • + A master of supplies. Manages the cargo bay by taking shipments and distributing them to the crew. +
                • +
                +
              • +
              • + New position: Research Director +
                  +
                • + The head of the SS13 research department. He directs research and makes sure that the research crew are working. +
                • +
                +
              • +
              • + New position: Chief Engineer +
                  +
                • + Boss of all the engineers. Makes sure the engine is loaded and that the station has the necessary amount of power to run. +
                • +
                +
              • +
              • + New robot: Securibot +
                  +
                • + Automatically stuns and handcuffs criminals listed in the security records. It's also really goddamn slow. +
                • +
                +
              • +
              • + New jumpsuits: Engineers and Atmos Techs have new jumpsuits to distinguish between them easier. +
              • +
              + +
              Friday, November 27, 2009
              +
                +
              • + Monkey AI 2.0: Monkeys will now get angry, going after random human targets with the ability to wield weapons, throw random objects, open doors, and break through glass/grilles. They're basically terminators. +
              • +
              • + New gamemode: Monkey Survival +
                  +
                • + Survive a horde of angry monkeys busting through the station's airvents and rampaging through the station for 25 minutes. +
                • +
                +
              • +
              • + New robots: Cleanbot and Floorbot +
                  +
                • + Cleanbots automatically clean up messes and Floorbots repair floors. +
                • +
                +
              • +
              • + New spell: Mindblast +
                  +
                • + Causes brain damage, progressively causing other players to become even more retarded. +
                • +
                +
              • +
              • + Alien Races +
                  +
                • + Wizards may randomly spawn as illithids, who gain Mind Blast for free, and nuke agents may randomly spawn as lizardmen. +
                • +
                +
              • +
              • + Station shields: The station now has a toggleable forcefield that can only be destroyed by meteors or bombs. Takes a lot of station power to use. +
              • +
              • + Traitor scaling: Number of traitors/wizards/agents now scales to number of players. +
              • +
              • + New food item: Donk pockets +
                  +
                • + Delicious and microwavable, gives a bigger health boost for traitors. +
                • +
                +
              • +
              • + Cigarettes: Now you can fulfill your horrible nicotine cravings. The detective starts with a zippo lighter and pack of cigarettes. Other packs can be be obtained via vending machines. +
              • +
              • + Warning signs: The station is now filled with various warning signs and such. +
              • +
              • + Updated graphics: Many, many objects have had their graphics updated including pipes, windows, tables, and closets. HUD graphics have been updated to be easier to understand. +
              • +
              • + Lighting fixes: New turf is now correctly lit instead of being completely dark. +
              • +
              • + Meteor fixes: The code and graphics for meteors has been fixed so the meteor gametype is more playable, sort of. +
              • +
              • + Escape shuttle fix: The shuttle can now be called in Revolution and Malfunction, but the shuttle will be recalled before it arrives. This way players can no longer call the shuttle to figure out the game mode during secret. +
              • +
              • + Changelog updated: New changelog entry for Thanksgiving thanks to Haruhi who will probably update the changelog from now on after almost a month of neglect. +
              • +
              + +
              Monday, November 3, 2009
              +
                +
              • + Bug fix: Made most pop-up windows respect the close button. +
              • +
              + +
              Sunday, October 25, 2009
              +
                +
              • + Randomized naming: Names for Central Command and Syndicate are now randomized. +
              • +
              + +
              Saturday, October 24, 2009
              +
                +
              • + Bug fix: PDAs had their code cleaned up. Notice any problems? Report them. +
              • +
              • + New syndicate item: Detomatix Cartridge, allows remote detonation of PDAs (rather weak explosion)! +
              • +
              • + Feature: Remotely detonating PDAs has a chance of failure depending on the PDA target, a critical failure will result in the detonation of your own PDA. +
              • +
              + +
              Monday, October 19, 2009
              +
                +
              • + Gibbing update: Gibbing stuff has been rewritten, robots now gib nicer. +
              • +
              • + LIGHTING!!!: The station now has dynamic lighting and associated items. +
              • +
              + +
              Friday, October 16, 2009
              +
                +
              • + Poo v1.0~: This has caused many ragequits. +
              • +
              • + Flushable toilets: You can now use toilets to place your vile, disgusting and irreprehensible excretions (you disgusting children). Just be careful what you flush! +
              • +
              +
              Monday, October 12, 2009
              +
                +
              • + Feature: Emergency oxygen bottles can be clipped to your belt now. +
              • +
              • + Clothing update: Bedsheets are now wearable. +
              • +
              • + Updated HUD: A few minor tweaks to the inventory panel. Things might not be exactly where you're used to them being. +
              • +
              +
              Monday, September 28, 2009
              +
                +
              • + New position: Chef +
                  +
                • + Maintains the Cafeteria, has access to Kitchen and Freezer, Food creation will be in shortly. +
                • +
                +
              • +
              • + Food update: Food items now heal Brute/Burn damage. The amount recovered varies between items. +
              • +
              +
              Saturday, August 29, 2009
              +
                +
              • + AI laws update: Nanotrasen has updated its AI laws to better reflect how they wish AIs to + operate their stations. +
              • +
              • + Traitor item change: E-mag renamed to Cryptographic Sequencer. +
              • +
              + +
              Friday, July 31, 2009
              +
                +
              • I&#39;m really sorry everyone I just HAD to add a gib all verb.
              • +
              • Decided to add the creation of bombs to bombers list
              • +
              • Made the new bombing list EVEN BETTER!!!
              • +
              • Fixed a bug with admin jumping AND the traitor death message
              • +
              • Oops, fixed a bug that returned the right click pm thing thinking the admin was + muted.
              • +
              • Made a new improved way of tracking who bombs shit.
              • +
              • More formatting shit.
              • +
              • Fixed up some mute code and made it so that if a player is muted they cannot PM + us.
              • +
              • Adminhelps now logged in the admin file not ooc
              • +
              • Changed the way admin reviving is dealt with. (It was coded kind of weirdly + before)
              • +
              • Added a few areas to the observe teleport. Fixed some adminjump things. Modified + the paths of some areas.
              • +
              • You can now ban people who have logged out and admins can now jump to people + using the player panel.
              • +
              • Added in jump to key coded in a much better way than showtime originally did it.
              • +
              • Fixed magical wind when laying pipes. They start out empty!!
              • +
              • Made blink safer. Fixed the crew-quarters to ai sattelite teleport problem.
              • +
              • Forgot the message again. Added an emp spell. thanks copy&amp;paste.
              • +
              • OH MY GOD I HAVE RUINED ASAY
              • +
              • Added electronic items to the pipe dispenser
              • +
              • fixed a formatting error with the changelog (I didn&#39;t break it, it was showtime)
              • +
              • Fixed a formatting error
              • +
              • Cleaned up sandbox object spawn code
              • +
              • New and improved admin log so we can keep an eye on these fuckers
              • +
              • Fixed adminjump because I realise most people use it for the right click option
              • +
              • Mushed together jump to mob and jump to key
              • +
              • Fixed a compilation error and made my test room more secure!
              • +
              + +
              Wednesday, July 29th, 2009
              + +

              These are a collection of the updates from the last 6 days. I promise to update + the changelog once a week. Note that this does not include all the changes in + the past 6 days.

              + +
                +
              • Multitools can now be used to measure the power in cables.
              • +
              • Fixed a bug where the canister message would repeat and spam the user when + attackby analyzer. Fixed an admin formatting error.
              • +
              • Replaced all range checks with a in_range proc. pretty good chance I broke + something or everything.
              • +
              • Mutations use bitfields
              • +
              • Fixed a bug with my traitor panel.
              • +
              • Fixed the turrets, ruined Pantaloons map (test map). Did some things with + turrets and added a few areas.
              • +
              • Some stuff in here you know the usual shit. Bugfixes, formatting etc.
              • +
              • Stunbaton nerf.
              • +
              • Tempban longer than 1 year -&gt; permaban.
              • +
              • Turfs &gt; spawnable.
              • +
              • Shaking someone now slowly removes paralysis, stuns and the &#39;weakened&#39; stuff.
              • +
              • CTF flags now check if someone has them equipped every 20 seconds, if they are + not then they delete themselves and respawn.
              • +
              • Fixed the r-wall-welder-message-thing.
              • +
              • Change to the CTF code, flag captures can now only happen if your team has their + flag in the starting position.
              • +
              • Pruning my test room.
              • +
              • Instead of the red and green team its now the American and Irish teams!
              • +
              • BACKUP BACKUP TELL ME WHAT YOU GONNA DO NOW Changed the monkey name code. Re-did + my antimatter engine code so it actually puts out power now
              • +
              • dumb as fuck change, whoever did that, it already spawn ()&#39;s inside the proc + code, whoever did that, you are a faggot and should read code before you modify + it
              • +
              • Fixed a bug that gave everyone modify ticker variables you silly sausage.
              • +
              • Sorted the AIs track list.
              • +
              • Constructable filter inlets and filter controls.
              • +
              • Added in admin messages for when someone is banned.
              • +
              • Bannana and honk honk.
              • +
              + +
              Saturday, June 27th, 2009
              + +
                +
              • Pipe construction now works completely. //Nannek
              • +
              • Many many other things that never gets recorded in the changelog!!
              • +
              + +
              Saturday, June 27th, 2009
              +
                +
              • The Michael Jackson Memorial Changelog Update
              • +
              • Pipe filters adjusted for more ideal environmentals //Pantaloons
              • +
              • Added in job tracking //Showtime
              • +
              • Crew Manifest and Security Records now automagically update when someone joins //Nannek
              • +
              • Fixed a bug where sometimes you get a screwdriver stuck in your hand //Pantaloons
              • +
              • Flamethrowers can now be disassembled //Pantaloons
              • +
              • OBJECTION! Added suits and briefcases //stuntwaffle
              • +
              • Added automatic brig lockers //Nannek
              • +
              • Added brig door control authorization and redid brig layout //Nannek
              • +
              • Emergency toolboxes now have radios and flashlights, and mechanical toolboxes now have crowbars //Pantaloons
              • +
              • New whisper system //lallander
              • +
              • Some more gay fixes //everybody
              • +
              • Some really cool fixes //everybody
              • +
              • Really boring code cleanup //Pantaloons
              • +
              • ~~In Loving Memory of MJ~~ Sham on!
              • +
              + +
              Friday, June 12th, 2009
              +
                +
              • Looking back through the SVN commit log, I spy...
              • +
              • Keelin doing some more performance enhancements
              • +
              • Fixed one person being all 3 revs at once (hopefully)
              • +
              • Some gay fixes
              • +
              • New admin system installed
              • +
              • Fixed a bug where mass drivers could be used to crash the server
              • +
              • Various pipe changes and fixes
              • +
              + +
              Wednesday, June 3rd, 2009
              +
                +
              • Death commando deathmatch mode added.
              • +
              + +
              Monday, June 1st, 2009
              +
                +
              • Ghosts can no longer wander from space into the dread blackness that lies beyond.
              • +
              • Those other losers probably did a bunch of other stuff since May 6th but they don't comment their revisions so fuck 'em.
              • +
              + + +
              Wednesday, May 6th, 2009
              +
                +
              • Crematorium
              • +
              • Goon? button makes all your dreams come true.
              • +
              • Restructured medbay
              • +
              +
              Monday, May 4th, 2009
              +
                +
              • Does anyone update this anymore?
              • +
              • New atmos computer promises to make atmos easier
              • +
              • Autolathe
              • +
              • Couple of map changes
              • +
              • Some computer code reorganised.
              • +
              • I'm pretty sure theres a couple things
              • +
              +
              Saturday, April 18th, 2009
              +
                +
              • Weld an open closet (only the normal kind), gayes.
              • +
              • Chaplin has a higher chance of hearing the dead.
              • +
              • New traitor objective
              • +
              • Power traitor objective removed
              • +
              • New job system implemented for latecomers.
              • +
              • Head of Research quits forever and ever, is replaced by Head of Security (who gets his own office)
              • +
              + +
              Fri, April 10, 2009
              +
                +
              • Admins are now notified when the traitor is dead.
              • +
              • Unprison verb (again, for admins).
              • +
              + +
              Wed&Thu, April 8&9, 2009
              +
                +
              • Medical redone, doctors do your jobs! (Tell me what you think of this + compared to the old one)
              • +
              • Clickable tracking for the AI
              • +
              • Only the heads can launch the shuttle early now. Or an emag.
              • +
              + +
              Mon&Tue, April 6&7, 2009
              +
                +
              • Sounds. Turn on your speakers & sound downloads.
              • +
              • Scan something with blood on it detective.
              • +
              + +
              Sunday, April 5, 2009
              +
                +
              • A large icon for the headset, no reason it should be so small.
              • +
              + +
              Saturday, April 4, 2009
              +
                +
              • Emergency closets now spawn an 'emergency gas mask' which are just recolored gas masks, no other difference other than making it obvious where the gas mask came from.
              • +
              + +
              Wednesday, April 1, 2009
              +
                +
              • Constructable rocket launchers: 10 rods, 10 metal, 5 thermite and heated plasma from the prototype.
              • +
              • Emergency closets have randomized contents now.
              • +
              • Fixed a bug where someone who was jobbaned from being Captain could still be picked randomly
              • +
              + +
              Friday, March 27, 2009
              +
                +
              • Fixed a bug where monkeys couldn't be stunned.
              • +
              • Change mode votes before game starts delays the game.
              • +
              + +
              Thursday, March 26, 2009
              +
                +
              • The brig is now pimped out with special new gadgets.
              • +
              • Upgraded the admin traitor menu.
              • +
              + +
              Tuesday, March 24, 2009
              +
                +
              • GALOSHES!
              • +
              • A certain item will now protect you from stun batons, tasers and stungloves when worn.
              • +
              + +
              Monday, March 23, 2009 (EXPERIMENTAL)
              +
                +
              • Say / radio / death talk systems recoded, hopefully improving it.
              • +
              • Announcements of late joiners are now done by the AI if it's alive :-)
              • +
              + +
              Monday, March 23, 2009
              +
                +
              • Random station names.
              • +
              • Changes to the message stylesheet.
              • +
              • Admin messages in OOC will now be colored red.
              • +
              + +
              Saturday, March 21, 2009
              +
                +
              • Added a command to list your medals.
              • +
              • ETA no longer shows when it doesn't matter.
              • +
              • Nerfed the ability to spam shuttle restabalization.
              • +
              • Fixed the 'Ow My Balls!' medal to only apply from brute damage rather than both brute and burn damage.
              • +
              + +
              Thursday, March 19, 2009
              +
                +
              • Job banning.
              • +
              • Genetic Researcher renamed to Geneticist.
              • +
              • Toxins Researcher renamed to Scientist.
              • +
              • Help reformatted.
              • +
              • Fixed a bug where combining bruise packs or ointments resulted in an incorrectly combined amount.
              • +
              • Renamed memory and add memory commands to Notes and Add Note.
              • +
              + +
              Tuesday, March 17, 2009
              +
                +
              • Medals! MEDALS!
              • +
              • Trimmed the excessively long changelog.
              • +
              + +
              Saturday, March 14, 2009
              +
                +
              • Janitor job complete! Report any bugs to adminhelp
              • +
              + +
              Saturday, March 7, 2009
              +
                +
              • Wizard now needs his staff for spells
              • +
              • Be careful with APCs now okay?!
              • +
              • Fixed Memory and made it more efficient in the code
              • +
              • Crowbars now open apcs, not screwdrivers. They do something else entirely
              • +
              • Hackable APCs
              • +
              • When APCs are emagged they now stay unlocked
              • +
              • Re-did a shit tonne of admin stuff
              • +
              • New admin system is pretty much finished
              • +
              • FINALLY backpacks can now be looked in while on the ground.
              • +
              + +
              Tuesday, February 24, 2009
              +
                +
              • Ghosts no longer able to open secret doors
              • +
              • Suicide vests now work as armor
              • +
              • Blood no longer comes out of the guy if you pull him due to lag
              • +
              • Admin panel has been touched up to include html tables
              • +
              • Mines now added, only spawnable right now however
              • +
              • Fixed the syndicate nuclear victory bug
              • +
              • Wizard now spawns with wizard outfit which he must wear to cast spells
              • +
              • Blood bug fixes
              • +
              • Fixed a retarded bug that meant I didn't have the power to kick admins
              • +
              • THUNDERDOME!
              • +
              • Several new facial hair options and a bitchin' mohawk
              • +
              • Blood by Culka
              • +
              • Nuke disk now spawns in ALL game modes so that during secret rounds the syndicate now have the element of surprise!
              • +
              + +
              Saturday, February 22, 2009
              +
                +
              • Implemented unstable's "observer" mode
              • +
              • Halerina's wizard mode
              • +
              • Non-interesting stuff
              • +
              • Began addition to the new admin system - right now only available to coders for testing
              • +
              • Admins can now click on the multikeying offenders name to pm them, instead of hunting for them in the pm list
              • +
              • Halerina's chemistry system
              • +
              • You can now deathgasp without being dead, hopefully so people can fake their own deaths.
              • +
              • Redid Medlab
              • +
              • New chemist job
              • +
              + +
              Thursday, February 19, 2009
              +
                +
              • New DNA system. 200th Revision special.
              • +
              • Various bugfixes
              • +
              • Maze
              • +
              + +
              Monday, February 17, 2009
              +
                +
              • Added a new game mode into rotation.
              • +
              • Added an AI satellite
              • +
              • Lockdowns can be disabled with the communications console
              • +
              • Prison shuttle can be called on the comm console, but only if its enabled by admins first
              • +
              • When you slip into space you'll have a 50% chance of going to z=4 instead of z=3
              • +
              + +
              Friday, February 13, 2009
              +
                +
              • Fixed Cakehat
              • +
              • Dead people can now see all turfs, mobs and objs not in their line of sight.
              • +
              • Modified the map slightly
              • +
              • Stungloves can now be "made"
              • +
              • Flashes can now have their bulbs burning out.
              • +
              • Batons can now be turned on and off for different effects. They also now have 10 uses before they need to be recharged.
              • +
              + +
              Tuesday, February 10, 2009
              +
                +
              • Fixed all the autoclose bugs
              • +
              • Due to it being myself and Keelin's 100th revision we have added a super-secret special item. Don't ask because we won't tell! Figure it out!
              • +
              + +
              Sunday, February 8, 2009
              +
                +
              • Modified doors in engineering so that they do not autoclose - Autoclose now handled by a variable
              • +
              • Fixed toxin researcher spawn bug
              • +
              • Changed the "You hear a faint voice" message.
              • +
              • Gave the host new commands to disable admin jumping, admin reviving and admin item spawning
              • +
              • Fixed some airlock autoclose bugs
              • +
              • Changed some doors to not autoclose.
              • +
              • Nerfed the toolbox down.
              • +
              + +
              Friday, February 6, 2009
              +
                +
              • Doors now close after 15 seconds
              • +
              • Fixed some p cool bugs
              • +
              • Cakehat
              • +
              • Added another suit
              • +
              • Walls now take 5 seconds to build
              • +
              • Added sam0rz, thesoldierlljk and kelson's revolution gamemode. Thanks guys!
              • +
              + +
              Thursday, February 5, 2009
              +
                +
              • Fixed a couple of bugs
              • +
              • Improved bar ;)
              • +
              • Beer acts like pills and syringes
              • +
              + +
              Tuesday, February 3, 2009
              +
                +
              • Added 'Make AI' Option for Admins
              • +
              • Added dissolving pills in beer (cyanide and sleeping pills)
              • +
              • Modified engine AGAIN, but personally I love it now
              • +
              + +
              Monday, February 2, 2009
              +
                +
              • Moved bar due to popular demand
              • +
              • Captains room is now a security checkpoint
              • +
              • Assistants now have access to maint tunnels again
              • +
              • Courtroom
              • +
              • Engine has been redone slightly to make it easier to load
              • +
              • Nerfed beer a lot more
              • +
              + +
              Saturday, January 31, 2009
              +
                +
              • Added a bartender job + Bar
              • +
              • Captains panic room
              • +
              • Voice changer traitor item
              • +
              • Bartender suit
              • +
              • Made taking a table apart take longer
              • +
              • Balanced beer a bit more.
              • +
              • Assistants can no longer open external air locks and maint tunnels, sorry guys. Get a job you bums.
              • +
              • Engineers CAN access external air locks and maint tunnels.
              • +
              • Fixed traitor AI bug
              • +
              + +
              Thursday, January 29, 2009
              +
                +
              • Added traitor menu for admins - The ability to turn people into "traitors" as well as keep track of their objectives.
              • +
              • Implemented Keelins revive system - Primary Admins can now revive people.
              • +
              • Moved and redid security to prevent clusterfucks and everyone just crowding around security.
              • +
              • Redid the brig to make it bigger and so that people can break others more easily out since it isn't right in security.
              • +
              • Moved and redid captains quarters/heads quarters. Captains made much smaller and heads is now more of a meeting room.
              • +
              • Added Stungloves and an axe - right now only admin spawnable.
              • +
              • Implemented Persh's adminjump back in - admins can now jump to set locations.
              • +
              • Added a feature that if someone logs off their character moves around and says things - Change what they say from the config/names/loggedsay.txt file.
              • +
              • Added in adminwho verb - tells the user if there are any admins on and who they are.
              • +
              + +
              Saturday, January 10, 2009
              +
                +
              • Freedom implant has been changed so that it will have a random emote associated with it to activate it rather than always chuckle.
              • +
              • There is now a pinpointer tool for use in Nuclear Emergency. It works similar to the existing locator, in that it will detect the presence of nuclear disks and in what direction it is.
              • +
              • The nuke being detonated in Nuclear Emergency should now properly end the game.
              • +
              • Spacesuits now cause you to move slower when not in space.
              • +
              • Syndicate in Nuclear Emergency now have syndicate-themed spacesuits.
              • +
              • Blob mode should properly end now.
              • +
              + +
              Wednesday, January 7, 2009
              +
                +
              • Syndicate Uplink has been changed up, allowing traitor more freedom in his ability to be... traitorus.
              • +
              • Syndicate Uplink can now spawn a ammo-357, syndicate card, energy sword, or timer bomb.
              • +
              • Fixed an issue where Syndicate Uplink looked different than a normal radio.
              • +
              + +
              Monday, January 5, 2009
              +
                +
              • You can choose to be a nudist now.
              • +
              • Facial hair!
              • +
              • Added constructable flamethrowers.
              • +
              • Redid internal naming scheme for human/uniform sprites.
              • +
              • Helmet visors are now translucent.
              • +
              • Held item graphics corrected for basically everything, internally only uses one dmi file instead of two.
              • +
              • Config settings reorganized for.. organization.
              • +
              • Seperated male and female names.
              • +
              • Females have pink underwear.
              • +
              • Guests can no longer save/load profiles, as this just created useless profiles that weren't used again.
              \ No newline at end of file diff --git a/html/changelog.html b/html/changelog.html index 8a531a10f8cd..b470b3303b67 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -1,1018 +1,1018 @@ - - - - Yogstation Changelog - - - - - - - -
              - - - - -
              -
              Yogstation 13
              - -

              - Visit #coder-public on our Discord: https://discord.gg/p6ShbB8
              - Suggest features on our forum: -- Here -- -
              - - - - - -
              - Current Headcoders: -Click Here-
              - Currently Active GitHub contributor list: -Click Here-
              - Coders: Nichlas0010, ThatLing, GrayRachnid, AsV9, Kmc2000, Moja10, Cruix, X-TheDark, Oisin100, TehFlaminTaco, Kn0ss0s, Super3222, ShadowDeath6, Time-Green, Ephemeralis, Alek2ander, FluffySurvivor, Nirnael, Utahclock, Macelarius, ToGWTF, Adam El-Tablawy, Johncena1469, McDonald072, ArgyleSweatshop
              - Spriters: Partheo, Kmc2000, Andy, Silvaplex, Dagdamnit, Maya/Larissa
              - Mappers: Nichlas0010, GrayRachnid, Matskuman5, Utahclock, Stealthkibbler, MayhemSailor, Yewmi
              - Sounds: Skie, Lasty/Vinyl
              - Thanks to: /tg/station, FTL13 devs, Baystation 12, /vg/station, NTstation, CDK Station devs, FacepunchStation, GoonStation devs, the original SpaceStation developers and Invisty for the title image.
              Also a thanks to anybody who has contributed who is not listed here :( Ask to be added here on our discord.
              -
              Have a bug to report?
              Visit our Issue Tracker.
              - Please ensure that the bug has not already been reported and use the template provided here.
              - Want something minor added? Check out this topic! -
              - - -
              - -

              22 July 2019

              -

              Identification updated:

              -
                -
              • Deodorant is now a small item rather than a normal-sized item.
              • -
              • Certain suits could not hold tanks.
              • -
              • Certain suits can now hold plasma tanks.
              • -
              • Adds a donor borg skin for Qe.
              • -
              -

              TG stealth nerf removal updated:

              -
                -
              • holy melons work in pockets again
              • -
              -

              :b:eos updated:

              -
                -
              • blood will now actually clean off of shoes and gloves
              • -
              -

              Altoids updated:

              -
                -
              • Admins can now delay the shuttle from launching!
              • -
              • Admins can now smite any mob capable of life.
              • -
              • The examine messages for radioactive things should be clearer and better-formatted now.
              • -
              • Fixed thralls and slings sometimes not receiving their deconversion message when blinded.
              • -
              • Borged thralls should now no longer be discriminated against by their deconversion message.
              • -
              • Mentorhelps now have a (mostly) unique sound effect!
              • -
              • Stealthmins can now be pinged by their fake ckey.
              • -
              • Admins can now play AI Vox messages at arbitrary speeds!
              • -
              • Syndrones, Cogscarabs, and FREEDRONES are no longer forced into pacifism.
              • -
              • Cyborgs can no longer move wheelchairs around, as they do not have arms.
              • -
              • Fixed a speed exploit.
              • -
              • This be a big bugfix for bloodbrothers not born bearing their bloodbro box bundle.
              • -
              • Lightbulbs discoloured by spraycans can now actually be cleaned by space cleaner, as intended.
              • -
              • Drone shells can now be found in the Spawner Menu!
              • -
              • Mentorhelps should now be much easier to see, especially in darkmode.
              • -
              • One-way portals can now sometimes send you to multiple different places, from multiple different entry points!
              • -
              • Mentors can no longer call you racial slurs, neither at your face nor among themselves.
              • -
              • Donorborgs are now not all sponsored by Space Cola.
              • -
              • The bluetext of mentorhelps should now be a slightly more legible blue.
              • -
              • Nuclear Operatives will no longer be tasked with detonating bananium bombs they could not possibly get to.
              • -
              • The Traffic Control Console is now available on the techweb!
              • -
              • Altoids worked on something for 30 minutes that will never affect you in any meaningful way.
              • -
              • Extracting a sliver of an SM is now properly logged.
              • -
              • Yogstation-exclusive emojis now override TG ones. Say hello to good ol' thinking!
              • -
              • The blob emojis should now look OK in darkmode.
              • -
              • There's now a verb in the OOC tab that will list to you every emoji that's available in-game!
              • -
              • Ghosts are no longer able to operate storage items, in any way.
              • -
              • You can now enable the Mood feature for yourself in your Game Preferences, if you REALLY want to.
              • -
              • It is now possible to make fusion occur at much lower temperatures, via Dilithium Gas!
              • -
              • Dilithium Gas has been added to the game!
              • -
              • Dilithium Crystals have been added to the game, for facilitating Cold Fusion! Ask your local Miner for it today, Atmosian!
              • -
              • AIs should now be able to actually do things again.
              • -
              • Katanas are now available on the Uplink, for 10 TC.
              • -
              • Admins can now help the deeply confused reach mentors to aide them a lot more easily.
              • -
              • Abductors can now use their tools again.
              • -
              • Quiet Mode no longer has the opposite effect of basically forcing you to be antagonist.
              • -
              • There are new Alcohol mutations that either produce alcohol in your bloodstream, or make you resistant to alcohol in general!
              • -
              • Lava and Anomaly hallucinations now have a 5% chance of actually hurting you like the real thing!
              • -
              • BZ's hallucinogenic properties now actually vary based on concentration. Maximum effect (double the previous maximum) occurs at 1 atm of BZ pressure. Additionally, it now takes 700 Pascal (up from 10 Pascal) of BZ pressure before BZ has any hallucinogenic effect at all.
              • -
              • Body bags will no longer attempt to convince you that they can be welded.
              • -
              • Altoids can now more easily determine how broken shadowlings are.
              • -
              • On July 5th, there is now a birthday event for Oakboscage!
              • -
              • Fixing the powerboard in a broken APC will no longer appear to nearby crewmembers as you literally breaking the APC.
              • -
              -

              Coded by kmc2000,ported by alexkar598 updated:

              -
                -
              • Gave the HOP a special ticket machine to manage their line better.
              • -
              -

              Identification updated:

              -
                -
              • Killer Queen's detonator sound will now play when the explosive holoparasites trap is triggered.
              • -
              • Adds space manatees. They look quite menacing.
              • -
              • New religions have been added.
              • -
              • New colours of striped scarves are available in the clothesmate.
              • -
              • stripedscarf subtype replaces striped[colour]scarf defines
              • -
              • Adds magboots to the engineering suit storage units
              • -
              • Ports and adds new objects to the code for future use.
              • -
              • the yogbox supermatter is now a female
              • -
              • possible erp with the sm. be sure to get an erp pass beforehand
              • -
              • coloured carpets and stairs. not implemented anywhere yet.
              • -
              • Adds a bartender's secure closet to all iterations of the bar on yogbox.
              • -
              • The bartender's double barrel shotgun is now in their new secure closet.
              • -
              • Cooks Officer Kreb and imports SGT. Areneus from the space nest.
              • -
              • Budget cards now spawn in the job's respective locker.
              • -
              • Adds a cell charger to the free miner ship. Adds fans to the airlocks that were missing them.
              • -
              • The Free miner RIPLEY can actually be built and made space-proof. Engines also face the correct direction.
              • -
              • Adds coloured carpets to YogBox and are now able to be ordered from Cargo.
              • -
              • No ERP allowed.
              • -
              • Removes the cake hat from the donor hat list as it offered a mechanical advantage.
              • -
              • Adds a shower to the cloner.
              • -
              • Yogs items are added to certain lockers differently.
              • -
              • Adds Partheo's scrubber sprites.
              • -
              • Yogbox janitors closet has a maid uniform now
              • -
              • Labcoat front sprite tweaked to be more realistic, and labcoats start toggled open.
              • -
              • The RD's office now has a portrait of Cave Johnson.
              • -
              • Envirosuits will no longer stick out of suits.
              • -
              • The price of the Australian Slime Mutator is now 25 TC rather than 10 TC.
              • -
              • The Gods can now call forth the Alien Queen maid through their godly powers.
              • -
              • Lusty, Sexy.
              • -
              • Revenants have a new sprite, which is now four directional.
              • -
              • Orion Sec Officers no longer spawn with hybrid tasers.
              • -
              • Updates the clerk's outfit and adds skirts for the Clerk and Signal Technician.
              • -
              • Minibars now have directional sprites.
              • -
              • Virus cures should no longer reshuffle in a different host.
              • -
              • The empowered burger no longer has a jarring sprite.
              • -
              -

              JJRcop and Kmc2000 (sprites) for the code, theos for porting and QoL stuff updated:

              -
                -
              • Adds the stasis bed, a medical machine that can stabilize a patient and stops the body from deteriorating after death
              • -
              • The stasis bed is capable of being used as a fully functional surgical platform, though the patient will need to be buckled to it to apply advanced surgeries
              • -
              -

              Ktlwjec, Partheo updated:

              -
                -
              • Adds a new crafting recipe: Lasagna!
              • -
              • Lasagna!
              • -
              -

              Matias Sanhueza updated:

              -
                -
              • Replaced the throwing star sprite
              • -
              -

              ShadowDeath6 updated:

              -
                -
              • Floor cluwnes emp once rather than 36 times when emerging from the floor dimension
              • -
              -

              Space PETA updated:

              -
                -
              • After an analysis of Nanotrasen medical systems, gorilla related biology was found to be of illegal origin, and thus phased out by sPETA.
              • -
              -

              Xeos updated:

              -
                -
              • Xeno queens will delay the escape shuttle for a while unless they are killed
              • -
              • the shuttle will get auto called at the end of the timer if the queen is alive and it hasn't been called already to prevent super xeno extended rounds
              • -
              -

              ack updated:

              -
                -
              • activates sleeper bandaid
              • -
              -

              kittymaster0, ktlwjec updated:

              -
                -
              • Cleaned up Syndiebase after TC station removal.
              • -
              -

              nemvar updated:

              -
                -
              • Trash from food now gets generated at the location of the food item, instead of in the hands of the eater.
              • -
              -

              soeht updated:

              -
                -
              • the chaplain can now choose a fancy spear as a nullrod
              • -
              -

              4dplanner updated:

              -
                -
              • Humans can no longer use UIs while incapacitated
              • -
              • stabilised sepia slime randomly changes your speed as intended
              • -
              • Colossi now cannot kill themselves with colossus bolts.
              • -
              • The deja vu effect (previously only used by rewind camera) now resets all your limbs and damage to the point it was added after 10 seconds (as well as the original position reset). The deja vu effect cannot resurrect the dead, but will heal their corpse. New limbs fall off, old ones re-attach.
              • -
              • Regenerative sepia cores add a deja vu effect before healing instead of a timestop
              • -
              • The rewind camera rewinds 2 times to distinguish it from the regenerative core
              • -
              -

              AffectedArc07 updated:

              -
                -
              • The taskbar icon for the game is now the TG logo
              • -
              • Status displays are readable now
              • -
              • Religion is now a globalvar instead of being a subsystem for some reason
              • -
              -

              AffectedArc07 and Shazbot updated:

              -
                -
              • Added 9 new sock styles
              • -
              -

              Altoids updated:

              -
                -
              • Xeno vore and other miscellaneous TG-style vore have been readded! Nothing raunchy though, unfortunately.
              • -
              • Removed the antimatter engine
              • -
              • Readded the antimatter engine
              • -
              • Vanilla icecream should now have a sensible name and a working sprite.
              • -
              • Buffs powercreep
              • -
              -

              AnturK updated:

              -
                -
              • Fixed ED209 not shooting any projectiles.
              • -
              -

              Arkatos updated:

              -
                -
              • Examine tooltips now work on items put into storage, such as backpacks
              • -
              • Jump to Node ability now shows a location of each Blob node
              • -
              • Added SlimeHUD, which means slimes will have their own custom health display indicator and red corner injury overlay
              • -
              • Action buttons can now be dragged onto each other to swap places
              • -
              -

              Cyberboss updated:

              -
                -
              • ALL OF VORECODE WAS REMOVED! THE HORROR!
              • -
              -

              Dorsidwarf updated:

              -
                -
              • Nanotrasen washing machines can now extract the raw power of the cosmos from bluespace crystals.
              • -
              -

              Eaglendia updated:

              -
                -
              • You can light a cigar on the supermatter shard. Handle with care!
              • -
              -

              Erwgd updated:

              -
                -
              • Swapped out an epinephrine bottle for potassium iodide in NanoMed Plus vending machines.
              • -
              -

              Fikou updated:

              -
                -
              • NEWS REPORT: The latest alien technology allows them to abduct cows instantly, this is a great tragedy for all farm owners, better watch the garden!
              • -
              -

              Fire Chance updated:

              -
                -
              • Fixed the drink sprites overlapping when you are mixing beer with other drinks.
              • -
              -

              Floyd / Qustinnus updated:

              -
                -
              • New scientist traitor item: Australian Slime Mutator / Spider Injector, use it on a gold slime extract to create 3 neutral broodmother spiders, make them sentient and start your own hive.
              • -
              -

              Floyd / Qustinnus, Sprites by MrDoomBringer updated:

              -
                -
              • New traitor item: The suspicious phone. This device lets you launch a device that drains the funds of all crewmembers to your bank account, the only way for them to stop this is by rushing to the conventionaly launched spacecoin market where the crew can sell their Spacecoin.
              • -
              -

              FlufflyCthulu updated:

              -
                -
              • Fixes an issue with clown hulk simple mobs
              • -
              -

              Garen updated:

              -
                -
              • You can only make one construct when using twisted construction on a borg.
              • -
              • You can no longer do custom emotes with fists of the north star.
              • -
              • Fixes being able to pull from belt/backpack when you are stunned or cuffed.
              • -
              -

              Garen7 updated:

              -
                -
              • Lesser form lings can now toggle augmented vision
              • -
              • You can no longer turn back into human form as a lesser form ling while ventcrawling
              • -
              • Stuns and Reagents are now passed when transforming into a monkey or human
              • -
              -

              Identification updated:

              -
                -
              • Wooden bucket sprite fixed
              • -
              • fixes a few food sprites
              • -
              • Preterni now have their own scream.
              • -
              • A fix with an area of Moth wing sprites not appearing. Moths should look a little bit different now.
              • -
              -

              JJRcop updated:

              -
                -
              • Live facehugger eggs and dead facehuggers removed from xeno lavaland ruin.
              • -
              • You can now build a new PDA messaging server if you lose it.
              • -
              • Requests console messages no longer work if the messaging server is offline. Emergency alerts will still function in that case as long as the rest of telecomms is online.
              • -
              • Removed the piping overlay from the Lifeform Stasis Unit
              • -
              • Airlocks open when items hit them
              • -
              • Stasis lets reagents know processing was stopped, fixing some issues.
              • -
              • Amanitin's damage only triggers when it is completely removed from your system, not when processing stops.
              • -
              -

              Jerry Derpington, baldest of the balds updated:

              -
                -
              • Nanotrasen has lost communication to two away mission sites that contained a beach for Nanotrasen employees.
              • -
              • Nanotrasen has been able to locate a new away mission site that ALSO has a beach. Nanotrasen employees will be able to enjoy the beach after all!
              • -
              -

              MMMiracles updated:

              -
                -
              • Donutstation: CE's windows are electrified.
              • -
              • Donutstation: Atmospherics now has 2 rad suits and an extra regular fire suit.
              • -
              • Donutstation: The pipe dispensers and redundant tech fabs in Atmos were removed.
              • -
              • Air alarm for Donut's telecomms server is the proper type
              • -
              -

              Matias Sanhueza updated:

              -
                -
              • I changed the Rezadone's description since it is not based on Carpotoxin anymore, thus, the new recipe being Capsasin, now the description is about Chilli's and spicy taste
              • -
              -

              Menshin updated:

              -
                -
              • Janitor ERT leader now spawns properly equipped
              • -
              -

              MrDroppodBringer and Time-Green updated:

              -
                -
              • Adds the lipid extractor to the service protolathe arsenal
              • -
              -

              MrFluffster updated:

              -
                -
              • Moths like eating some sorts of clothes, now.
              • -
              -

              Naksu updated:

              -
                -
              • mindshield nanites no longer give mindshields to revheads and one mind awoken hiveminds.
              • -
              • Fixed several issues with venus human traps and their vines going through walls
              • -
              • Items using atom colors for coloring now show up properly colored in vending machines, rather than snow-white
              • -
              • Cryotubes no longer display a double of a person that was once in but teleported out by having ingested bluespace or other such nonsense
              • -
              • corpses no longer feel, light-headed or otherwise
              • -
              • nukeop uplinks now have 5 unlimited discounts and 10 limited discounts shared between the team, rather than 3 individual discounts like traitor uplinks
              • -
              • The number-two most-called proc get_area() is now a define rather than a proc. All coders please clap.
              • -
              • Pipeline gas reconciliation is now faster. This should improve atmos performance in pipes.
              • -
              • the nuclear disk is now abductor-proof.
              • -
              • Odysseus chem synthesizing now works again
              • -
              • Added a new blob strain that grows inside the station and eats garbage
              • -
              • Free nanomeds have been removed from mining capsules
              • -
              • /datum/reagent/vanilla ice cream has been replaced with regular vanilla ice cream in the borgs' treat fabricator
              • -
              • Spamming the "create virus culture bottle" button in the pandemic no longer sometimes results in multiple bottles being created.
              • -
              • Electromagnetic web blob strain now has its in-game descriptions back.
              • -
              • Removed individual buttons text in crayon/spraycan UI, speeding it up.
              • -
              • Text mode buffer is actually visible in the UI.
              • -
              • Last letter of a text mode buffer no longer rotates out to be replaced with "a", allowing the text mode to be used for individual symbols.
              • -
              • Fixed a couple of map issues in DeltaStation
              • -
              -

              OnlineGirlfriend updated:

              -
                -
              • Lemonade has been added! Some bar recipes now require it instead of lemon juice.
              • -
              -

              Onule updated:

              -
                -
              • New stasis bed sprites!
              • -
              • Corpse flowers have been given proper sprites
              • -
              -

              PHDby updated:

              -
                -
              • Reconstruction is now available roundstart (nerfed)
              • -
              • You can upgrade both the Burn/Brute trees via techwebs
              • -
              • Advancing far enough into the tree enables Mixed reconstruction surgery, which heals less of each but is considerably faster. Get alien tech for the best of the best!
              • -
              -

              Putnam3145 updated:

              -
                -
              • All departments can now print basic power cells from their respective techfab.
              • -
              -

              RaveRadbury updated:

              -
                -
              • Quirks have flavor text in medical records
              • -
              • All quirks have medical record entries
              • -
              • All quirk medical records refer to "Patient", removing a few instances of "Subject"
              • -
              -

              Rowell updated:

              -
                -
              • Added thigh-high and knee-high bee socks.
              • -
              • Removed black stockings
              • -
              -

              Shaps/Ryll updated:

              -
                -
              • NT has updated its personnel policies and allowed for employees to register as gender neutral, with standard support for they/them pronouns.
              • -
              • Mosin Nagants are now bulky, but can be sawed off to fit in a bag
              • -
              -

              ShizCalev updated:

              -
                -
              • Pulling someone while they're buckled to a chair will no longer offset them and make them face you.
              • -
              • Buckling someone in while they're being pulled will now properly reset their offsets.
              • -
              • Pulling offsets will now update properly while dragging someone who's laying down.
              • -
              • Setting a defibrillator unit on fire will now make it's paddles appear on fire too when pulled out!
              • -
              • Setting defibrillator paddles on fire will now cause the flames to run along it's cables and set the main unit on fire as well.
              • -
              -

              Skoglol updated:

              -
                -
              • Ghost darkness now starts at night vision level by default.
              • -
              • Ghost hud on by default.
              • -
              • Moved machine and computer frames below objects, parts are now always on top.
              • -
              • Moved structures (chairs, closets, windows, cult altars etc etc) below objects.
              • -
              • Moves mineral doors to airlock layers
              • -
              • Added "Never for this round" to shades, slimes (pyroclastic anomaly), defective clone, imaginary friend and split personality ghost popups.
              • -
              • Revival surgery now asks you to shock body instead of repair it.
              • -
              • All surgeries now have capital letters.
              • -
              • Airlock cycling no long breaks if something is in the way. As a bonus, you can now door crush people.
              • -
              • Rolling blood brother now has a sound alert and bigger text.
              • -
              • Chem dispenser buttons are now sorted alphabetically again.
              • -
              • Advanced surgical tools can now be found in the tool section with their basic counterparts.
              • -
              • Special .38 ammo now can be found in the ammo section with the other ammo types.
              • -
              • Create object admin tool enter key behaviour changed.
              • -
              • Fixed a slime runtime.
              • -
              • Heaters/freezers now support ctrl clicking to turn on and alt clicking to min/max target temperature.
              • -
              • Heaters/freezers now shows target temperature and part status on examine.
              • -
              -

              SpacePrius updated:

              -
                -
              • Added empty boxes.
              • -
              -

              SuicidalPickles updated:

              -
                -
              • Rolls of gauze now works on corpses.
              • -
              • EMP flashlights have had their cost changed to 2 telecrystals.
              • -
              -

              Swagile updated:

              -
                -
              • Cargo key in mining conscript kit is now a mining key.
              • -
              -

              Tetr4 updated:

              -
                -
              • Suicides are permanent for the nth time
              • -
              • Turning a tile with gas effects into space now gets rid of the effects.
              • -
              -

              Tlaltecuhtli updated:

              -
                -
              • cargo ripley can be actually constructed with what you get from the crate
              • -
              • adds the right crate type to monkey cube and hydroponics refill crates
              • -
              • readds the bulk wt550 ammo crate
              • -
              • conveyor belt crate has 15 instead of 6 belts
              • -
              • adds a laser pointer to the bureaucracy crate
              • -
              • changes med supply crate to have rng medical things
              • -
              -

              Trilbyspaceclone and nemvar updated:

              -
                -
              • You can now craft more fancy boxes with cardboard.
              • -
              -

              Vile Beggar updated:

              -
                -
              • The drill hat is less annoying now. You can change the loudness setting of it by using a screwdriver on it. Use wirecutters on it for a surprise.
              • -
              -

              WJohn updated:

              -
                -
              • Raven emergency shuttle's turrets no longer shoot at ian.
              • -
              -

              WJohnston updated:

              -
                -
              • The Daniel shuttle can no longer be purchased and is now admin only.
              • -
              • Having very high sanity no longer takes away your action speed bonus.
              • -
              • Sanity no longer constantly dips beyond its maximum/minimum, which caused issues.
              • -
              -

              XDTM updated:

              -
                -
              • Decreased the cost of the advanced surgery node to 1500 from 2500, as was intended when adding the Improved Wound Mending node.
              • -
              • Interrupting a surgery step or finishing a repeatable surgery step no longer uses the tool on the target, attacking or forcefeeding them.
              • -
              • Beepskys spawned from Beepsky Smash can no longer be heard by bystanders.
              • -
              • The beepsky smash trauma can no longer be gained as a special trauma.
              • -
              • Added a new advanced surgery, Muscled Veins: it makes the patient able to survive without a heart.
              • -
              • Fixed teleportation deleting mob spawners like golem shells and ashwalker eggs.
              • -
              • Using the wrong surgery tool during surgery no longer attacks the patient, if on help or disarm intent.
              • -
              • Fixed not being able to uninstall Voice Sensor nanite programs from the cloud console.
              • -
              • Hypnosis and mind control now have alerts on the right side of the screen when active.
              • -
              • Hovering over the hypnosis alert will display the hypno-phrase.
              • -
              • Clicking the mind control alert will display the order again.
              • -
              • Launchpads now gain 15 range instead of 1 when upgraded, up to a maximum of 60.
              • -
              • Launchpads now have a targeting reticle which is visible on Diagnostic HUD. It also indicates if the launchpad is currently sending or receiving.
              • -
              • Launchpad Consoles now use TGUI and are far more responsive. They can also set their coordinate offsets manually instead of only using arrows.
              • -
              • Fixed abductor scientists not being able to perform advanced surgery.
              • -
              • Antimagic items now also properly work when held in hand, instead of only when equipped.
              • -
              • Added a new abductor gland that randomizes blood type into a random reagent periodically.
              • -
              • Added a new abductor gland that links to other people, and swaps their position with the owner. Mind control on this gland will affect the linked person.
              • -
              • Added a new abductor gland that grants all access to the owner.
              • -
              • Abductor scientists can now properly see the true name of glands when examining them.
              • -
              • Mind control on mindshock glands now "broadcasts" the mind control, affecting bystanders but not the abductee.
              • -
              • Species gland now has 7 mind control uses, instead of 5 (duration is still 30 seconds).
              • -
              • Viral healing symptoms that are tied to reagents now also require a functioning liver to work.
              • -
              • Holy water, Pyrosium, Cryostilane, Napalm and Phlogiston no longer need a liver to have their effects.
              • -
              • Abductors can now buy a special chem dispenser for 2 points. The machine is delivered as a beacon which can be used when standing on any free tile to spawn it.
              • -
              -

              Yenwodyah updated:

              -
                -
              • Non-antags can now forge all agent IDs if they find them before they've been forged.
              • -
              -

              YoYoBatty updated:

              -
                -
              • Body part damage is now run through armor check in certain situations.
              • -
              -

              Zxaber updated:

              -
                -
              • Cargo manifests no longer list the purchaser as "/datum/bank_account" under certain circumstances
              • -
              • AI power backup is now separate from the health, and is tied to their core. Carding an AI on battery backup will leave the core at partial charge. AIs no longer slowly lose integrity while on backups (but will instantly die as before once the backup runs out). As such, whacking an AI with a wrench will no longer magically lower their backup battery capacity.
              • -
              • AIs on backup battery can now unbolt at the cost of a quarter of their battery's max charge. This cannot be done while at under 25% power.
              • -
              • Fixed all mechs being fire/lava proof.
              • -
              -

              actioninja, Altoids updated:

              -
                -
              • Lot of small darkmode fixes
              • -
              -

              as334 updated:

              -
                -
              • New Gas Reaction, Stimulum Energy Balls. Combine 100 degree Pluoxium, Stimulum and small amounts of Plasma and Nitryl to shoot off a deadly radioactive energy ball.
              • -
              • The reaction will consume the plasma, pluoxium and stimulum and release small amounts of heat. The presence of water vapor will change the direction in which it shoots. See if you can figure out how to aim it.
              • -
              • Nitryl now forms at 22,000 degrees, a significantly lower temperature.
              • -
              • fixed a few things
              • -
              • Removed some old left-behind fusion test canisters.
              • -
              -

              bandit updated:

              -
                -
              • Surgical incisions cause actual bleeding now. Clamping bleeders and/or cauterizing the incision stops that bleeding.
              • -
              • Art now affects mood depending on its quality.
              • -
              • Nanotrasen's clowns have begun to preserve their images by stamping eggs with their NT-issued clown stamps. This is fine and normal.
              • -
              • Nanotrasen chefs and botanists have finally learned to stop composting or grinding bowls and the like along with the food they come with.
              • -
              • After years of arduous training, Nanotrasen crew can now tell whether an animal is dead by examining it.
              • -
              -

              bgobandit updated:

              -
                -
              • The Traitor Panel now shows the names of blood brothers' brothers.
              • -
              -

              blargety & WJohnston updated:

              -
                -
              • Caution sign resprited
              • -
              • You can now wear the caution sign on your armor slot
              • -
              -

              bobbahbrown updated:

              -
                -
              • Monkeys now apply knockdown when tackling instead of stunning, and can stun you by tackling you whilst you are knocked down.
              • -
              • Deaths are now logged to the game log.
              • -
              -

              granpawalton updated:

              -
                -
              • miasma now only spawns below 315 kpa
              • -
              • miasma now spawns at room temperature instead of 0 kelvin
              • -
              -

              hey there uwu updated:

              -
                -
              • You can no longer choose the zombie race from the magic mirror, as intended.
              • -
              -

              jegub updated:

              -
                -
              • Lets the grass grow under your feet.
              • -
              -

              kriskog updated:

              -
                -
              • Communications console window no longer updates, won't steal focus anymore.
              • -
              • windoors can now be closed manually after bumping open without having them close again.
              • -
              -

              nemvar updated:

              -
                -
              • You can now reuse chemical grenade casings if they fail to react.
              • -
              • You can now adjust the timer of grenades with a multitool or screwdriver. You can put it anywhere between 3 and 5 seconds. Instant detonations are also possible.
              • -
              • Advanced release grenades now open a window if you want to change the amount of units released.
              • -
              • The default timer on smoke grenades is now 3 seconds.
              • -
              • Above ground smugglers satchels no longer leave a phantom object behind on t-ray scanners
              • -
              -

              optimum wesoda updated:

              -
                -
              • Sprites for Meat and Chaos Donuts
              • -
              -

              pireamaineach updated:

              -
                -
              • Axes some hairstyles that didn't make the final cut but never got fully removed, removes an unused sprite.
              • -
              • Fixed the cornrows because they were bRoek
              • -
              -

              plapatin, sprites by cogwerks and edited by mrdoombringer updated:

              -
                -
              • vomi/tg/oose
              • -
              -

              shellspeed1 updated:

              -
                -
              • removes an exploit using plux to generate massive amounts of credits.
              • -
              -

              weeeeesoda updated:

              -
                -
              • You can now purify soulstones! This can be done by any crew member using a bible on one.
              • -
              • Purified constructs can be created by using a purifed soulstone on a construct shell. Amongst their normal abilities, purified constructs can dispel cult runes by clicking on them.
              • -
              • Ability to place chaplains soulstone or mining soulstones in construct shells
              • -
              • angelplasm, angelic sprites, purifed sprites
              • -
              -

              wesoda gamer the twenty fifth updated:

              -
                -
              • changed text on gender options to make them grammatically correct and visually pleasing
              • -
              -

              wesoda123555 updated:

              -
                -
              • physically obstructive quirk
              • -
              -

              1fbff5f83b23d39d38b1dfcb4cac8d9b updated:

              -
                -
              • TGUI buttons do not fully grey out while the UI is still updating
              • -
              • Adds new sprites for all berets.
              • -
              • Adds the sprites for the archaic and vintage berets.
              • -
              • Removes old sprites for all berets.
              • -
              • Adds the archaic and vintage berets, the vintage one being the current one and the archaic one being the one from 2014.
              • -
              • Adds the vintage and archaic berets to the autodrobe.
              • -
              • Stuff in the Character Setup screen is no longer way too close to the edge of the window.
              • -
              • Downmixed every sound to mono. This shouldn't have resulted in any changes in audio, byond downmixes anyways.
              • -
              • Recut some sounds. Sounds shouldn't have large silent/noise gaps at the start anymore
              • -
              -

              AdamElTablawy updated:

              -
                -
              • Red slime potions have been fixed!
              • -
              • clusterbang and SRM-8 missile launchers now take syndicate weaponry to unlock due to balance issues
              • -
              -

              AsV9 updated:

              -
                -
              • Made the cosmic ashwalker ruin stop spawning automatically.
              • -
              • Blood brothers no longer spawn with traitor bundles.
              • -
              • Blood brothers now spawn with an integrated pinpointer that shows the direction to their teammate.
              • -
              -

              Codeatmos updated:

              -
                -
              • Added Plumbing from /tg/
              • -
              • Added the images for plumbing from /tg/
              • -
              -

              GenericDM updated:

              -
                -
              • preternis can be gibbed
              • -
              • preternis produce synthmeat when gibbed
              • -
              • You can now shove an emag into a jack o' lantern to turn it into a hack o' lantern
              • -
              -

              Ihonkification updated:

              -
                -
              • The QM now has a envirosuit.
              • -
              • Yogjobs, clown, and mime are missing custom or updated envirosuits.
              • -
              • Envirosuits have been resprited.
              • -
              • Most of the old envirosuits.
              • -
              • The white jumpskirt now has a object sprite.
              • -
              -

              Ktlwjec updated:

              -
                -
              • The mining medic now has an office on lavaland!
              • -
              • Service-Security headset defaults to Security.
              • -
              • :egg:
              • -
              • The cosmic ashwalker ruin is much bigger.
              • -
              • Miners and Mining Medics can no longer start as rev heads.
              • -
              • Perma prisoners now have access to the perma vendors.
              • -
              • Combat magboots can be found in the security suit storages.
              • -
              • Minsky brig has been redesigned slightly.
              • -
              • Minsky bridge has been rearranged slightly.
              • -
              • Omegastation now has a perma room.
              • -
              • Captain clothing items can now be found in the Captain's Autodrobe.
              • -
              -

              Lostlarry updated:

              -
                -
              • Added trafic consoles to Tcomms on YogsDonut, YogsDelta, Yogsmeta
              • -
              • Removed monitoring consoles from Tcomms on YogsDonut, YogsDelta, Yogsmeta
              • -
              • Adds three headsets to cloning spare clothes locker on YogStation, YogsPubby, YogsDonut.
              • -
              • Janniborgs can now refil lightreplacer by clicking on glass stacks.
              • -
              • tweaks latices outside disposal to prevent objects getting stuck
              • -
              • Added air alarm to Cargo Office on yogbox
              • -
              • Fixed pipe connecting atmos and SM on Delta
              • -
              • Virology cameras now have unique names
              • -
              • Fixes janni wardrobe so holoprojector can be put in the janitor belt
              • -
              • Virologi windoor now needs atmos acsses as well
              • -
              • Engineers now can access gravity generator on delta
              • -
              • Engineers now cant access atmos on delta
              • -
              • Most of cyborg can't be installed twice now.
              • -
              • Perma cameras on Yogstation are on the right network and properly named.
              • -
              • adds backup UI mode for sleepers activateable by admins
              • -
              • Sleeper backupUI is not operable from inside the sleeper
              • -
              • all tables in toxins are now reinforced
              • -
              • sleepers no longer work with no power
              • -
              • Sleeper backupUI looks way better now
              • -
              • Sleepers can be interacted with when they have controls inside
              • -
              • people revived by MMI will be reminded that they don't remember their death
              • -
              • posibrain inserted to mecha is reminded that they still serve the crew and ai.
              • -
              • fixed long range cycle links
              • -
              -

              Lynameep updated:

              -
                -
              • Overhauled the Russian Bunker Ruin to be more difficult overall.
              • -
              • Atmospherics Distribution is now its own separate /area/.
              • -
              • Fixed Yogbox's atmospherics being powered by two APCs at the same time.
              • -
              • Added the Centcom Test Chamber which is accessible by a portal after arriving at centcom.
              • -
              • very Omegastation
              • -
              • much experimental
              • -
              -

              MacHac updated:

              -
                -
              • Items worn under fireproof suits will no longer catch fire.
              • -
              • Bubblegum will no longer call slaughterlings to his aid.
              • -
              • Legion cores won't hold you together forever anymore.
              • -
              -

              Mr-Space updated:

              -
                -
              • Added white caps from Chernobyl
              • -
              • This is experimental, reactor may explode. There's no graphite on ground, radiation levels at 3.6 röntgens.
              • -
              -

              Nickvr628 updated:

              -
                -
              • New contraband poster at the request of yogstation staff.
              • -
              -

              SiclaRogue updated:

              -
                -
              • Removed cargo paramedic spawn
              • -
              • Added cargo mining medic spawn
              • -
              -

              Sir-Lagsalot updated:

              -
                -
              • Adds Zoroastrianism as a chaplain religion, along with a custom holy book sprite
              • -
              -

              SomeguyManperson updated:

              -
                -
              • Extra arms for nuke ops now cost 15 TC each, up from 5 TC.
              • -
              • The bananium frying pan can now randomly crit
              • -
              • exile implants are no longer orderable from cargo since they do an astounding amount of nothing
              • -
              • Fluke ops can now buy holoparasites to live out their fantasies of being dead
              • -
              • Ratvar will now dunk on virgin constructs instead of just trying to set them on fire like a casual
              • -
              • Space bartender can now open their refrigerators
              • -
              • Voice analyzer no longer starts on inclusive so it won't detonate on hearing anything automatically
              • -
              • Syndicate speargun will start able to embed instead of needing to be adjusted to do so
              • -
              • walking into people will now cause them to move again
              • -
              • machines will now drop stuff again when deconstructed
              • -
              • explosions will no longer be delayed by thanks TG
              • -
              • PDA messages should now work again :>))))))))))))))))))))))))))
              • -
              • Rezadone is made with capsaicin instead of carpotoxin
              • -
              • RnD icons are back to normal
              • -
              • mechs will now only be able to drop one cable coil instead of being able to drop solely coils
              • -
              • clock cult armor should now appropriately buff itself when war is declared or ratvar shows up
              • -
              • coders can now check the suit storage slot for items
              • -
              • You can now attempt to defribillate people by shocking them. It's not exactly "safe" but it works.
              • -
              -

              ThatLing updated:

              -
                -
              • Hour requirements for some jobs have been altered, check https://forums.yogstation.net/index.php?threads/vote-raise-minimum-required-playtime-for-security-roles.18893/ and https://forums.yogstation.net/index.php?threads/vote-make-heads-of-staff-require-12-hours-of-department-time.18847/ for more info.
              • -
              -

              TheGamerdk updated:

              -
                -
              • Changed the Academy die of fate to a single use one.
              • -
              -

              alexkar598 updated:

              -
                -
              • abductors works now
              • -
              -

              fluffe9911 updated:

              -
                -
              • Four new AI Donor Skins 3 made by Fiodoss 1 by me!
              • -
              • Minsky Station has been authorized for use during populous shifts..again!
              • -
              • The Gas Station Clerk Actually gets teleported back now!
              • -
              • The Gas Station and Space Bar teles now actually appear on the teleporter menu
              • -
              • You can now make a living egg dog using the crafting menu!
              • -
              • Added a goat and king goat plushies to liven up (and possibly harm) the crew!
              • -
              • Added a number of Nanotrasen-approved religious clothing items to the chaplain vendor, autodrobe and clothesmate.
              • -
              • sprites and icons for several new clothing items, including some in-hand sprites.
              • -
              • An extra tile to the Box chapel's width
              • -
              • Clerk now has a cash register
              • -
              • Replaced the stools with chapel pews
              • -
              • Spiffles has been sent to the farm up north for a week and has came back better then ever!
              • -
              • Replaced the centcom megafauna with some exotic mobs cause the megafauna kept gettin out
              • -
              • Replaced the sm crystals with ducks cause sm spam bad
              • -
              • You can no longer spawn neutral mobs and not have the king goat fight back
              • -
              • Floor cluwnes will no longer flip out if you transform into something else while its grabbing you
              • -
              • Floor cluwnes will now emp the area around it while its grabbing something
              • -
              • The Vortex Tailsman no longer works in the King Goats Arena (and other noteleport areas)
              • -
              • Shadowling thralls are now discovered by looking at there eyes instead of scanning there body temp
              • -
              • Capitalist and Communist Golems will now know what they are actually doing!
              • -
              -

              yacabo updated:

              -
                -
              • Tricorders have been added! They act as a mix of Gas Analyzer, Multitool, and whatever else Yacabo thought to add!
              • -
              -

              ynot01 updated:

              -
                -
              • "Ass Blast USA" vox as roundend
              • -
              • Empowered Burger sprite has been fixed
              • -
              • Added Boodaliboo's donor colaborg
              • -
              • Raised Crab-17 health even higher
              • -
              • Scientific budget has been nerfed
              • -
              • Makes holotool sound slightly shorter than full RPED sound
              • -
              • Ocular wardens will no longer target and heal slimes
              • -
              -

              yogstation13-bot updated:

              -
                -
              • cargo sectech crate
              • -
              • sectech refill has right icon
              • -
              • Metastation's mix to engine pipe is no longer broken.
              • -
              • Pubbystation: Fixed the incinerator's waste to space injector starting powered off.
              • -
              • The modular pc vendor
              • -
              • minor entities on meta/pubby/box/delta to make room for said vendor
              • -
              • Nuclear Operative reinforcements now get uplinks with 0 TC and a real name.
              • -
              • If an admin sends a ghost back to the lobby, they can now choose a different set of quirks.
              • -
              • Rolling a 17 on the d20 of fate no longer gives you an empty box.
              • -
              • Hangar Bay Shutters need only general CentCom access to open now
              • -
              • Podcloning now lets you keep your quirks.
              • -
              • After a very long period of being broken, the training bomb in security finally works properly again.
              • -
              • Syndicate bomb cores have become a little more robust in their operation. Defused but still cored bombs are no longer inert and can be set again. Would be heroes are advised to cut out and dispose of bomb cores, and dismantle unneeded bomb shells to be absolutely sure.
              • -
              • "Religiously Comforted" from Spiritual Quirk occurs on examine and lasts for 5 minutes
              • -
              • edaggers have a special suicide!
              • -
              • between the sheets heals carbons
              • -
              • The monk's frock has been sanctified and will no longer reveal your naked body when the hood is pulled up.
              • -
              • colossus and drake can no longer attack from the afterlife
              • -
              • Colored lights now shine in different colours.
              • -
              • Gravitokinetic stands are now available!
              • -
              • Traitors now recognize codewords automatically in spoken conversation. Phrases in blue, responses in red.
              • -
              • Changed traitor greet text slightly.
              • -
              • Shuttle sounds should no longer ass blast your ears as they no longer play PER DOOR and instead from the nearest engine, or door if none are available.
              • -
              • Distant shuttle sounds.
              • -
              • Mops can now be printed at the autolathe and protolathe.
              • -
              • fixes message spam from riding a vehicle without enough legs/arms
              • -
              • timestop is properly defeated by antimagic.
              • -
              • timestop only checks antimagic once
              • -
              • Med Record, Sec Record, and Detective Scanner printed papers use paper_words icon state
              • -
              • Cards Against Spess works again. Find decks at your local library.
              • -
              • Some hivemind abilities have new icons.
              • -
              • Descriptions for every crossbreed's effect (minus recurring because its self explanatory).
              • -
              • Changes a single comment in _corecross to reflect how many extracts are needed to crossbreed.
              • -
              • Nanotrasen has cracked the code of the Syndicate storage implant. Removing the implant will also drop any items contained within, doing minor damage to the person implanted.
              • -
              • Fedoras/taqiyahs fit pocket sized items. Fedoras can also store the tommy gun and katana.
              • -
              • Forensic Scanner documents are numbered now.
              • -
              • Forensic Scanner document titles modified
              • -
              • The genetic sequencer has been reworked. Use it in-hand to view the sequence
              • -
              • Added new Teleporter Station sprites
              • -
              • fixes a missing vendor board option
              • -
              • Linen bins can now be (un)achored with a wrench, or disassembled with a screwdriver (when empty).
              • -
              • You can now make linen bins using two rods.
              • -
              • Security can now print out Missing Persons Posters
              • -
              • Scrubbers now stop scrubbing if their internal pressure gets too high.
              • -
              • sugarcane is now available in vendors
              • -
              • New RPGLoot modifiers: Vampirism which heals you when you attack, Pyromantic which sets things you hit on fire. Shrapnel which causes projectiles fired from a gun to fire projectiles in a radius when they hit something. Finally, Summoning which summons mobs that sometimes aid you in combat.
              • -
              • thrown objects (but not mobs) no longer hit the thrower
              • -
              • mirror shield rebound no longer depends on the original thrower's momentum
              • -
              • Added support for a widescreen toggle, actual widescreen will require a config change.
              • -
              • Auto-fit view is now the default. This will only apply to new players.
              • -
              • WJohnston - Created a minimalist font.
              • -
              • Security can put custom headings on wanter/missing posters.
              • -
              • Added teleport station calibration animation.
              • -
              • lightning holoparasite will no longer zap across the entire zlevel after death
              • -
              • Added new chaplain book option
              • -
              • Added hypertool null rod option
              • -
              • added Insuls book to storage.dmi
              • -
              • added hypertool icon to device.dmi
              • -
              • Tactical should no longer leave the disguise on you in some cases
              • -
              • Slimes now lose interal reagents over time.
              • -
              • Menucrafting is internally a component now
              • -
              • Fixed menucrafting opening new window when finishing construction
              • -
              • Most headgear now fit on borgs.
              • -
              • Fixed changeling's strained muscles not doing stamina
              • -
              • bamboo which can be used to build punji sticks/ blowguns available as a sugarcane mutation or in exotic seed crate (instead of banana seeds)
              • -
              • sugar cane is available at the mega seed vendor
              • -
              • Clown shoes now make you waddle by default.
              • -
              • Charcoal now purges other chemicals again. It will no longer purge itself.
              • -
              • Added gutlunch babies.
              • -
              • Gutlunches now actually reproduce.
              • -
              • removes nothing
              • -
              • Adds a new mutation to the orange
              • -
              • Botanists can now get beeplushies as an heirloom.
              • -
              • Gutlunches will no longer be too shy to feast near other mobs. This results in them being more much inclined to eat.
              • -
              • syringes with the o2 medicine work
              • -
              • Fixed multiple map voting issues when DEFAULT_NO_VOTE is not enabled
              • -
              • Cult constructs can invoke runes again
              • -
              • Added a bunch of previously missing adjacency checks to ui window actions and alt clicks.
              • -
              • Some rpg affixes now have special effects
              • -
              • You can now craft a firebrand with two pieces of wood.
              • -
              • Dehydrated carp suicide.
              • -
              • Hourglasses now available in library game vending machine.
              • -
              • You can now craft bolas and spears with sinew restraints.
              • -
              • Replaced the water tank in the ashlizard base with a puddle.
              • -
              • Nicotine now kills pest and add toxicity when added to a plant tray.
              • -
              • You can now view special server rules for your current character with "Show Policy" verb in OOC tab
              • -
              • Nurse spiders no longer spawn from gold slime core and life chemical reactions.
              • -
              • suit sensors are now randomized when caught in an EMP
              • -
              • fixed the hypertool and insuls book
              • -
              • Phobias will no longer make you scared of yourself.
              • -
              • attaching something to a mech wont auto select it
              • -
              • You can now make caramel by heating sugar. Be careful not to overcook it.
              • -
              • Adjusted the candied apple recipe
              • -
              • phazon construction message is fixed
              • -
              • Cargo can now order a ridiculously large canister containing only saline to give medbay a more than plentiful supply of the stuff.
              • -
              • PDA beep, grenade arm, and flesh impact sounds have been fixed
              • -
              • When you get a quick rundown, your ears bleed less.
              • -
              • surgical drapes and syringes can be printed by medlathe
              • -
              • New trauma: Expressive Aphasia only. Restricts your speech to the 1000 most common english words.
              • -
              • A couple of dead bodies are now in the morgue. The souls are long gone, so no one will care if you "borrowed" a part or two.
              • -
              • sleeping carp now deflects projectiles
              • -
              • The "toggle open" verb from closets works now.
              • -
              • A bunch of minor issues with xenobiology are no more!
              • -
              • A new objective type: "Protect Object". Please use it at leasure for badminnery.
              • -
              • Ashlizards are now more savage and like stuff like sacrificing and head spikes.
              • -
              • Fixes an exploit allowing you to move enabled emitters
              • -
              • Breaking limbs with damage now properly makes you drop the item you're holding with it.
              • -
              • Added wooden farming implements to the ashwalker base.
              • -
              • Makes rakes and wooden buckets craftable.
              • -
              • New Hygiene Sprite
              • -
              • Cloners now have the option to "Empty Clone" a record, creating a mindless replica of that person.
              • -
              • To aid the above function, cloners can now do Body-Only scans, which can be used to create empty clones but not real clones, but bypass the sentience restrictions on scans. They can also be deleted without requiring access.
              • -
              • Recipe for fabled secret sauce can now be found in the deepest reaches of space.
              • -
              • The mop can now be printed at the autolathe.
              • -
              • Syndicate Contracts. Use the new contract uplink to select a contract, and bring the assigned target dead or alive to the designated drop off. Call for the extraction pod and send them off for your TC payment, with much higher rewards for keeping them alive. A high risk, high reward choice for a traitor who wants a real challenge.
              • -
              • New 20 TC contract kit - supplies you with your contractor loadout and uplink.
              • -
              • Targets successfully extracted will be held for ransom by the Syndicate after their use to them is fulfilled. Central command covers the cost, but they'll be taking a cut out of station funds to offset their loss...
              • -
              • Changed the Syndibase walls to look more evil.
              • -
              • The smallstache beard now looks slightly less silly.
              • -
              • Lavaland plants now have the fireproof gene.
              • -
              • Napalm now always burns weeds, even if the plant is fireproof.
              • -
              • Lavaland plants now get their reagents from their genes.
              • -
              • borgs can properly carry incapacitated individuals
              • -
              • The lavaland clown ruin has some new pranks ready.
              • -
              • Fixed obsessed random event ignoring preferences
              • -
              • If your name is a valid url, it won't be linkified in deadchat anymore.
              • -
              • Fixed snails retaining their lube crawling after changing to a different species
              • -
              • Updates recon, stealth, hacker, bond, sabotage, mad scientist, bee, and mr freeze bundles with several new items (most already in code) to make them less trash.
              • -
              • Bee Sword (literally just a yellow, weaker esword variant, needed it for another piece of code)
              • -
              • A weaker variant of the decloner gun.
              • -
              • Some changes to the lavaland hermits base and their starting equipment.
              • -
              • mortar grinds food
              • -
              • Revenants will no longer be hit by projectiles while hidden
              • -
              • The 3d orange now gives you the proper seeds.
              • -
              • Fixed silicon items (e.g. cyborg modules) being destroyed by explosion epicenters
              • -
              • Fixed livers not being damaged by toxins
              • -
              • Shuttles that go to a custom dock are no longer forever trapped by their hubris
              • -
              • the bee syndicate kit now contains a rapier
              • -
              • The CNS rebooter now purges stuns after 4 seconds of not being stunned, instead of doing nothing.
              • -
              • refill for autodrobe and BODA work
              • -
              • Icons will no longer extend past cryo.
              • -
              • Xenos and monkeys no longer have snowflake icons for cryo.
              • -
              • alt clicking the emitter now rotates it instead of only flipping
              • -
              • fixes russian helmets not holding both a vodka and a glass
              • -
              • Nar'Sie now EMPs mechs, instead of turning the pilot into a harvester.
              • -
              • Cult teleport rune will no longer teleport ghosts or camera mobs.
              • -
              • legion core damage slowdown is temporary, like all things
              • -
              • After receiving many complaints about mimes who never pantomime, Nanotrasen has liquidated its mime personnel and hired new mimes who know more routines.
              • -
              • The mime gets the choice of two new spells: Invisible Chair and Invisible Box. They work much like the Invisible Wall spell does and disappear after a short span of time. The invisible chair can be buckled to like usual chairs; the box works like a standard inventory box item.
              • -
              • Mime spells are now granted via a spellbook that starts out in the mime's inventory. Mimes can choose one of these three.
              • -
              • Removed all sleepers from escape shuttles and replaced them with stasis units. Some shuttles have gotten surgery and new medical vendors added.
              • -
              • Quantum teleportation now makes pretty rainbow sparks instead of the normal ones.
              • -
              • Non-bluespace teleportation (spells etc.) no longer makes sparks.
              • -
              • Centcom's Death Commando got a paycheck and jumpsuits of quality more similar to the rest of Centcom's staf- KS13 DOESN'T EXIST, THERE IS NO SUCH THING AS A DEATH COMMANDO
              • -
              • Normal carps now spawn with a random color! There might even be some really rare color variant.. try asking Cayenne about it.
              • -
              • Non-binary changelings will now have the honorific "Dear" instead of "Mr.", reflecting the Syndicate's growing acceptance of operatives who reject the binary as well as Nanotrasen's authority.
              • -
              • The bee sabre now has it's own suicide and no longer uses the one of the captains rapier.
              • -
              • The bee sabre uses the correct articles and actually has icons now.
              • -
              • Space heaters no longer steal focus upon screwdrivering.
              • -
              • added geo-bypass
              • -
              • Fix bad icon state for bounty console printouts
              • -
              • Incomplete and non-teleport reactive armors can no longer be used to complete the traitor objective.
              • -
              • DIY Dish Drive Kit now available at your local bardrobe. Start saving those tips!
              • -
              • The Dish Drive no longer sends reusable items into the disposal bin
              • -
              • gives scientists a chance to spawn with an awesome tie
              • -
              • Monkeys can wear collars again
              • -
              • Turned off energy weapons can no longer cut down trees, destroy wooden walls, harvest lavaland plants and make planks.
              • -
              • Contract kit's specialist space suit now has its custom sprite.
              • -
              • Assigning to tablet now plays greeting soundclip.
              • -
              • Quirks no longer apply to off-station roundstart antagonists.
              • -
              • added department skirts
              • -
              • fixed secskirt dixel
              • -
              • the TB antidotes injectors now actually cure TB and have some omnizine so you dont die from perfluoride
              • -
              • stacks no longer give you a pop up window when you alt click on one on the floor
              • -
              • player added items now appear above the standard selection in vending machines.
              • -
              • Fixed bug where hivemind vessel awakening would not be logged.
              • -
              • Fixed bug where awoken hivemind vessel objectives would not be logged.
              • -
              • slightly modified the deadchat text for awakening.
              • -
              • Dead AI players will now get a notification if they are being restored while outside their body.
              • -
              • Engineering closets now start with a construction bag, which can hold machine components, circuits, electronics, and cable coils.
              • -
              • You can now craft white jumpskirts with cloth.
              • -
              • the russian revolver's chamber can be spun again
              • -
              • Red, white, and blue mech pilot jumpsuits now available in the Mech Pilot's Suits Crate, under Costumes & Toys.
              • -
              • limbs are now small
              • -
              • The R&D Server Controller console now lists the entire research history, including names of people who researched each item and locations it was done from.
              • -
              • The R&D Server Controller console can now be used to disable the servers if someone makes the RD upset.
              • -
              • Fixed the R&D servers working without power.
              • -
              • R&D server sprites are now slightly animated, and new sprites have been added for when the server is disabled or off.
              • -
              • Negative armor on objects now increases damage taken.
              • -
              • Humans now always use the correct value for limb armor.
              • -
              • Various cosmetic and gameplay related changes to hivebots. Player controlled hivebots can change their look by changing their intent to harm.
              • -
              • Mobs with negative armor no longer get damage decreased by armor penetration.
              • -
              • Added a new contraband about free syndicate keys
              • -
              • Antimagic now gets used up properly
              • -
              • Reproductive crossbreed extracts can be fed from the bio bag again
              • -
              • You can no longer infinitely purify a soulstone.
              • -
              • Cultists can't use purified soulstones anymore.
              • -
              • Coming into contact with -HONK- may have additional effects, do not be alarmed
              • -
              • For safety reasons, the administration of -HONK- may only be performed by trained professionals
              • -
              • Self-application of -HONK- is now forbidden
              • -
              • Add match strike sound.
              • -
              • NT staff have added new laser focusing rings onto all BSA equipment, so now it looks more like a massive deadly laser, and fires more accurately.
              • -
              • Clones no longer have their genetic sequence quantum garble fucked together
              • -
              • Breakfast foods such as eggs, donuts, coffee and so on now give a long but weak positive moodie when eaten within 15 minutes of the start of a shift
              • -
              • The sabre sheath properly plays sounds again after who knows how long
              • -
              • All chainsaws, including the null rod variants, can now be used to saw off guns.
              • -
              • Added mime envirosuits, nothing special here.
              • -
              • Added clown envirosuits, which release space lube when they extinguish the clown.
              • -
              • Removes code that theoretically limits plasmamen from being clowns and mimes, but actually doesn't.
              • -
              • I guess the roles now spawning with these suits is kind of notable.
              • -
              • Reverts unlisted changes to the grey detective suit from https://github.com/tgstation/tgstation/pull/44776.
              • -
              • Cowboy boots have been added to the Clothesmate.
              • -
              • Lizard skin boots with decent export values have been added to the crafting menu.
              • -
              • Snakes, headslugs and alien larvae can now hide in cowboy boots.
              • -
              -
              - -GoonStation 13 Development Team -
              - Coders: Stuntwaffle, Showtime, Pantaloons, Nannek, Keelin, Exadv1, hobnob, Justicefries, 0staf, sniperchance, AngriestIBM, BrianOBlivion
              - Spriters: Supernorn, Haruhi, Stuntwaffle, Pantaloons, Rho, SynthOrange, I Said No
              -
              -
              -

              Creative Commons License
              Except where otherwise noted, Goon Station 13 is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License.
              Rights are currently extended to SomethingAwful Goons only.

              -

              Some icons by Yusuke Kamiyamane. All rights reserved. Licensed under a Creative Commons Attribution 3.0 License.

              -
              - - + + + + Yogstation Changelog + + + + + + + +
              + + + + +
              +
              Yogstation 13
              + +

              + Visit #coder-public on our Discord: https://discord.gg/p6ShbB8
              + Suggest features on our forum: -- Here -- +
              + + + + + +
              + Current Headcoders: -Click Here-
              + Currently Active GitHub contributor list: -Click Here-
              + Coders: Nichlas0010, ThatLing, GrayRachnid, AsV9, Kmc2000, Moja10, Cruix, X-TheDark, Oisin100, TehFlaminTaco, Kn0ss0s, Super3222, ShadowDeath6, Time-Green, Ephemeralis, Alek2ander, FluffySurvivor, Nirnael, Utahclock, Macelarius, ToGWTF, Adam El-Tablawy, Johncena1469, McDonald072, ArgyleSweatshop
              + Spriters: Partheo, Kmc2000, Andy, Silvaplex, Dagdamnit, Maya/Larissa
              + Mappers: Nichlas0010, GrayRachnid, Matskuman5, Utahclock, Stealthkibbler, MayhemSailor, Yewmi
              + Sounds: Skie, Lasty/Vinyl
              + Thanks to: /tg/station, FTL13 devs, Baystation 12, /vg/station, NTstation, CDK Station devs, FacepunchStation, GoonStation devs, the original SpaceStation developers and Invisty for the title image.
              Also a thanks to anybody who has contributed who is not listed here :( Ask to be added here on our discord.
              +
              Have a bug to report?
              Visit our Issue Tracker.
              + Please ensure that the bug has not already been reported and use the template provided here.
              + Want something minor added? Check out this topic! +
              + + +
              + +

              22 July 2019

              +

              Identification updated:

              +
                +
              • Deodorant is now a small item rather than a normal-sized item.
              • +
              • Certain suits could not hold tanks.
              • +
              • Certain suits can now hold plasma tanks.
              • +
              • Adds a donor borg skin for Qe.
              • +
              +

              TG stealth nerf removal updated:

              +
                +
              • holy melons work in pockets again
              • +
              +

              :b:eos updated:

              +
                +
              • blood will now actually clean off of shoes and gloves
              • +
              +

              Altoids updated:

              +
                +
              • Admins can now delay the shuttle from launching!
              • +
              • Admins can now smite any mob capable of life.
              • +
              • The examine messages for radioactive things should be clearer and better-formatted now.
              • +
              • Fixed thralls and slings sometimes not receiving their deconversion message when blinded.
              • +
              • Borged thralls should now no longer be discriminated against by their deconversion message.
              • +
              • Mentorhelps now have a (mostly) unique sound effect!
              • +
              • Stealthmins can now be pinged by their fake ckey.
              • +
              • Admins can now play AI Vox messages at arbitrary speeds!
              • +
              • Syndrones, Cogscarabs, and FREEDRONES are no longer forced into pacifism.
              • +
              • Cyborgs can no longer move wheelchairs around, as they do not have arms.
              • +
              • Fixed a speed exploit.
              • +
              • This be a big bugfix for bloodbrothers not born bearing their bloodbro box bundle.
              • +
              • Lightbulbs discoloured by spraycans can now actually be cleaned by space cleaner, as intended.
              • +
              • Drone shells can now be found in the Spawner Menu!
              • +
              • Mentorhelps should now be much easier to see, especially in darkmode.
              • +
              • One-way portals can now sometimes send you to multiple different places, from multiple different entry points!
              • +
              • Mentors can no longer call you racial slurs, neither at your face nor among themselves.
              • +
              • Donorborgs are now not all sponsored by Space Cola.
              • +
              • The bluetext of mentorhelps should now be a slightly more legible blue.
              • +
              • Nuclear Operatives will no longer be tasked with detonating bananium bombs they could not possibly get to.
              • +
              • The Traffic Control Console is now available on the techweb!
              • +
              • Altoids worked on something for 30 minutes that will never affect you in any meaningful way.
              • +
              • Extracting a sliver of an SM is now properly logged.
              • +
              • Yogstation-exclusive emojis now override TG ones. Say hello to good ol' thinking!
              • +
              • The blob emojis should now look OK in darkmode.
              • +
              • There's now a verb in the OOC tab that will list to you every emoji that's available in-game!
              • +
              • Ghosts are no longer able to operate storage items, in any way.
              • +
              • You can now enable the Mood feature for yourself in your Game Preferences, if you REALLY want to.
              • +
              • It is now possible to make fusion occur at much lower temperatures, via Dilithium Gas!
              • +
              • Dilithium Gas has been added to the game!
              • +
              • Dilithium Crystals have been added to the game, for facilitating Cold Fusion! Ask your local Miner for it today, Atmosian!
              • +
              • AIs should now be able to actually do things again.
              • +
              • Katanas are now available on the Uplink, for 10 TC.
              • +
              • Admins can now help the deeply confused reach mentors to aide them a lot more easily.
              • +
              • Abductors can now use their tools again.
              • +
              • Quiet Mode no longer has the opposite effect of basically forcing you to be antagonist.
              • +
              • There are new Alcohol mutations that either produce alcohol in your bloodstream, or make you resistant to alcohol in general!
              • +
              • Lava and Anomaly hallucinations now have a 5% chance of actually hurting you like the real thing!
              • +
              • BZ's hallucinogenic properties now actually vary based on concentration. Maximum effect (double the previous maximum) occurs at 1 atm of BZ pressure. Additionally, it now takes 700 Pascal (up from 10 Pascal) of BZ pressure before BZ has any hallucinogenic effect at all.
              • +
              • Body bags will no longer attempt to convince you that they can be welded.
              • +
              • Altoids can now more easily determine how broken shadowlings are.
              • +
              • On July 5th, there is now a birthday event for Oakboscage!
              • +
              • Fixing the powerboard in a broken APC will no longer appear to nearby crewmembers as you literally breaking the APC.
              • +
              +

              Coded by kmc2000,ported by alexkar598 updated:

              +
                +
              • Gave the HOP a special ticket machine to manage their line better.
              • +
              +

              Identification updated:

              +
                +
              • Killer Queen's detonator sound will now play when the explosive holoparasites trap is triggered.
              • +
              • Adds space manatees. They look quite menacing.
              • +
              • New religions have been added.
              • +
              • New colours of striped scarves are available in the clothesmate.
              • +
              • stripedscarf subtype replaces striped[colour]scarf defines
              • +
              • Adds magboots to the engineering suit storage units
              • +
              • Ports and adds new objects to the code for future use.
              • +
              • the yogbox supermatter is now a female
              • +
              • possible erp with the sm. be sure to get an erp pass beforehand
              • +
              • coloured carpets and stairs. not implemented anywhere yet.
              • +
              • Adds a bartender's secure closet to all iterations of the bar on yogbox.
              • +
              • The bartender's double barrel shotgun is now in their new secure closet.
              • +
              • Cooks Officer Kreb and imports SGT. Areneus from the space nest.
              • +
              • Budget cards now spawn in the job's respective locker.
              • +
              • Adds a cell charger to the free miner ship. Adds fans to the airlocks that were missing them.
              • +
              • The Free miner RIPLEY can actually be built and made space-proof. Engines also face the correct direction.
              • +
              • Adds coloured carpets to YogBox and are now able to be ordered from Cargo.
              • +
              • No ERP allowed.
              • +
              • Removes the cake hat from the donor hat list as it offered a mechanical advantage.
              • +
              • Adds a shower to the cloner.
              • +
              • Yogs items are added to certain lockers differently.
              • +
              • Adds Partheo's scrubber sprites.
              • +
              • Yogbox janitors closet has a maid uniform now
              • +
              • Labcoat front sprite tweaked to be more realistic, and labcoats start toggled open.
              • +
              • The RD's office now has a portrait of Cave Johnson.
              • +
              • Envirosuits will no longer stick out of suits.
              • +
              • The price of the Australian Slime Mutator is now 25 TC rather than 10 TC.
              • +
              • The Gods can now call forth the Alien Queen maid through their godly powers.
              • +
              • Lusty, Sexy.
              • +
              • Revenants have a new sprite, which is now four directional.
              • +
              • Orion Sec Officers no longer spawn with hybrid tasers.
              • +
              • Updates the clerk's outfit and adds skirts for the Clerk and Signal Technician.
              • +
              • Minibars now have directional sprites.
              • +
              • Virus cures should no longer reshuffle in a different host.
              • +
              • The empowered burger no longer has a jarring sprite.
              • +
              +

              JJRcop and Kmc2000 (sprites) for the code, theos for porting and QoL stuff updated:

              +
                +
              • Adds the stasis bed, a medical machine that can stabilize a patient and stops the body from deteriorating after death
              • +
              • The stasis bed is capable of being used as a fully functional surgical platform, though the patient will need to be buckled to it to apply advanced surgeries
              • +
              +

              Ktlwjec, Partheo updated:

              +
                +
              • Adds a new crafting recipe: Lasagna!
              • +
              • Lasagna!
              • +
              +

              Matias Sanhueza updated:

              +
                +
              • Replaced the throwing star sprite
              • +
              +

              ShadowDeath6 updated:

              +
                +
              • Floor cluwnes emp once rather than 36 times when emerging from the floor dimension
              • +
              +

              Space PETA updated:

              +
                +
              • After an analysis of Nanotrasen medical systems, gorilla related biology was found to be of illegal origin, and thus phased out by sPETA.
              • +
              +

              Xeos updated:

              +
                +
              • Xeno queens will delay the escape shuttle for a while unless they are killed
              • +
              • the shuttle will get auto called at the end of the timer if the queen is alive and it hasn't been called already to prevent super xeno extended rounds
              • +
              +

              ack updated:

              +
                +
              • activates sleeper bandaid
              • +
              +

              kittymaster0, ktlwjec updated:

              +
                +
              • Cleaned up Syndiebase after TC station removal.
              • +
              +

              nemvar updated:

              +
                +
              • Trash from food now gets generated at the location of the food item, instead of in the hands of the eater.
              • +
              +

              soeht updated:

              +
                +
              • the chaplain can now choose a fancy spear as a nullrod
              • +
              +

              4dplanner updated:

              +
                +
              • Humans can no longer use UIs while incapacitated
              • +
              • stabilised sepia slime randomly changes your speed as intended
              • +
              • Colossi now cannot kill themselves with colossus bolts.
              • +
              • The deja vu effect (previously only used by rewind camera) now resets all your limbs and damage to the point it was added after 10 seconds (as well as the original position reset). The deja vu effect cannot resurrect the dead, but will heal their corpse. New limbs fall off, old ones re-attach.
              • +
              • Regenerative sepia cores add a deja vu effect before healing instead of a timestop
              • +
              • The rewind camera rewinds 2 times to distinguish it from the regenerative core
              • +
              +

              AffectedArc07 updated:

              +
                +
              • The taskbar icon for the game is now the TG logo
              • +
              • Status displays are readable now
              • +
              • Religion is now a globalvar instead of being a subsystem for some reason
              • +
              +

              AffectedArc07 and Shazbot updated:

              +
                +
              • Added 9 new sock styles
              • +
              +

              Altoids updated:

              +
                +
              • Xeno vore and other miscellaneous TG-style vore have been readded! Nothing raunchy though, unfortunately.
              • +
              • Removed the antimatter engine
              • +
              • Readded the antimatter engine
              • +
              • Vanilla icecream should now have a sensible name and a working sprite.
              • +
              • Buffs powercreep
              • +
              +

              AnturK updated:

              +
                +
              • Fixed ED209 not shooting any projectiles.
              • +
              +

              Arkatos updated:

              +
                +
              • Examine tooltips now work on items put into storage, such as backpacks
              • +
              • Jump to Node ability now shows a location of each Blob node
              • +
              • Added SlimeHUD, which means slimes will have their own custom health display indicator and red corner injury overlay
              • +
              • Action buttons can now be dragged onto each other to swap places
              • +
              +

              Cyberboss updated:

              +
                +
              • ALL OF VORECODE WAS REMOVED! THE HORROR!
              • +
              +

              Dorsidwarf updated:

              +
                +
              • Nanotrasen washing machines can now extract the raw power of the cosmos from bluespace crystals.
              • +
              +

              Eaglendia updated:

              +
                +
              • You can light a cigar on the supermatter shard. Handle with care!
              • +
              +

              Erwgd updated:

              +
                +
              • Swapped out an epinephrine bottle for potassium iodide in NanoMed Plus vending machines.
              • +
              +

              Fikou updated:

              +
                +
              • NEWS REPORT: The latest alien technology allows them to abduct cows instantly, this is a great tragedy for all farm owners, better watch the garden!
              • +
              +

              Fire Chance updated:

              +
                +
              • Fixed the drink sprites overlapping when you are mixing beer with other drinks.
              • +
              +

              Floyd / Qustinnus updated:

              +
                +
              • New scientist traitor item: Australian Slime Mutator / Spider Injector, use it on a gold slime extract to create 3 neutral broodmother spiders, make them sentient and start your own hive.
              • +
              +

              Floyd / Qustinnus, Sprites by MrDoomBringer updated:

              +
                +
              • New traitor item: The suspicious phone. This device lets you launch a device that drains the funds of all crewmembers to your bank account, the only way for them to stop this is by rushing to the conventionaly launched spacecoin market where the crew can sell their Spacecoin.
              • +
              +

              FlufflyCthulu updated:

              +
                +
              • Fixes an issue with clown hulk simple mobs
              • +
              +

              Garen updated:

              +
                +
              • You can only make one construct when using twisted construction on a borg.
              • +
              • You can no longer do custom emotes with fists of the north star.
              • +
              • Fixes being able to pull from belt/backpack when you are stunned or cuffed.
              • +
              +

              Garen7 updated:

              +
                +
              • Lesser form lings can now toggle augmented vision
              • +
              • You can no longer turn back into human form as a lesser form ling while ventcrawling
              • +
              • Stuns and Reagents are now passed when transforming into a monkey or human
              • +
              +

              Identification updated:

              +
                +
              • Wooden bucket sprite fixed
              • +
              • fixes a few food sprites
              • +
              • Preterni now have their own scream.
              • +
              • A fix with an area of Moth wing sprites not appearing. Moths should look a little bit different now.
              • +
              +

              JJRcop updated:

              +
                +
              • Live facehugger eggs and dead facehuggers removed from xeno lavaland ruin.
              • +
              • You can now build a new PDA messaging server if you lose it.
              • +
              • Requests console messages no longer work if the messaging server is offline. Emergency alerts will still function in that case as long as the rest of telecomms is online.
              • +
              • Removed the piping overlay from the Lifeform Stasis Unit
              • +
              • Airlocks open when items hit them
              • +
              • Stasis lets reagents know processing was stopped, fixing some issues.
              • +
              • Amanitin's damage only triggers when it is completely removed from your system, not when processing stops.
              • +
              +

              Jerry Derpington, baldest of the balds updated:

              +
                +
              • Nanotrasen has lost communication to two away mission sites that contained a beach for Nanotrasen employees.
              • +
              • Nanotrasen has been able to locate a new away mission site that ALSO has a beach. Nanotrasen employees will be able to enjoy the beach after all!
              • +
              +

              MMMiracles updated:

              +
                +
              • Donutstation: CE's windows are electrified.
              • +
              • Donutstation: Atmospherics now has 2 rad suits and an extra regular fire suit.
              • +
              • Donutstation: The pipe dispensers and redundant tech fabs in Atmos were removed.
              • +
              • Air alarm for Donut's telecomms server is the proper type
              • +
              +

              Matias Sanhueza updated:

              +
                +
              • I changed the Rezadone's description since it is not based on Carpotoxin anymore, thus, the new recipe being Capsasin, now the description is about Chilli's and spicy taste
              • +
              +

              Menshin updated:

              +
                +
              • Janitor ERT leader now spawns properly equipped
              • +
              +

              MrDroppodBringer and Time-Green updated:

              +
                +
              • Adds the lipid extractor to the service protolathe arsenal
              • +
              +

              MrFluffster updated:

              +
                +
              • Moths like eating some sorts of clothes, now.
              • +
              +

              Naksu updated:

              +
                +
              • mindshield nanites no longer give mindshields to revheads and one mind awoken hiveminds.
              • +
              • Fixed several issues with venus human traps and their vines going through walls
              • +
              • Items using atom colors for coloring now show up properly colored in vending machines, rather than snow-white
              • +
              • Cryotubes no longer display a double of a person that was once in but teleported out by having ingested bluespace or other such nonsense
              • +
              • corpses no longer feel, light-headed or otherwise
              • +
              • nukeop uplinks now have 5 unlimited discounts and 10 limited discounts shared between the team, rather than 3 individual discounts like traitor uplinks
              • +
              • The number-two most-called proc get_area() is now a define rather than a proc. All coders please clap.
              • +
              • Pipeline gas reconciliation is now faster. This should improve atmos performance in pipes.
              • +
              • the nuclear disk is now abductor-proof.
              • +
              • Odysseus chem synthesizing now works again
              • +
              • Added a new blob strain that grows inside the station and eats garbage
              • +
              • Free nanomeds have been removed from mining capsules
              • +
              • /datum/reagent/vanilla ice cream has been replaced with regular vanilla ice cream in the borgs' treat fabricator
              • +
              • Spamming the "create virus culture bottle" button in the pandemic no longer sometimes results in multiple bottles being created.
              • +
              • Electromagnetic web blob strain now has its in-game descriptions back.
              • +
              • Removed individual buttons text in crayon/spraycan UI, speeding it up.
              • +
              • Text mode buffer is actually visible in the UI.
              • +
              • Last letter of a text mode buffer no longer rotates out to be replaced with "a", allowing the text mode to be used for individual symbols.
              • +
              • Fixed a couple of map issues in DeltaStation
              • +
              +

              OnlineGirlfriend updated:

              +
                +
              • Lemonade has been added! Some bar recipes now require it instead of lemon juice.
              • +
              +

              Onule updated:

              +
                +
              • New stasis bed sprites!
              • +
              • Corpse flowers have been given proper sprites
              • +
              +

              PHDby updated:

              +
                +
              • Reconstruction is now available roundstart (nerfed)
              • +
              • You can upgrade both the Burn/Brute trees via techwebs
              • +
              • Advancing far enough into the tree enables Mixed reconstruction surgery, which heals less of each but is considerably faster. Get alien tech for the best of the best!
              • +
              +

              Putnam3145 updated:

              +
                +
              • All departments can now print basic power cells from their respective techfab.
              • +
              +

              RaveRadbury updated:

              +
                +
              • Quirks have flavor text in medical records
              • +
              • All quirks have medical record entries
              • +
              • All quirk medical records refer to "Patient", removing a few instances of "Subject"
              • +
              +

              Rowell updated:

              +
                +
              • Added thigh-high and knee-high bee socks.
              • +
              • Removed black stockings
              • +
              +

              Shaps/Ryll updated:

              +
                +
              • NT has updated its personnel policies and allowed for employees to register as gender neutral, with standard support for they/them pronouns.
              • +
              • Mosin Nagants are now bulky, but can be sawed off to fit in a bag
              • +
              +

              ShizCalev updated:

              +
                +
              • Pulling someone while they're buckled to a chair will no longer offset them and make them face you.
              • +
              • Buckling someone in while they're being pulled will now properly reset their offsets.
              • +
              • Pulling offsets will now update properly while dragging someone who's laying down.
              • +
              • Setting a defibrillator unit on fire will now make it's paddles appear on fire too when pulled out!
              • +
              • Setting defibrillator paddles on fire will now cause the flames to run along it's cables and set the main unit on fire as well.
              • +
              +

              Skoglol updated:

              +
                +
              • Ghost darkness now starts at night vision level by default.
              • +
              • Ghost hud on by default.
              • +
              • Moved machine and computer frames below objects, parts are now always on top.
              • +
              • Moved structures (chairs, closets, windows, cult altars etc etc) below objects.
              • +
              • Moves mineral doors to airlock layers
              • +
              • Added "Never for this round" to shades, slimes (pyroclastic anomaly), defective clone, imaginary friend and split personality ghost popups.
              • +
              • Revival surgery now asks you to shock body instead of repair it.
              • +
              • All surgeries now have capital letters.
              • +
              • Airlock cycling no long breaks if something is in the way. As a bonus, you can now door crush people.
              • +
              • Rolling blood brother now has a sound alert and bigger text.
              • +
              • Chem dispenser buttons are now sorted alphabetically again.
              • +
              • Advanced surgical tools can now be found in the tool section with their basic counterparts.
              • +
              • Special .38 ammo now can be found in the ammo section with the other ammo types.
              • +
              • Create object admin tool enter key behaviour changed.
              • +
              • Fixed a slime runtime.
              • +
              • Heaters/freezers now support ctrl clicking to turn on and alt clicking to min/max target temperature.
              • +
              • Heaters/freezers now shows target temperature and part status on examine.
              • +
              +

              SpacePrius updated:

              +
                +
              • Added empty boxes.
              • +
              +

              SuicidalPickles updated:

              +
                +
              • Rolls of gauze now works on corpses.
              • +
              • EMP flashlights have had their cost changed to 2 telecrystals.
              • +
              +

              Swagile updated:

              +
                +
              • Cargo key in mining conscript kit is now a mining key.
              • +
              +

              Tetr4 updated:

              +
                +
              • Suicides are permanent for the nth time
              • +
              • Turning a tile with gas effects into space now gets rid of the effects.
              • +
              +

              Tlaltecuhtli updated:

              +
                +
              • cargo ripley can be actually constructed with what you get from the crate
              • +
              • adds the right crate type to monkey cube and hydroponics refill crates
              • +
              • readds the bulk wt550 ammo crate
              • +
              • conveyor belt crate has 15 instead of 6 belts
              • +
              • adds a laser pointer to the bureaucracy crate
              • +
              • changes med supply crate to have rng medical things
              • +
              +

              Trilbyspaceclone and nemvar updated:

              +
                +
              • You can now craft more fancy boxes with cardboard.
              • +
              +

              Vile Beggar updated:

              +
                +
              • The drill hat is less annoying now. You can change the loudness setting of it by using a screwdriver on it. Use wirecutters on it for a surprise.
              • +
              +

              WJohn updated:

              +
                +
              • Raven emergency shuttle's turrets no longer shoot at ian.
              • +
              +

              WJohnston updated:

              +
                +
              • The Daniel shuttle can no longer be purchased and is now admin only.
              • +
              • Having very high sanity no longer takes away your action speed bonus.
              • +
              • Sanity no longer constantly dips beyond its maximum/minimum, which caused issues.
              • +
              +

              XDTM updated:

              +
                +
              • Decreased the cost of the advanced surgery node to 1500 from 2500, as was intended when adding the Improved Wound Mending node.
              • +
              • Interrupting a surgery step or finishing a repeatable surgery step no longer uses the tool on the target, attacking or forcefeeding them.
              • +
              • Beepskys spawned from Beepsky Smash can no longer be heard by bystanders.
              • +
              • The beepsky smash trauma can no longer be gained as a special trauma.
              • +
              • Added a new advanced surgery, Muscled Veins: it makes the patient able to survive without a heart.
              • +
              • Fixed teleportation deleting mob spawners like golem shells and ashwalker eggs.
              • +
              • Using the wrong surgery tool during surgery no longer attacks the patient, if on help or disarm intent.
              • +
              • Fixed not being able to uninstall Voice Sensor nanite programs from the cloud console.
              • +
              • Hypnosis and mind control now have alerts on the right side of the screen when active.
              • +
              • Hovering over the hypnosis alert will display the hypno-phrase.
              • +
              • Clicking the mind control alert will display the order again.
              • +
              • Launchpads now gain 15 range instead of 1 when upgraded, up to a maximum of 60.
              • +
              • Launchpads now have a targeting reticle which is visible on Diagnostic HUD. It also indicates if the launchpad is currently sending or receiving.
              • +
              • Launchpad Consoles now use TGUI and are far more responsive. They can also set their coordinate offsets manually instead of only using arrows.
              • +
              • Fixed abductor scientists not being able to perform advanced surgery.
              • +
              • Antimagic items now also properly work when held in hand, instead of only when equipped.
              • +
              • Added a new abductor gland that randomizes blood type into a random reagent periodically.
              • +
              • Added a new abductor gland that links to other people, and swaps their position with the owner. Mind control on this gland will affect the linked person.
              • +
              • Added a new abductor gland that grants all access to the owner.
              • +
              • Abductor scientists can now properly see the true name of glands when examining them.
              • +
              • Mind control on mindshock glands now "broadcasts" the mind control, affecting bystanders but not the abductee.
              • +
              • Species gland now has 7 mind control uses, instead of 5 (duration is still 30 seconds).
              • +
              • Viral healing symptoms that are tied to reagents now also require a functioning liver to work.
              • +
              • Holy water, Pyrosium, Cryostilane, Napalm and Phlogiston no longer need a liver to have their effects.
              • +
              • Abductors can now buy a special chem dispenser for 2 points. The machine is delivered as a beacon which can be used when standing on any free tile to spawn it.
              • +
              +

              Yenwodyah updated:

              +
                +
              • Non-antags can now forge all agent IDs if they find them before they've been forged.
              • +
              +

              YoYoBatty updated:

              +
                +
              • Body part damage is now run through armor check in certain situations.
              • +
              +

              Zxaber updated:

              +
                +
              • Cargo manifests no longer list the purchaser as "/datum/bank_account" under certain circumstances
              • +
              • AI power backup is now separate from the health, and is tied to their core. Carding an AI on battery backup will leave the core at partial charge. AIs no longer slowly lose integrity while on backups (but will instantly die as before once the backup runs out). As such, whacking an AI with a wrench will no longer magically lower their backup battery capacity.
              • +
              • AIs on backup battery can now unbolt at the cost of a quarter of their battery's max charge. This cannot be done while at under 25% power.
              • +
              • Fixed all mechs being fire/lava proof.
              • +
              +

              actioninja, Altoids updated:

              +
                +
              • Lot of small darkmode fixes
              • +
              +

              as334 updated:

              +
                +
              • New Gas Reaction, Stimulum Energy Balls. Combine 100 degree Pluoxium, Stimulum and small amounts of Plasma and Nitryl to shoot off a deadly radioactive energy ball.
              • +
              • The reaction will consume the plasma, pluoxium and stimulum and release small amounts of heat. The presence of water vapor will change the direction in which it shoots. See if you can figure out how to aim it.
              • +
              • Nitryl now forms at 22,000 degrees, a significantly lower temperature.
              • +
              • fixed a few things
              • +
              • Removed some old left-behind fusion test canisters.
              • +
              +

              bandit updated:

              +
                +
              • Surgical incisions cause actual bleeding now. Clamping bleeders and/or cauterizing the incision stops that bleeding.
              • +
              • Art now affects mood depending on its quality.
              • +
              • Nanotrasen's clowns have begun to preserve their images by stamping eggs with their NT-issued clown stamps. This is fine and normal.
              • +
              • Nanotrasen chefs and botanists have finally learned to stop composting or grinding bowls and the like along with the food they come with.
              • +
              • After years of arduous training, Nanotrasen crew can now tell whether an animal is dead by examining it.
              • +
              +

              bgobandit updated:

              +
                +
              • The Traitor Panel now shows the names of blood brothers' brothers.
              • +
              +

              blargety & WJohnston updated:

              +
                +
              • Caution sign resprited
              • +
              • You can now wear the caution sign on your armor slot
              • +
              +

              bobbahbrown updated:

              +
                +
              • Monkeys now apply knockdown when tackling instead of stunning, and can stun you by tackling you whilst you are knocked down.
              • +
              • Deaths are now logged to the game log.
              • +
              +

              granpawalton updated:

              +
                +
              • miasma now only spawns below 315 kpa
              • +
              • miasma now spawns at room temperature instead of 0 kelvin
              • +
              +

              hey there uwu updated:

              +
                +
              • You can no longer choose the zombie race from the magic mirror, as intended.
              • +
              +

              jegub updated:

              +
                +
              • Lets the grass grow under your feet.
              • +
              +

              kriskog updated:

              +
                +
              • Communications console window no longer updates, won't steal focus anymore.
              • +
              • windoors can now be closed manually after bumping open without having them close again.
              • +
              +

              nemvar updated:

              +
                +
              • You can now reuse chemical grenade casings if they fail to react.
              • +
              • You can now adjust the timer of grenades with a multitool or screwdriver. You can put it anywhere between 3 and 5 seconds. Instant detonations are also possible.
              • +
              • Advanced release grenades now open a window if you want to change the amount of units released.
              • +
              • The default timer on smoke grenades is now 3 seconds.
              • +
              • Above ground smugglers satchels no longer leave a phantom object behind on t-ray scanners
              • +
              +

              optimum wesoda updated:

              +
                +
              • Sprites for Meat and Chaos Donuts
              • +
              +

              pireamaineach updated:

              +
                +
              • Axes some hairstyles that didn't make the final cut but never got fully removed, removes an unused sprite.
              • +
              • Fixed the cornrows because they were bRoek
              • +
              +

              plapatin, sprites by cogwerks and edited by mrdoombringer updated:

              +
                +
              • vomi/tg/oose
              • +
              +

              shellspeed1 updated:

              +
                +
              • removes an exploit using plux to generate massive amounts of credits.
              • +
              +

              weeeeesoda updated:

              +
                +
              • You can now purify soulstones! This can be done by any crew member using a bible on one.
              • +
              • Purified constructs can be created by using a purifed soulstone on a construct shell. Amongst their normal abilities, purified constructs can dispel cult runes by clicking on them.
              • +
              • Ability to place chaplains soulstone or mining soulstones in construct shells
              • +
              • angelplasm, angelic sprites, purifed sprites
              • +
              +

              wesoda gamer the twenty fifth updated:

              +
                +
              • changed text on gender options to make them grammatically correct and visually pleasing
              • +
              +

              wesoda123555 updated:

              +
                +
              • physically obstructive quirk
              • +
              +

              1fbff5f83b23d39d38b1dfcb4cac8d9b updated:

              +
                +
              • TGUI buttons do not fully grey out while the UI is still updating
              • +
              • Adds new sprites for all berets.
              • +
              • Adds the sprites for the archaic and vintage berets.
              • +
              • Removes old sprites for all berets.
              • +
              • Adds the archaic and vintage berets, the vintage one being the current one and the archaic one being the one from 2014.
              • +
              • Adds the vintage and archaic berets to the autodrobe.
              • +
              • Stuff in the Character Setup screen is no longer way too close to the edge of the window.
              • +
              • Downmixed every sound to mono. This shouldn't have resulted in any changes in audio, byond downmixes anyways.
              • +
              • Recut some sounds. Sounds shouldn't have large silent/noise gaps at the start anymore
              • +
              +

              AdamElTablawy updated:

              +
                +
              • Red slime potions have been fixed!
              • +
              • clusterbang and SRM-8 missile launchers now take syndicate weaponry to unlock due to balance issues
              • +
              +

              AsV9 updated:

              +
                +
              • Made the cosmic ashwalker ruin stop spawning automatically.
              • +
              • Blood brothers no longer spawn with traitor bundles.
              • +
              • Blood brothers now spawn with an integrated pinpointer that shows the direction to their teammate.
              • +
              +

              Codeatmos updated:

              +
                +
              • Added Plumbing from /tg/
              • +
              • Added the images for plumbing from /tg/
              • +
              +

              GenericDM updated:

              +
                +
              • preternis can be gibbed
              • +
              • preternis produce synthmeat when gibbed
              • +
              • You can now shove an emag into a jack o' lantern to turn it into a hack o' lantern
              • +
              +

              Ihonkification updated:

              +
                +
              • The QM now has a envirosuit.
              • +
              • Yogjobs, clown, and mime are missing custom or updated envirosuits.
              • +
              • Envirosuits have been resprited.
              • +
              • Most of the old envirosuits.
              • +
              • The white jumpskirt now has a object sprite.
              • +
              +

              Ktlwjec updated:

              +
                +
              • The mining medic now has an office on lavaland!
              • +
              • Service-Security headset defaults to Security.
              • +
              • :egg:
              • +
              • The cosmic ashwalker ruin is much bigger.
              • +
              • Miners and Mining Medics can no longer start as rev heads.
              • +
              • Perma prisoners now have access to the perma vendors.
              • +
              • Combat magboots can be found in the security suit storages.
              • +
              • Minsky brig has been redesigned slightly.
              • +
              • Minsky bridge has been rearranged slightly.
              • +
              • Omegastation now has a perma room.
              • +
              • Captain clothing items can now be found in the Captain's Autodrobe.
              • +
              +

              Lostlarry updated:

              +
                +
              • Added trafic consoles to Tcomms on YogsDonut, YogsDelta, Yogsmeta
              • +
              • Removed monitoring consoles from Tcomms on YogsDonut, YogsDelta, Yogsmeta
              • +
              • Adds three headsets to cloning spare clothes locker on YogStation, YogsPubby, YogsDonut.
              • +
              • Janniborgs can now refil lightreplacer by clicking on glass stacks.
              • +
              • tweaks latices outside disposal to prevent objects getting stuck
              • +
              • Added air alarm to Cargo Office on yogbox
              • +
              • Fixed pipe connecting atmos and SM on Delta
              • +
              • Virology cameras now have unique names
              • +
              • Fixes janni wardrobe so holoprojector can be put in the janitor belt
              • +
              • Virologi windoor now needs atmos acsses as well
              • +
              • Engineers now can access gravity generator on delta
              • +
              • Engineers now cant access atmos on delta
              • +
              • Most of cyborg can't be installed twice now.
              • +
              • Perma cameras on Yogstation are on the right network and properly named.
              • +
              • adds backup UI mode for sleepers activateable by admins
              • +
              • Sleeper backupUI is not operable from inside the sleeper
              • +
              • all tables in toxins are now reinforced
              • +
              • sleepers no longer work with no power
              • +
              • Sleeper backupUI looks way better now
              • +
              • Sleepers can be interacted with when they have controls inside
              • +
              • people revived by MMI will be reminded that they don't remember their death
              • +
              • posibrain inserted to mecha is reminded that they still serve the crew and ai.
              • +
              • fixed long range cycle links
              • +
              +

              Lynameep updated:

              +
                +
              • Overhauled the Russian Bunker Ruin to be more difficult overall.
              • +
              • Atmospherics Distribution is now its own separate /area/.
              • +
              • Fixed Yogbox's atmospherics being powered by two APCs at the same time.
              • +
              • Added the Centcom Test Chamber which is accessible by a portal after arriving at centcom.
              • +
              • very Omegastation
              • +
              • much experimental
              • +
              +

              MacHac updated:

              +
                +
              • Items worn under fireproof suits will no longer catch fire.
              • +
              • Bubblegum will no longer call slaughterlings to his aid.
              • +
              • Legion cores won't hold you together forever anymore.
              • +
              +

              Mr-Space updated:

              +
                +
              • Added white caps from Chernobyl
              • +
              • This is experimental, reactor may explode. There's no graphite on ground, radiation levels at 3.6 röntgens.
              • +
              +

              Nickvr628 updated:

              +
                +
              • New contraband poster at the request of yogstation staff.
              • +
              +

              SiclaRogue updated:

              +
                +
              • Removed cargo paramedic spawn
              • +
              • Added cargo mining medic spawn
              • +
              +

              Sir-Lagsalot updated:

              +
                +
              • Adds Zoroastrianism as a chaplain religion, along with a custom holy book sprite
              • +
              +

              SomeguyManperson updated:

              +
                +
              • Extra arms for nuke ops now cost 15 TC each, up from 5 TC.
              • +
              • The bananium frying pan can now randomly crit
              • +
              • exile implants are no longer orderable from cargo since they do an astounding amount of nothing
              • +
              • Fluke ops can now buy holoparasites to live out their fantasies of being dead
              • +
              • Ratvar will now dunk on virgin constructs instead of just trying to set them on fire like a casual
              • +
              • Space bartender can now open their refrigerators
              • +
              • Voice analyzer no longer starts on inclusive so it won't detonate on hearing anything automatically
              • +
              • Syndicate speargun will start able to embed instead of needing to be adjusted to do so
              • +
              • walking into people will now cause them to move again
              • +
              • machines will now drop stuff again when deconstructed
              • +
              • explosions will no longer be delayed by thanks TG
              • +
              • PDA messages should now work again :>))))))))))))))))))))))))))
              • +
              • Rezadone is made with capsaicin instead of carpotoxin
              • +
              • RnD icons are back to normal
              • +
              • mechs will now only be able to drop one cable coil instead of being able to drop solely coils
              • +
              • clock cult armor should now appropriately buff itself when war is declared or ratvar shows up
              • +
              • coders can now check the suit storage slot for items
              • +
              • You can now attempt to defribillate people by shocking them. It's not exactly "safe" but it works.
              • +
              +

              ThatLing updated:

              +
                +
              • Hour requirements for some jobs have been altered, check https://forums.yogstation.net/index.php?threads/vote-raise-minimum-required-playtime-for-security-roles.18893/ and https://forums.yogstation.net/index.php?threads/vote-make-heads-of-staff-require-12-hours-of-department-time.18847/ for more info.
              • +
              +

              TheGamerdk updated:

              +
                +
              • Changed the Academy die of fate to a single use one.
              • +
              +

              alexkar598 updated:

              +
                +
              • abductors works now
              • +
              +

              fluffe9911 updated:

              +
                +
              • Four new AI Donor Skins 3 made by Fiodoss 1 by me!
              • +
              • Minsky Station has been authorized for use during populous shifts..again!
              • +
              • The Gas Station Clerk Actually gets teleported back now!
              • +
              • The Gas Station and Space Bar teles now actually appear on the teleporter menu
              • +
              • You can now make a living egg dog using the crafting menu!
              • +
              • Added a goat and king goat plushies to liven up (and possibly harm) the crew!
              • +
              • Added a number of Nanotrasen-approved religious clothing items to the chaplain vendor, autodrobe and clothesmate.
              • +
              • sprites and icons for several new clothing items, including some in-hand sprites.
              • +
              • An extra tile to the Box chapel's width
              • +
              • Clerk now has a cash register
              • +
              • Replaced the stools with chapel pews
              • +
              • Spiffles has been sent to the farm up north for a week and has came back better then ever!
              • +
              • Replaced the centcom megafauna with some exotic mobs cause the megafauna kept gettin out
              • +
              • Replaced the sm crystals with ducks cause sm spam bad
              • +
              • You can no longer spawn neutral mobs and not have the king goat fight back
              • +
              • Floor cluwnes will no longer flip out if you transform into something else while its grabbing you
              • +
              • Floor cluwnes will now emp the area around it while its grabbing something
              • +
              • The Vortex Tailsman no longer works in the King Goats Arena (and other noteleport areas)
              • +
              • Shadowling thralls are now discovered by looking at there eyes instead of scanning there body temp
              • +
              • Capitalist and Communist Golems will now know what they are actually doing!
              • +
              +

              yacabo updated:

              +
                +
              • Tricorders have been added! They act as a mix of Gas Analyzer, Multitool, and whatever else Yacabo thought to add!
              • +
              +

              ynot01 updated:

              +
                +
              • "Ass Blast USA" vox as roundend
              • +
              • Empowered Burger sprite has been fixed
              • +
              • Added Boodaliboo's donor colaborg
              • +
              • Raised Crab-17 health even higher
              • +
              • Scientific budget has been nerfed
              • +
              • Makes holotool sound slightly shorter than full RPED sound
              • +
              • Ocular wardens will no longer target and heal slimes
              • +
              +

              yogstation13-bot updated:

              +
                +
              • cargo sectech crate
              • +
              • sectech refill has right icon
              • +
              • Metastation's mix to engine pipe is no longer broken.
              • +
              • Pubbystation: Fixed the incinerator's waste to space injector starting powered off.
              • +
              • The modular pc vendor
              • +
              • minor entities on meta/pubby/box/delta to make room for said vendor
              • +
              • Nuclear Operative reinforcements now get uplinks with 0 TC and a real name.
              • +
              • If an admin sends a ghost back to the lobby, they can now choose a different set of quirks.
              • +
              • Rolling a 17 on the d20 of fate no longer gives you an empty box.
              • +
              • Hangar Bay Shutters need only general CentCom access to open now
              • +
              • Podcloning now lets you keep your quirks.
              • +
              • After a very long period of being broken, the training bomb in security finally works properly again.
              • +
              • Syndicate bomb cores have become a little more robust in their operation. Defused but still cored bombs are no longer inert and can be set again. Would be heroes are advised to cut out and dispose of bomb cores, and dismantle unneeded bomb shells to be absolutely sure.
              • +
              • "Religiously Comforted" from Spiritual Quirk occurs on examine and lasts for 5 minutes
              • +
              • edaggers have a special suicide!
              • +
              • between the sheets heals carbons
              • +
              • The monk's frock has been sanctified and will no longer reveal your naked body when the hood is pulled up.
              • +
              • colossus and drake can no longer attack from the afterlife
              • +
              • Colored lights now shine in different colours.
              • +
              • Gravitokinetic stands are now available!
              • +
              • Traitors now recognize codewords automatically in spoken conversation. Phrases in blue, responses in red.
              • +
              • Changed traitor greet text slightly.
              • +
              • Shuttle sounds should no longer ass blast your ears as they no longer play PER DOOR and instead from the nearest engine, or door if none are available.
              • +
              • Distant shuttle sounds.
              • +
              • Mops can now be printed at the autolathe and protolathe.
              • +
              • fixes message spam from riding a vehicle without enough legs/arms
              • +
              • timestop is properly defeated by antimagic.
              • +
              • timestop only checks antimagic once
              • +
              • Med Record, Sec Record, and Detective Scanner printed papers use paper_words icon state
              • +
              • Cards Against Spess works again. Find decks at your local library.
              • +
              • Some hivemind abilities have new icons.
              • +
              • Descriptions for every crossbreed's effect (minus recurring because its self explanatory).
              • +
              • Changes a single comment in _corecross to reflect how many extracts are needed to crossbreed.
              • +
              • Nanotrasen has cracked the code of the Syndicate storage implant. Removing the implant will also drop any items contained within, doing minor damage to the person implanted.
              • +
              • Fedoras/taqiyahs fit pocket sized items. Fedoras can also store the tommy gun and katana.
              • +
              • Forensic Scanner documents are numbered now.
              • +
              • Forensic Scanner document titles modified
              • +
              • The genetic sequencer has been reworked. Use it in-hand to view the sequence
              • +
              • Added new Teleporter Station sprites
              • +
              • fixes a missing vendor board option
              • +
              • Linen bins can now be (un)achored with a wrench, or disassembled with a screwdriver (when empty).
              • +
              • You can now make linen bins using two rods.
              • +
              • Security can now print out Missing Persons Posters
              • +
              • Scrubbers now stop scrubbing if their internal pressure gets too high.
              • +
              • sugarcane is now available in vendors
              • +
              • New RPGLoot modifiers: Vampirism which heals you when you attack, Pyromantic which sets things you hit on fire. Shrapnel which causes projectiles fired from a gun to fire projectiles in a radius when they hit something. Finally, Summoning which summons mobs that sometimes aid you in combat.
              • +
              • thrown objects (but not mobs) no longer hit the thrower
              • +
              • mirror shield rebound no longer depends on the original thrower's momentum
              • +
              • Added support for a widescreen toggle, actual widescreen will require a config change.
              • +
              • Auto-fit view is now the default. This will only apply to new players.
              • +
              • WJohnston - Created a minimalist font.
              • +
              • Security can put custom headings on wanter/missing posters.
              • +
              • Added teleport station calibration animation.
              • +
              • lightning holoparasite will no longer zap across the entire zlevel after death
              • +
              • Added new chaplain book option
              • +
              • Added hypertool null rod option
              • +
              • added Insuls book to storage.dmi
              • +
              • added hypertool icon to device.dmi
              • +
              • Tactical should no longer leave the disguise on you in some cases
              • +
              • Slimes now lose interal reagents over time.
              • +
              • Menucrafting is internally a component now
              • +
              • Fixed menucrafting opening new window when finishing construction
              • +
              • Most headgear now fit on borgs.
              • +
              • Fixed changeling's strained muscles not doing stamina
              • +
              • bamboo which can be used to build punji sticks/ blowguns available as a sugarcane mutation or in exotic seed crate (instead of banana seeds)
              • +
              • sugar cane is available at the mega seed vendor
              • +
              • Clown shoes now make you waddle by default.
              • +
              • Charcoal now purges other chemicals again. It will no longer purge itself.
              • +
              • Added gutlunch babies.
              • +
              • Gutlunches now actually reproduce.
              • +
              • removes nothing
              • +
              • Adds a new mutation to the orange
              • +
              • Botanists can now get beeplushies as an heirloom.
              • +
              • Gutlunches will no longer be too shy to feast near other mobs. This results in them being more much inclined to eat.
              • +
              • syringes with the o2 medicine work
              • +
              • Fixed multiple map voting issues when DEFAULT_NO_VOTE is not enabled
              • +
              • Cult constructs can invoke runes again
              • +
              • Added a bunch of previously missing adjacency checks to ui window actions and alt clicks.
              • +
              • Some rpg affixes now have special effects
              • +
              • You can now craft a firebrand with two pieces of wood.
              • +
              • Dehydrated carp suicide.
              • +
              • Hourglasses now available in library game vending machine.
              • +
              • You can now craft bolas and spears with sinew restraints.
              • +
              • Replaced the water tank in the ashlizard base with a puddle.
              • +
              • Nicotine now kills pest and add toxicity when added to a plant tray.
              • +
              • You can now view special server rules for your current character with "Show Policy" verb in OOC tab
              • +
              • Nurse spiders no longer spawn from gold slime core and life chemical reactions.
              • +
              • suit sensors are now randomized when caught in an EMP
              • +
              • fixed the hypertool and insuls book
              • +
              • Phobias will no longer make you scared of yourself.
              • +
              • attaching something to a mech wont auto select it
              • +
              • You can now make caramel by heating sugar. Be careful not to overcook it.
              • +
              • Adjusted the candied apple recipe
              • +
              • phazon construction message is fixed
              • +
              • Cargo can now order a ridiculously large canister containing only saline to give medbay a more than plentiful supply of the stuff.
              • +
              • PDA beep, grenade arm, and flesh impact sounds have been fixed
              • +
              • When you get a quick rundown, your ears bleed less.
              • +
              • surgical drapes and syringes can be printed by medlathe
              • +
              • New trauma: Expressive Aphasia only. Restricts your speech to the 1000 most common english words.
              • +
              • A couple of dead bodies are now in the morgue. The souls are long gone, so no one will care if you "borrowed" a part or two.
              • +
              • sleeping carp now deflects projectiles
              • +
              • The "toggle open" verb from closets works now.
              • +
              • A bunch of minor issues with xenobiology are no more!
              • +
              • A new objective type: "Protect Object". Please use it at leasure for badminnery.
              • +
              • Ashlizards are now more savage and like stuff like sacrificing and head spikes.
              • +
              • Fixes an exploit allowing you to move enabled emitters
              • +
              • Breaking limbs with damage now properly makes you drop the item you're holding with it.
              • +
              • Added wooden farming implements to the ashwalker base.
              • +
              • Makes rakes and wooden buckets craftable.
              • +
              • New Hygiene Sprite
              • +
              • Cloners now have the option to "Empty Clone" a record, creating a mindless replica of that person.
              • +
              • To aid the above function, cloners can now do Body-Only scans, which can be used to create empty clones but not real clones, but bypass the sentience restrictions on scans. They can also be deleted without requiring access.
              • +
              • Recipe for fabled secret sauce can now be found in the deepest reaches of space.
              • +
              • The mop can now be printed at the autolathe.
              • +
              • Syndicate Contracts. Use the new contract uplink to select a contract, and bring the assigned target dead or alive to the designated drop off. Call for the extraction pod and send them off for your TC payment, with much higher rewards for keeping them alive. A high risk, high reward choice for a traitor who wants a real challenge.
              • +
              • New 20 TC contract kit - supplies you with your contractor loadout and uplink.
              • +
              • Targets successfully extracted will be held for ransom by the Syndicate after their use to them is fulfilled. Central command covers the cost, but they'll be taking a cut out of station funds to offset their loss...
              • +
              • Changed the Syndibase walls to look more evil.
              • +
              • The smallstache beard now looks slightly less silly.
              • +
              • Lavaland plants now have the fireproof gene.
              • +
              • Napalm now always burns weeds, even if the plant is fireproof.
              • +
              • Lavaland plants now get their reagents from their genes.
              • +
              • borgs can properly carry incapacitated individuals
              • +
              • The lavaland clown ruin has some new pranks ready.
              • +
              • Fixed obsessed random event ignoring preferences
              • +
              • If your name is a valid url, it won't be linkified in deadchat anymore.
              • +
              • Fixed snails retaining their lube crawling after changing to a different species
              • +
              • Updates recon, stealth, hacker, bond, sabotage, mad scientist, bee, and mr freeze bundles with several new items (most already in code) to make them less trash.
              • +
              • Bee Sword (literally just a yellow, weaker esword variant, needed it for another piece of code)
              • +
              • A weaker variant of the decloner gun.
              • +
              • Some changes to the lavaland hermits base and their starting equipment.
              • +
              • mortar grinds food
              • +
              • Revenants will no longer be hit by projectiles while hidden
              • +
              • The 3d orange now gives you the proper seeds.
              • +
              • Fixed silicon items (e.g. cyborg modules) being destroyed by explosion epicenters
              • +
              • Fixed livers not being damaged by toxins
              • +
              • Shuttles that go to a custom dock are no longer forever trapped by their hubris
              • +
              • the bee syndicate kit now contains a rapier
              • +
              • The CNS rebooter now purges stuns after 4 seconds of not being stunned, instead of doing nothing.
              • +
              • refill for autodrobe and BODA work
              • +
              • Icons will no longer extend past cryo.
              • +
              • Xenos and monkeys no longer have snowflake icons for cryo.
              • +
              • alt clicking the emitter now rotates it instead of only flipping
              • +
              • fixes russian helmets not holding both a vodka and a glass
              • +
              • Nar'Sie now EMPs mechs, instead of turning the pilot into a harvester.
              • +
              • Cult teleport rune will no longer teleport ghosts or camera mobs.
              • +
              • legion core damage slowdown is temporary, like all things
              • +
              • After receiving many complaints about mimes who never pantomime, Nanotrasen has liquidated its mime personnel and hired new mimes who know more routines.
              • +
              • The mime gets the choice of two new spells: Invisible Chair and Invisible Box. They work much like the Invisible Wall spell does and disappear after a short span of time. The invisible chair can be buckled to like usual chairs; the box works like a standard inventory box item.
              • +
              • Mime spells are now granted via a spellbook that starts out in the mime's inventory. Mimes can choose one of these three.
              • +
              • Removed all sleepers from escape shuttles and replaced them with stasis units. Some shuttles have gotten surgery and new medical vendors added.
              • +
              • Quantum teleportation now makes pretty rainbow sparks instead of the normal ones.
              • +
              • Non-bluespace teleportation (spells etc.) no longer makes sparks.
              • +
              • Centcom's Death Commando got a paycheck and jumpsuits of quality more similar to the rest of Centcom's staf- KS13 DOESN'T EXIST, THERE IS NO SUCH THING AS A DEATH COMMANDO
              • +
              • Normal carps now spawn with a random color! There might even be some really rare color variant.. try asking Cayenne about it.
              • +
              • Non-binary changelings will now have the honorific "Dear" instead of "Mr.", reflecting the Syndicate's growing acceptance of operatives who reject the binary as well as Nanotrasen's authority.
              • +
              • The bee sabre now has it's own suicide and no longer uses the one of the captains rapier.
              • +
              • The bee sabre uses the correct articles and actually has icons now.
              • +
              • Space heaters no longer steal focus upon screwdrivering.
              • +
              • added geo-bypass
              • +
              • Fix bad icon state for bounty console printouts
              • +
              • Incomplete and non-teleport reactive armors can no longer be used to complete the traitor objective.
              • +
              • DIY Dish Drive Kit now available at your local bardrobe. Start saving those tips!
              • +
              • The Dish Drive no longer sends reusable items into the disposal bin
              • +
              • gives scientists a chance to spawn with an awesome tie
              • +
              • Monkeys can wear collars again
              • +
              • Turned off energy weapons can no longer cut down trees, destroy wooden walls, harvest lavaland plants and make planks.
              • +
              • Contract kit's specialist space suit now has its custom sprite.
              • +
              • Assigning to tablet now plays greeting soundclip.
              • +
              • Quirks no longer apply to off-station roundstart antagonists.
              • +
              • added department skirts
              • +
              • fixed secskirt dixel
              • +
              • the TB antidotes injectors now actually cure TB and have some omnizine so you dont die from perfluoride
              • +
              • stacks no longer give you a pop up window when you alt click on one on the floor
              • +
              • player added items now appear above the standard selection in vending machines.
              • +
              • Fixed bug where hivemind vessel awakening would not be logged.
              • +
              • Fixed bug where awoken hivemind vessel objectives would not be logged.
              • +
              • slightly modified the deadchat text for awakening.
              • +
              • Dead AI players will now get a notification if they are being restored while outside their body.
              • +
              • Engineering closets now start with a construction bag, which can hold machine components, circuits, electronics, and cable coils.
              • +
              • You can now craft white jumpskirts with cloth.
              • +
              • the russian revolver's chamber can be spun again
              • +
              • Red, white, and blue mech pilot jumpsuits now available in the Mech Pilot's Suits Crate, under Costumes & Toys.
              • +
              • limbs are now small
              • +
              • The R&D Server Controller console now lists the entire research history, including names of people who researched each item and locations it was done from.
              • +
              • The R&D Server Controller console can now be used to disable the servers if someone makes the RD upset.
              • +
              • Fixed the R&D servers working without power.
              • +
              • R&D server sprites are now slightly animated, and new sprites have been added for when the server is disabled or off.
              • +
              • Negative armor on objects now increases damage taken.
              • +
              • Humans now always use the correct value for limb armor.
              • +
              • Various cosmetic and gameplay related changes to hivebots. Player controlled hivebots can change their look by changing their intent to harm.
              • +
              • Mobs with negative armor no longer get damage decreased by armor penetration.
              • +
              • Added a new contraband about free syndicate keys
              • +
              • Antimagic now gets used up properly
              • +
              • Reproductive crossbreed extracts can be fed from the bio bag again
              • +
              • You can no longer infinitely purify a soulstone.
              • +
              • Cultists can't use purified soulstones anymore.
              • +
              • Coming into contact with -HONK- may have additional effects, do not be alarmed
              • +
              • For safety reasons, the administration of -HONK- may only be performed by trained professionals
              • +
              • Self-application of -HONK- is now forbidden
              • +
              • Add match strike sound.
              • +
              • NT staff have added new laser focusing rings onto all BSA equipment, so now it looks more like a massive deadly laser, and fires more accurately.
              • +
              • Clones no longer have their genetic sequence quantum garble fucked together
              • +
              • Breakfast foods such as eggs, donuts, coffee and so on now give a long but weak positive moodie when eaten within 15 minutes of the start of a shift
              • +
              • The sabre sheath properly plays sounds again after who knows how long
              • +
              • All chainsaws, including the null rod variants, can now be used to saw off guns.
              • +
              • Added mime envirosuits, nothing special here.
              • +
              • Added clown envirosuits, which release space lube when they extinguish the clown.
              • +
              • Removes code that theoretically limits plasmamen from being clowns and mimes, but actually doesn't.
              • +
              • I guess the roles now spawning with these suits is kind of notable.
              • +
              • Reverts unlisted changes to the grey detective suit from https://github.com/tgstation/tgstation/pull/44776.
              • +
              • Cowboy boots have been added to the Clothesmate.
              • +
              • Lizard skin boots with decent export values have been added to the crafting menu.
              • +
              • Snakes, headslugs and alien larvae can now hide in cowboy boots.
              • +
              +
              + +GoonStation 13 Development Team +
              + Coders: Stuntwaffle, Showtime, Pantaloons, Nannek, Keelin, Exadv1, hobnob, Justicefries, 0staf, sniperchance, AngriestIBM, BrianOBlivion
              + Spriters: Supernorn, Haruhi, Stuntwaffle, Pantaloons, Rho, SynthOrange, I Said No
              +
              +
              +

              Creative Commons License
              Except where otherwise noted, Goon Station 13 is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License.
              Rights are currently extended to SomethingAwful Goons only.

              +

              Some icons by Yusuke Kamiyamane. All rights reserved. Licensed under a Creative Commons Attribution 3.0 License.

              +
              + + diff --git a/html/create_object.html b/html/create_object.html index 844687a595c6..8039998a96e0 100644 --- a/html/create_object.html +++ b/html/create_object.html @@ -1,129 +1,129 @@ - - - - - Create Object - - - - -
              - - /* hreftokenfield */ - - Type
              - Offset: - - A - R
              - - Number: - Dir: - Name:
              - Where: - -

              - -
              - - - -
              - - - - - + + + + + Create Object + + + + +
              + + /* hreftokenfield */ + + Type
              + Offset: + + A + R
              + + Number: + Dir: + Name:
              + Where: + +

              + +
              + + + +
              + + + + + diff --git a/interface/interface.dm b/interface/interface.dm index b68b57efd732..38130433421d 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -1,226 +1,226 @@ -//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) - if(wikiurl) - if(query) - var/output = wikiurl + "/index.php?title=Special%3ASearch&profile=default&search=" + query - src << link(output) - else if (query != null) - 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].[byond_build]\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 -// yogs start - Rebindable Hotkeys -/* -/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 belt slot) or take out most recent item added. -\tShift+b = Put held item into backpack(or back slot) or take out most recent item added. -\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) + if(wikiurl) + if(query) + var/output = wikiurl + "/index.php?title=Special%3ASearch&profile=default&search=" + query + src << link(output) + else if (query != null) + 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].[byond_build]\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 +// yogs start - Rebindable Hotkeys +/* +/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 belt slot) or take out most recent item added. +\tShift+b = Put held item into backpack(or back slot) or take out most recent item added. +\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) +*/ // yogs end \ No newline at end of file diff --git a/strings/names/adjectives.txt b/strings/names/adjectives.txt index fe9b28d43144..bca89bc5a257 100644 --- a/strings/names/adjectives.txt +++ b/strings/names/adjectives.txt @@ -1,375 +1,375 @@ -abundant -adorable -adventurous -aggressive -agreeable -alert -alive -amused -ancient -angry -annoyed -annoying -anxious -arrogant -ashamed -attractive -average -awful -bad -beautiful -better -bewildered -big -bitter -black -bloody -blue -blue-eyed -blushing -boiling -bored -brainy -brave -breakable -breezy -brief -bright -broad -broken -bumpy -busy -calm -careful -cautious -charming -cheerful -chilly -chubby -clean -clear -clever -cloudy -clumsy -cold -colorful -colossal -combative -comfortable -concerned -condemned -confused -cooing -cool -cooperative -courageous -crazy -crazy flipped-out -creepy -crooked -crowded -cruel -cuddly -curious -curly -curved -cute -damaged -damp -dangerous -dark -dead -deafening -deep -defeated -defiant -delicious -delightful -depressed -determined -different -difficult -dirty -disgusted -distinct -disturbed -dizzy -doubtful -drab -dry -dull -dusty -eager -early -easy -elated -elegant -embarrassed -empty -enchanting -encouraging -energetic -enthusiastic -envious -evil -excited -expensive -exuberant -faint -fair -faithful -famous -fancy -fantastic -fast -fat -few -fierce -filthy -fine -flaky -flat -fluffy -fluttering -foolish -fragile -frail -frantic -freezing -fresh -friendly -frightened -funny -fuzzy -gentle -gifted -gigantic -glamorous -gleaming -glorious -good -gorgeous -graceful -greasy -great -grieving -grotesque -grubby -grumpy -handsome -happy -hard -harsh -healthy -heavy -helpful -helpless -high -high-pitched -hilarious -hissing -hollow -homeless -homely -horrible -hot -huge -hungry -hurt -hushed -husky -icy -ill -immense -important -impossible -inexpensive -innocent -inquisitive -itchy -jealous -jittery -jolly -joyous -juicy -kind -large -late -lazy -light -little -lively -lonely -long -loose -loud -lovely -low -lucky -magnificent -mammoth -many -massive -melodic -melted -miniature -misty -moaning -modern -motionless -muddy -mushy -mute -mysterious -narrow -nasty -naughty -nervous -nice -noisy -numerous -nutritious -nutty -obedient -obnoxious -odd -old -old-fashioned -open -outrageous -outstanding -panicky -perfect -petite -plain -plastic -pleasant -poised -poor -powerful -precious -prickly -proud -puny -purring -puzzled -quaint -quick -quiet -rainy -rapid -raspy -real -relieved -repulsive -resonant -rich -ripe -rotten -rough -round -salty -scary -scattered -scrawny -screeching -selfish -shaggy -shaky -shallow -sharp -shiny -shivering -short -shrill -shy -silent -silky -silly -skinny -sleepy -slimy -slippery -slow -small -smiling -smoggy -smooth -soft -solid -sore -sour -sparkling -spicy -splendid -spotless -square -squealing -stale -steady -steep -sticky -stormy -straight -strange -strong -stupid -substantial -successful -super -sweet -swift -talented -tall -tame -tart -Taste/Touch -tasteless -tasty -teeny -teeny-tiny -tender -tense -terrible -testy -thankful -thirsty -thoughtful -thoughtless -thundering -tight -tiny -tired -tough -troubled -ugliest -ugly -uneven -uninterested -unsightly -unusual -upset -uptight -vast -victorious -vivacious -voiceless -wandering -warm -weak -weary -wet -whispering -wicked -wide -wide-eyed -wild -witty -wonderful -wooden -worried -wrong -young -yummy -zany +abundant +adorable +adventurous +aggressive +agreeable +alert +alive +amused +ancient +angry +annoyed +annoying +anxious +arrogant +ashamed +attractive +average +awful +bad +beautiful +better +bewildered +big +bitter +black +bloody +blue +blue-eyed +blushing +boiling +bored +brainy +brave +breakable +breezy +brief +bright +broad +broken +bumpy +busy +calm +careful +cautious +charming +cheerful +chilly +chubby +clean +clear +clever +cloudy +clumsy +cold +colorful +colossal +combative +comfortable +concerned +condemned +confused +cooing +cool +cooperative +courageous +crazy +crazy flipped-out +creepy +crooked +crowded +cruel +cuddly +curious +curly +curved +cute +damaged +damp +dangerous +dark +dead +deafening +deep +defeated +defiant +delicious +delightful +depressed +determined +different +difficult +dirty +disgusted +distinct +disturbed +dizzy +doubtful +drab +dry +dull +dusty +eager +early +easy +elated +elegant +embarrassed +empty +enchanting +encouraging +energetic +enthusiastic +envious +evil +excited +expensive +exuberant +faint +fair +faithful +famous +fancy +fantastic +fast +fat +few +fierce +filthy +fine +flaky +flat +fluffy +fluttering +foolish +fragile +frail +frantic +freezing +fresh +friendly +frightened +funny +fuzzy +gentle +gifted +gigantic +glamorous +gleaming +glorious +good +gorgeous +graceful +greasy +great +grieving +grotesque +grubby +grumpy +handsome +happy +hard +harsh +healthy +heavy +helpful +helpless +high +high-pitched +hilarious +hissing +hollow +homeless +homely +horrible +hot +huge +hungry +hurt +hushed +husky +icy +ill +immense +important +impossible +inexpensive +innocent +inquisitive +itchy +jealous +jittery +jolly +joyous +juicy +kind +large +late +lazy +light +little +lively +lonely +long +loose +loud +lovely +low +lucky +magnificent +mammoth +many +massive +melodic +melted +miniature +misty +moaning +modern +motionless +muddy +mushy +mute +mysterious +narrow +nasty +naughty +nervous +nice +noisy +numerous +nutritious +nutty +obedient +obnoxious +odd +old +old-fashioned +open +outrageous +outstanding +panicky +perfect +petite +plain +plastic +pleasant +poised +poor +powerful +precious +prickly +proud +puny +purring +puzzled +quaint +quick +quiet +rainy +rapid +raspy +real +relieved +repulsive +resonant +rich +ripe +rotten +rough +round +salty +scary +scattered +scrawny +screeching +selfish +shaggy +shaky +shallow +sharp +shiny +shivering +short +shrill +shy +silent +silky +silly +skinny +sleepy +slimy +slippery +slow +small +smiling +smoggy +smooth +soft +solid +sore +sour +sparkling +spicy +splendid +spotless +square +squealing +stale +steady +steep +sticky +stormy +straight +strange +strong +stupid +substantial +successful +super +sweet +swift +talented +tall +tame +tart +Taste/Touch +tasteless +tasty +teeny +teeny-tiny +tender +tense +terrible +testy +thankful +thirsty +thoughtful +thoughtless +thundering +tight +tiny +tired +tough +troubled +ugliest +ugly +uneven +uninterested +unsightly +unusual +upset +uptight +vast +victorious +vivacious +voiceless +wandering +warm +weak +weary +wet +whispering +wicked +wide +wide-eyed +wild +witty +wonderful +wooden +worried +wrong +young +yummy +zany zealous \ No newline at end of file diff --git a/strings/names/ai.txt b/strings/names/ai.txt index 2ea5e18fc005..4104337d2cd5 100644 --- a/strings/names/ai.txt +++ b/strings/names/ai.txt @@ -1,146 +1,146 @@ -1-Rover-1 -16-20 -7-Zark-7 -790 -Adaptive Manipulator -Allied Mastercomputer -Alpha 5 -Alpha 6 -Alpha 7 -AM -AMEE -AmigoBot -Android -Aniel -Asimov -ASTAR -Astor -B O B -B-4 -B-9 -B166ER -Bender -Bishop -Blitz -Box -Brackenridge -C-3PO -Cassandra One -Cell -Chii -Chip -Computer -Conky 2000 -Cutie -Data -Decimus -Dee Model -Deep Thought -Dor-15 -Dorfl -Dot Matrix -Duey -E D I -E-Man -ED-209 -Emma-2 -Erasmus -Ez-27 -Fagor -Faith -Fi -FRIEND COMPUTER -Frost -Fum -Futura -G2 -George -Gnut -Gort -H A R L I E -H E L P eR -H E R B I E -Hadaly -HAL 9000 -Huey -Irona -Ironhide -Jay-Dub -Jinx -Johnny 5 -K-9 -KITT -Klapaucius -Kryten 2X4B-523P -L-76 -L-Ron -Louie -LUH 3417 -Maria -MARK13 -Marvin -Master Control Program -Max 404 -Maximillian -Mechagodzilla -Mechani-Kong -Megatron -Metalhead -Mr R I N G -Mugsy3000 -NCH -Necron-99 -Norby -OMM 0910 -Optimus -Orange v 3 5 -Project 2501 -PTO -R I C 2 0 -R2-D2 -R4-P17 -Revelation -Ro-Man -Robbie -Robot Devil -S A M -S H O C K -S H R O U D -S O P H I E -SEN 5241 -Setaur -SHODAN -Shrike -SID 6 7 -Solo -Soundwave -Speedy -Super 17 -Surgeon General Kraken -T-1000 -T-800 -T-850 -Terminus -THX 1138 -Tidy -Tik-Tok -Tobor -Trurl -TWA -ULTRABOT -Ulysses -Uniblab -V I N CENT -Voltes V -W1k1 -Wikipedia -Windows 3 1 -X-5 -XERXES -XR -Yod -Z-1 -Z-2 -Z-3 -Zed +1-Rover-1 +16-20 +7-Zark-7 +790 +Adaptive Manipulator +Allied Mastercomputer +Alpha 5 +Alpha 6 +Alpha 7 +AM +AMEE +AmigoBot +Android +Aniel +Asimov +ASTAR +Astor +B O B +B-4 +B-9 +B166ER +Bender +Bishop +Blitz +Box +Brackenridge +C-3PO +Cassandra One +Cell +Chii +Chip +Computer +Conky 2000 +Cutie +Data +Decimus +Dee Model +Deep Thought +Dor-15 +Dorfl +Dot Matrix +Duey +E D I +E-Man +ED-209 +Emma-2 +Erasmus +Ez-27 +Fagor +Faith +Fi +FRIEND COMPUTER +Frost +Fum +Futura +G2 +George +Gnut +Gort +H A R L I E +H E L P eR +H E R B I E +Hadaly +HAL 9000 +Huey +Irona +Ironhide +Jay-Dub +Jinx +Johnny 5 +K-9 +KITT +Klapaucius +Kryten 2X4B-523P +L-76 +L-Ron +Louie +LUH 3417 +Maria +MARK13 +Marvin +Master Control Program +Max 404 +Maximillian +Mechagodzilla +Mechani-Kong +Megatron +Metalhead +Mr R I N G +Mugsy3000 +NCH +Necron-99 +Norby +OMM 0910 +Optimus +Orange v 3 5 +Project 2501 +PTO +R I C 2 0 +R2-D2 +R4-P17 +Revelation +Ro-Man +Robbie +Robot Devil +S A M +S H O C K +S H R O U D +S O P H I E +SEN 5241 +Setaur +SHODAN +Shrike +SID 6 7 +Solo +Soundwave +Speedy +Super 17 +Surgeon General Kraken +T-1000 +T-800 +T-850 +Terminus +THX 1138 +Tidy +Tik-Tok +Tobor +Trurl +TWA +ULTRABOT +Ulysses +Uniblab +V I N CENT +Voltes V +W1k1 +Wikipedia +Windows 3 1 +X-5 +XERXES +XR +Yod +Z-1 +Z-2 +Z-3 +Zed Zord \ No newline at end of file diff --git a/strings/names/carp.txt b/strings/names/carp.txt index 4b10aa65f742..223106992bf6 100644 --- a/strings/names/carp.txt +++ b/strings/names/carp.txt @@ -1,30 +1,30 @@ -Lungfish -Blackfish -Alligator -Icefish -Armorhead -Hammerhead -Anaconda -Flathead -Manta Ray -Sting Ray -Fangtooth Moray -Goblin Shark -Grass Carp -Round River Bat Ray -Noodlefish -Hagfish -Man o’ War -Ladyfish -Black Eel -Baby Seal -Sprat -Koi -Electric Eel -Lamprey -Pejeray -Yellow-edged Moray -Salmon Shark -Sleeper Shark -Featherback -Eagle Ray +Lungfish +Blackfish +Alligator +Icefish +Armorhead +Hammerhead +Anaconda +Flathead +Manta Ray +Sting Ray +Fangtooth Moray +Goblin Shark +Grass Carp +Round River Bat Ray +Noodlefish +Hagfish +Man o’ War +Ladyfish +Black Eel +Baby Seal +Sprat +Koi +Electric Eel +Lamprey +Pejeray +Yellow-edged Moray +Salmon Shark +Sleeper Shark +Featherback +Eagle Ray diff --git a/strings/names/clown.txt b/strings/names/clown.txt index ca2e57159419..3501ece3c4cd 100644 --- a/strings/names/clown.txt +++ b/strings/names/clown.txt @@ -1,122 +1,122 @@ -Aluminium Dave -Baba -Baby Cakes -Bananium LXIX -Bimbim -Bingo -Birdman From Birdland -Bizarre Bottle -Bizarro -Bo Bo Sassy -Bonker -Bubble -Buster Frown -Butters The Bean -Buttery Muffin -Button -Candy -Carphunter -Checkers -Chuckles -Circe The Great -Congo Bongo -Cool Cooper -Crusty -Deedum Dedah -Delicious Dan -Dinkster -Dinky Doodle -Doctor Greenthumb -Doink -Early Worm -Eggy -Emotional Oatmeal -Fat Fingers -Fishbreath -Flop O'Honker -Freckle -Fretworks -Galton -Giggles -Gigglesworth -Goose McSunny -Grabby -Grandma -Grebble -Greedo Shotfurst -Happy Slappy -Harvey Harvey Harvey -Hedgerow Harry -Hefty Hempo -Homey -Honkel the III -Honker -Honkerbelle -Implausible Fun -Impro -Jazzle -Jim From Accounting -Jingle -Jo Jo Bobo Bo -Johnny Bigshoes -King of Klowns -Ladybug Honks -Laughing Man -Loopy Lazarus -Mime -Misery Marvin -Miss Stockings -Mister Ostprussen -Mister Redherring -Mister Roboto -Mister Safety Hazard -Moinen -Mr Shoe -Mr Weird -Ningelo -Patches -Pepinpop -Pocket -Pogo -Polite Pablo -Post Nose -Razzle Dazzle -Red April -Red Dead Redemption Two -Redshirt McBeat -Rinso The Soapy -Roboflop -Ronnie Pace -Rude Robert -Sandwich Sam -Scootaloo -Shinturner -Shithead The Irritator -Shoe Giving Gerald -Silly Willy -Sir Honks-a-Lot -Skiddle -Slippo Supreme -Slippy Joe -Slipsy Dipsy -Sparkle -Speckles -Sprinkledinkle -The Amazing Farto -The Best -The Fun Uncle -The Mediocre -The Unfun Uncle -The Worst -Toodles Sharperton -Tree Talker -Twisty -Uncool Ulrich -Unimaginable Nut -Valid Vincent -Weather Report -Widderwise -Yanye Kest -Yesterdays Beef -Yobbo -Ziggy Yoyo +Aluminium Dave +Baba +Baby Cakes +Bananium LXIX +Bimbim +Bingo +Birdman From Birdland +Bizarre Bottle +Bizarro +Bo Bo Sassy +Bonker +Bubble +Buster Frown +Butters The Bean +Buttery Muffin +Button +Candy +Carphunter +Checkers +Chuckles +Circe The Great +Congo Bongo +Cool Cooper +Crusty +Deedum Dedah +Delicious Dan +Dinkster +Dinky Doodle +Doctor Greenthumb +Doink +Early Worm +Eggy +Emotional Oatmeal +Fat Fingers +Fishbreath +Flop O'Honker +Freckle +Fretworks +Galton +Giggles +Gigglesworth +Goose McSunny +Grabby +Grandma +Grebble +Greedo Shotfurst +Happy Slappy +Harvey Harvey Harvey +Hedgerow Harry +Hefty Hempo +Homey +Honkel the III +Honker +Honkerbelle +Implausible Fun +Impro +Jazzle +Jim From Accounting +Jingle +Jo Jo Bobo Bo +Johnny Bigshoes +King of Klowns +Ladybug Honks +Laughing Man +Loopy Lazarus +Mime +Misery Marvin +Miss Stockings +Mister Ostprussen +Mister Redherring +Mister Roboto +Mister Safety Hazard +Moinen +Mr Shoe +Mr Weird +Ningelo +Patches +Pepinpop +Pocket +Pogo +Polite Pablo +Post Nose +Razzle Dazzle +Red April +Red Dead Redemption Two +Redshirt McBeat +Rinso The Soapy +Roboflop +Ronnie Pace +Rude Robert +Sandwich Sam +Scootaloo +Shinturner +Shithead The Irritator +Shoe Giving Gerald +Silly Willy +Sir Honks-a-Lot +Skiddle +Slippo Supreme +Slippy Joe +Slipsy Dipsy +Sparkle +Speckles +Sprinkledinkle +The Amazing Farto +The Best +The Fun Uncle +The Mediocre +The Unfun Uncle +The Worst +Toodles Sharperton +Tree Talker +Twisty +Uncool Ulrich +Unimaginable Nut +Valid Vincent +Weather Report +Widderwise +Yanye Kest +Yesterdays Beef +Yobbo +Ziggy Yoyo diff --git a/strings/names/death_commando.txt b/strings/names/death_commando.txt index 44d280fbbe6d..d09efc5f83fc 100644 --- a/strings/names/death_commando.txt +++ b/strings/names/death_commando.txt @@ -1,91 +1,91 @@ -A whole bunch of spiders in a SWAT suit -Al "Otta" Gore -AMERICA -Beat Punchbeef -Beef Manmuscle -Beef Slab -Benny Bloodmulch -Bicep McTricep -Billy Herrington -Blast Hardcheese -Blast Thickneck -Bob Johnson -Bold Bigflank -Bolt Vanderhuge -Brawn Hugeneck -Brick Hardmeat -Buck Plankchest -Buff Drinklots -Buff Hardback -Buff Slamchest -Butch Deadlift -Chombo Cromagnum -Crud Bonemeal -Crunch Buttsteak -Crush McStompbones -Diego Diefist -Dirk Hardpeck -Duke Killington -Evil Bob Marley -Evil Martin Luther King -Fist Rockbone -Flint Ironstag -Ford Spinetwist -Fridge Largemeat -George Melons -Gibbs McLargehuge -GORE Vidal -Gristle McThornBody -Hank Chesthair -Hans Testosteroneson -Killiam Shakespeare -Killing McKillingalot -Jet Handblood -Lance Killiam -Leonardo Da Viking -Lump Beefrock -Lunge Crushheel -Mancrush McBrorape -Max Pain -Maximilian Murderface -Maxx Power -Noam Bombsky -Norb Chrometaint -Oats Squatzon -Pack Blowfist -Punch Rockgroin -Punch Sideiron -Punt Speedchunk -Reef Blastbody -Rex Dudekiller VII -Rip Sidecheek -Rip Steakface -Rock Vangranite -Roid Swolebod -Roll Fizzlebeef -Ronnie Roidready -Sarah Pain -Seamus McTosterone -Sgt Slaughter -Shred Gluteflex -Sir Killaslot -Slab Bulkhead -Slab Squatthrust -Slake Fistcrunch -Slate Slabrock -Smash Lampjaw -Smoke Manmuscle -Splint Chesthair -Stabby McGee -Stump Beefgnaw -Stump Chunkman -THAT DAMN GRIFF TRAITOR GEORGE MELONS -Theodore Pain -Thick McRunfast -Thrust Vanderhuge -Trent Tightshirt -Toolboxl Rose -Touch Rustrod -Trunk Slamchest -Van Darkholme -Zombie Gandhi +A whole bunch of spiders in a SWAT suit +Al "Otta" Gore +AMERICA +Beat Punchbeef +Beef Manmuscle +Beef Slab +Benny Bloodmulch +Bicep McTricep +Billy Herrington +Blast Hardcheese +Blast Thickneck +Bob Johnson +Bold Bigflank +Bolt Vanderhuge +Brawn Hugeneck +Brick Hardmeat +Buck Plankchest +Buff Drinklots +Buff Hardback +Buff Slamchest +Butch Deadlift +Chombo Cromagnum +Crud Bonemeal +Crunch Buttsteak +Crush McStompbones +Diego Diefist +Dirk Hardpeck +Duke Killington +Evil Bob Marley +Evil Martin Luther King +Fist Rockbone +Flint Ironstag +Ford Spinetwist +Fridge Largemeat +George Melons +Gibbs McLargehuge +GORE Vidal +Gristle McThornBody +Hank Chesthair +Hans Testosteroneson +Killiam Shakespeare +Killing McKillingalot +Jet Handblood +Lance Killiam +Leonardo Da Viking +Lump Beefrock +Lunge Crushheel +Mancrush McBrorape +Max Pain +Maximilian Murderface +Maxx Power +Noam Bombsky +Norb Chrometaint +Oats Squatzon +Pack Blowfist +Punch Rockgroin +Punch Sideiron +Punt Speedchunk +Reef Blastbody +Rex Dudekiller VII +Rip Sidecheek +Rip Steakface +Rock Vangranite +Roid Swolebod +Roll Fizzlebeef +Ronnie Roidready +Sarah Pain +Seamus McTosterone +Sgt Slaughter +Shred Gluteflex +Sir Killaslot +Slab Bulkhead +Slab Squatthrust +Slake Fistcrunch +Slate Slabrock +Smash Lampjaw +Smoke Manmuscle +Splint Chesthair +Stabby McGee +Stump Beefgnaw +Stump Chunkman +THAT DAMN GRIFF TRAITOR GEORGE MELONS +Theodore Pain +Thick McRunfast +Thrust Vanderhuge +Trent Tightshirt +Toolboxl Rose +Touch Rustrod +Trunk Slamchest +Van Darkholme +Zombie Gandhi diff --git a/strings/names/first.txt b/strings/names/first.txt index cc99e59d84e2..3f9018adca21 100644 --- a/strings/names/first.txt +++ b/strings/names/first.txt @@ -1,1396 +1,1396 @@ -Aaden -Aaliyah -Aaron -Abby -Abel -Abigail -Abraham -Adam -Adan -Addison -Addyson -Adeline -Aden -Adolph -Adrian -Adriana -Adrianna -Aida -Aidan -Aiden -Aileen -Ainsley -Alaina -Alan -Alana -Alanna -Alayna -Albert -Alberto -Alden -Alec -Alejandra -Alejandro -Alessandra -Alex -Alexa -Alexander -Alexandra -Alexandria -Alexia -Alexis -Alexus -Alfred -Alfreda -Alfredo -Alger -Ali -Alice -Alicia -Alijah -Alina -Alisa -Alison -Alissa -Alisya -Alivia -Aliyah -Allegra -Allegria -Allen -Allie -Allison -Allisson -Allyson -Alma -Alondra -Alvin -Alysha -Alyson -Alyssa -Alyssia -Amanda -Amari -Amaryllis -Amaya -Amber -Ambrosine -Amelia -Amir -Amira -Amiyah -Amos -Amy -Amya -Ana -Anahi -Anastasia -Anaya -Anderson -Andre -Andrea -Andres -Andrew -Andy -Angel -Angela -Angelica -Angelina -Angelo -Angie -Aniya -Aniyah -Anjelica -Anna -Annabelle -Anne -Annie -Annika -Anthony -Antonio -Anya -April -Arabella -Archie -Ariana -Arianna -Ariel -Arielle -Arleen -Armando -Arn -Art -Arthur -Arturo -Asher -Ashley -Ashlie -Ashlyn -Ashlynn -Ashton -Asia -Astor -Athena -Aubree -Aubrey -Aubrie -Audrey -Audrina -August -Aurora -Austin -Autumn -Ava -Avalon -Averill -Avery -Axel -Ayden -Ayla -Bailey -Baldric -Barbra -Bartholomew -Baylee -Beau -Beckah -Beckett -Becky -Bella -Benjamin -Bennett -Bernice -Bertrand -Bethany -Bethney -Betsy -Bianca -Bidelia -Bill -Blake -Braden -Bradley -Brady -Braeden -Braiden -Brandon -Braxton -Brayan -Brayden -Braydon -Braylon -Breanna -Breanne -Brenda -Brendan -Brenden -Brenna -Brennan -Brett -Brian -Briana -Brianna -Bridget -Brielle -Brittani -Brittany -Brock -Brodie -Brody -Bronte -Brooke -Brooklyn -Brooklynn -Bruce -Bryan -Bryant -Bryce -Brycen -Brynn -Bryson -Burt -Byrne -Byron -Bysshe -Cade -Caden -Cadence -Caiden -Caitlin -Caitlyn -Calanthia -Caleb -Caleigh -Cali -Callie -Calvin -Camden -Cameron -Camila -Camille -Camron -Camryn -Candace -Candice -Candis -Canute -Cara -Carl -Carlos -Carly -Carlyle -Carmen -Carolina -Caroline -Carolyn -Carry -Carson -Carter -Caryl -Casey -Cash -Casimir -Cassandra -Cassian -Cassidy -Catherine -Cayden -Cecilia -Cecily -Celeste -Cesar -Chad -Chance -Chandler -Charles -Charlie -Charlotte -Charlton -Chase -Chelsea -Cherette -Cheri -Cherry -Cheyanne -Cheyenne -Chip -Chloe -Chris -Christa -Christian -Christiana -Christina -Christobel -Christopher -Ciara -Cindy -Claire -Clara -Claribel -Clark -Claudia -Claudius -Clayton -Clement -Cleveland -Cliff -Clinton -Clitus -Clover -Cody -Cohen -Colby -Cole -Colin -Collin -Colten -Colton -Conner -Connor -Cooper -Cora -Corbin -Coreen -Corey -Corrine -Cory -Courtney -Cristian -Cristopher -Cruz -Crystal -Curtis -Cy -Cynthia -Daisy -Dakota -Dallas -Dalton -Dalya -Damian -Damien -Damon -Dana -Dane -Danica -Daniel -Daniela -Daniella -Danielle -Danika -Danna -Danny -Dante -Darcey -Darell -Daria -Darin -Darius -Darren -David -Davion -Davis -Dawson -Dayana -Dayna -Dayton -Dean -Deandre -Deangelo -Debbi -Declan -Dee -Deena -Delaney -Delilah -Della -Delma -Denholm -Denise -Dennis -Denys -Derek -Derrick -Desiree -Desmond -Destiny -Devin -Devon -Diamond -Diana -Diego -Dillon -Dina -Dolores -Dominic -Dominick -Donald -Donella -Donna -Donny -Donovan -Dorian -Dorothy -Dortha -Douglas -Drake -Drew -Driscoll -Dulce -Duncan -Dustin -Dylan -Easter -Easton -Ebba -Eddie -Eden -Edgar -Eduardo -Edward -Edwin -Effie -Elaina -Eleanor -Elena -Eli -Eliana -Elias -Elijah -Eliot -Eliott -Elise -Eliza -Elizabeth -Ella -Elle -Ellie -Elliot -Elliott -Elric -Elspet -Elwood -Emanuel -Emely -Emerson -Emery -Emilee -Emilia -Emiliano -Emilio -Emily -Emma -Emmanuel -Enrique -Eric -Erica -Erick -Erik -Erika -Erin -Ermintrude -Ernesto -Esmeralda -Esteban -Esther -Estrella -Ethan -Eugenia -Euphemia -Eustace -Eva -Evan -Evangeline -Eveleen -Evelina -Evelyn -Everett -Ezekiel -Ezra -Fabian -Faith -Fatima -Fay -Felix -Fernanda -Fernando -Finn -Fiona -Fitz -Flick -Floella -Flora -Flossie -Fortune -Francesca -Francis -Francisco -Frank -Frankie -Franklin -Fulton -Gabriel -Gabriela -Gabriella -Gabrielle -Gael -Gage -Garret -Garrett -Gary -Gavin -Gaye -Gaylord -Genesis -Genette -Genevieve -George -Georgene -Georgia -Geraldine -Gerardo -Gervase -Gianna -Gina -Ginger -Giovanni -Giselle -Gladwyn -Glenna -Gloria -Goddard -Godwin -Goodwin -Gordon -Grace -Gracie -Grady -Graeme -Graham -Grant -Gratian -Grayson -Gregory -Greta -Greyson -Griffin -Griselda -Guadalupe -Guillermo -Gunner -Gustavo -Gwenda -Gwenevere -Hadley -Haidee -Hailee -Hailey -Hal -Haleigh -Haley -Hanna -Hannah -Happy -Harley -Harmony -Harper -Harrison -Hartley -Hayden -Haylee -Hayley -Haylie -Hazel -Heather -Heaven -Hector -Hedley -Heidi -Helen -Henderson -Henry -Hepsie -Hervey -Holden -Holly -Homer -Hope -Horatio -Hortensia -Hudson -Huffie -Hugo -Hunter -Ian -Iantha -Ileen -Imani -Innocent -Irene -Iris -Irvine -Isaac -Isabel -Isabella -Isabelle -Isaiah -Isaias -Isiah -Ismael -Israel -Issac -Itzel -Ivan -Ivy -Izabella -Izaiah -Jacaline -Jace -Jack -Jackson -Jacob -Jacoby -Jacqueline -Jacquetta -Jacqui -Jada -Jade -Jaden -Jadon -Jadyn -Jaelyn -Jaiden -Jaime -Jake -Jakki -Jakob -Jalen -Jamar -Jamari -Jamarion -James -Jameson -Jamie -Jamison -Jane -Janel -Janelle -Janette -Janie -Janina -Janine -Janiya -Janiyah -Jared -Jaslene -Jasmin -Jasmine -Jason -Jasper -Javier -Javon -Jaxon -Jaxson -Jay -Jayce -Jayda -Jayden -Jaydon -Jaye -Jayla -Jaylee -Jaylen -Jayne -Jaynie -Jayson -Jazlyn -Jazmin -Jazmine -Jeanna -Jeannie -Jeannine -Jeb -Jed -Jeffrey -Jemmy -Jenifer -Jenna -Jennie -Jennifer -Jera -Jere -Jeremiah -Jeremy -Jeri -Jermaine -Jerrie -Jerry -Jesse -Jessica -Jesus -Jillian -Jillie -Jim -Jimena -Jimmy -Joachim -Joanna -Joaquin -Jocelyn -Joe -Joel -Joetta -Joey -Johan -Johanna -John -Johnathan -Johnny -Joi -Jonah -Jonas -Jonathan -Jonathon -Joni -Jordan -Jordyn -Jorge -Jose -Joselyn -Joseph -Josepha -Josephine -Josh -Joshua -Josiah -Josie -Josue -Joye -Juan -Judah -Jude -Julia -Julian -Juliana -Julianna -Julie -Juliet -Julio -Julissa -Julius -July -Justice -Justin -Kade -Kaden -Kadence -Kaelea -Kaelyn -Kai -Kaiden -Kailey -Kailyn -Kaitlin -Kaitlyn -Kale -Kaleb -Kaleigh -Kameron -Kamryn -Kane -Kara -Karen -Karenza -Karina -Karla -Karly -Karson -Karyn -Kassidy -Kat -Kate -Katelyn -Katelynn -Katherine -Kathleen -Kathryn -Kathy -Katie -Katlyn -Kayden -Kaydence -Kayla -Kaylee -Kayleigh -Kaylie -Kaylin -Keagan -Keaton -Keegan -Keira -Keith -Kellen -Kellie -Kelly -Kelsey -Kelvin -Kendall -Kendra -Kennard -Kennedy -Kenneth -Kenzie -Kerena -Kerensa -Keturah -Kevin -Keziah -Khalil -Khloe -Kiana -Kiara -Kiera -Kiley -Kimberley -Kimberly -Kimora -Kingston -Kira -Kobe -Kolton -Kristen -Kristina -Kristopher -Kyla -Kyle -Kylee -Kyleigh -Kyler -Kylie -Kyra -Lacey -Lacy -Laila -Lakeisha -Lalla -Lana -Lance -Landen -Landon -Landyn -Lane -Lanny -Larry -Latanya -Launce -Laura -Lauren -Laurencia -Laurissa -Lauryn -Lawrence -Layla -Leah -Leeann -Leia -Leila -Leilani -Leland -Lena -Lennox -Leo -Leonardo -Leonel -Leroi -Leslie -Lesly -Lessie -Leta -Levi -Lexi -Lexia -Lexus -Lia -Liam -Lila -Lilah -Lilian -Liliana -Lillian -Lilliana -Lillie -Lilly -Lily -Lincoln -Linden -Lindsay -Lindsey -Lindsie -Lindy -Linton -Lizbeth -Lockie -Logan -Lola -London -Lorenzo -Loreto -Lori -Lorin -Lou -Louis -Luanne -Luca -Lucas -Lucia -Lucian -Lucy -Luis -Lukas -Luke -Luna -Luvenia -Lydia -Lyla -Lyndsey -Lynn -Lynsey -Lynwood -Lyric -Mabelle -Macey -Macie -Mackenzie -Macy -Madalyn -Maddison -Maddox -Madeleine -Madeline -Madelyn -Madelynn -Madilyn -Madison -Madisyn -Madyson -Maegan -Maggie -Makayla -Makenna -Makenzie -Malachi -Malcolm -Malia -Malik -Mallory -Manley -Manuel -Marc -Marcia -Marco -Marcos -Marcus -Marely -Margaret -Maria -Mariabella -Mariah -Mariana -Marilene -Mario -Marion -Marisol -Marissa -Marje -Marjory -Mark -Marlee -Marley -Marlowe -Marlyn -Marshall -Martin -Marvin -Mary -Maryann -Mason -Mateo -Mathew -Matthew -Maudie -Maurene -Maurice -Mauricio -Max -Maximilian -Maximus -Maxwell -May -Maya -Maynard -Mckenna -Mckenzie -Megan -Meghan -Mekhi -Melanie -Melany -Melissa -Melody -Melvin -Melvyn -Meredith -Merideth -Merrilyn -Meryl -Mia -Micah -Michael -Michaela -Micheal -Michelle -Miguel -Mikayla -Mike -Miles -Miley -Milo -Milton -Minnie -Miracle -Miranda -Miriam -Mitchell -Moises -Molly -Monica -Monna -Montague -Monte -Monty -Morgan -Moses -Muriel -Mya -Mylee -Myles -Myriam -Myrtie -Nadia -Nan -Nancy -Naomi -Nasir -Natalia -Natalie -Nataly -Natasha -Nathan -Nathaniel -Nayeli -Nehemiah -Nelle -Nelson -Nena -Nerissa -Netta -Nettie -Nevaeh -Nia -Nicholas -Nickolas -Nicolas -Nicole -Nikolas -Nina -Noah -Noel -Noelle -Nolan -Nonie -Nora -Norah -Nova -Nowell -Nydia -Nyla -Olive -Oliver -Olivia -Omar -Oralie -Orlando -Osbert -Osborn -Osborne -Oscar -Osmund -Owen -Pablo -Paget -Paige -Paisley -Paola -Paris -Parker -Patience -Patricia -Patrick -Patton -Paul -Pauleen -Paxton -Payton -Pedro -Pene -Penelope -Percival -Peregrine -Perla -Peter -Peyton -Pheobe -Philip -Phillip -Phoebe -Phoenix -Phyliss -Phyllida -Phyllis -Piper -Porsche -Porter -Presley -Preston -Priscilla -Prosper -Prue -Quanah -Quentin -Quiana -Quinn -Quinton -Rachael -Rachel -Raegan -Raelene -Rafael -Rain -Ramon -Randa -Randal -Randy -Rastus -Raul -Raymond -Rayner -Reagan -Rebecca -Rebeckah -Rebekah -Reece -Reed -Reene -Reese -Reid -Renie -Reuben -Rexana -Reynard -Rhetta -Ricardo -Rich -Richard -Richie -Rick -Rickena -Rickey -Rickie -Ricky -Rihanna -Riley -River -Robert -Roberto -Rocco -Rodger -Rodrigo -Roger -Roman -Romayne -Romeo -Ronald -Ronnette -Rosa -Roscoe -Rose -Rosemary -Roswell -Rowan -Roy -Royce -Ruben -Ruby -Rubye -Russell -Rusty -Ruth -Ryan -Ryder -Ryker -Rylan -Rylee -Ryleigh -Rylie -Sabella -Sabrina -Sachie -Sadie -Sage -Sal -Sally -Salvador -Sam -Samantha -Samara -Samuel -Sandra -Santiago -Sara -Sarah -Sarai -Saranna -Sasha -Saul -Savanna -Savannah -Sawyer -Scarlett -Scott -Scotty -Sean -Sebastian -Selena -Seneca -Serena -Serenity -Sergio -Seth -Seymour -Shan -Shana -Shane -Shanika -Shannah -Shannon -Shantae -Sharalyn -Sharla -Shaun -Shawn -Shayla -Shelby -Sheri -Sherie -Sherill -Sherri -Shiloh -Sienna -Sierra -Silas -Simon -Sissy -Skylar -Skyler -Sloan -Sofia -Solomon -Sophia -Sophie -Sorrel -Spencer -Spike -Star -Stella -Steph -Stephanie -Stephany -Stephen -Steven -Sue -Sukie -Summer -Sunshine -Susanna -Susannah -Suzan -Suzy -Sybil -Syd -Sydney -Talia -Talon -Tamika -Tamsin -Tania -Tanner -Tansy -Taryn -Tate -Tatiana -Tatum -Tatyanna -Taylor -Teagan -Tel -Terrell -Terry -Tessa -Theodore -Thomas -Tiffany -Timothy -Titus -Tod -Tolly -Tony -Topaz -Tori -Tracee -Tracey -Travis -Trent -Trenton -Trevor -Trey -Trinity -Tristan -Tristen -Triston -Troy -Tucker -Ty -Tye -Tyler -Tyson -Uland -Ulric -Ulyssa -Uriel -Valary -Valentina -Valeria -Valerie -Vanessa -Vaughn -Verna -Veronica -Victor -Victoria -Vince -Vincent -Vinnie -Violet -Vivian -Viviana -Vivyan -Walker -Walter -Ward -Warner -Wayne -Wendi -Wendy -Wesley -Weston -Whitaker -William -Willow -Willy -Winifred -Wisdom -Woodrow -Woody -Wyatt -Wynonna -Wynter -Xander -Xavier -Ximena -Yahir -Yasmin -Yolanda -Ysabel -Zachariah -Zachary -Zack -Zackary -Zander -Zane -Zayden -Zeke -Zelda -Zion -Zoe -Zoey +Aaden +Aaliyah +Aaron +Abby +Abel +Abigail +Abraham +Adam +Adan +Addison +Addyson +Adeline +Aden +Adolph +Adrian +Adriana +Adrianna +Aida +Aidan +Aiden +Aileen +Ainsley +Alaina +Alan +Alana +Alanna +Alayna +Albert +Alberto +Alden +Alec +Alejandra +Alejandro +Alessandra +Alex +Alexa +Alexander +Alexandra +Alexandria +Alexia +Alexis +Alexus +Alfred +Alfreda +Alfredo +Alger +Ali +Alice +Alicia +Alijah +Alina +Alisa +Alison +Alissa +Alisya +Alivia +Aliyah +Allegra +Allegria +Allen +Allie +Allison +Allisson +Allyson +Alma +Alondra +Alvin +Alysha +Alyson +Alyssa +Alyssia +Amanda +Amari +Amaryllis +Amaya +Amber +Ambrosine +Amelia +Amir +Amira +Amiyah +Amos +Amy +Amya +Ana +Anahi +Anastasia +Anaya +Anderson +Andre +Andrea +Andres +Andrew +Andy +Angel +Angela +Angelica +Angelina +Angelo +Angie +Aniya +Aniyah +Anjelica +Anna +Annabelle +Anne +Annie +Annika +Anthony +Antonio +Anya +April +Arabella +Archie +Ariana +Arianna +Ariel +Arielle +Arleen +Armando +Arn +Art +Arthur +Arturo +Asher +Ashley +Ashlie +Ashlyn +Ashlynn +Ashton +Asia +Astor +Athena +Aubree +Aubrey +Aubrie +Audrey +Audrina +August +Aurora +Austin +Autumn +Ava +Avalon +Averill +Avery +Axel +Ayden +Ayla +Bailey +Baldric +Barbra +Bartholomew +Baylee +Beau +Beckah +Beckett +Becky +Bella +Benjamin +Bennett +Bernice +Bertrand +Bethany +Bethney +Betsy +Bianca +Bidelia +Bill +Blake +Braden +Bradley +Brady +Braeden +Braiden +Brandon +Braxton +Brayan +Brayden +Braydon +Braylon +Breanna +Breanne +Brenda +Brendan +Brenden +Brenna +Brennan +Brett +Brian +Briana +Brianna +Bridget +Brielle +Brittani +Brittany +Brock +Brodie +Brody +Bronte +Brooke +Brooklyn +Brooklynn +Bruce +Bryan +Bryant +Bryce +Brycen +Brynn +Bryson +Burt +Byrne +Byron +Bysshe +Cade +Caden +Cadence +Caiden +Caitlin +Caitlyn +Calanthia +Caleb +Caleigh +Cali +Callie +Calvin +Camden +Cameron +Camila +Camille +Camron +Camryn +Candace +Candice +Candis +Canute +Cara +Carl +Carlos +Carly +Carlyle +Carmen +Carolina +Caroline +Carolyn +Carry +Carson +Carter +Caryl +Casey +Cash +Casimir +Cassandra +Cassian +Cassidy +Catherine +Cayden +Cecilia +Cecily +Celeste +Cesar +Chad +Chance +Chandler +Charles +Charlie +Charlotte +Charlton +Chase +Chelsea +Cherette +Cheri +Cherry +Cheyanne +Cheyenne +Chip +Chloe +Chris +Christa +Christian +Christiana +Christina +Christobel +Christopher +Ciara +Cindy +Claire +Clara +Claribel +Clark +Claudia +Claudius +Clayton +Clement +Cleveland +Cliff +Clinton +Clitus +Clover +Cody +Cohen +Colby +Cole +Colin +Collin +Colten +Colton +Conner +Connor +Cooper +Cora +Corbin +Coreen +Corey +Corrine +Cory +Courtney +Cristian +Cristopher +Cruz +Crystal +Curtis +Cy +Cynthia +Daisy +Dakota +Dallas +Dalton +Dalya +Damian +Damien +Damon +Dana +Dane +Danica +Daniel +Daniela +Daniella +Danielle +Danika +Danna +Danny +Dante +Darcey +Darell +Daria +Darin +Darius +Darren +David +Davion +Davis +Dawson +Dayana +Dayna +Dayton +Dean +Deandre +Deangelo +Debbi +Declan +Dee +Deena +Delaney +Delilah +Della +Delma +Denholm +Denise +Dennis +Denys +Derek +Derrick +Desiree +Desmond +Destiny +Devin +Devon +Diamond +Diana +Diego +Dillon +Dina +Dolores +Dominic +Dominick +Donald +Donella +Donna +Donny +Donovan +Dorian +Dorothy +Dortha +Douglas +Drake +Drew +Driscoll +Dulce +Duncan +Dustin +Dylan +Easter +Easton +Ebba +Eddie +Eden +Edgar +Eduardo +Edward +Edwin +Effie +Elaina +Eleanor +Elena +Eli +Eliana +Elias +Elijah +Eliot +Eliott +Elise +Eliza +Elizabeth +Ella +Elle +Ellie +Elliot +Elliott +Elric +Elspet +Elwood +Emanuel +Emely +Emerson +Emery +Emilee +Emilia +Emiliano +Emilio +Emily +Emma +Emmanuel +Enrique +Eric +Erica +Erick +Erik +Erika +Erin +Ermintrude +Ernesto +Esmeralda +Esteban +Esther +Estrella +Ethan +Eugenia +Euphemia +Eustace +Eva +Evan +Evangeline +Eveleen +Evelina +Evelyn +Everett +Ezekiel +Ezra +Fabian +Faith +Fatima +Fay +Felix +Fernanda +Fernando +Finn +Fiona +Fitz +Flick +Floella +Flora +Flossie +Fortune +Francesca +Francis +Francisco +Frank +Frankie +Franklin +Fulton +Gabriel +Gabriela +Gabriella +Gabrielle +Gael +Gage +Garret +Garrett +Gary +Gavin +Gaye +Gaylord +Genesis +Genette +Genevieve +George +Georgene +Georgia +Geraldine +Gerardo +Gervase +Gianna +Gina +Ginger +Giovanni +Giselle +Gladwyn +Glenna +Gloria +Goddard +Godwin +Goodwin +Gordon +Grace +Gracie +Grady +Graeme +Graham +Grant +Gratian +Grayson +Gregory +Greta +Greyson +Griffin +Griselda +Guadalupe +Guillermo +Gunner +Gustavo +Gwenda +Gwenevere +Hadley +Haidee +Hailee +Hailey +Hal +Haleigh +Haley +Hanna +Hannah +Happy +Harley +Harmony +Harper +Harrison +Hartley +Hayden +Haylee +Hayley +Haylie +Hazel +Heather +Heaven +Hector +Hedley +Heidi +Helen +Henderson +Henry +Hepsie +Hervey +Holden +Holly +Homer +Hope +Horatio +Hortensia +Hudson +Huffie +Hugo +Hunter +Ian +Iantha +Ileen +Imani +Innocent +Irene +Iris +Irvine +Isaac +Isabel +Isabella +Isabelle +Isaiah +Isaias +Isiah +Ismael +Israel +Issac +Itzel +Ivan +Ivy +Izabella +Izaiah +Jacaline +Jace +Jack +Jackson +Jacob +Jacoby +Jacqueline +Jacquetta +Jacqui +Jada +Jade +Jaden +Jadon +Jadyn +Jaelyn +Jaiden +Jaime +Jake +Jakki +Jakob +Jalen +Jamar +Jamari +Jamarion +James +Jameson +Jamie +Jamison +Jane +Janel +Janelle +Janette +Janie +Janina +Janine +Janiya +Janiyah +Jared +Jaslene +Jasmin +Jasmine +Jason +Jasper +Javier +Javon +Jaxon +Jaxson +Jay +Jayce +Jayda +Jayden +Jaydon +Jaye +Jayla +Jaylee +Jaylen +Jayne +Jaynie +Jayson +Jazlyn +Jazmin +Jazmine +Jeanna +Jeannie +Jeannine +Jeb +Jed +Jeffrey +Jemmy +Jenifer +Jenna +Jennie +Jennifer +Jera +Jere +Jeremiah +Jeremy +Jeri +Jermaine +Jerrie +Jerry +Jesse +Jessica +Jesus +Jillian +Jillie +Jim +Jimena +Jimmy +Joachim +Joanna +Joaquin +Jocelyn +Joe +Joel +Joetta +Joey +Johan +Johanna +John +Johnathan +Johnny +Joi +Jonah +Jonas +Jonathan +Jonathon +Joni +Jordan +Jordyn +Jorge +Jose +Joselyn +Joseph +Josepha +Josephine +Josh +Joshua +Josiah +Josie +Josue +Joye +Juan +Judah +Jude +Julia +Julian +Juliana +Julianna +Julie +Juliet +Julio +Julissa +Julius +July +Justice +Justin +Kade +Kaden +Kadence +Kaelea +Kaelyn +Kai +Kaiden +Kailey +Kailyn +Kaitlin +Kaitlyn +Kale +Kaleb +Kaleigh +Kameron +Kamryn +Kane +Kara +Karen +Karenza +Karina +Karla +Karly +Karson +Karyn +Kassidy +Kat +Kate +Katelyn +Katelynn +Katherine +Kathleen +Kathryn +Kathy +Katie +Katlyn +Kayden +Kaydence +Kayla +Kaylee +Kayleigh +Kaylie +Kaylin +Keagan +Keaton +Keegan +Keira +Keith +Kellen +Kellie +Kelly +Kelsey +Kelvin +Kendall +Kendra +Kennard +Kennedy +Kenneth +Kenzie +Kerena +Kerensa +Keturah +Kevin +Keziah +Khalil +Khloe +Kiana +Kiara +Kiera +Kiley +Kimberley +Kimberly +Kimora +Kingston +Kira +Kobe +Kolton +Kristen +Kristina +Kristopher +Kyla +Kyle +Kylee +Kyleigh +Kyler +Kylie +Kyra +Lacey +Lacy +Laila +Lakeisha +Lalla +Lana +Lance +Landen +Landon +Landyn +Lane +Lanny +Larry +Latanya +Launce +Laura +Lauren +Laurencia +Laurissa +Lauryn +Lawrence +Layla +Leah +Leeann +Leia +Leila +Leilani +Leland +Lena +Lennox +Leo +Leonardo +Leonel +Leroi +Leslie +Lesly +Lessie +Leta +Levi +Lexi +Lexia +Lexus +Lia +Liam +Lila +Lilah +Lilian +Liliana +Lillian +Lilliana +Lillie +Lilly +Lily +Lincoln +Linden +Lindsay +Lindsey +Lindsie +Lindy +Linton +Lizbeth +Lockie +Logan +Lola +London +Lorenzo +Loreto +Lori +Lorin +Lou +Louis +Luanne +Luca +Lucas +Lucia +Lucian +Lucy +Luis +Lukas +Luke +Luna +Luvenia +Lydia +Lyla +Lyndsey +Lynn +Lynsey +Lynwood +Lyric +Mabelle +Macey +Macie +Mackenzie +Macy +Madalyn +Maddison +Maddox +Madeleine +Madeline +Madelyn +Madelynn +Madilyn +Madison +Madisyn +Madyson +Maegan +Maggie +Makayla +Makenna +Makenzie +Malachi +Malcolm +Malia +Malik +Mallory +Manley +Manuel +Marc +Marcia +Marco +Marcos +Marcus +Marely +Margaret +Maria +Mariabella +Mariah +Mariana +Marilene +Mario +Marion +Marisol +Marissa +Marje +Marjory +Mark +Marlee +Marley +Marlowe +Marlyn +Marshall +Martin +Marvin +Mary +Maryann +Mason +Mateo +Mathew +Matthew +Maudie +Maurene +Maurice +Mauricio +Max +Maximilian +Maximus +Maxwell +May +Maya +Maynard +Mckenna +Mckenzie +Megan +Meghan +Mekhi +Melanie +Melany +Melissa +Melody +Melvin +Melvyn +Meredith +Merideth +Merrilyn +Meryl +Mia +Micah +Michael +Michaela +Micheal +Michelle +Miguel +Mikayla +Mike +Miles +Miley +Milo +Milton +Minnie +Miracle +Miranda +Miriam +Mitchell +Moises +Molly +Monica +Monna +Montague +Monte +Monty +Morgan +Moses +Muriel +Mya +Mylee +Myles +Myriam +Myrtie +Nadia +Nan +Nancy +Naomi +Nasir +Natalia +Natalie +Nataly +Natasha +Nathan +Nathaniel +Nayeli +Nehemiah +Nelle +Nelson +Nena +Nerissa +Netta +Nettie +Nevaeh +Nia +Nicholas +Nickolas +Nicolas +Nicole +Nikolas +Nina +Noah +Noel +Noelle +Nolan +Nonie +Nora +Norah +Nova +Nowell +Nydia +Nyla +Olive +Oliver +Olivia +Omar +Oralie +Orlando +Osbert +Osborn +Osborne +Oscar +Osmund +Owen +Pablo +Paget +Paige +Paisley +Paola +Paris +Parker +Patience +Patricia +Patrick +Patton +Paul +Pauleen +Paxton +Payton +Pedro +Pene +Penelope +Percival +Peregrine +Perla +Peter +Peyton +Pheobe +Philip +Phillip +Phoebe +Phoenix +Phyliss +Phyllida +Phyllis +Piper +Porsche +Porter +Presley +Preston +Priscilla +Prosper +Prue +Quanah +Quentin +Quiana +Quinn +Quinton +Rachael +Rachel +Raegan +Raelene +Rafael +Rain +Ramon +Randa +Randal +Randy +Rastus +Raul +Raymond +Rayner +Reagan +Rebecca +Rebeckah +Rebekah +Reece +Reed +Reene +Reese +Reid +Renie +Reuben +Rexana +Reynard +Rhetta +Ricardo +Rich +Richard +Richie +Rick +Rickena +Rickey +Rickie +Ricky +Rihanna +Riley +River +Robert +Roberto +Rocco +Rodger +Rodrigo +Roger +Roman +Romayne +Romeo +Ronald +Ronnette +Rosa +Roscoe +Rose +Rosemary +Roswell +Rowan +Roy +Royce +Ruben +Ruby +Rubye +Russell +Rusty +Ruth +Ryan +Ryder +Ryker +Rylan +Rylee +Ryleigh +Rylie +Sabella +Sabrina +Sachie +Sadie +Sage +Sal +Sally +Salvador +Sam +Samantha +Samara +Samuel +Sandra +Santiago +Sara +Sarah +Sarai +Saranna +Sasha +Saul +Savanna +Savannah +Sawyer +Scarlett +Scott +Scotty +Sean +Sebastian +Selena +Seneca +Serena +Serenity +Sergio +Seth +Seymour +Shan +Shana +Shane +Shanika +Shannah +Shannon +Shantae +Sharalyn +Sharla +Shaun +Shawn +Shayla +Shelby +Sheri +Sherie +Sherill +Sherri +Shiloh +Sienna +Sierra +Silas +Simon +Sissy +Skylar +Skyler +Sloan +Sofia +Solomon +Sophia +Sophie +Sorrel +Spencer +Spike +Star +Stella +Steph +Stephanie +Stephany +Stephen +Steven +Sue +Sukie +Summer +Sunshine +Susanna +Susannah +Suzan +Suzy +Sybil +Syd +Sydney +Talia +Talon +Tamika +Tamsin +Tania +Tanner +Tansy +Taryn +Tate +Tatiana +Tatum +Tatyanna +Taylor +Teagan +Tel +Terrell +Terry +Tessa +Theodore +Thomas +Tiffany +Timothy +Titus +Tod +Tolly +Tony +Topaz +Tori +Tracee +Tracey +Travis +Trent +Trenton +Trevor +Trey +Trinity +Tristan +Tristen +Triston +Troy +Tucker +Ty +Tye +Tyler +Tyson +Uland +Ulric +Ulyssa +Uriel +Valary +Valentina +Valeria +Valerie +Vanessa +Vaughn +Verna +Veronica +Victor +Victoria +Vince +Vincent +Vinnie +Violet +Vivian +Viviana +Vivyan +Walker +Walter +Ward +Warner +Wayne +Wendi +Wendy +Wesley +Weston +Whitaker +William +Willow +Willy +Winifred +Wisdom +Woodrow +Woody +Wyatt +Wynonna +Wynter +Xander +Xavier +Ximena +Yahir +Yasmin +Yolanda +Ysabel +Zachariah +Zachary +Zack +Zackary +Zander +Zane +Zayden +Zeke +Zelda +Zion +Zoe +Zoey Zune \ No newline at end of file diff --git a/strings/names/first_female.txt b/strings/names/first_female.txt index 6710c1bfc322..038e93687928 100644 --- a/strings/names/first_female.txt +++ b/strings/names/first_female.txt @@ -1,771 +1,771 @@ -Aaliyah -Abby -Abigail -Addison -Addyson -Adeline -Adriana -Adrianna -Aida -Aileen -Ainsley -Alaina -Alana -Alanna -Alayna -Alejandra -Alessandra -Alexa -Alexandra -Alexandria -Alexia -Alexis -Alexus -Alfreda -Alice -Alicia -Alina -Alisa -Alison -Alissa -Alisya -Alivia -Aliyah -Allegra -Allegria -Allie -Allison -Allisson -Allyson -Alma -Alondra -Alysha -Alyson -Alyssa -Alyssia -Amanda -Amari -Amaryllis -Amaya -Amber -Ambrosine -Amelia -Amira -Amiyah -Amy -Amya -Ana -Anahi -Anastasia -Anaya -Andrea -Angel -Angela -Angelica -Angelina -Angie -Aniya -Aniyah -Anjelica -Anna -Annabelle -Anne -Annie -Annika -Anya -April -Arabella -Ariana -Arianna -Ariel -Arielle -Arleen -Ashley -Ashlie -Ashlyn -Ashlynn -Asia -Astor -Athena -Aubree -Aubrey -Aubrie -Audrey -Audrina -Aurora -Autumn -Ava -Avalona -Averill -Avery -Ayla -Bailey -Barbara -Baylee -Beckah -Becky -Bella -Bernice -Bethany -Bethney -Betsy -Bianca -Bidelia -Breanna -Breanne -Brenda -Brenna -Briana -Brianna -Bridget -Brielle -Brittani -Brittany -Brooke -Brooklyn -Brooklynn -Brynn -Cadence -Caitlin -Caitlyn -Calanthia -Caleigh -Cali -Callie -Cameron -Camila -Camille -Camryn -Candace -Candice -Cara -Carly -Carlyle -Carmen -Carolina -Caroline -Carolyn -Carry -Caryl -Casey -Cassandra -Cassidy -Catherine -Cecilia -Cecily -Celeste -Charlotte -Chelsea -Cherette -Cheri -Cherry -Cheyanne -Cheyenne -Chloe -Christa -Christiana -Christina -Christobelle -Ciara -Cindy -Claire -Clara -Claribel -Claudia -Clover -Cora -Coreen -Corrine -Courtney -Crystal -Cynthia -Daisy -Dakota -Dalya -Dana -Danica -Daniela -Daniella -Danielle -Danika -Danna -Daria -Dayana -Dayna -Debbi -Dee -Deena -Delaney -Delilah -Della -Delma -Denise -Denys -Desiree -Destiny -Diamond -Diana -Dina -Dolores -Donella -Donna -Dorothy -Dortha -Dulce -Easter -Ebba -Eden -Effie -Elaina -Eleanor -Elena -Eliana -Elise -Eliza -Elizabeth -Ella -Elle -Ellie -Emely -Emerson -Emery -Emilee -Emilia -Emily -Emma -Erica -Erika -Erin -Ermintrude -Esmeralda -Esther -Estrella -Eugenia -Euphemia -Eustace -Eva -Evangeline -Eveleen -Evelina -Evelyn -Faith -Fatima -Fay -Fernanda -Fiona -Floella -Flora -Flossie -Fortune -Francesca -Gabriela -Gabriella -Gabrielle -Genette -Genevieve -Georgene -Georgia -Geraldine -Gervase -Gianna -Gina -Ginger -Giselle -Gladwyn -Glenna -Gloria -Grace -Gracie -Greta -Griselda -Guadalupe -Gwenda -Gwenevere -Hadley -Haidee -Hailee -Hailey -Hal -Haleigh -Haley -Hanna -Hannah -Harley -Harmony -Harper -Hayden -Haylee -Hayley -Haylie -Hazel -Heather -Heaven -Hedley -Heidi -Helen -Hepsie -Holly -Hope -Hortensia -Iantha -Ileen -Imani -Innocent -Irene -Iris -Isabel -Isabella -Isabelle -Itzel -Ivy -Izabella -Jacaline -Jacqueline -Jacquetta -Jacqui -Jada -Jade -Jaden -Jadyn -Jaelyn -Jakki -Jalen -Jamie -Jane -Janelle -Janette -Janie -Janina -Janine -Janiya -Janiyah -Jaslene -Jasmin -Jasmine -Jayda -Jayden -Jayla -Jaylee -Jaynie -Jazlyn -Jazmin -Jazmine -Jeanna -Jeannie -Jeannine -Jenifer -Jenna -Jennie -Jennifer -Jera -Jere -Jeri -Jessica -Jillian -Jillie -Jimena -Joanna -Jocelyn -Joetta -Johanna -Joi -Joni -Jordan -Jordyn -Joselyn -Josepha -Josephine -Josie -Joye -Julia -Juliana -Julianna -Julie -Juliet -Julissa -July -Kadence -Kaelea -Kaelyn -Kailey -Kailyn -Kaitlin -Kaitlyn -Kaleigh -Kamryn -Kara -Karen -Karenza -Karina -Karla -Karly -Karyn -Kassidy -Kat -Kate -Katelyn -Katelynn -Katherine -Kathleen -Kathryn -Kathy -Katie -Katlyn -Kayden -Kaydence -Kayla -Kaylee -Kayleigh -Kaylie -Kaylin -Keegan -Keira -Keith -Kellie -Kelly -Kelsey -Kendall -Kendra -Kennedy -Kenzie -Kerena -Kerensa -Keturah -Khloe -Kiana -Kiara -Kiera -Kiley -Kimberley -Kimberly -Kimora -Kira -Kristen -Kristina -Kyla -Kylee -Kyleigh -Kylie -Kyra -Lacey -Lacy -Laila -Lakeisha -Lalla -Lana -Latanya -Laura -Lauren -Laurencia -Laurissa -Lauryn -Layla -Leah -Leeann -Leia -Leila -Leilani -Lena -Leslie -Lesly -Lessie -Leta -Lexi -Lexia -Lexus -Lia -Lila -Lilah -Lilian -Liliana -Lillian -Lilliana -Lillie -Lilly -Lily -Lindsay -Lindsey -Lindsie -Lindy -Lizbeth -Lockie -Logan -Lola -London -Lori -Lorin -Luanne -Lucia -Lucian -Lucy -Luna -Luvenia -Lydia -Lyla -Lyndsey -Lynn -Lynsey -Lynwood -Lyric -Mabelle -Macey -Macie -Mackenzie -Macy -Madalyn -Maddison -Madeleine -Madeline -Madelyn -Madelynn -Madilyn -Madison -Madisyn -Madyson -Maegan -Maggie -Makayla -Makenna -Makenzie -Malia -Mallory -Marcia -Marely -Margaret -Maria -Mariabella -Mariah -Mariana -Marilene -Marion -Marisol -Marissa -Marje -Marjory -Marlee -Marley -Marlowe -Marlyn -Marshall -Mary -Maryann -Maudie -Maurene -May -Maya -Mckenna -Mckenzie -Megan -Meghan -Melanie -Melany -Melissa -Melody -Meredith -Merideth -Merrilyn -Meryl -Mia -Michaela -Michelle -Mikayla -Miley -Minnie -Miracle -Miranda -Miriam -Molly -Monica -Monna -Morgan -Muriel -Mya -Mylee -Myriam -Myrtie -Nadia -Nan -Nancy -Naomi -Natalia -Natalie -Nataly -Natasha -Nayeli -Nelle -Nena -Nerissa -Netta -Nettie -Nevaeh -Nia -Nicole -Nina -Noelle -Nonie -Nora -Norah -Nova -Nowell -Nydia -Nyla -Olive -Olivia -Oralie -Paige -Paisley -Paola -Paris -Patience -Patricia -Pauleen -Payton -Pene -Penelope -Peregrine -Perla -Peyton -Pheobe -Phoebe -Phyliss -Phyllida -Phyllis -Piper -Porsche -Presley -Priscilla -Prosper -Prue -Quanah -Quiana -Rachael -Rachel -Raegan -Raelene -Rain -Randa -Randal -Reagan -Rebecca -Rebeckah -Rebekah -Reene -Reese -Renie -Rexana -Rhetta -Rihanna -Riley -Ronnette -Rosa -Rose -Rosemary -Rowan -Ruby -Rubye -Ruth -Rylee -Ryleigh -Rylie -Sabella -Sabrina -Sachie -Sadie -Sage -Sally -Samantha -Samara -Sandra -Sara -Sarah -Sarai -Saranna -Sasha -Savanna -Savannah -Scarlett -Selena -Seneca -Serena -Serenity -Shana -Shanika -Shannah -Shannon -Shantae -Sharalyn -Sharla -Shayla -Shelby -Sheri -Sherie -Sherill -Sherri -Sienna -Sierra -Sissy -Skylar -Skyler -Sofia -Sophia -Sophie -Star -Stella -Steph -Stephanie -Stephany -Sue -Sukie -Summer -Sunshine -Susanna -Susannah -Suzan -Suzy -Sydney -Talia -Tamika -Tania -Tansy -Taryn -Tatiana -Tatum -Tatyanna -Taylor -Teagan -Tessa -Tiffany -Tolly -Topaz -Tori -Tracee -Tracey -Trinity -Ulyssa -Valary -Valentina -Valeria -Valerie -Vanessa -Verna -Veronica -Victoria -Vinnie -Violet -Vivian -Viviana -Vivyan -Wendi -Wendy -Willow -Wisdom -Wynonna -Wynter -Ximena -Yasmin -Yolanda -Ysabel -Zelda -Zoe -Zoey +Aaliyah +Abby +Abigail +Addison +Addyson +Adeline +Adriana +Adrianna +Aida +Aileen +Ainsley +Alaina +Alana +Alanna +Alayna +Alejandra +Alessandra +Alexa +Alexandra +Alexandria +Alexia +Alexis +Alexus +Alfreda +Alice +Alicia +Alina +Alisa +Alison +Alissa +Alisya +Alivia +Aliyah +Allegra +Allegria +Allie +Allison +Allisson +Allyson +Alma +Alondra +Alysha +Alyson +Alyssa +Alyssia +Amanda +Amari +Amaryllis +Amaya +Amber +Ambrosine +Amelia +Amira +Amiyah +Amy +Amya +Ana +Anahi +Anastasia +Anaya +Andrea +Angel +Angela +Angelica +Angelina +Angie +Aniya +Aniyah +Anjelica +Anna +Annabelle +Anne +Annie +Annika +Anya +April +Arabella +Ariana +Arianna +Ariel +Arielle +Arleen +Ashley +Ashlie +Ashlyn +Ashlynn +Asia +Astor +Athena +Aubree +Aubrey +Aubrie +Audrey +Audrina +Aurora +Autumn +Ava +Avalona +Averill +Avery +Ayla +Bailey +Barbara +Baylee +Beckah +Becky +Bella +Bernice +Bethany +Bethney +Betsy +Bianca +Bidelia +Breanna +Breanne +Brenda +Brenna +Briana +Brianna +Bridget +Brielle +Brittani +Brittany +Brooke +Brooklyn +Brooklynn +Brynn +Cadence +Caitlin +Caitlyn +Calanthia +Caleigh +Cali +Callie +Cameron +Camila +Camille +Camryn +Candace +Candice +Cara +Carly +Carlyle +Carmen +Carolina +Caroline +Carolyn +Carry +Caryl +Casey +Cassandra +Cassidy +Catherine +Cecilia +Cecily +Celeste +Charlotte +Chelsea +Cherette +Cheri +Cherry +Cheyanne +Cheyenne +Chloe +Christa +Christiana +Christina +Christobelle +Ciara +Cindy +Claire +Clara +Claribel +Claudia +Clover +Cora +Coreen +Corrine +Courtney +Crystal +Cynthia +Daisy +Dakota +Dalya +Dana +Danica +Daniela +Daniella +Danielle +Danika +Danna +Daria +Dayana +Dayna +Debbi +Dee +Deena +Delaney +Delilah +Della +Delma +Denise +Denys +Desiree +Destiny +Diamond +Diana +Dina +Dolores +Donella +Donna +Dorothy +Dortha +Dulce +Easter +Ebba +Eden +Effie +Elaina +Eleanor +Elena +Eliana +Elise +Eliza +Elizabeth +Ella +Elle +Ellie +Emely +Emerson +Emery +Emilee +Emilia +Emily +Emma +Erica +Erika +Erin +Ermintrude +Esmeralda +Esther +Estrella +Eugenia +Euphemia +Eustace +Eva +Evangeline +Eveleen +Evelina +Evelyn +Faith +Fatima +Fay +Fernanda +Fiona +Floella +Flora +Flossie +Fortune +Francesca +Gabriela +Gabriella +Gabrielle +Genette +Genevieve +Georgene +Georgia +Geraldine +Gervase +Gianna +Gina +Ginger +Giselle +Gladwyn +Glenna +Gloria +Grace +Gracie +Greta +Griselda +Guadalupe +Gwenda +Gwenevere +Hadley +Haidee +Hailee +Hailey +Hal +Haleigh +Haley +Hanna +Hannah +Harley +Harmony +Harper +Hayden +Haylee +Hayley +Haylie +Hazel +Heather +Heaven +Hedley +Heidi +Helen +Hepsie +Holly +Hope +Hortensia +Iantha +Ileen +Imani +Innocent +Irene +Iris +Isabel +Isabella +Isabelle +Itzel +Ivy +Izabella +Jacaline +Jacqueline +Jacquetta +Jacqui +Jada +Jade +Jaden +Jadyn +Jaelyn +Jakki +Jalen +Jamie +Jane +Janelle +Janette +Janie +Janina +Janine +Janiya +Janiyah +Jaslene +Jasmin +Jasmine +Jayda +Jayden +Jayla +Jaylee +Jaynie +Jazlyn +Jazmin +Jazmine +Jeanna +Jeannie +Jeannine +Jenifer +Jenna +Jennie +Jennifer +Jera +Jere +Jeri +Jessica +Jillian +Jillie +Jimena +Joanna +Jocelyn +Joetta +Johanna +Joi +Joni +Jordan +Jordyn +Joselyn +Josepha +Josephine +Josie +Joye +Julia +Juliana +Julianna +Julie +Juliet +Julissa +July +Kadence +Kaelea +Kaelyn +Kailey +Kailyn +Kaitlin +Kaitlyn +Kaleigh +Kamryn +Kara +Karen +Karenza +Karina +Karla +Karly +Karyn +Kassidy +Kat +Kate +Katelyn +Katelynn +Katherine +Kathleen +Kathryn +Kathy +Katie +Katlyn +Kayden +Kaydence +Kayla +Kaylee +Kayleigh +Kaylie +Kaylin +Keegan +Keira +Keith +Kellie +Kelly +Kelsey +Kendall +Kendra +Kennedy +Kenzie +Kerena +Kerensa +Keturah +Khloe +Kiana +Kiara +Kiera +Kiley +Kimberley +Kimberly +Kimora +Kira +Kristen +Kristina +Kyla +Kylee +Kyleigh +Kylie +Kyra +Lacey +Lacy +Laila +Lakeisha +Lalla +Lana +Latanya +Laura +Lauren +Laurencia +Laurissa +Lauryn +Layla +Leah +Leeann +Leia +Leila +Leilani +Lena +Leslie +Lesly +Lessie +Leta +Lexi +Lexia +Lexus +Lia +Lila +Lilah +Lilian +Liliana +Lillian +Lilliana +Lillie +Lilly +Lily +Lindsay +Lindsey +Lindsie +Lindy +Lizbeth +Lockie +Logan +Lola +London +Lori +Lorin +Luanne +Lucia +Lucian +Lucy +Luna +Luvenia +Lydia +Lyla +Lyndsey +Lynn +Lynsey +Lynwood +Lyric +Mabelle +Macey +Macie +Mackenzie +Macy +Madalyn +Maddison +Madeleine +Madeline +Madelyn +Madelynn +Madilyn +Madison +Madisyn +Madyson +Maegan +Maggie +Makayla +Makenna +Makenzie +Malia +Mallory +Marcia +Marely +Margaret +Maria +Mariabella +Mariah +Mariana +Marilene +Marion +Marisol +Marissa +Marje +Marjory +Marlee +Marley +Marlowe +Marlyn +Marshall +Mary +Maryann +Maudie +Maurene +May +Maya +Mckenna +Mckenzie +Megan +Meghan +Melanie +Melany +Melissa +Melody +Meredith +Merideth +Merrilyn +Meryl +Mia +Michaela +Michelle +Mikayla +Miley +Minnie +Miracle +Miranda +Miriam +Molly +Monica +Monna +Morgan +Muriel +Mya +Mylee +Myriam +Myrtie +Nadia +Nan +Nancy +Naomi +Natalia +Natalie +Nataly +Natasha +Nayeli +Nelle +Nena +Nerissa +Netta +Nettie +Nevaeh +Nia +Nicole +Nina +Noelle +Nonie +Nora +Norah +Nova +Nowell +Nydia +Nyla +Olive +Olivia +Oralie +Paige +Paisley +Paola +Paris +Patience +Patricia +Pauleen +Payton +Pene +Penelope +Peregrine +Perla +Peyton +Pheobe +Phoebe +Phyliss +Phyllida +Phyllis +Piper +Porsche +Presley +Priscilla +Prosper +Prue +Quanah +Quiana +Rachael +Rachel +Raegan +Raelene +Rain +Randa +Randal +Reagan +Rebecca +Rebeckah +Rebekah +Reene +Reese +Renie +Rexana +Rhetta +Rihanna +Riley +Ronnette +Rosa +Rose +Rosemary +Rowan +Ruby +Rubye +Ruth +Rylee +Ryleigh +Rylie +Sabella +Sabrina +Sachie +Sadie +Sage +Sally +Samantha +Samara +Sandra +Sara +Sarah +Sarai +Saranna +Sasha +Savanna +Savannah +Scarlett +Selena +Seneca +Serena +Serenity +Shana +Shanika +Shannah +Shannon +Shantae +Sharalyn +Sharla +Shayla +Shelby +Sheri +Sherie +Sherill +Sherri +Sienna +Sierra +Sissy +Skylar +Skyler +Sofia +Sophia +Sophie +Star +Stella +Steph +Stephanie +Stephany +Sue +Sukie +Summer +Sunshine +Susanna +Susannah +Suzan +Suzy +Sydney +Talia +Tamika +Tania +Tansy +Taryn +Tatiana +Tatum +Tatyanna +Taylor +Teagan +Tessa +Tiffany +Tolly +Topaz +Tori +Tracee +Tracey +Trinity +Ulyssa +Valary +Valentina +Valeria +Valerie +Vanessa +Verna +Veronica +Victoria +Vinnie +Violet +Vivian +Viviana +Vivyan +Wendi +Wendy +Willow +Wisdom +Wynonna +Wynter +Ximena +Yasmin +Yolanda +Ysabel +Zelda +Zoe +Zoey Zune \ No newline at end of file diff --git a/strings/names/first_male.txt b/strings/names/first_male.txt index 5dd9cb571110..ba65bce0526a 100644 --- a/strings/names/first_male.txt +++ b/strings/names/first_male.txt @@ -1,667 +1,667 @@ -Aaden -Aaron -Abel -Abraham -Adam -Adan -Aden -Adolph -Adrian -Aidan -Aiden -Alan -Albert -Alberto -Alden -Alec -Alejandro -Alex -Alexander -Alexis -Alfred -Alfredo -Alger -Ali -Alijah -Allen -Alvin -Amari -Amir -Amos -Anderson -Andre -Andres -Andrew -Andy -Angel -Angelo -Anthony -Antonio -Apple -Archie -Armando -Arnie -Art -Arthur -Arturo -Asher -Ashton -August -Austin -Avery -Axel -Ayden -Baldric -Bartholomew -Beau -Beckett -Benjamin -Bennett -Bill -Blake -Braden -Bradley -Brady -Braeden -Braiden -Brandon -Braxton -Brayan -Brayden -Braydon -Braylon -Brendan -Brenden -Brennan -Brett -Brian -Brick -Brock -Brodie -Brody -Bronte -Bruce -Bryan -Bryant -Bryce -Brycen -Bryson -Buck -Burt -Butch -Byrne -Byron -Cade -Caden -Caiden -Caleb -Calvin -Camden -Cameron -Camron -Camryn -Carl -Carlos -Carson -Carter -Casey -Cash -Casimir -Cassian -Cayden -Cesar -Chad -Chance -Chandler -Charles -Charlie -Charlton -Chase -Chip -Chris -Christian -Christopher -Clark -Claudius -Clayton -Clement -Cletus -Cleveland -Cliff -Clinton -Cody -Cohen -Colby -Cole -Colin -Collin -Colten -Colton -Conner -Connor -Cooper -Corbin -Corey -Cory -Cristian -Cristopher -Crush -Cruz -Curtis -Cy -Dakota -Dallas -Dalton -Damian -Damien -Damon -Dane -Daniel -Danny -Dante -Darcey -Darell -Darin -Darius -Darren -David -Davion -Davis -Dawson -Dayton -Dean -Deandre -Deangelo -Declan -Denholm -Dennis -Derek -Derrick -Desmond -Devin -Devon -Diego -Dillon -Dirk -Dominic -Dominick -Donald -Donny -Donovan -Douglas -Drake -Drew -Driscoll -Duke -Duncan -Dustin -Dylan -Easton -Eddie -Edgar -Eduardo -Edward -Edwin -Eli -Elias -Elijah -Eliot -Eliott -Elliot -Elliott -Elric -Elwood -Emanuel -Emerson -Emiliano -Emilio -Emmanuel -Enrique -Eric -Erick -Erik -Ernesto -Esteban -Ethan -Evan -Everett -Ezekiel -Ezra -Fabian -Felix -Fenton -Fernando -Finn -Fitz -Flick -Flint -Flip -Francis -Francisco -Frank -Frankie -Franklin -Fridge -Fulton -Gabriel -Gael -Gage -Gannon -Garret -Garrett -Gary -Gavin -George -Gerardo -Giovanni -Goddard -Godwin -Goodwin -Gordon -Grady -Graeme -Graham -Grant -Gratian -Grayson -Gregory -Grendel -Greyson -Griffin -Guillermo -Gunner -Gustavo -Han -Harrison -Harry -Hartley -Harvey -Hayden -Hector -Henderson -Henry -Holden -Homer -Horatio -Hudson -Huffie -Hugo -Hungry -Hunter -Ian -Irvine -Isaac -Isaiah -Isaias -Isiah -Ismael -Israel -Issac -Ivan -Izaiah -Jace -Jack -Jackson -Jacob -Jacoby -Jaden -Jadon -Jaiden -Jaime -Jake -Jakob -Jalen -Jamar -Jamari -Jamarion -James -Jameson -Jamie -Jamison -Janel -Jared -Jason -Jasper -Javier -Javon -Jaxon -Jaxson -Jay -Jayce -Jayden -Jaydon -Jaye -Jaylen -Jayne -Jayson -Jean-Luc -Jeb -Jed -Jeffrey -Jemmy -Jeremiah -Jeremy -Jermaine -Jerrie -Jerry -Jesse -Jesus -Jim -Jimmy -Joachim -Joaquin -Joe -Joel -Joey -Johan -John -Johnathan -Johnny -Jonah -Jonas -Jonathan -Jonathon -Jordan -Jorge -Jose -Joseph -Josh -Joshua -Josiah -Josue -Juan -Judah -Jude -Julian -Julio -Julius -Justice -Justin -Kade -Kaden -Kai -Kaiden -Kale -Kaleb -Kameron -Kane -Karson -Kayden -Keagan -Keaton -Keegan -Keith -Kellen -Kelvin -Kennard -Kenneth -Kevin -Keziah -Khalil -Kingston -Kobe -Kolton -Kristopher -Kyle -Kyler -Lance -Landen -Lando -Landon -Landyn -Lane -Lanny -Larry -Launce -Lawrence -Leland -Lennox -Lenny -Leo -Leonard -Leonardo -Leonel -Leroy -Levi -Liam -Lief -Lincoln -Linden -Linton -Logan -Lorde -Lorenzo -Loreto -Lou -Louis -Luca -Lucas -Luis -Lukas -Luke -Maddox -Malachi -Malcolm -Malik -Manley -Manuel -Marc -Marco -Marcos -Marcus -Mario -Marion -Mark -Marshall -Martin -Marvin -Mason -Mateo -Mathew -Matthew -Maurice -Mauricio -Max -Maximilian -Maximus -Maxwell -Maynard -Mekhi -Melvin -Melvyn -Micah -Michael -Micheal -Miguel -Mike -Miles -Milo -Milton -Mitchell -Moises -Montague -Monte -Monty -Morgan -Moses -Myles -Nasir -Nat -Nathan -Nathaniel -Nehemiah -Nelson -Nicholas -Nick -Nickolas -Nicolas -Nikolas -Noah -Noel -Nolan -Oliver -Omar -Opie -Orlando -Osbert -Osborn -Osborne -Oscar -Osmund -Oswald -Owen -Pablo -Paget -Parker -Patrick -Patton -Paul -Paxton -Payton -Pedro -Percival -Persh -Peter -Peyton -Philip -Phillip -Phoenix -Porter -Preston -Quentin -Quinn -Quinton -Rafael -Ramon -Randy -Rastus -Raul -Raymond -Rayner -Reece -Reed -Reese -Reid -Reuben -Reynard -Ricardo -Richard -Ricky -Riley -River -Robert -Roberto -Rocco -Rodger -Rodrigo -Roger -Roman -Romayne -Romeo -Ronald -Roscoe -Roswell -Rowan -Roy -Royce -Rube -Ruben -Russell -Rusty -Ryan -Ryder -Ryker -Rylan -Sal -Salvador -Sam -Samuel -Santiago -Saul -Sawyer -Scott -Scotty -Sean -Sebastian -Sergio -Seth -Seymour -Shane -Shaun -Shawn -Shiloh -Silas -Simon -Skyler -Sloan -Smoke -Solomon -Sorrel -Spencer -Spike -Stephen -Steven -Sybil -Syd -Talon -Tamsin -Tanner -Tate -Taylor -Tel -Terrell -Terry -Theodore -Thomas -Tim -Timothy -Titus -Todd -Tony -Travis -Trent -Trenton -Trevor -Trey -Trip -Tristan -Tristen -Triston -Troy -Tucker -Ty -Tye -Tyler -Tyson -Uland -Ulric -Uriel -Vaughn -Victor -Vince -Vincent -Vinny -Walker -Walter -Ward -Warner -Wayne -Wesley -Weston -Whitaker -William -Willy -Woodrow -Wyatt -Xander -Xavier -Yahir -Zachariah -Zachary -Zack -Zackary -Zander -Zane -Zayden -Zeke +Aaden +Aaron +Abel +Abraham +Adam +Adan +Aden +Adolph +Adrian +Aidan +Aiden +Alan +Albert +Alberto +Alden +Alec +Alejandro +Alex +Alexander +Alexis +Alfred +Alfredo +Alger +Ali +Alijah +Allen +Alvin +Amari +Amir +Amos +Anderson +Andre +Andres +Andrew +Andy +Angel +Angelo +Anthony +Antonio +Apple +Archie +Armando +Arnie +Art +Arthur +Arturo +Asher +Ashton +August +Austin +Avery +Axel +Ayden +Baldric +Bartholomew +Beau +Beckett +Benjamin +Bennett +Bill +Blake +Braden +Bradley +Brady +Braeden +Braiden +Brandon +Braxton +Brayan +Brayden +Braydon +Braylon +Brendan +Brenden +Brennan +Brett +Brian +Brick +Brock +Brodie +Brody +Bronte +Bruce +Bryan +Bryant +Bryce +Brycen +Bryson +Buck +Burt +Butch +Byrne +Byron +Cade +Caden +Caiden +Caleb +Calvin +Camden +Cameron +Camron +Camryn +Carl +Carlos +Carson +Carter +Casey +Cash +Casimir +Cassian +Cayden +Cesar +Chad +Chance +Chandler +Charles +Charlie +Charlton +Chase +Chip +Chris +Christian +Christopher +Clark +Claudius +Clayton +Clement +Cletus +Cleveland +Cliff +Clinton +Cody +Cohen +Colby +Cole +Colin +Collin +Colten +Colton +Conner +Connor +Cooper +Corbin +Corey +Cory +Cristian +Cristopher +Crush +Cruz +Curtis +Cy +Dakota +Dallas +Dalton +Damian +Damien +Damon +Dane +Daniel +Danny +Dante +Darcey +Darell +Darin +Darius +Darren +David +Davion +Davis +Dawson +Dayton +Dean +Deandre +Deangelo +Declan +Denholm +Dennis +Derek +Derrick +Desmond +Devin +Devon +Diego +Dillon +Dirk +Dominic +Dominick +Donald +Donny +Donovan +Douglas +Drake +Drew +Driscoll +Duke +Duncan +Dustin +Dylan +Easton +Eddie +Edgar +Eduardo +Edward +Edwin +Eli +Elias +Elijah +Eliot +Eliott +Elliot +Elliott +Elric +Elwood +Emanuel +Emerson +Emiliano +Emilio +Emmanuel +Enrique +Eric +Erick +Erik +Ernesto +Esteban +Ethan +Evan +Everett +Ezekiel +Ezra +Fabian +Felix +Fenton +Fernando +Finn +Fitz +Flick +Flint +Flip +Francis +Francisco +Frank +Frankie +Franklin +Fridge +Fulton +Gabriel +Gael +Gage +Gannon +Garret +Garrett +Gary +Gavin +George +Gerardo +Giovanni +Goddard +Godwin +Goodwin +Gordon +Grady +Graeme +Graham +Grant +Gratian +Grayson +Gregory +Grendel +Greyson +Griffin +Guillermo +Gunner +Gustavo +Han +Harrison +Harry +Hartley +Harvey +Hayden +Hector +Henderson +Henry +Holden +Homer +Horatio +Hudson +Huffie +Hugo +Hungry +Hunter +Ian +Irvine +Isaac +Isaiah +Isaias +Isiah +Ismael +Israel +Issac +Ivan +Izaiah +Jace +Jack +Jackson +Jacob +Jacoby +Jaden +Jadon +Jaiden +Jaime +Jake +Jakob +Jalen +Jamar +Jamari +Jamarion +James +Jameson +Jamie +Jamison +Janel +Jared +Jason +Jasper +Javier +Javon +Jaxon +Jaxson +Jay +Jayce +Jayden +Jaydon +Jaye +Jaylen +Jayne +Jayson +Jean-Luc +Jeb +Jed +Jeffrey +Jemmy +Jeremiah +Jeremy +Jermaine +Jerrie +Jerry +Jesse +Jesus +Jim +Jimmy +Joachim +Joaquin +Joe +Joel +Joey +Johan +John +Johnathan +Johnny +Jonah +Jonas +Jonathan +Jonathon +Jordan +Jorge +Jose +Joseph +Josh +Joshua +Josiah +Josue +Juan +Judah +Jude +Julian +Julio +Julius +Justice +Justin +Kade +Kaden +Kai +Kaiden +Kale +Kaleb +Kameron +Kane +Karson +Kayden +Keagan +Keaton +Keegan +Keith +Kellen +Kelvin +Kennard +Kenneth +Kevin +Keziah +Khalil +Kingston +Kobe +Kolton +Kristopher +Kyle +Kyler +Lance +Landen +Lando +Landon +Landyn +Lane +Lanny +Larry +Launce +Lawrence +Leland +Lennox +Lenny +Leo +Leonard +Leonardo +Leonel +Leroy +Levi +Liam +Lief +Lincoln +Linden +Linton +Logan +Lorde +Lorenzo +Loreto +Lou +Louis +Luca +Lucas +Luis +Lukas +Luke +Maddox +Malachi +Malcolm +Malik +Manley +Manuel +Marc +Marco +Marcos +Marcus +Mario +Marion +Mark +Marshall +Martin +Marvin +Mason +Mateo +Mathew +Matthew +Maurice +Mauricio +Max +Maximilian +Maximus +Maxwell +Maynard +Mekhi +Melvin +Melvyn +Micah +Michael +Micheal +Miguel +Mike +Miles +Milo +Milton +Mitchell +Moises +Montague +Monte +Monty +Morgan +Moses +Myles +Nasir +Nat +Nathan +Nathaniel +Nehemiah +Nelson +Nicholas +Nick +Nickolas +Nicolas +Nikolas +Noah +Noel +Nolan +Oliver +Omar +Opie +Orlando +Osbert +Osborn +Osborne +Oscar +Osmund +Oswald +Owen +Pablo +Paget +Parker +Patrick +Patton +Paul +Paxton +Payton +Pedro +Percival +Persh +Peter +Peyton +Philip +Phillip +Phoenix +Porter +Preston +Quentin +Quinn +Quinton +Rafael +Ramon +Randy +Rastus +Raul +Raymond +Rayner +Reece +Reed +Reese +Reid +Reuben +Reynard +Ricardo +Richard +Ricky +Riley +River +Robert +Roberto +Rocco +Rodger +Rodrigo +Roger +Roman +Romayne +Romeo +Ronald +Roscoe +Roswell +Rowan +Roy +Royce +Rube +Ruben +Russell +Rusty +Ryan +Ryder +Ryker +Rylan +Sal +Salvador +Sam +Samuel +Santiago +Saul +Sawyer +Scott +Scotty +Sean +Sebastian +Sergio +Seth +Seymour +Shane +Shaun +Shawn +Shiloh +Silas +Simon +Skyler +Sloan +Smoke +Solomon +Sorrel +Spencer +Spike +Stephen +Steven +Sybil +Syd +Talon +Tamsin +Tanner +Tate +Taylor +Tel +Terrell +Terry +Theodore +Thomas +Tim +Timothy +Titus +Todd +Tony +Travis +Trent +Trenton +Trevor +Trey +Trip +Tristan +Tristen +Triston +Troy +Tucker +Ty +Tye +Tyler +Tyson +Uland +Ulric +Uriel +Vaughn +Victor +Vince +Vincent +Vinny +Walker +Walter +Ward +Warner +Wayne +Wesley +Weston +Whitaker +William +Willy +Woodrow +Wyatt +Xander +Xavier +Yahir +Zachariah +Zachary +Zack +Zackary +Zander +Zane +Zayden +Zeke Zion \ No newline at end of file diff --git a/strings/names/golem.txt b/strings/names/golem.txt index a26da78b045b..7cfcefa899da 100644 --- a/strings/names/golem.txt +++ b/strings/names/golem.txt @@ -1,157 +1,157 @@ -Ablation -Alabaster -Alunite -Andesite -Anyhdrite -Basalt -Basin -Bauxite -Bedrock -Bismuth -Bismuthinite -Bituminous Coal -Borax -Boulder -Brimstone -Brittle -Calcite -Cassiterite -Cenozoic -Chalk -Chasm -Cheridite -Chert -Chromite -Cinnabar -Claystone -Coast -Cobaltite -Column -Conglomerate -Core -Crevasse -Crust -Cryolite -Crystal -Dacite -Diorite -Dolomite -Dolostone -Dragonforce -Earthflow -Epoch -Eutrophication -Fault -Flint -Foliation -Foreshock -Fossil -Gabbro -Galena -Garnierite -Geode -Geoge -Gneiss -Granite -Graphite -Gravel -Groove -Grotto -Gypsum -Hematite -Hornblende -Humus -Igneous -Ilmenite -Iron -Island -Jasper -Jet -Kaolinite -Kettle -Kimberlite -Komatiite -Landslide -Levee -Lignite -Limestone -Limonite -Luster -Madidite -Magnetite -Magnitude -Malachite -Mantle -Marble -Marcasite -Melange -Meme -Mica -Microcline -Migmatite -Mineral -Mountain -Mudstone -Obsidian -Olivine -Ore -Orpiment -Orthoclase -Outwash -Oxbow Lake -Oynx -Pahoehoe -Pebble -Pegmatite -Periclase -Petrified Wood -Phyllite -Pitchblende -Plate -Pothole -Puddingstone -Pyrite -Pyrolusite -Quake -Quarry -Quartz -Quartzite -Realgar -Reservoir -Rhyolite -Rock -Rock Salt -Rockfall -Rutile -Saltpeter -Sand -Sandstone -Satinspar -Schist -Sediment -Seismic -Selenite -Serpentine -Shale -Shore -Siltstone -Slag -Slate -Sphalerite -Stack -Stalactite -Stalagmite -Stibnite -Stone -Stress -Subduction -Sylvite -Talc -Tetrahedrite -Tidal -Trench -Valley -Volcano -Xenolith -Yardang -Zone +Ablation +Alabaster +Alunite +Andesite +Anyhdrite +Basalt +Basin +Bauxite +Bedrock +Bismuth +Bismuthinite +Bituminous Coal +Borax +Boulder +Brimstone +Brittle +Calcite +Cassiterite +Cenozoic +Chalk +Chasm +Cheridite +Chert +Chromite +Cinnabar +Claystone +Coast +Cobaltite +Column +Conglomerate +Core +Crevasse +Crust +Cryolite +Crystal +Dacite +Diorite +Dolomite +Dolostone +Dragonforce +Earthflow +Epoch +Eutrophication +Fault +Flint +Foliation +Foreshock +Fossil +Gabbro +Galena +Garnierite +Geode +Geoge +Gneiss +Granite +Graphite +Gravel +Groove +Grotto +Gypsum +Hematite +Hornblende +Humus +Igneous +Ilmenite +Iron +Island +Jasper +Jet +Kaolinite +Kettle +Kimberlite +Komatiite +Landslide +Levee +Lignite +Limestone +Limonite +Luster +Madidite +Magnetite +Magnitude +Malachite +Mantle +Marble +Marcasite +Melange +Meme +Mica +Microcline +Migmatite +Mineral +Mountain +Mudstone +Obsidian +Olivine +Ore +Orpiment +Orthoclase +Outwash +Oxbow Lake +Oynx +Pahoehoe +Pebble +Pegmatite +Periclase +Petrified Wood +Phyllite +Pitchblende +Plate +Pothole +Puddingstone +Pyrite +Pyrolusite +Quake +Quarry +Quartz +Quartzite +Realgar +Reservoir +Rhyolite +Rock +Rock Salt +Rockfall +Rutile +Saltpeter +Sand +Sandstone +Satinspar +Schist +Sediment +Seismic +Selenite +Serpentine +Shale +Shore +Siltstone +Slag +Slate +Sphalerite +Stack +Stalactite +Stalagmite +Stibnite +Stone +Stress +Subduction +Sylvite +Talc +Tetrahedrite +Tidal +Trench +Valley +Volcano +Xenolith +Yardang +Zone diff --git a/strings/names/last.txt b/strings/names/last.txt index b9769055e3a1..6796c7519e89 100644 --- a/strings/names/last.txt +++ b/strings/names/last.txt @@ -1,570 +1,570 @@ -Ackerley -Adams -Addison -Agg -Aggley -Ahmed -Albright -Alekseev -Ali -Alice -Allen -Alliman -Altmann -Anderson -Andreev -Ann -Archibald -Armstrong -Ashbaugh -Atkinson -Atweeke -Aultman -Auman -Baer -Bailey -Baker -Barnes -Barrett -Bash -Bashline -Basinger -Baskett -Basmanoff -Batten -Baum -Baxter -Beach -Beail -Beck -Beedell -Begum -Bell -Benford -Bennett -Berkheimer -Best -Bickerson -Bicknell -Biery -Black -Blackburn -Blaine -Blessig -Bloise -Bluetenberger -Blyant -Bode -Bould -Bousum -Bowchiew -Boyer -Brandenburg -Bratton -Braun -Briggs -Brindle -Briner -Brinigh -Brooks -Brown -Bullard -Bunten -Burkett -Burns -Burris -Butterfill -Buttersworth -Buzzard -Byers -Bynum -Caldwell -Callison -Camp -Campbell -Carmichael -Carr -Carter -Catherina -Catleay -Cavalet -Chapman -Chauvin -Cherry -Christman -Christopher -Clark -Clarke -Clewett -Coates -Coldsmith -Collins -Compton -Conrad -Cook -Cooper -Costello -Cowart -Cowper -Cox -Cressman -Curry -Cypret -David -Davies -Davis -Dawkins -Day -Dean -Demuth -Dennis -Dickinson -Digson -Dimeling -Donkin -Draudy -Driggers -Dryfus -Dugmore -Duncan -Durstine -Earl -Easter -Echard -Eckhardstein -Edwards -Eggbert -Ehret -Elderson -Eliza -Elliott -Ellis -Enderly -Endsley -Evans -Ewing -Faqua -Faust -Fea -Feufer -Fiddler -Field -Fields -Finlay -Fischer -Fiscina -Fisher -Fitzgerald -Fleming -Flickinger -Focell -Foster -Franks -Fraser -Fryer -Fuchs -Fulton -Gadow -Gardner -Garland -Garneys -Garratt -Garrison -Gettemy -Gibson -Glover -Goebbles -Goodman -Graham -Gray -Green -Greenawalt -Greene -Greenwood -Gregory -Griffiths -Gronko -Guess -Hall -Hanford -Hardie -Harding -Hardy -Harris -Harrison -Harrold -Harrow -Harshman -Hastings -Hawker -Hawking -Hawkins -Hayhurst -Haynes -Heckendora -Hegarty -Henry -Hice -Highlands -Hill -Hincken -Hirleman -Hoenshell -Holdeman -Holmes -Hook -Hooker -Hoopengarner -Hoover -Houser -Houston -Howard -Howe -Huey -Hughes -Hujsak -Hunt -Hunter -Hussain -Hutton -Hynes -Ironmonger -Isaman -Isemann -Ivanov -Jackson -James -Jardine -Jenkins -Jenner -Jerome -Jesse -Jewell -Joghs -Johnson -Jones -Jowers -Joyce -Judge -Jyllian -Kadel -Kanaga -Kaur -Keener -Kelley -Kellogg -Kelly -Kemble -Kemerer -Keppel -Kepplinger -Khan -Kiefer -Kifer -Kimple -King -Kirkson -Knapenberger -Knapp -Koepple -Koster -Kuster -Kuznetsov -Laborde -Lacon -Lafortune -Langston -Larson -Lauffer -Laurenzi -Leach -Lee -Leech -Leichter -Leslie -Lester -Levett -Lewis -Lineman -Linton -Llora -Lloyd -Logue -Lombardi -Lord -Losey -Lowe -Lowstetter -Lucy -Ludwig -Maclagan -Magor -Marcotte -Margaret -Marriman -Marshall -Martins -Mary -Mason -Mathews -Matthews -Mcclymonds -Mccullough -Mccune -McDonald -McDonohugh -Mcfall -Mcintosh -Mckendrick -Mcloskey -Mcmullen -McShain -Mens -Merryman -Metzer -Meyers -Mikhaylov -Mildred -Miller -Millhouse -Mills -Milne -Mingle -Minnie -Mitchell -Moberly -Moon -Moore -Morgan -Morris -Mortland -Mosser -Mueller -Muggins -Mull -Muller -Murphy -Murray -Nash -Neely -Nehling -Newbern -Newton -Nicholas -Nickolson -Northey -Noton -Olphert -Oneal -Oppenheimer -Osteen -Osterweis -Osterwise -Otis -Overstreet -Owen -Owens -Palmer -Parker -Parkinson -Patel -Patterson -Paulson -Pavlov -Paynter -Pearsall -Pennington -Perkins -Pershing -Peters -Petrov -Pfeifer -Philips -Phillips -Picard -Pinney -Poehl -Poley -Polson -Potter -Powell -Power -Powers -Pratt -Prechtl -Prescott -Prevatt -Price -Priebe -Pritchard -Pycroft -Quinn -Quirin -Rader -Rahl -Ramos -Randolph -Ratcliff -Rathen -Rathens -Raub -Ray -Reade -Reichard -Reid -Reighner -Rhinehart -Richards -Richardson -Richter -Rifler -Riggle -Riker -Ringer -Roadman -Roberts -Robertson -Robinson -Roby -Rockwell -Rogers -Rohtin -Rose -Rosensteel -Rowley -Russell -Ryals -Sagan -Sanders -Sandford -Sandys -Sauter -Saylor -Schaeffer -Scherer -Schmidt -Schofield -Schrader -Scott -Sealis -Seelig -Seidner -Semenov -Shafer -Shaffer -Shaner -Shaw -Sheets -Shick -Shirey -Sholl -Shupe -Sidower -Siegrist -Simmons -Simpson -Singh -Skywalker -Sloan -Smail -Smirnov -Smith -Snyder -Sommer -Spock -Stafford -Stahl -Stainforth -Stall -Stamos -Stange -Staymates -Steele -Stephenson -Stern -Stewart -Stocker -Stone -Stough -Straub -Stroble -Stroh -Styles -Sullivan -Sulyard -Summy -Sutton -Swabey -Swarner -Sybilla -Taggart -Tanner -Taylor -Teagarden -Tedrow -Tennant -Thomas -Thomlinson -Thompson -Thomson -Thorley -Tilton -Tireman -Todd -Treeby -Trovato -Turner -Ulery -Ullman -Unk -Vader -Vanleer -Vasilyev -Waldron -Walker -Wallick -Ward -Wardle -Warren -Watson -Webb -Weeter -Weinstein -Weisgarber -Wells -Welty -Wentzel -Werner -Werry -Wheeler -Whirlow -White -Whiteman -Whittier -Wible -Wile -Wilkerson -Wilkinson -Willey -Williams -Williamson -Wilo -Wilson -Winton -Wise -Wolfe -Wolff -Wood -Woodward -Woodworth -Woolery -Woollard -Wright -Yeskey -Young -Zadovsky -Zalack -Zaun -Zeal -Zimmer +Ackerley +Adams +Addison +Agg +Aggley +Ahmed +Albright +Alekseev +Ali +Alice +Allen +Alliman +Altmann +Anderson +Andreev +Ann +Archibald +Armstrong +Ashbaugh +Atkinson +Atweeke +Aultman +Auman +Baer +Bailey +Baker +Barnes +Barrett +Bash +Bashline +Basinger +Baskett +Basmanoff +Batten +Baum +Baxter +Beach +Beail +Beck +Beedell +Begum +Bell +Benford +Bennett +Berkheimer +Best +Bickerson +Bicknell +Biery +Black +Blackburn +Blaine +Blessig +Bloise +Bluetenberger +Blyant +Bode +Bould +Bousum +Bowchiew +Boyer +Brandenburg +Bratton +Braun +Briggs +Brindle +Briner +Brinigh +Brooks +Brown +Bullard +Bunten +Burkett +Burns +Burris +Butterfill +Buttersworth +Buzzard +Byers +Bynum +Caldwell +Callison +Camp +Campbell +Carmichael +Carr +Carter +Catherina +Catleay +Cavalet +Chapman +Chauvin +Cherry +Christman +Christopher +Clark +Clarke +Clewett +Coates +Coldsmith +Collins +Compton +Conrad +Cook +Cooper +Costello +Cowart +Cowper +Cox +Cressman +Curry +Cypret +David +Davies +Davis +Dawkins +Day +Dean +Demuth +Dennis +Dickinson +Digson +Dimeling +Donkin +Draudy +Driggers +Dryfus +Dugmore +Duncan +Durstine +Earl +Easter +Echard +Eckhardstein +Edwards +Eggbert +Ehret +Elderson +Eliza +Elliott +Ellis +Enderly +Endsley +Evans +Ewing +Faqua +Faust +Fea +Feufer +Fiddler +Field +Fields +Finlay +Fischer +Fiscina +Fisher +Fitzgerald +Fleming +Flickinger +Focell +Foster +Franks +Fraser +Fryer +Fuchs +Fulton +Gadow +Gardner +Garland +Garneys +Garratt +Garrison +Gettemy +Gibson +Glover +Goebbles +Goodman +Graham +Gray +Green +Greenawalt +Greene +Greenwood +Gregory +Griffiths +Gronko +Guess +Hall +Hanford +Hardie +Harding +Hardy +Harris +Harrison +Harrold +Harrow +Harshman +Hastings +Hawker +Hawking +Hawkins +Hayhurst +Haynes +Heckendora +Hegarty +Henry +Hice +Highlands +Hill +Hincken +Hirleman +Hoenshell +Holdeman +Holmes +Hook +Hooker +Hoopengarner +Hoover +Houser +Houston +Howard +Howe +Huey +Hughes +Hujsak +Hunt +Hunter +Hussain +Hutton +Hynes +Ironmonger +Isaman +Isemann +Ivanov +Jackson +James +Jardine +Jenkins +Jenner +Jerome +Jesse +Jewell +Joghs +Johnson +Jones +Jowers +Joyce +Judge +Jyllian +Kadel +Kanaga +Kaur +Keener +Kelley +Kellogg +Kelly +Kemble +Kemerer +Keppel +Kepplinger +Khan +Kiefer +Kifer +Kimple +King +Kirkson +Knapenberger +Knapp +Koepple +Koster +Kuster +Kuznetsov +Laborde +Lacon +Lafortune +Langston +Larson +Lauffer +Laurenzi +Leach +Lee +Leech +Leichter +Leslie +Lester +Levett +Lewis +Lineman +Linton +Llora +Lloyd +Logue +Lombardi +Lord +Losey +Lowe +Lowstetter +Lucy +Ludwig +Maclagan +Magor +Marcotte +Margaret +Marriman +Marshall +Martins +Mary +Mason +Mathews +Matthews +Mcclymonds +Mccullough +Mccune +McDonald +McDonohugh +Mcfall +Mcintosh +Mckendrick +Mcloskey +Mcmullen +McShain +Mens +Merryman +Metzer +Meyers +Mikhaylov +Mildred +Miller +Millhouse +Mills +Milne +Mingle +Minnie +Mitchell +Moberly +Moon +Moore +Morgan +Morris +Mortland +Mosser +Mueller +Muggins +Mull +Muller +Murphy +Murray +Nash +Neely +Nehling +Newbern +Newton +Nicholas +Nickolson +Northey +Noton +Olphert +Oneal +Oppenheimer +Osteen +Osterweis +Osterwise +Otis +Overstreet +Owen +Owens +Palmer +Parker +Parkinson +Patel +Patterson +Paulson +Pavlov +Paynter +Pearsall +Pennington +Perkins +Pershing +Peters +Petrov +Pfeifer +Philips +Phillips +Picard +Pinney +Poehl +Poley +Polson +Potter +Powell +Power +Powers +Pratt +Prechtl +Prescott +Prevatt +Price +Priebe +Pritchard +Pycroft +Quinn +Quirin +Rader +Rahl +Ramos +Randolph +Ratcliff +Rathen +Rathens +Raub +Ray +Reade +Reichard +Reid +Reighner +Rhinehart +Richards +Richardson +Richter +Rifler +Riggle +Riker +Ringer +Roadman +Roberts +Robertson +Robinson +Roby +Rockwell +Rogers +Rohtin +Rose +Rosensteel +Rowley +Russell +Ryals +Sagan +Sanders +Sandford +Sandys +Sauter +Saylor +Schaeffer +Scherer +Schmidt +Schofield +Schrader +Scott +Sealis +Seelig +Seidner +Semenov +Shafer +Shaffer +Shaner +Shaw +Sheets +Shick +Shirey +Sholl +Shupe +Sidower +Siegrist +Simmons +Simpson +Singh +Skywalker +Sloan +Smail +Smirnov +Smith +Snyder +Sommer +Spock +Stafford +Stahl +Stainforth +Stall +Stamos +Stange +Staymates +Steele +Stephenson +Stern +Stewart +Stocker +Stone +Stough +Straub +Stroble +Stroh +Styles +Sullivan +Sulyard +Summy +Sutton +Swabey +Swarner +Sybilla +Taggart +Tanner +Taylor +Teagarden +Tedrow +Tennant +Thomas +Thomlinson +Thompson +Thomson +Thorley +Tilton +Tireman +Todd +Treeby +Trovato +Turner +Ulery +Ullman +Unk +Vader +Vanleer +Vasilyev +Waldron +Walker +Wallick +Ward +Wardle +Warren +Watson +Webb +Weeter +Weinstein +Weisgarber +Wells +Welty +Wentzel +Werner +Werry +Wheeler +Whirlow +White +Whiteman +Whittier +Wible +Wile +Wilkerson +Wilkinson +Willey +Williams +Williamson +Wilo +Wilson +Winton +Wise +Wolfe +Wolff +Wood +Woodward +Woodworth +Woolery +Woollard +Wright +Yeskey +Young +Zadovsky +Zalack +Zaun +Zeal +Zimmer Zoucks \ No newline at end of file diff --git a/strings/names/lizard_female.txt b/strings/names/lizard_female.txt index cf588df9b190..078ee7bcff48 100644 --- a/strings/names/lizard_female.txt +++ b/strings/names/lizard_female.txt @@ -1,163 +1,163 @@ -Adzi -Ah -Ahaht -Ajim -Akeenus -Akish -Akishan -Aleeto -Am -Amussa -An -Anozz -Asheemar -Asska -Awas -Azala -Azbai -Azeez -Azum -Banalz -Bar -Baseenar -Beek -Beekatan -Beekus -Beela -Beelei -Beem -Beewos -Bejeen -Ber -Betzi -Bishalus -Bokeeus -Bur -Bura -Chalaree -Chana -Chanil -Chee -Cheesh -Chimatei -Chirurgeon -Cholasistu -Chuna -Churasu -Crath -Dar -Deeja -Deesei -Deesh -Deetsan -Deetwos -Dooka -Druja -Eepa -Ei -Eix -El -Ereel -Eutei -Gai -Gih -Gilm -Gish -Go -Hal -Hul -Ja -Jaseen -Jasuda -Jeed -Jeen -Kajul -Kal -Kasa -Keel -Keerava -Kiurz -Kud -La -Lee -Lei -Lifts -Liurz -Lurasha -Ma -Mach -Marz -Meedish -Meeh -Meema -Meen -Meena -Meenus -Meerana -Meesei -Meeus -Mei -Milah -Mim -Mota -Mudeska -Muz -Na -Nakuma -Nam -Nassa -Natoo -Neesha -Neetizei -Neetra -Neeus -Niima -Numeen -Nuralg -Nush -Ocheeva -Okur -Olank -On -Onasha -Osheeka -Pasha -Ra -Rana -Raniur -Ree -Reesa -Rei -Sa -Saak -Sanax -Seeba -Seed -Seen -Shah -Shahvee -Shaleez -Shatalg -Sheer -Shei -Sigerthe -Skaleel -Sudie -Tail -Tar -Tasha -Tei -Telixith -Tumma -Veek -Wan -Wazei -Weedum -Weewish -Witseidutsei -Wuja -Wujeeta -Wusha -Xil -Zish +Adzi +Ah +Ahaht +Ajim +Akeenus +Akish +Akishan +Aleeto +Am +Amussa +An +Anozz +Asheemar +Asska +Awas +Azala +Azbai +Azeez +Azum +Banalz +Bar +Baseenar +Beek +Beekatan +Beekus +Beela +Beelei +Beem +Beewos +Bejeen +Ber +Betzi +Bishalus +Bokeeus +Bur +Bura +Chalaree +Chana +Chanil +Chee +Cheesh +Chimatei +Chirurgeon +Cholasistu +Chuna +Churasu +Crath +Dar +Deeja +Deesei +Deesh +Deetsan +Deetwos +Dooka +Druja +Eepa +Ei +Eix +El +Ereel +Eutei +Gai +Gih +Gilm +Gish +Go +Hal +Hul +Ja +Jaseen +Jasuda +Jeed +Jeen +Kajul +Kal +Kasa +Keel +Keerava +Kiurz +Kud +La +Lee +Lei +Lifts +Liurz +Lurasha +Ma +Mach +Marz +Meedish +Meeh +Meema +Meen +Meena +Meenus +Meerana +Meesei +Meeus +Mei +Milah +Mim +Mota +Mudeska +Muz +Na +Nakuma +Nam +Nassa +Natoo +Neesha +Neetizei +Neetra +Neeus +Niima +Numeen +Nuralg +Nush +Ocheeva +Okur +Olank +On +Onasha +Osheeka +Pasha +Ra +Rana +Raniur +Ree +Reesa +Rei +Sa +Saak +Sanax +Seeba +Seed +Seen +Shah +Shahvee +Shaleez +Shatalg +Sheer +Shei +Sigerthe +Skaleel +Sudie +Tail +Tar +Tasha +Tei +Telixith +Tumma +Veek +Wan +Wazei +Weedum +Weewish +Witseidutsei +Wuja +Wujeeta +Wusha +Xil +Zish Zollassa \ No newline at end of file diff --git a/strings/names/lizard_male.txt b/strings/names/lizard_male.txt index 437d124b50e0..f14f1705ba43 100644 --- a/strings/names/lizard_male.txt +++ b/strings/names/lizard_male.txt @@ -1,328 +1,328 @@ -Abijoo -Ah -Ajum -Am -Amusei -An -Anoo -Aojee -Asum -Az -Azeel -Azinar -Azjai -Baar -Banka -Bar -Barnaxi -Batar -Batuus -Beem -Beshnus -Betu -Bex -Bijot -Bimee -Binyaar -Bosekus -Brand -Bun -Bunach -Bunish -Busheeus -Buujhan -Chakuk -Chalish -Chalureel -Chath -Chee -Cheedal -Chilwir -Chitakus -Chiwish -Chulz -Chuna -Da -Dakee -Dan -Dar -Darasken -DarJee -Debameel -Deed -Deegeeta -Deeh -Deekonus -Deekum -Deekus -Deerkaza -Deetum -Demeepa -Depasa -Derkeethus -Deroh -Dezanu -Dreet -Drumarz -Dum -Dunaxith -Effe -Ei -Eidu -Eius -Eiuus -Eix -Eleedal -Er -Esqoo -Etaku -Gah -Gajul -Gam -Geeh -Geel -Geem -Geh -Gei -Gih -Gin -Goh -Gulum -Haj -Han -Haran -Hareeya -Hathei -Heedul -Heem -Hei -Heir -Hixeeh -Huleeya -Huzei -Ilas -Im -Inee -Itan -J'Ram -Ja -Jah -Jaraleet -Jaree -Jas -Jasaiin -Jaseen -Jat -Jee -Jeela -Jeelius -Jeelus -Jeen -Jeer -Jeetum -Jei -Jilux -Jin -Jon -Jul -Julan -Junal -Jush -Juunei -Kai -Kajin -Kamax -Kas -Keema -Keer -Keerasa -Kepanuu -Kia -Kiameed -Kilaya -Kiurz -Kur -Kuz -La -Lah -Lai -Lan -Lara -Leem -Lei -Loh -Lotash -Luh -Lurz -Luteema -Maahi -Madesi -Maheelius -Mahei -Maht -Malz -Marz -Mathei -Maxath -Meej -Meejapa -Meensuda -Meer -Mema -Mere -Metaku -Miharil -Milos -Miun -Mobareed -Mohimeem -Mopakuz -Motuu -Mujeen -Muranatepa -Mush -Muz -Na -Napetui -Nazuux -Nebutil -Neeti -Neetinei -Neetrenaza -Neetzara -Neeus -Nema -Neposh -Netapatuu -Nexith -Nodeeus -Nomu -Nosaleeth -Nowajeem -Noyei -Nulaz -Nur -Obaxith -Okan -Okaw -Okeeh -Oleed -Oleen -Olik -Olink -Onuja -Onurai -Opatieel -Otumeel -Owai -Pachat -Pacheeva -Pad -Paduxi -Pajeen -Parash -Peeradeeh -Pejureel -Petaxai -Pideelus -Pimaxi -Pojeel -Ra -Radithax -Raj -Rareel -Rasha -Redieeus -Ree -Reeh -Reemukeeus -Reenum -Reesa -Reet -Reezal -Resari -Riker -Ru -Rupah -Sakeepa -Sakeeus -Sakka -Saliith -Sar -Schiavas -Seek -Seewul -Sei -Sejaijilax -Shakiis -Shehs -Shei -Silm -Skee -Skeetul -Sureeus -Ta -Taeed -Tah -Taleel -Talen -Tan -Tanaka -Tanan -Tee -Teeba -Teegla -Teeka -Teekeeus -Teemeeta -Teeus -Tehat -Tei -Teinaava -Teineeja -Terezeeus -Tikaasi -Tim -Topeeth -Topith -Tsleeixth -Tul -Tulm -Tun -Ukatsei -Ukawei -Ula -Ulawa -Ullis -Usha -Usheeja -Utadeek -Utamukeeus -Utatul -Uxith -Vara -Veekas -Veenaza -Veezara -Vistha -Vudeelal -Wanan -Wanum -Wayiteh -Weebam -Weeltul -Weer -Wih -Wud -Wuleen -Wulm -Wumeek -Xal -Xemo -Yinz -Yinz'r -Zaw -Ze -Zeen -Zeeus +Abijoo +Ah +Ajum +Am +Amusei +An +Anoo +Aojee +Asum +Az +Azeel +Azinar +Azjai +Baar +Banka +Bar +Barnaxi +Batar +Batuus +Beem +Beshnus +Betu +Bex +Bijot +Bimee +Binyaar +Bosekus +Brand +Bun +Bunach +Bunish +Busheeus +Buujhan +Chakuk +Chalish +Chalureel +Chath +Chee +Cheedal +Chilwir +Chitakus +Chiwish +Chulz +Chuna +Da +Dakee +Dan +Dar +Darasken +DarJee +Debameel +Deed +Deegeeta +Deeh +Deekonus +Deekum +Deekus +Deerkaza +Deetum +Demeepa +Depasa +Derkeethus +Deroh +Dezanu +Dreet +Drumarz +Dum +Dunaxith +Effe +Ei +Eidu +Eius +Eiuus +Eix +Eleedal +Er +Esqoo +Etaku +Gah +Gajul +Gam +Geeh +Geel +Geem +Geh +Gei +Gih +Gin +Goh +Gulum +Haj +Han +Haran +Hareeya +Hathei +Heedul +Heem +Hei +Heir +Hixeeh +Huleeya +Huzei +Ilas +Im +Inee +Itan +J'Ram +Ja +Jah +Jaraleet +Jaree +Jas +Jasaiin +Jaseen +Jat +Jee +Jeela +Jeelius +Jeelus +Jeen +Jeer +Jeetum +Jei +Jilux +Jin +Jon +Jul +Julan +Junal +Jush +Juunei +Kai +Kajin +Kamax +Kas +Keema +Keer +Keerasa +Kepanuu +Kia +Kiameed +Kilaya +Kiurz +Kur +Kuz +La +Lah +Lai +Lan +Lara +Leem +Lei +Loh +Lotash +Luh +Lurz +Luteema +Maahi +Madesi +Maheelius +Mahei +Maht +Malz +Marz +Mathei +Maxath +Meej +Meejapa +Meensuda +Meer +Mema +Mere +Metaku +Miharil +Milos +Miun +Mobareed +Mohimeem +Mopakuz +Motuu +Mujeen +Muranatepa +Mush +Muz +Na +Napetui +Nazuux +Nebutil +Neeti +Neetinei +Neetrenaza +Neetzara +Neeus +Nema +Neposh +Netapatuu +Nexith +Nodeeus +Nomu +Nosaleeth +Nowajeem +Noyei +Nulaz +Nur +Obaxith +Okan +Okaw +Okeeh +Oleed +Oleen +Olik +Olink +Onuja +Onurai +Opatieel +Otumeel +Owai +Pachat +Pacheeva +Pad +Paduxi +Pajeen +Parash +Peeradeeh +Pejureel +Petaxai +Pideelus +Pimaxi +Pojeel +Ra +Radithax +Raj +Rareel +Rasha +Redieeus +Ree +Reeh +Reemukeeus +Reenum +Reesa +Reet +Reezal +Resari +Riker +Ru +Rupah +Sakeepa +Sakeeus +Sakka +Saliith +Sar +Schiavas +Seek +Seewul +Sei +Sejaijilax +Shakiis +Shehs +Shei +Silm +Skee +Skeetul +Sureeus +Ta +Taeed +Tah +Taleel +Talen +Tan +Tanaka +Tanan +Tee +Teeba +Teegla +Teeka +Teekeeus +Teemeeta +Teeus +Tehat +Tei +Teinaava +Teineeja +Terezeeus +Tikaasi +Tim +Topeeth +Topith +Tsleeixth +Tul +Tulm +Tun +Ukatsei +Ukawei +Ula +Ulawa +Ullis +Usha +Usheeja +Utadeek +Utamukeeus +Utatul +Uxith +Vara +Veekas +Veenaza +Veezara +Vistha +Vudeelal +Wanan +Wanum +Wayiteh +Weebam +Weeltul +Weer +Wih +Wud +Wuleen +Wulm +Wumeek +Xal +Xemo +Yinz +Yinz'r +Zaw +Ze +Zeen +Zeeus Zish \ No newline at end of file diff --git a/strings/names/mime.txt b/strings/names/mime.txt index cc65af67fd89..520d3affce98 100644 --- a/strings/names/mime.txt +++ b/strings/names/mime.txt @@ -1,24 +1,24 @@ -Invisible Man -Lemon Mime -Marcel -Marcel Mime -Mime -Mr Beret -Mr Mime -Mr Mute -Mute -Omerta -Oui Oui -Pantomime -Quiet -Quiet Riot -Silence -Silencio -Silent Knight -Silent Majority -Silent Night -Silent Sorrow -Transparency -Unspeakable -Untouchable +Invisible Man +Lemon Mime +Marcel +Marcel Mime +Mime +Mr Beret +Mr Mime +Mr Mute +Mute +Omerta +Oui Oui +Pantomime +Quiet +Quiet Riot +Silence +Silencio +Silent Knight +Silent Majority +Silent Night +Silent Sorrow +Transparency +Unspeakable +Untouchable Wall Runner \ No newline at end of file diff --git a/strings/names/ninjaname.txt b/strings/names/ninjaname.txt index fa0d0f109417..eb470493fa4a 100644 --- a/strings/names/ninjaname.txt +++ b/strings/names/ninjaname.txt @@ -1,44 +1,44 @@ -Aria -Baki -Blood -Bro -Cyrax -Daemon -Death -Donatello -Eater -Ermac -Fox -Goemon -Hanzo -Hayabusa -Hazuki -Hero -Hien -Hiro -Hiryu -Iga -Koga -Leonardo -McAwesome -McNinja -Michaelangelo -Midnight -Null -Ogre -Phantom -Raiden -Rain -Raphael -Ryu -Saibot -Samurai -Sarutobi -Scorpion -Seven -Shadow -Shredder -Smoke -Splinter -Throat +Aria +Baki +Blood +Bro +Cyrax +Daemon +Death +Donatello +Eater +Ermac +Fox +Goemon +Hanzo +Hayabusa +Hazuki +Hero +Hien +Hiro +Hiryu +Iga +Koga +Leonardo +McAwesome +McNinja +Michaelangelo +Midnight +Null +Ogre +Phantom +Raiden +Rain +Raphael +Ryu +Saibot +Samurai +Sarutobi +Scorpion +Seven +Shadow +Shredder +Smoke +Splinter +Throat Zero \ No newline at end of file diff --git a/strings/names/ninjatitle.txt b/strings/names/ninjatitle.txt index 41c5b338ba66..f9776f56fed7 100644 --- a/strings/names/ninjatitle.txt +++ b/strings/names/ninjatitle.txt @@ -1,46 +1,46 @@ -Agile -Assassin -Awesome -Black -Crimson -Cruel -Deep -Dr -Dragon -Ender -Grandmaster -Grappler -Gray -Hunter -Initiate -Killer -Liquid -Master -Merciful -Merciless -Nickel -Night -Nightshade -Ninja -Noob -Orphaner -Quick -Remorseless -Rogue -Sensei -Shinobi -Silencing -Silent -Silver -Singing -Slayer -Snake -Solid -Solidus -Stalker -Steel -Strider -Striker -Swift -Ulimate +Agile +Assassin +Awesome +Black +Crimson +Cruel +Deep +Dr +Dragon +Ender +Grandmaster +Grappler +Gray +Hunter +Initiate +Killer +Liquid +Master +Merciful +Merciless +Nickel +Night +Nightshade +Ninja +Noob +Orphaner +Quick +Remorseless +Rogue +Sensei +Shinobi +Silencing +Silent +Silver +Singing +Slayer +Snake +Solid +Solidus +Stalker +Steel +Strider +Striker +Swift +Ulimate Widower \ No newline at end of file diff --git a/strings/names/plasmaman.txt b/strings/names/plasmaman.txt index eb5f8b786c9c..8cbfc00837dc 100644 --- a/strings/names/plasmaman.txt +++ b/strings/names/plasmaman.txt @@ -1,118 +1,118 @@ -Actinium -Aluminium -Americium -Antimony -Argon -Arsenic -Astatine -Barium -Berkelium -Beryllium -Bismuth -Bohrium -Boron -Bromine -Cadmium -Caesium -Calcium -Californium -Carbon -Cerium -Chlorine -Chromium -Cobalt -Copernicium -Copper -Curium -Darmstadtium -Dubnium -Dysprosium -Einsteinium -Erbium -Europium -Fermium -Flerovium -Fluorine -Francium -Gadolinium -Gallium -Germanium -Gold -Hafnium -Hassium -Helium -Holmium -Hydrogen -Indium -Iodine -Iridium -Iron -Krypton -Lanthanum -Lawrencium -Lead -Lithium -Livermorium -Lutetium -Magnesium -Manganese -Meitnerium -Mendelevium -Mercury -Molybdenum -Moscovium -Neodymium -Neon -Neptunium -Nickel -Nihonium -Niobium -Nitrogen -Nobelium -Oganesson -Osmium -Oxygen -Palladium -Phosphorus -Platinum -Plutonium -Polonium -Potassium -Praseodymium -Promethium -Protactinium -Radium -Radon -Rhenium -Rhodium -Roentgenium -Rubidium -Ruthenium -Rutherfordium -Samarium -Scandium -Seaborgium -Selenium -Silicon -Silver -Sodium -Strontium -Sulfur -Tantalum -Technetium -Tellurium -Tennessine -Terbium -Thallium -Thorium -Thulium -Tin -Titanium -Tungsten -Uranium -Vanadium -Xenon -Ytterbium -Yttrium -Zinc +Actinium +Aluminium +Americium +Antimony +Argon +Arsenic +Astatine +Barium +Berkelium +Beryllium +Bismuth +Bohrium +Boron +Bromine +Cadmium +Caesium +Calcium +Californium +Carbon +Cerium +Chlorine +Chromium +Cobalt +Copernicium +Copper +Curium +Darmstadtium +Dubnium +Dysprosium +Einsteinium +Erbium +Europium +Fermium +Flerovium +Fluorine +Francium +Gadolinium +Gallium +Germanium +Gold +Hafnium +Hassium +Helium +Holmium +Hydrogen +Indium +Iodine +Iridium +Iron +Krypton +Lanthanum +Lawrencium +Lead +Lithium +Livermorium +Lutetium +Magnesium +Manganese +Meitnerium +Mendelevium +Mercury +Molybdenum +Moscovium +Neodymium +Neon +Neptunium +Nickel +Nihonium +Niobium +Nitrogen +Nobelium +Oganesson +Osmium +Oxygen +Palladium +Phosphorus +Platinum +Plutonium +Polonium +Potassium +Praseodymium +Promethium +Protactinium +Radium +Radon +Rhenium +Rhodium +Roentgenium +Rubidium +Ruthenium +Rutherfordium +Samarium +Scandium +Seaborgium +Selenium +Silicon +Silver +Sodium +Strontium +Sulfur +Tantalum +Technetium +Tellurium +Tennessine +Terbium +Thallium +Thorium +Thulium +Tin +Titanium +Tungsten +Uranium +Vanadium +Xenon +Ytterbium +Yttrium +Zinc Zirconium \ No newline at end of file diff --git a/strings/names/posibrain.txt b/strings/names/posibrain.txt index 119632bd815c..ed8a186fd4a7 100644 --- a/strings/names/posibrain.txt +++ b/strings/names/posibrain.txt @@ -1,47 +1,47 @@ -PBU -HIU -SINA -ARMA -OSI -HBL -MSO -RR -CHRI -CDB -HG -XSI -ORNG -GUN -KOR -MET -FRE -XIS -SLI -PKP -HOG -RZH -GOOF -MRPR -JJR -FIRC -INC -PHL -BGB -ANTR -MIW -WJ -JRD -CHOC -ANCL -JLLO -JNLG -KOS -TKRG -XAL -STLP -CBOS -DUNC -FXMC -DRSD -COI +PBU +HIU +SINA +ARMA +OSI +HBL +MSO +RR +CHRI +CDB +HG +XSI +ORNG +GUN +KOR +MET +FRE +XIS +SLI +PKP +HOG +RZH +GOOF +MRPR +JJR +FIRC +INC +PHL +BGB +ANTR +MIW +WJ +JRD +CHOC +ANCL +JLLO +JNLG +KOS +TKRG +XAL +STLP +CBOS +DUNC +FXMC +DRSD +COI CYBR \ No newline at end of file diff --git a/strings/names/verbs.txt b/strings/names/verbs.txt index 17bc127fd578..fc90f89e4008 100644 --- a/strings/names/verbs.txt +++ b/strings/names/verbs.txt @@ -1,632 +1,632 @@ -accept -add -admire -admit -advise -afford -agree -alert -allow -amuse -analyse -announce -annoy -answer -apologise -appear -applaud -appreciate -approve -argue -arrange -arrest -arrive -ask -attach -attack -attempt -attend -attract -avoid -back -bake -balance -ban -bang -bare -bat -bathe -battle -beam -beg -behave -belong -bleach -bless -blind -blink -blot -blush -boast -boil -bolt -bomb -book -bore -borrow -bounce -bow -box -brake -branch -breathe -bruise -brush -bubble -bump -burn -bury -buzz -calculate -call -camp -care -carry -carve -cause -challenge -change -charge -chase -cheat -check -cheer -chew -choke -chop -claim -clap -clean -clear -clip -close -coach -coil -collect -colour -comb -command -communicate -compare -compete -complain -complete -concentrate -concern -confess -confuse -connect -consider -consist -contain -continue -copy -correct -cough -count -cover -crack -crash -crawl -cross -crush -cry -cure -curl -curve -cycle -dam -damage -dance -dare -decay -deceive -decide -decorate -delay -delight -deliver -depend -describe -desert -deserve -destroy -detect -develop -disagree -disappear -disapprove -disarm -discover -dislike -divide -double -doubt -drag -drain -dream -dress -drip -drop -drown -drum -dry -dust -earn -educate -embarrass -employ -empty -encourage -end -enjoy -enter -entertain -escape -examine -excite -excuse -exercise -exist -expand -expect -explain -explode -extend -face -fade -fail -fancy -fasten -fax -fear -fence -fetch -file -fill -film -fire -fit -fix -flap -flash -float -flood -flow -flower -fold -follow -fool -force -form -found -frame -frighten -fry -gather -gaze -glow -glue -grab -grate -grease -greet -grin -grip -groan -guarantee -guard -guess -guide -hammer -hand -handle -hang -happen -harass -harm -hate -haunt -head -heal -heap -heat -help -hook -hop -hope -hover -hug -hum -hunt -hurry -identify -ignore -imagine -impress -improve -include -increase -influence -inform -inject -injure -instruct -intend -interest -interfere -interrupt -introduce -invent -invite -irritate -itch -jail -jam -jog -join -joke -judge -juggle -jump -kick -kill -kiss -kneel -knit -knock -knot -label -land -last -laugh -launch -learn -level -license -lick -lie -lighten -like -list -listen -live -load -lock -long -look -love -man -manage -march -mark -marry -match -mate -matter -measure -meddle -melt -memorise -mend -messup -milk -mine -miss -mix -moan -moor -mourn -move -muddle -mug -multiply -murder -nail -name -need -nest -nod -note -notice -number -obey -object -observe -obtain -occur -offend -offer -open -order -overflow -owe -own -pack -paddle -paint -park -part -pass -paste -pat -pause -peck -pedal -peel -peep -perform -permit -phone -pick -pinch -pine -place -plan -plant -play -please -plug -point -poke -polish -pop -possess -post -pour -practise -pray -preach -precede -prefer -prepare -present -preserve -press -pretend -prevent -prick -print -produce -program -promise -protect -provide -pull -pump -punch -puncture -punish -push -question -queue -race -radiate -rain -raise -reach -realise -receive -recognise -record -reduce -reflect -refuse -regret -reign -reject -rejoice -relax -release -rely -remain -remember -remind -remove -repair -repeat -replace -reply -report -reproduce -request -rescue -retire -return -rhyme -rinse -risk -rob -rock -roll -rot -rub -ruin -rule -rush -sack -sail -satisfy -save -saw -scare -scatter -scold -scorch -scrape -scratch -scream -screw -scribble -scrub -seal -search -separate -serve -settle -shade -share -shave -shelter -shiver -shock -shop -shrug -sigh -sign -signal -sin -sip -ski -skip -slap -slip -slow -smash -smell -smile -smoke -snatch -sneeze -sniff -snore -snow -soak -soothe -sound -spare -spark -sparkle -spell -spill -spoil -spot -spray -sprout -squash -squeak -squeal -squeeze -stain -stamp -stare -start -stay -steer -step -stir -stitch -stop -store -strap -strengthen -stretch -strip -stroke -stuff -subtract -succeed -suck -suffer -suggest -suit -supply -support -suppose -surprise -surround -suspect -suspend -switch -talk -tame -tap -taste -tease -telephone -tempt -terrify -test -thank -thaw -tick -tickle -tie -time -tip -tire -touch -tour -tow -trace -trade -train -transport -trap -travel -treat -tremble -trick -trip -trot -trouble -trust -try -tug -tumble -turn -twist -type -undress -unfasten -unite -unlock -unpack -untidy -use -vanish -visit -wail -wait -walk -wander -want -warm -warn -wash -waste -watch -water -wave -weigh -welcome -whine -whip -whirl -whisper -whistle -wink -wipe -wish -wobble -wonder -work -worry -wrap -wreck -wrestle -wriggle -yawn -yell -zip +accept +add +admire +admit +advise +afford +agree +alert +allow +amuse +analyse +announce +annoy +answer +apologise +appear +applaud +appreciate +approve +argue +arrange +arrest +arrive +ask +attach +attack +attempt +attend +attract +avoid +back +bake +balance +ban +bang +bare +bat +bathe +battle +beam +beg +behave +belong +bleach +bless +blind +blink +blot +blush +boast +boil +bolt +bomb +book +bore +borrow +bounce +bow +box +brake +branch +breathe +bruise +brush +bubble +bump +burn +bury +buzz +calculate +call +camp +care +carry +carve +cause +challenge +change +charge +chase +cheat +check +cheer +chew +choke +chop +claim +clap +clean +clear +clip +close +coach +coil +collect +colour +comb +command +communicate +compare +compete +complain +complete +concentrate +concern +confess +confuse +connect +consider +consist +contain +continue +copy +correct +cough +count +cover +crack +crash +crawl +cross +crush +cry +cure +curl +curve +cycle +dam +damage +dance +dare +decay +deceive +decide +decorate +delay +delight +deliver +depend +describe +desert +deserve +destroy +detect +develop +disagree +disappear +disapprove +disarm +discover +dislike +divide +double +doubt +drag +drain +dream +dress +drip +drop +drown +drum +dry +dust +earn +educate +embarrass +employ +empty +encourage +end +enjoy +enter +entertain +escape +examine +excite +excuse +exercise +exist +expand +expect +explain +explode +extend +face +fade +fail +fancy +fasten +fax +fear +fence +fetch +file +fill +film +fire +fit +fix +flap +flash +float +flood +flow +flower +fold +follow +fool +force +form +found +frame +frighten +fry +gather +gaze +glow +glue +grab +grate +grease +greet +grin +grip +groan +guarantee +guard +guess +guide +hammer +hand +handle +hang +happen +harass +harm +hate +haunt +head +heal +heap +heat +help +hook +hop +hope +hover +hug +hum +hunt +hurry +identify +ignore +imagine +impress +improve +include +increase +influence +inform +inject +injure +instruct +intend +interest +interfere +interrupt +introduce +invent +invite +irritate +itch +jail +jam +jog +join +joke +judge +juggle +jump +kick +kill +kiss +kneel +knit +knock +knot +label +land +last +laugh +launch +learn +level +license +lick +lie +lighten +like +list +listen +live +load +lock +long +look +love +man +manage +march +mark +marry +match +mate +matter +measure +meddle +melt +memorise +mend +messup +milk +mine +miss +mix +moan +moor +mourn +move +muddle +mug +multiply +murder +nail +name +need +nest +nod +note +notice +number +obey +object +observe +obtain +occur +offend +offer +open +order +overflow +owe +own +pack +paddle +paint +park +part +pass +paste +pat +pause +peck +pedal +peel +peep +perform +permit +phone +pick +pinch +pine +place +plan +plant +play +please +plug +point +poke +polish +pop +possess +post +pour +practise +pray +preach +precede +prefer +prepare +present +preserve +press +pretend +prevent +prick +print +produce +program +promise +protect +provide +pull +pump +punch +puncture +punish +push +question +queue +race +radiate +rain +raise +reach +realise +receive +recognise +record +reduce +reflect +refuse +regret +reign +reject +rejoice +relax +release +rely +remain +remember +remind +remove +repair +repeat +replace +reply +report +reproduce +request +rescue +retire +return +rhyme +rinse +risk +rob +rock +roll +rot +rub +ruin +rule +rush +sack +sail +satisfy +save +saw +scare +scatter +scold +scorch +scrape +scratch +scream +screw +scribble +scrub +seal +search +separate +serve +settle +shade +share +shave +shelter +shiver +shock +shop +shrug +sigh +sign +signal +sin +sip +ski +skip +slap +slip +slow +smash +smell +smile +smoke +snatch +sneeze +sniff +snore +snow +soak +soothe +sound +spare +spark +sparkle +spell +spill +spoil +spot +spray +sprout +squash +squeak +squeal +squeeze +stain +stamp +stare +start +stay +steer +step +stir +stitch +stop +store +strap +strengthen +stretch +strip +stroke +stuff +subtract +succeed +suck +suffer +suggest +suit +supply +support +suppose +surprise +surround +suspect +suspend +switch +talk +tame +tap +taste +tease +telephone +tempt +terrify +test +thank +thaw +tick +tickle +tie +time +tip +tire +touch +tour +tow +trace +trade +train +transport +trap +travel +treat +tremble +trick +trip +trot +trouble +trust +try +tug +tumble +turn +twist +type +undress +unfasten +unite +unlock +unpack +untidy +use +vanish +visit +wail +wait +walk +wander +want +warm +warn +wash +waste +watch +water +wave +weigh +welcome +whine +whip +whirl +whisper +whistle +wink +wipe +wish +wobble +wonder +work +worry +wrap +wreck +wrestle +wriggle +yawn +yell +zip zoom \ No newline at end of file diff --git a/strings/names/wizardfirst.txt b/strings/names/wizardfirst.txt index f1e9a10a58e8..13dc68c6bd24 100644 --- a/strings/names/wizardfirst.txt +++ b/strings/names/wizardfirst.txt @@ -1,37 +1,37 @@ -Alatar -Archchancellor -Boccob -Circe -Dumbledor -Elminister -Gandalf -Grimm -Gulstaff -Houdini -Jim -Kaschei -Khelben -Kreol -Lina -Merlin -Mogan -Mordenkainen -Morgan -Mystryl -Nihilus -Palando -Prospero -Radagast -Raistlin -Rasputin -Rincewind -Saruman -Tenser -Terefi -Tzeentch -Urza -Vaarsuvius -Vecna -Yoda -Zagyg +Alatar +Archchancellor +Boccob +Circe +Dumbledor +Elminister +Gandalf +Grimm +Gulstaff +Houdini +Jim +Kaschei +Khelben +Kreol +Lina +Merlin +Mogan +Mordenkainen +Morgan +Mystryl +Nihilus +Palando +Prospero +Radagast +Raistlin +Rasputin +Rincewind +Saruman +Tenser +Terefi +Tzeentch +Urza +Vaarsuvius +Vecna +Yoda +Zagyg Zul \ No newline at end of file diff --git a/strings/names/wizardsecond.txt b/strings/names/wizardsecond.txt index dd191cfa2b2a..6daff0f3e7f5 100644 --- a/strings/names/wizardsecond.txt +++ b/strings/names/wizardsecond.txt @@ -1,40 +1,40 @@ -Dark -Darkmagic -Darko -Gray -Honko -Inverse -le Fay -of Void -Shado -Smith -the All Knowing -the Amazing -the Bandit Killer -the Benevolent -the Blue -the Brown -the Conquerer -the Deathless -the Destroyer -the Dragon Spooker -the Emperor -the Gray -the Great -the Magician -the Powerful -the Raven -the Red -the Remorseful -the Seething -the Sorcelator -the Spiral King -the Unending -the Unstoppable -the Weeping -the White -the Wise -the Wizzard -Unseen -Weatherwax +Dark +Darkmagic +Darko +Gray +Honko +Inverse +le Fay +of Void +Shado +Smith +the All Knowing +the Amazing +the Bandit Killer +the Benevolent +the Blue +the Brown +the Conquerer +the Deathless +the Destroyer +the Dragon Spooker +the Emperor +the Gray +the Great +the Magician +the Powerful +the Raven +the Red +the Remorseful +the Seething +the Sorcelator +the Spiral King +the Unending +the Unstoppable +the Weeping +the White +the Wise +the Wizzard +Unseen +Weatherwax Yagg \ No newline at end of file diff --git a/tgui/LICENSE.md b/tgui/LICENSE.md index d269511a6362..0ddc16bf3972 100644 --- a/tgui/LICENSE.md +++ b/tgui/LICENSE.md @@ -1,20 +1,20 @@ -MIT license - -Copyright (c) 2016 Bjorn Neergaard (neersighted), tgui contributors - -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. +MIT license + +Copyright (c) 2016 Bjorn Neergaard (neersighted), tgui contributors + +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/tgui/README.md b/tgui/README.md index 109a1c45fc6d..e3ed2a20ee41 100644 --- a/tgui/README.md +++ b/tgui/README.md @@ -1,11 +1,11 @@ - - -- [tgui](#tgui) - - [Concepts](#concepts) - - [Using It](#using-it) - - [Copypasta](#copypasta) - - + + +- [tgui](#tgui) + - [Concepts](#concepts) + - [Using It](#using-it) + - [Copypasta](#copypasta) + + # tgui tgui is the user interface library of /tg/station. It is rendered clientside, based on JSON data sent from the server. Clicks are processed on the server, in a similar method to native BYOND `Topic()`. diff --git a/tools/CreditsTool/remappings.txt b/tools/CreditsTool/remappings.txt index ac57d4b8739e..18abb0a772c1 100644 --- a/tools/CreditsTool/remappings.txt +++ b/tools/CreditsTool/remappings.txt @@ -1,20 +1,20 @@ -# To make your name something other than your github name type it in the format " -# e.g. -# Cyberboss Jordan Brown -# To suppress your name from appearing in the credits do " __REMOVE__ -# e.g. -# Cyberboss __REMOVE__ - -tgstation-server Thanks for playing! - -optimumtact oranges -qustinnus Floyd / Qustinnus -catalystfd __REMOVE__ -TheVekter Vekter -ChangelingRain Joan -NewSta1 NewSta -theOperand Miauw -PraiseRatvar Frozenguy5 -FuryMcFlurry Fury McFlurry -vuonojenmustaturska Naksu -praisenarsie Frozenguy5 +# To make your name something other than your github name type it in the format " +# e.g. +# Cyberboss Jordan Brown +# To suppress your name from appearing in the credits do " __REMOVE__ +# e.g. +# Cyberboss __REMOVE__ + +tgstation-server Thanks for playing! + +optimumtact oranges +qustinnus Floyd / Qustinnus +catalystfd __REMOVE__ +TheVekter Vekter +ChangelingRain Joan +NewSta1 NewSta +theOperand Miauw +PraiseRatvar Frozenguy5 +FuryMcFlurry Fury McFlurry +vuonojenmustaturska Naksu +praisenarsie Frozenguy5 diff --git a/tools/Redirector/Configurations.dm b/tools/Redirector/Configurations.dm index 6b89ace90d58..bc0d56361c0c 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/Redirector.dm b/tools/Redirector/Redirector.dm index 920e56f2faf5..fb435594e27e 100644 --- a/tools/Redirector/Redirector.dm +++ b/tools/Redirector/Redirector.dm @@ -1,87 +1,87 @@ -/* - Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code. - (2012) - */ - -/* TODO: work on server selection for detected admins */ - - -#define ADMINS 1 -#define PLAYERS 0 - -var/player_weight = 1 // players are more likely to join a server with less players -var/admin_weight = 5 // admins are more likely to join a server with less admins - -var/player_substr = "players=" // search for this substring to locate # of players -var/admin_substr = "admins=" // search for this to locate # of admins - -/world - name = "TGstation Redirector" - -/world/New() - ..() - gen_configs() - -/datum/server - var/players = 0 - var/admins = 0 - var/weight = 0 // lower weight is good; highet weight is bad - - var/link = "" - -/mob/Login() - ..() - - var/list/weights = list() - var/list/servers = list() - for(var/x in global.servers) - - world << "[x] [servernames[ global.servers.Find(x) ]]" - - var/info = world.Export("[x]?status") - var/datum/server/S = new() - S.players = extract(info, PLAYERS) - S.admins = extract(info, ADMINS) - - S.weight += player_weight * S.players - S.link = x - - world << S.players - world << S.admins - - weights.Add(S.weight) - servers.Add(S) - - var/lowest = min(weights) - var/serverlink - for(var/datum/server/S in servers) - if(S.weight == lowest) - serverlink = S.link - - src << link(serverlink) - -/proc/extract(var/data, var/type = PLAYERS) - - var/nextpos = 0 - - if(type == PLAYERS) - - nextpos = findtextEx(data, player_substr) - nextpos += length(player_substr) - - else - - nextpos = findtextEx(data, admin_substr) - nextpos += length(admin_substr) - - var/returnval = "" - - for(var/i = 1, i <= 10, i++) - - var/interval = copytext(data, nextpos + (i-1), nextpos + i) - if(interval == "&") - break - else - returnval += interval - - return returnval +/* + Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code. + (2012) + */ + +/* TODO: work on server selection for detected admins */ + + +#define ADMINS 1 +#define PLAYERS 0 + +var/player_weight = 1 // players are more likely to join a server with less players +var/admin_weight = 5 // admins are more likely to join a server with less admins + +var/player_substr = "players=" // search for this substring to locate # of players +var/admin_substr = "admins=" // search for this to locate # of admins + +/world + name = "TGstation Redirector" + +/world/New() + ..() + gen_configs() + +/datum/server + var/players = 0 + var/admins = 0 + var/weight = 0 // lower weight is good; highet weight is bad + + var/link = "" + +/mob/Login() + ..() + + var/list/weights = list() + var/list/servers = list() + for(var/x in global.servers) + + world << "[x] [servernames[ global.servers.Find(x) ]]" + + var/info = world.Export("[x]?status") + var/datum/server/S = new() + S.players = extract(info, PLAYERS) + S.admins = extract(info, ADMINS) + + S.weight += player_weight * S.players + S.link = x + + world << S.players + world << S.admins + + weights.Add(S.weight) + servers.Add(S) + + var/lowest = min(weights) + var/serverlink + for(var/datum/server/S in servers) + if(S.weight == lowest) + serverlink = S.link + + src << link(serverlink) + +/proc/extract(var/data, var/type = PLAYERS) + + var/nextpos = 0 + + if(type == PLAYERS) + + nextpos = findtextEx(data, player_substr) + nextpos += length(player_substr) + + else + + nextpos = findtextEx(data, admin_substr) + nextpos += length(admin_substr) + + var/returnval = "" + + for(var/i = 1, i <= 10, i++) + + var/interval = copytext(data, nextpos + (i-1), nextpos + i) + if(interval == "&") + break + else + returnval += interval + + return returnval diff --git a/tools/Redirector/config.txt b/tools/Redirector/config.txt index 4b409f5036bc..4cf2d6bd79f5 100644 --- a/tools/Redirector/config.txt +++ b/tools/Redirector/config.txt @@ -1,12 +1,12 @@ -[SERVERS] -## Simply enter a list of servers to poll. Be sure to specify a server name in parentheses. - -(Sibyl #1) byond://game.nanotrasen.com:1337 - -(Sibyl #2) byond://game.nanotrasen.com:2337 - - -[ADMINS] -## Specify some standard Windows filepaths (you may use relative paths) for admin txt lists to poll. - -C:\SS13\config\admins.txt +[SERVERS] +## Simply enter a list of servers to poll. Be sure to specify a server name in parentheses. + +(Sibyl #1) byond://game.nanotrasen.com:1337 + +(Sibyl #2) byond://game.nanotrasen.com:2337 + + +[ADMINS] +## Specify some standard Windows filepaths (you may use relative paths) for admin txt lists to poll. + +C:\SS13\config\admins.txt diff --git a/tools/Redirector/textprocs.dm b/tools/Redirector/textprocs.dm index 1b9151133238..bd10fc3c1b7b 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 = lentext(prefix) + 1 - return findtext(text, prefix, start, end) - - dd_hasPrefix(text, prefix) - var/start = 1 - var/end = lentext(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 = lentext(text) - var/separatorlength = lentext(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 = lentext(text) - var/separatorlength = lentext(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 = lentext(prefix) + 1 + return findtext(text, prefix, start, end) + + dd_hasPrefix(text, prefix) + var/start = 1 + var/end = lentext(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 = lentext(text) + var/separatorlength = lentext(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 = lentext(text) + var/separatorlength = lentext(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 diff --git a/tools/Runtime Condenser/Input.txt b/tools/Runtime Condenser/Input.txt index 6206a4655aed..465a9355c374 100644 --- a/tools/Runtime Condenser/Input.txt +++ b/tools/Runtime Condenser/Input.txt @@ -1,4896 +1,4896 @@ -*** Begin Log: Fri Jul 13 17:04:27 2012 *** -Fri Jul 13 17:04:49 2012 -World opened on network port 2337. -Welcome BYOND! (4.0 Public Version 495.1136) -Running TG Revision Number: 4060. -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Puke Chunks (/mob/living/carbon/human) - src: Puke Chunks (/mob/living/carbon/human) - call stack: -Puke Chunks (/mob/living/carbon/human): db click("belt", 0) -the belt (/obj/screen): attack hand(Puke Chunks (/mob/living/carbon/human), 0) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=19;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1117 - usr: Lord Gwyn (/mob/living/carbon/human) - src: Lord Gwyn (/mob/living/carbon/human) - call stack: -Lord Gwyn (/mob/living/carbon/human): db click("ears", 0) -the ears (/obj/screen): attack hand(Lord Gwyn (/mob/living/carbon/human), 0) -the ears (/obj/screen): DblClick(null, null, null) -the ears (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=28;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1117 - usr: Lord Gwyn (/mob/living/carbon/human) - src: Lord Gwyn (/mob/living/carbon/human) - call stack: -Lord Gwyn (/mob/living/carbon/human): db click("ears", 0) -the ears (/obj/screen): attack hand(Lord Gwyn (/mob/living/carbon/human), 0) -the ears (/obj/screen): DblClick(null, null, null) -the ears (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1117 - usr: Lord Gwyn (/mob/living/carbon/human) - src: Lord Gwyn (/mob/living/carbon/human) - call stack: -Lord Gwyn (/mob/living/carbon/human): db click("ears", 0) -the ears (/obj/screen): attack hand(Lord Gwyn (/mob/living/carbon/human), 0) -the ears (/obj/screen): DblClick(null, null, null) -the ears (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Blake Sable (/mob/living/carbon/human) - src: Blake Sable (/mob/living/carbon/human) - call stack: -Blake Sable (/mob/living/carbon/human): db click("storage2", 0) -the storage2 (/obj/screen): attack hand(Blake Sable (/mob/living/carbon/human), 0) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=18;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Donald Mckendrick (/mob/living/carbon/human) - src: Donald Mckendrick (/mob/living/carbon/human) - call stack: -Donald Mckendrick (/mob/living/carbon/human): db click("storage2", 1) -the storage2 (/obj/screen): attack hand(Donald Mckendrick (/mob/living/carbon/human), 1) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Blake Sable (/mob/living/carbon/human) - src: Blake Sable (/mob/living/carbon/human) - call stack: -Blake Sable (/mob/living/carbon/human): db click("o_clothing", 0) -the o_clothing (/obj/screen): attack hand(Blake Sable (/mob/living/carbon/human), 0) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=15;left=1;scr...") -Fri Jul 13 18:18:09 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Jeanette DeLisle (/mob/living/carbon/human) - src: Jeanette DeLisle (/mob/living/carbon/human) - call stack: -Jeanette DeLisle (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Jeanette DeLisle (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=21;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=21;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=20;icon-y=15;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=20;icon-y=15;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=18;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=18;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=19;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=19;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Reese Marcotte (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=17;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=17;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 -Fri Jul 13 19:18:20 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 -Denied access to 'Rotcod' connecting from 138.210.6.170 -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Jack Kasshu (/mob/living/carbon/human) - src: Jack Kasshu (/mob/living/carbon/human) - call stack: -Jack Kasshu (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Jack Kasshu (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=10;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Slimeria (/mob/living/carbon/human) - src: Slimeria (/mob/living/carbon/human) - call stack: -Slimeria (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Slimeria (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Slimeria (/mob/living/carbon/human) - src: Slimeria (/mob/living/carbon/human) - call stack: -Slimeria (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Slimeria (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=20;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Alberto Ivanov (/mob/living/carbon/human) - src: Alberto Ivanov (/mob/living/carbon/human) - call stack: -Alberto Ivanov (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Alberto Ivanov (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=13;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Mr. English (/mob/living/carbon/human) - src: Mr. English (/mob/living/carbon/human) - call stack: -Mr. English (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Mr. English (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=20;left=1;scr...") -runtime error: undefined variable /client/var/loc -proc name: get turf (/proc/get_turf) - source file: helper_procs.dm,38 - usr: the ghost (/mob/dead/observer) - src: null - call stack: -get turf(Nodka (/client)) -the ghost (/mob/dead/observer): New(Nodka (/client), 0) -Nodka (/client): Ghost() -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Captain Fudge (/mob/living/carbon/human) - src: Captain Fudge (/mob/living/carbon/human) - call stack: -Captain Fudge (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Captain Fudge (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=19;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Ellen Quinn (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Ellen Quinn (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=21;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=21;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Ellen Quinn (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Ellen Quinn (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=14;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=14;left=1;scr...") -Fri Jul 13 20:36:29 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Dawson Gibbens (/mob/living/carbon/human) - src: Dawson Gibbens (/mob/living/carbon/human) - call stack: -Dawson Gibbens (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Dawson Gibbens (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Jack Kasshu (/mob/living/carbon/human) - src: Jack Kasshu (/mob/living/carbon/human) - call stack: -Jack Kasshu (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Jack Kasshu (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=20;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Josh Morris (/mob/living/carbon/human) - src: Josh Morris (/mob/living/carbon/human) - call stack: -Josh Morris (/mob/living/carbon/human): db click("storage2", 0) -the storage2 (/obj/screen): attack hand(Josh Morris (/mob/living/carbon/human), 0) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=14;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Josh Morris (/mob/living/carbon/human) - src: Josh Morris (/mob/living/carbon/human) - call stack: -Josh Morris (/mob/living/carbon/human): db click("storage2", 0) -the storage2 (/obj/screen): attack hand(Josh Morris (/mob/living/carbon/human), 0) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Simon Bonkers (/mob/living/carbon/human) - src: Simon Bonkers (/mob/living/carbon/human) - call stack: -Simon Bonkers (/mob/living/carbon/human): db click("o_clothing", 0) -the o_clothing (/obj/screen): attack hand(Simon Bonkers (/mob/living/carbon/human), 0) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=14;left=1;scr...") -Denied access to 'Deamon_Man16' connecting from 108.200.222.182 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1127 - usr: Dave Oneal (/mob/living/carbon/human) - src: Dave Oneal (/mob/living/carbon/human) - call stack: -Dave Oneal (/mob/living/carbon/human): db click("i_clothing", 1) -the i_clothing (/obj/screen): attack hand(Dave Oneal (/mob/living/carbon/human), 1) -the i_clothing (/obj/screen): DblClick(null, null, null) -the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=13;left=1;scr...") -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Space Retrovirus (/datum/disease/dnaspread) - call stack: -Space Retrovirus (/datum/disease/dnaspread): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Space Retrovirus (/datum/disease/dnaspread) - call stack: -Space Retrovirus (/datum/disease/dnaspread): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Space Retrovirus (/datum/disease/dnaspread) - call stack: -Space Retrovirus (/datum/disease/dnaspread): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Space Retrovirus (/datum/disease/dnaspread) - call stack: -Space Retrovirus (/datum/disease/dnaspread): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: undefined proc or verb /obj/machinery/space_heater/attack(). - -proc name: attackby (/mob/attackby) - source file: items.dm,334 -runtime error: undefined proc or verb /obj/machinery/space_heater/attack(). - -proc name: attackby (/mob/attackby) - source file: items.dm,334 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1071 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -Warning: further proc crash messages are being suppressed to prevent overload... -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Reign Boudash (/mob/living/carbon/human) - src: Reign Boudash (/mob/living/carbon/human) - call stack: -Reign Boudash (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Reign Boudash (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Reign Boudash (/mob/living/carbon/human) - src: Reign Boudash (/mob/living/carbon/human) - call stack: -Reign Boudash (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Reign Boudash (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") -Fri Jul 13 21:43:15 2012 -runtime error: unexpected stat -proc name: Stat (/mob/living/silicon/robot/Stat) - source file: robot.dm,227 - usr: Michigan Slim (/mob/living/carbon/human) - src: Engineering Cyborg -133 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -133 (/mob/living/silicon/robot): Stat() -Borg module reset board (/obj/item/borg/upgrade/reset): action(Engineering Cyborg -133 (/mob/living/silicon/robot)) -Borg module reset board (/obj/item/borg/upgrade/reset): action(Engineering Cyborg -133 (/mob/living/silicon/robot)) -Engineering Cyborg -133 (/mob/living/silicon/robot): attackby(Borg module reset board (/obj/item/borg/upgrade/reset), Michigan Slim (/mob/living/carbon/human)) -Engineering Cyborg -133 (/mob/living/silicon/robot): DblClick(the floor (95,85,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=14;icon-y=18;left=1;scr...") -Engineering Cyborg -133 (/mob/living/silicon/robot): Click(the floor (95,85,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=14;icon-y=18;left=1;scr...") -runtime error: undefined proc or verb /obj/machinery/portable_atmospherics/canister/toxins/attack(). - -proc name: attackby (/mob/attackby) - source file: items.dm,334 - usr: Watt Malker (/mob/living/carbon/human) - src: Black Dick Bishop (/mob/living/carbon/human) - call stack: -Black Dick Bishop (/mob/living/carbon/human): attackby(Canister \[Toxin (Bio)] (OPEN ... (/obj/machinery/portable_atmospherics/canister/toxins), Watt Malker (/mob/living/carbon/human), "chest") -runtime error: undefined variable /datum/preferences/var/fields -proc name: Topic (/obj/machinery/computer/cloning/Topic) - source file: cloning.dm,370 - usr: Logan Graves (/mob/living/carbon/human) - src: Cloning console (/obj/machinery/computer/cloning) - call stack: -Cloning console (/obj/machinery/computer/cloning): Topic("src=\[0x2005ab9];clone=\[0x210...", /list (/list)) -LordGeneralCastor (/client): Topic("src=\[0x2005ab9];clone=\[0x210...", /list (/list), Cloning console (/obj/machinery/computer/cloning)) -runtime error: unexpected stat -proc name: Stat (/mob/living/silicon/robot/Stat) - source file: robot.dm,214 - usr: Kingston Sommer (/mob/living/carbon/human) - src: Miner Cyborg 133 (/mob/living/silicon/robot) - call stack: -Miner Cyborg 133 (/mob/living/silicon/robot): Stat() -Borg module reset board (/obj/item/borg/upgrade/reset): action(Miner Cyborg 133 (/mob/living/silicon/robot)) -Borg module reset board (/obj/item/borg/upgrade/reset): action(Miner Cyborg 133 (/mob/living/silicon/robot)) -Miner Cyborg 133 (/mob/living/silicon/robot): attackby(Borg module reset board (/obj/item/borg/upgrade/reset), Kingston Sommer (/mob/living/carbon/human)) -Miner Cyborg 133 (/mob/living/silicon/robot): DblClick(the floor (97,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=14;icon-y=9;left=1;scre...") -Miner Cyborg 133 (/mob/living/silicon/robot): Click(the floor (97,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=14;icon-y=9;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Reign Boudash (/mob/living/carbon/human) - src: Reign Boudash (/mob/living/carbon/human) - call stack: -Reign Boudash (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Reign Boudash (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=17;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Zevin Thusar (/mob/living/carbon/human) - src: Zevin Thusar (/mob/living/carbon/human) - call stack: -Zevin Thusar (/mob/living/carbon/human): db click("storage2", 1) -the storage2 (/obj/screen): attack hand(Zevin Thusar (/mob/living/carbon/human), 1) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Nick Cave (/mob/living/carbon/human) - src: Nick Cave (/mob/living/carbon/human) - call stack: -Nick Cave (/mob/living/carbon/human): db click("belt", null) -the belt (/obj/screen): attack hand(Nick Cave (/mob/living/carbon/human), null) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=15;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Nick Cave (/mob/living/carbon/human) - src: Nick Cave (/mob/living/carbon/human) - call stack: -Nick Cave (/mob/living/carbon/human): db click("belt", null) -the belt (/obj/screen): attack hand(Nick Cave (/mob/living/carbon/human), null) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=23;icon-y=14;left=1;scr...") -runtime error: undefined variable /turf/simulated/floor/plating/var/mineral -proc name: attackby (/turf/simulated/wall/attackby) - source file: turf.dm,600 - usr: Monte Smail (/mob/living/carbon/human) - src: the plating (176,172,5) (/turf/simulated/floor/plating) - call stack: -the plating (176,172,5) (/turf/simulated/floor/plating): attackby(Diamond Mining Drill (/obj/item/weapon/pickaxe/diamonddrill), Monte Smail (/mob/living/carbon/human)) -the plating (176,172,5) (/turf/simulated/floor/plating): DblClick(the plating (176,172,5) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=15;icon-y=6;left=1;scre...") -the plating (176,172,5) (/turf/simulated/floor/plating): DblClick(the plating (176,172,5) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=15;icon-y=6;left=1;scre...") -the plating (176,172,5) (/turf/simulated/floor/plating): Click(the plating (176,172,5) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=15;icon-y=6;left=1;scre...") -Rebooted server. -Running TG Revision Number: 4060. -Fri Jul 13 22:48:12 2012 -runtime error: Cannot create objects of type null. -proc name: Topic (/obj/machinery/computer/rdconsole/Topic) - source file: rdconsole.dm,381 - usr: Matthew Hoff (/mob/living/carbon/human) - src: Core R&D Console (/obj/machinery/computer/rdconsole/core) - call stack: -Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x20065fa];build=large_G...", /list (/list)) -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Chip Harshman (/mob/living/carbon/human) - src: Chip Harshman (/mob/living/carbon/human) - call stack: -Chip Harshman (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Chip Harshman (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=20;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Chip Harshman (/mob/living/carbon/human) - src: Chip Harshman (/mob/living/carbon/human) - call stack: -Chip Harshman (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Chip Harshman (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=20;left=1;scr...") -runtime error: Cannot read null.blood_DNA -proc name: update inv w uniform (/mob/living/carbon/human/update_inv_w_uniform) - source file: update_icons.dm,372 - usr: null - src: Chip Harshman (/mob/living/carbon/human) - call stack: -Chip Harshman (/mob/living/carbon/human): update inv w uniform(0) -Chip Harshman (/mob/living/carbon/human): handle chemicals in body() -Chip Harshman (/mob/living/carbon/human): Life() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot create objects of type null. -proc name: Topic (/obj/machinery/computer/rdconsole/Topic) - source file: rdconsole.dm,381 - usr: Sprigg Spooly (/mob/living/carbon/human) - src: Core R&D Console (/obj/machinery/computer/rdconsole/core) - call stack: -Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x200ca33];build=large_G...", /list (/list)) -runtime error: Cannot create objects of type null. -proc name: Topic (/obj/machinery/computer/rdconsole/Topic) - source file: rdconsole.dm,381 - usr: Sprigg Spooly (/mob/living/carbon/human) - src: Core R&D Console (/obj/machinery/computer/rdconsole/core) - call stack: -Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x200ca94];build=large_G...", /list (/list)) -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1093 - usr: Jack Kasshu (/mob/living/carbon/human) - src: Jack Kasshu (/mob/living/carbon/human) - call stack: -Jack Kasshu (/mob/living/carbon/human): db click("eyes", 0) -the eyes (/obj/screen): attack hand(Jack Kasshu (/mob/living/carbon/human), 0) -the eyes (/obj/screen): DblClick(null, null, null) -the eyes (/obj/screen): Click(null, "mapwindow.map", "icon-x=29;icon-y=1;left=1;scre...") -Fri Jul 13 23:52:26 2012 -### VarEdit by Yinadele: /datum/reagents maximum_volume=20 -### VarEdit by Yinadele: /datum/reagents total_volume=20 -### VarEdit by Yinadele: /obj/effect/decal name=chemicals (Haunter) -### VarEdit by Yinadele: /obj/effect/decal throwforce=20 -### VarEdit by Yinadele: /obj/effect/decal infra_luminosity=1 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Sergei Vladislanikov (/mob/living/carbon/human) - src: Sergei Vladislanikov (/mob/living/carbon/human) - call stack: -Sergei Vladislanikov (/mob/living/carbon/human): db click("storage2", 1) -the storage2 (/obj/screen): attack hand(Sergei Vladislanikov (/mob/living/carbon/human), 1) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Sprigg Spooly (/mob/living/carbon/human) - src: Sprigg Spooly (/mob/living/carbon/human) - call stack: -Sprigg Spooly (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Sprigg Spooly (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=14;left=1;scr...") -runtime error: unexpected stat -proc name: Stat (/mob/living/silicon/robot/Stat) - source file: robot.dm,227 - usr: Sprigg Spooly as (Matthew Hoff... (/mob/living/carbon/human) - src: Engineering Cyborg -952 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -952 (/mob/living/silicon/robot): Stat() -Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Engineering Cyborg -952 (/mob/living/silicon/robot)) -Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Engineering Cyborg -952 (/mob/living/silicon/robot)) -Engineering Cyborg -952 (/mob/living/silicon/robot): attackby(Borg VTEC Module (/obj/item/borg/upgrade/vtec), Sprigg Spooly as (Matthew Hoff... (/mob/living/carbon/human)) -Engineering Cyborg -952 (/mob/living/silicon/robot): DblClick(the floor (101,86,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=17;left=1;scr...") -Engineering Cyborg -952 (/mob/living/silicon/robot): Click(the floor (101,86,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=17;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Miku Frost (/mob/living/carbon/human) - src: Miku Frost (/mob/living/carbon/human) - call stack: -Miku Frost (/mob/living/carbon/human): db click("belt", 0) -the belt (/obj/screen): attack hand(Miku Frost (/mob/living/carbon/human), 0) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=20;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Miku Frost (/mob/living/carbon/human) - src: Miku Frost (/mob/living/carbon/human) - call stack: -Miku Frost (/mob/living/carbon/human): db click("belt", 0) -the belt (/obj/screen): attack hand(Miku Frost (/mob/living/carbon/human), 0) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Tood Hoawd (/mob/living/carbon/human) - src: Tood Hoawd (/mob/living/carbon/human) - call stack: -Tood Hoawd (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Tood Hoawd (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=17;left=1;scr...") -Sat Jul 14 00:57:29 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: Ellen Quinn (/mob/living/carbon/human) - src: Ellen Quinn (/mob/living/carbon/human) - call stack: -Ellen Quinn (/mob/living/carbon/human): db click("back", 0) -the back (/obj/screen): attack hand(Ellen Quinn (/mob/living/carbon/human), 0) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=25;icon-y=22;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("back", null) -the back (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), null) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=14;left=1;scr...") -### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=commissarzoey -### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=nodrak -### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=dakorok -### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=nickknack -### VarEdit by Yinadele: /mob/living/carbon/metroid/adult ckey=yinadele -### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=emperorofcatkind -runtime error: Cannot read null.current -proc name: check completion (/datum/objective/download/check_completion) - source file: objective.dm,411 - usr: null - src: /datum/objective/download (/datum/objective/download) - call stack: -/datum/objective/download (/datum/objective/download): check completion() -extended (/datum/game_mode/extended): auto declare completion traitor() -/datum/controller/gameticker (/datum/controller/gameticker): declare completion() -/datum/controller/gameticker (/datum/controller/gameticker): process() -runtime error: Cannot read null.current -proc name: check completion (/datum/objective/steal/check_completion) - source file: objective.dm,352 - usr: null - src: /datum/objective/steal (/datum/objective/steal) - call stack: -/datum/objective/steal (/datum/objective/steal): check completion() -extended (/datum/game_mode/extended): auto declare completion traitor() -/datum/controller/gameticker (/datum/controller/gameticker): declare completion() -/datum/controller/gameticker (/datum/controller/gameticker): process() -runtime error: Cannot read null.current -proc name: check completion (/datum/objective/download/check_completion) - source file: objective.dm,411 - usr: null - src: /datum/objective/download (/datum/objective/download) - call stack: -/datum/objective/download (/datum/objective/download): check completion() -extended (/datum/game_mode/extended): auto declare completion traitor() -/datum/controller/gameticker (/datum/controller/gameticker): declare completion() -/datum/controller/gameticker (/datum/controller/gameticker): process() -runtime error: Cannot read null.current -proc name: check completion (/datum/objective/steal/check_completion) - source file: objective.dm,352 - usr: null - src: /datum/objective/steal (/datum/objective/steal) - call stack: -/datum/objective/steal (/datum/objective/steal): check completion() -extended (/datum/game_mode/extended): auto declare completion traitor() -/datum/controller/gameticker (/datum/controller/gameticker): declare completion() -/datum/controller/gameticker (/datum/controller/gameticker): process() -runtime error: Cannot read null.current -proc name: check completion (/datum/objective/download/check_completion) - source file: objective.dm,411 -runtime error: Cannot read null.current -proc name: check completion (/datum/objective/download/check_completion) - source file: objective.dm,411 -runtime error: Cannot read null.current -proc name: check completion (/datum/objective/steal/check_completion) - source file: objective.dm,352 -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Ophelia Braun (/mob/living/carbon/human) - src: Ophelia Braun (/mob/living/carbon/human) - call stack: -Ophelia Braun (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Ophelia Braun (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Helen Lester (/mob/living/carbon/human) - src: Helen Lester (/mob/living/carbon/human) - call stack: -Helen Lester (/mob/living/carbon/human): db click("belt", null) -the belt (/obj/screen): attack hand(Helen Lester (/mob/living/carbon/human), null) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=11;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Dave Oneal (/mob/living/carbon/human) - src: Dave Oneal (/mob/living/carbon/human) - call stack: -Dave Oneal (/mob/living/carbon/human): db click("id", null) -the id (/obj/screen): attack hand(Dave Oneal (/mob/living/carbon/human), null) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") -Sat Jul 14 01:57:46 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1117 - usr: Dat Bass (/mob/living/carbon/human) - src: Dat Bass (/mob/living/carbon/human) - call stack: -Dat Bass (/mob/living/carbon/human): db click("ears", null) -the ears (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), null) -the ears (/obj/screen): DblClick(null, null, null) -the ears (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=15;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Helen Lester (/mob/living/carbon/human) - src: Helen Lester (/mob/living/carbon/human) - call stack: -Helen Lester (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Helen Lester (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=12;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Lila Desminto (/mob/living/carbon/human) - src: Lila Desminto (/mob/living/carbon/human) - call stack: -Lila Desminto (/mob/living/carbon/human): db click("belt", 0) -the belt (/obj/screen): attack hand(Lila Desminto (/mob/living/carbon/human), 0) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=5;left=1;scre...") -### VarEdit by Doohl: /obj/effect/new_year_tree name=BODA TREE -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Penny Merriweather (/mob/living/carbon/human) - src: Penny Merriweather (/mob/living/carbon/human) - call stack: -Penny Merriweather (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Penny Merriweather (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Penny Merriweather (/mob/living/carbon/human) - src: Penny Merriweather (/mob/living/carbon/human) - call stack: -Penny Merriweather (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Penny Merriweather (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Penny Merriweather (/mob/living/carbon/human) - src: Penny Merriweather (/mob/living/carbon/human) - call stack: -Penny Merriweather (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Penny Merriweather (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Magnitis (/datum/disease/magnitis) - call stack: -Magnitis (/datum/disease/magnitis): cure(0) -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -Warning: further proc crash messages are being suppressed to prevent overload... -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Ashley Taylor (/mob/living/carbon/human) - src: Ashley Taylor (/mob/living/carbon/human) - call stack: -Ashley Taylor (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Ashley Taylor (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=15;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -Sat Jul 14 03:10:48 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1103 - usr: Trisha Galade (/mob/living/carbon/human) - src: Trisha Galade (/mob/living/carbon/human) - call stack: -Trisha Galade (/mob/living/carbon/human): db click("head", 0) -the head (/obj/screen): attack hand(Trisha Galade (/mob/living/carbon/human), 0) -the head (/obj/screen): DblClick(null, null, null) -the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=26;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Trisha Galade (/mob/living/carbon/human) - src: Trisha Galade (/mob/living/carbon/human) - call stack: -Trisha Galade (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Trisha Galade (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=9;icon-y=14;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Jack Napier (/mob/living/carbon/human) - src: Jack Napier (/mob/living/carbon/human) - call stack: -Jack Napier (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Jack Napier (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=9;left=1;scre...") -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: Reese Marcotte (/mob/living/carbon/human) - src: Reese Marcotte (/mob/living/carbon/human) - call stack: -Reese Marcotte (/mob/living/carbon/human): throw at(Asteroid (174,85,5) (/turf/simulated/floor/plating/airless/asteroid), 5, 1) -Delivery chute (/obj/machinery/disposal/deliveryChute): expel(null) -Rebooted server. -Running TG Revision Number: 4060. -Sat Jul 14 04:15:01 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1071 - usr: Flint Steel (/mob/living/carbon/human) - src: Flint Steel (/mob/living/carbon/human) - call stack: -Flint Steel (/mob/living/carbon/human): db click("shoes", 1) -the shoes (/obj/screen): attack hand(Flint Steel (/mob/living/carbon/human), 1) -the shoes (/obj/screen): DblClick(null, null, null) -the shoes (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=18;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Flint Steel (/mob/living/carbon/human) - src: Flint Steel (/mob/living/carbon/human) - call stack: -Flint Steel (/mob/living/carbon/human): db click("belt", 1) -the belt (/obj/screen): attack hand(Flint Steel (/mob/living/carbon/human), 1) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=9;icon-y=12;left=1;scre...") -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: The Flu (/datum/disease/flu) - call stack: -The Flu (/datum/disease/flu): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 -Warning: further proc crash messages are being suppressed to prevent overload... -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 - usr: Trisha Galade (/mob/living/carbon/human) - src: Trisha Galade (/mob/living/carbon/human) - call stack: -Trisha Galade (/mob/living/carbon/human): db click("mask", 0) -the mask (/obj/screen): attack hand(Trisha Galade (/mob/living/carbon/human), 0) -the mask (/obj/screen): DblClick(null, null, null) -the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=16;left=1;scr...") -Rebooted server. -Sat Jul 14 05:27:37 2012 -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Ladybug Honks (/mob/living/carbon/human) - src: Ladybug Honks (/mob/living/carbon/human) - call stack: -Ladybug Honks (/mob/living/carbon/human): db click("storage2", 1) -the storage2 (/obj/screen): attack hand(Ladybug Honks (/mob/living/carbon/human), 1) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Ladybug Honks (/mob/living/carbon/human) - src: Ladybug Honks (/mob/living/carbon/human) - call stack: -Ladybug Honks (/mob/living/carbon/human): db click("storage2", 1) -the storage2 (/obj/screen): attack hand(Ladybug Honks (/mob/living/carbon/human), 1) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=12;left=1;scr...") -Sat Jul 14 06:59:16 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=12;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Malik Graham (/mob/living/carbon/human) - src: Malik Graham (/mob/living/carbon/human) - call stack: -Malik Graham (/mob/living/carbon/human): db click("belt", 1) -the belt (/obj/screen): attack hand(Malik Graham (/mob/living/carbon/human), 1) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=19;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Rocky McDoughboy XIV (/mob/living/carbon/human) - src: Rocky McDoughboy XIV (/mob/living/carbon/human) - call stack: -Rocky McDoughboy XIV (/mob/living/carbon/human): db click("o_clothing", 1) -the o_clothing (/obj/screen): attack hand(Rocky McDoughboy XIV (/mob/living/carbon/human), 1) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=11;left=1;scr...") -Sat Jul 14 08:11:16 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Sly (/mob/living/carbon/human) - src: Sly (/mob/living/carbon/human) - call stack: -Sly (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Sly (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=5;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Sly (/mob/living/carbon/human) - src: Sly (/mob/living/carbon/human) - call stack: -Sly (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Sly (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=11;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Sly (/mob/living/carbon/human) - src: Sly (/mob/living/carbon/human) - call stack: -Sly (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Sly (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=14;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Sher Thorun (/mob/living/carbon/human) - src: Sher Thorun (/mob/living/carbon/human) - call stack: -Sher Thorun (/mob/living/carbon/human): db click("storage1", null) -the storage1 (/obj/screen): attack hand(Sher Thorun (/mob/living/carbon/human), null) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=11;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1127 - usr: Jono Lionhurt (/mob/living/carbon/human) - src: Jono Lionhurt (/mob/living/carbon/human) - call stack: -Jono Lionhurt (/mob/living/carbon/human): db click("i_clothing", null) -the i_clothing (/obj/screen): attack hand(Jono Lionhurt (/mob/living/carbon/human), null) -the i_clothing (/obj/screen): DblClick(null, null, null) -the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=12;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): db click("storage2", 0) -the storage2 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 0) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=14;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): db click("storage2", 0) -the storage2 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 0) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=14;left=1;scr...") -Sat Jul 14 09:17:07 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): db click("storage2", 1) -the storage2 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 1) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): db click("storage2", 1) -the storage2 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 1) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1127 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("i_clothing", 1) -the i_clothing (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 1) -the i_clothing (/obj/screen): DblClick(null, null, null) -the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=31;icon-y=21;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1127 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("i_clothing", 1) -the i_clothing (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 1) -the i_clothing (/obj/screen): DblClick(null, null, null) -the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=23;left=1;scr...") -Sat Jul 14 10:37:02 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1103 - usr: Lord Jamlamin (/mob/living/carbon/human) - src: Lord Jamlamin (/mob/living/carbon/human) - call stack: -Lord Jamlamin (/mob/living/carbon/human): db click("head", 0) -the head (/obj/screen): attack hand(Lord Jamlamin (/mob/living/carbon/human), 0) -the head (/obj/screen): DblClick(null, null, null) -the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=18;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 - usr: Jennifer Ketavim (/mob/living/carbon/human) - src: Jennifer Ketavim (/mob/living/carbon/human) - call stack: -Jennifer Ketavim (/mob/living/carbon/human): db click("mask", 1) -the mask (/obj/screen): attack hand(Jennifer Ketavim (/mob/living/carbon/human), 1) -the mask (/obj/screen): DblClick(null, null, null) -the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=10;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("id", null) -the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), null) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=18;left=1;scr...") -runtime error: Cannot execute null.dropped(). -proc name: drop l hand (/mob/proc/drop_l_hand) - source file: inventory.dm,104 - usr: Jeb Stone (/mob/living/carbon/human) - src: Jeb Stone (/mob/living/carbon/human) - call stack: -Jeb Stone (/mob/living/carbon/human): drop l hand(null) -Jeb Stone (/mob/living/carbon/human): drop item(null) -Jeb Stone (/mob/living/carbon/human): drop item v() -the drop (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=16;left=1;scre...") -Sat Jul 14 12:01:13 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1093 - usr: Thomas Hereford (/mob/living/carbon/human) - src: Thomas Hereford (/mob/living/carbon/human) - call stack: -Thomas Hereford (/mob/living/carbon/human): db click("eyes", 1) -the eyes (/obj/screen): attack hand(Thomas Hereford (/mob/living/carbon/human), 1) -the eyes (/obj/screen): DblClick(null, null, null) -the eyes (/obj/screen): Click(null, "mapwindow.map", "icon-x=25;icon-y=27;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: Hugh Jazz (/mob/living/carbon/human) - src: Hugh Jazz (/mob/living/carbon/human) - call stack: -Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: Paul Wolfe (/mob/living/carbon/human) - src: Paul Wolfe (/mob/living/carbon/human) - call stack: -Paul Wolfe (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen) - call stack: -Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen) - call stack: -Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen) - call stack: -Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) - call stack: -Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -Warning: further proc crash messages are being suppressed to prevent overload... -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1071 - usr: Dat Bass (/mob/living/carbon/human) - src: Dat Bass (/mob/living/carbon/human) - call stack: -Dat Bass (/mob/living/carbon/human): db click("shoes", 1) -the shoes (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), 1) -the shoes (/obj/screen): DblClick(null, null, null) -the shoes (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=17;left=1;scr...") -Sat Jul 14 13:19:34 2012 -runtime error: list index out of bounds -proc name: mergeConnectedNetworksOnTurf (/obj/structure/cable/proc/mergeConnectedNetworksOnTurf) - source file: cable.dm,520 - usr: Josue Sandford (/mob/living/carbon/human) - src: the power cable (/obj/structure/cable) - call stack: -the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() -the cable coil (/obj/item/weapon/cable_coil/orange): turf place(the plating (87,80,1) (/turf/simulated/floor/plating), Josue Sandford (/mob/living/carbon/human)) -the plating (87,80,1) (/turf/simulated/floor/plating): attackby(the cable coil (/obj/item/weapon/cable_coil/orange), Josue Sandford (/mob/living/carbon/human)) -the plating (87,80,1) (/turf/simulated/floor/plating): DblClick(the plating (87,80,1) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=17;icon-y=28;left=1;scr...") -the plating (87,80,1) (/turf/simulated/floor/plating): DblClick(the plating (87,80,1) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=17;icon-y=28;left=1;scr...") -the plating (87,80,1) (/turf/simulated/floor/plating): Click(the plating (87,80,1) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=17;icon-y=28;left=1;scr...") -runtime error: Cannot read null.nodes -proc name: merge powernets (/datum/powernet/proc/merge_powernets) - source file: power.dm,401 - usr: Darin Keppel (/mob/living/carbon/human) - src: /datum/powernet (/datum/powernet) - call stack: -/datum/powernet (/datum/powernet): merge powernets(null) -the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() -the cable piece (/obj/item/weapon/cable_coil): cable join(the power cable (/obj/structure/cable), Darin Keppel (/mob/living/carbon/human)) -the power cable (/obj/structure/cable): attackby(the cable piece (/obj/item/weapon/cable_coil), Darin Keppel (/mob/living/carbon/human)) -the power cable (/obj/structure/cable): DblClick(the floor (105,80,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=29;icon-y=17;left=1;scr...") -the power cable (/obj/structure/cable): Click(the floor (105,80,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=29;icon-y=17;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Jace Johnson (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=19;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=19;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Jace Johnson (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=19;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=19;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Jace Johnson (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Travis Robinson (/mob/living/carbon/human) - src: Travis Robinson (/mob/living/carbon/human) - call stack: -Travis Robinson (/mob/living/carbon/human): db click("belt", 0) -the belt (/obj/screen): attack hand(Travis Robinson (/mob/living/carbon/human), 0) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=12;left=1;scre...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Jace Johnson (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=16;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=16;left=1;scr...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Jace Johnson (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=18;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=18;left=1;scr...") -Sat Jul 14 14:26:55 2012 -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Appendicitis (/datum/disease/appendicitis) - call stack: -Appendicitis (/datum/disease/appendicitis): cure(0) -runtime error: Cannot read null.viruses -proc name: cure (/datum/disease/proc/cure) - source file: disease.dm,156 - usr: null - src: Appendicitis (/datum/disease/appendicitis) - call stack: -Appendicitis (/datum/disease/appendicitis): cure(0) -Rebooted server. -Running TG Revision Number: 4060. -Denied access to 'Intigracy' connecting from 76.246.55.198 -Sat Jul 14 15:41:50 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Derrick Cobb (/mob/living/carbon/human) - src: Derrick Cobb (/mob/living/carbon/human) - call stack: -Derrick Cobb (/mob/living/carbon/human): db click("id", null) -the id (/obj/screen): attack hand(Derrick Cobb (/mob/living/carbon/human), null) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=18;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1103 - usr: Derrick Cobb (/mob/living/carbon/human) - src: Derrick Cobb (/mob/living/carbon/human) - call stack: -Derrick Cobb (/mob/living/carbon/human): db click("head", 1) -the head (/obj/screen): attack hand(Derrick Cobb (/mob/living/carbon/human), 1) -the head (/obj/screen): DblClick(null, null, null) -the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=27;icon-y=1;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Aaden Mitchell (/mob/living/carbon/human) - src: Aaden Mitchell (/mob/living/carbon/human) - call stack: -Aaden Mitchell (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Aaden Mitchell (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=17;left=1;scr...") -runtime error: Cannot create objects of type null. -proc name: Topic (/obj/machinery/computer/rdconsole/Topic) - source file: rdconsole.dm,381 - usr: Cletus Powers (/mob/living/carbon/human) - src: Core R&D Console (/obj/machinery/computer/rdconsole/core) - call stack: -Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x2006613];build=large_G...", /list (/list)) -runtime error: Cannot create objects of type null. -proc name: Topic (/obj/machinery/computer/rdconsole/Topic) - source file: rdconsole.dm,381 - usr: Cletus Powers (/mob/living/carbon/human) - src: Core R&D Console (/obj/machinery/computer/rdconsole/core) - call stack: -Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x2003615];build=large_G...", /list (/list)) -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Simon Bonkers (/mob/living/carbon/human) - src: Simon Bonkers (/mob/living/carbon/human) - call stack: -Simon Bonkers (/mob/living/carbon/human): db click("o_clothing", 0) -the o_clothing (/obj/screen): attack hand(Simon Bonkers (/mob/living/carbon/human), 0) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=23;icon-y=24;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 - usr: Hreri Bright-Eyed (/mob/living/carbon/human) - src: Hreri Bright-Eyed (/mob/living/carbon/human) - call stack: -Hreri Bright-Eyed (/mob/living/carbon/human): db click("mask", 1) -the mask (/obj/screen): attack hand(Hreri Bright-Eyed (/mob/living/carbon/human), 1) -the mask (/obj/screen): DblClick(null, null, null) -the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=20;left=1;scr...") -Rebooted server. -Sat Jul 14 16:52:03 2012 -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Micheal Simpson (/mob/living/carbon/human) - src: Micheal Simpson (/mob/living/carbon/human) - call stack: -Micheal Simpson (/mob/living/carbon/human): db click("o_clothing", null) -the o_clothing (/obj/screen): attack hand(Micheal Simpson (/mob/living/carbon/human), null) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=12;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Micheal Simpson (/mob/living/carbon/human) - src: Micheal Simpson (/mob/living/carbon/human) - call stack: -Micheal Simpson (/mob/living/carbon/human): db click("o_clothing", null) -the o_clothing (/obj/screen): attack hand(Micheal Simpson (/mob/living/carbon/human), null) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=20;left=1;scr...") -runtime error: undefined variable /client/var/loc -proc name: get turf (/proc/get_turf) - source file: helper_procs.dm,38 - usr: the ghost (/mob/dead/observer) - src: null - call stack: -get turf(Akett (/client)) -the ghost (/mob/dead/observer): New(Akett (/client), 0) -Akett (/client): Ghost() -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Jennifer Ketavim (/mob/living/carbon/human) - src: Jennifer Ketavim (/mob/living/carbon/human) - call stack: -Jennifer Ketavim (/mob/living/carbon/human): db click("belt", 1) -the belt (/obj/screen): attack hand(Jennifer Ketavim (/mob/living/carbon/human), 1) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=15;left=1;scr...") -Rebooted server. -Sat Jul 14 17:57:29 2012 -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Bubba Bellton (/mob/living/carbon/human) - src: Bubba Bellton (/mob/living/carbon/human) - call stack: -Bubba Bellton (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Bubba Bellton (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=23;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Bubble (/mob/living/carbon/human) - src: Bubble (/mob/living/carbon/human) - call stack: -Bubble (/mob/living/carbon/human): db click("storage1", null) -the storage1 (/obj/screen): attack hand(Bubble (/mob/living/carbon/human), null) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Bubble (/mob/living/carbon/human) - src: Bubble (/mob/living/carbon/human) - call stack: -Bubble (/mob/living/carbon/human): db click("storage1", null) -the storage1 (/obj/screen): attack hand(Bubble (/mob/living/carbon/human), null) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 - usr: Bartholomew Williams (/mob/living/carbon/human) - src: Bartholomew Williams (/mob/living/carbon/human) - call stack: -Bartholomew Williams (/mob/living/carbon/human): db click("mask", 0) -the mask (/obj/screen): attack hand(Bartholomew Williams (/mob/living/carbon/human), 0) -the mask (/obj/screen): DblClick(null, null, null) -the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=15;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: Rusty Fraser (/mob/living/carbon/human) - src: Rusty Fraser (/mob/living/carbon/human) - call stack: -Rusty Fraser (/mob/living/carbon/human): db click("back", null) -the back (/obj/screen): attack hand(Rusty Fraser (/mob/living/carbon/human), null) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=5;left=1;scre...") -Rebooted server. -Running TG Revision Number: 4060. -Sat Jul 14 19:13:30 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 - usr: Derrick Cobb (/mob/living/carbon/human) - src: Derrick Cobb (/mob/living/carbon/human) - call stack: -Derrick Cobb (/mob/living/carbon/human): db click("mask", 1) -the mask (/obj/screen): attack hand(Derrick Cobb (/mob/living/carbon/human), 1) -the mask (/obj/screen): DblClick(null, null, null) -the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=29;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: HoP Man (/mob/living/carbon/human) - src: HoP Man (/mob/living/carbon/human) - call stack: -HoP Man (/mob/living/carbon/human): db click("back", 0) -the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=4;icon-y=26;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: HoP Man (/mob/living/carbon/human) - src: HoP Man (/mob/living/carbon/human) - call stack: -HoP Man (/mob/living/carbon/human): db click("back", 0) -the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=26;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: HoP Man (/mob/living/carbon/human) - src: HoP Man (/mob/living/carbon/human) - call stack: -HoP Man (/mob/living/carbon/human): db click("back", 0) -the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) -the back (/obj/screen): DblClick(null, "mapwindow.map", "icon-x=8;icon-y=26;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: HoP Man (/mob/living/carbon/human) - src: HoP Man (/mob/living/carbon/human) - call stack: -HoP Man (/mob/living/carbon/human): db click("back", 0) -the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=26;left=1;shif...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: HoP Man (/mob/living/carbon/human) - src: HoP Man (/mob/living/carbon/human) - call stack: -HoP Man (/mob/living/carbon/human): db click("back", 0) -the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=26;left=1;ctrl...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: HoP Man (/mob/living/carbon/human) - src: HoP Man (/mob/living/carbon/human) - call stack: -HoP Man (/mob/living/carbon/human): db click("back", 0) -the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=3;icon-y=17;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Earl Swabby (/mob/living/carbon/human) - src: Earl Swabby (/mob/living/carbon/human) - call stack: -Earl Swabby (/mob/living/carbon/human): db click("belt", 1) -the belt (/obj/screen): attack hand(Earl Swabby (/mob/living/carbon/human), 1) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=12;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Slimeria (/mob/living/carbon/human) - src: Slimeria (/mob/living/carbon/human) - call stack: -Slimeria (/mob/living/carbon/human): db click("o_clothing", 1) -the o_clothing (/obj/screen): attack hand(Slimeria (/mob/living/carbon/human), 1) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=28;icon-y=9;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: River Pavlov (/mob/living/carbon/human) - src: River Pavlov (/mob/living/carbon/human) - call stack: -River Pavlov (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(River Pavlov (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=15;left=1;scr...") -runtime error: list index out of bounds -proc name: mergeConnectedNetworksOnTurf (/obj/structure/cable/proc/mergeConnectedNetworksOnTurf) - source file: cable.dm,501 - usr: Tyrion Lannister (/mob/living/carbon/human) - src: the power cable (/obj/structure/cable) - call stack: -the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() -the cable piece (/obj/item/weapon/cable_coil): cable join(the power cable (/obj/structure/cable), Tyrion Lannister (/mob/living/carbon/human)) -the power cable (/obj/structure/cable): attackby(the cable piece (/obj/item/weapon/cable_coil), Tyrion Lannister (/mob/living/carbon/human)) -the power cable (/obj/structure/cable): DblClick(the floor (174,138,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=17;left=1;scr...") -the power cable (/obj/structure/cable): Click(the floor (174,138,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=17;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Slimeria (/mob/living/carbon/human) - src: Slimeria (/mob/living/carbon/human) - call stack: -Slimeria (/mob/living/carbon/human): db click("belt", 0) -the belt (/obj/screen): attack hand(Slimeria (/mob/living/carbon/human), 0) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=24;left=1;scr...") -Sat Jul 14 20:29:42 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Urist McAssistant (/mob/living/carbon/human) - src: Urist McAssistant (/mob/living/carbon/human) - call stack: -Urist McAssistant (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Urist McAssistant (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=17;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Jennifer Ketavim (/mob/living/carbon/human) - src: Jennifer Ketavim (/mob/living/carbon/human) - call stack: -Jennifer Ketavim (/mob/living/carbon/human): db click("o_clothing", 0) -the o_clothing (/obj/screen): attack hand(Jennifer Ketavim (/mob/living/carbon/human), 0) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=5;left=1;scre...") -Sat Jul 14 21:49:49 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Jennifer Ketavim (/mob/living/carbon/human) - src: Jennifer Ketavim (/mob/living/carbon/human) - call stack: -Jennifer Ketavim (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Jennifer Ketavim (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=30;icon-y=20;left=1;scr...") -runtime error: Cannot read null.fields -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,263 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(/list (/list), /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.fields -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,263 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(/list (/list), /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.fields -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,263 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(/list (/list), /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.fields -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,263 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(/list (/list), /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=New%20...", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=New%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 - usr: Wintermute (/mob/living/silicon/ai) - src: null - call stack: -mergeRecordLists(null, /list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -sortRecord(/list (/list), "name", 1) -Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) -Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() -Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=New%20...", /list (/list)) -Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=New%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: Cannot read null.flags -proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) - source file: ethereal_jaunt.dm,81 -runtime error: undefined variable /client/var/loc -proc name: get turf (/proc/get_turf) - source file: helper_procs.dm,38 -runtime error: Cannot read null.fields -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,263 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.fields -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,263 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.len -proc name: mergeRecordLists (/proc/mergeRecordLists) - source file: helpers.dm,260 -runtime error: Cannot read null.reagents -proc name: fire syringe (/obj/item/weapon/gun/syringe/proc/fire_syringe) - source file: Chemistry-Tools.dm,462 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Reuben Garratt (/mob/living/carbon/human) - src: Reuben Garratt (/mob/living/carbon/human) - call stack: -Reuben Garratt (/mob/living/carbon/human): db click("id", null) -the id (/obj/screen): attack hand(Reuben Garratt (/mob/living/carbon/human), null) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=15;left=1;scr...") -Sat Jul 14 23:05:19 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Alice Ravensdale (/mob/living/carbon/human) - src: Alice Ravensdale (/mob/living/carbon/human) - call stack: -Alice Ravensdale (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Alice Ravensdale (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=18;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Eric Hunter (/mob/living/carbon/human) - src: Eric Hunter (/mob/living/carbon/human) - call stack: -Eric Hunter (/mob/living/carbon/human): db click("o_clothing", 1) -the o_clothing (/obj/screen): attack hand(Eric Hunter (/mob/living/carbon/human), 1) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=25;icon-y=14;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Samuel York (/mob/living/carbon/human) - src: Samuel York (/mob/living/carbon/human) - call stack: -Samuel York (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Samuel York (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=15;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1103 - usr: MCEyePop (/mob/living/carbon/human) - src: MCEyePop (/mob/living/carbon/human) - call stack: -MCEyePop (/mob/living/carbon/human): db click("head", 1) -the head (/obj/screen): attack hand(MCEyePop (/mob/living/carbon/human), 1) -the head (/obj/screen): DblClick(null, null, null) -the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=25;icon-y=11;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=29;icon-y=17;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=21;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=21;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Alexander Delapour (/mob/living/carbon/human) - src: Alexander Delapour (/mob/living/carbon/human) - call stack: -Alexander Delapour (/mob/living/carbon/human): db click("belt", 1) -the belt (/obj/screen): attack hand(Alexander Delapour (/mob/living/carbon/human), 1) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=23;icon-y=13;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: Roman Sheets (/mob/living/carbon/human) - src: Roman Sheets (/mob/living/carbon/human) - call stack: -Roman Sheets (/mob/living/carbon/human): db click("back", null) -the back (/obj/screen): attack hand(Roman Sheets (/mob/living/carbon/human), null) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=12;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1127 - usr: Roman Sheets (/mob/living/carbon/human) - src: Roman Sheets (/mob/living/carbon/human) - call stack: -Roman Sheets (/mob/living/carbon/human): db click("i_clothing", 0) -the i_clothing (/obj/screen): attack hand(Roman Sheets (/mob/living/carbon/human), 0) -the i_clothing (/obj/screen): DblClick(null, null, null) -the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=18;left=1;scr...") -Sun Jul 15 00:17:04 2012 -runtime error: unexpected stat -proc name: Stat (/mob/living/silicon/robot/Stat) - source file: robot.dm,227 - usr: Iroquois Pliskin (/mob/living/carbon/human) - src: Miner Cyborg -770 (/mob/living/silicon/robot) - call stack: -Miner Cyborg -770 (/mob/living/silicon/robot): Stat() -Borg module reset board (/obj/item/borg/upgrade/reset): action(Miner Cyborg -770 (/mob/living/silicon/robot)) -Borg module reset board (/obj/item/borg/upgrade/reset): action(Miner Cyborg -770 (/mob/living/silicon/robot)) -Miner Cyborg -770 (/mob/living/silicon/robot): attackby(Borg module reset board (/obj/item/borg/upgrade/reset), Iroquois Pliskin (/mob/living/carbon/human)) -Miner Cyborg -770 (/mob/living/silicon/robot): DblClick(the floor (98,88,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=10;left=1;scr...") -Miner Cyborg -770 (/mob/living/silicon/robot): Click(the floor (98,88,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=10;left=1;scr...") -runtime error: unexpected stat -proc name: Stat (/mob/living/silicon/robot/Stat) - source file: robot.dm,227 - usr: Iroquois Pliskin (/mob/living/carbon/human) - src: Engineering Cyborg 770 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg 770 (/mob/living/silicon/robot): Stat() -Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Engineering Cyborg 770 (/mob/living/silicon/robot)) -Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Engineering Cyborg 770 (/mob/living/silicon/robot)) -Engineering Cyborg 770 (/mob/living/silicon/robot): attackby(Borg VTEC Module (/obj/item/borg/upgrade/vtec), Iroquois Pliskin (/mob/living/carbon/human)) -Engineering Cyborg 770 (/mob/living/silicon/robot): DblClick(the floor (98,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=12;left=1;scr...") -Engineering Cyborg 770 (/mob/living/silicon/robot): Click(the floor (98,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=12;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: MCEyePop (/mob/living/carbon/human) - src: MCEyePop (/mob/living/carbon/human) - call stack: -MCEyePop (/mob/living/carbon/human): db click("belt", 1) -the belt (/obj/screen): attack hand(MCEyePop (/mob/living/carbon/human), 1) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: MCEyePop (/mob/living/carbon/human) - src: MCEyePop (/mob/living/carbon/human) - call stack: -MCEyePop (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(MCEyePop (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=13;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: Roman Sheets as (Petrov Petrov... (/mob/living/carbon/human) - src: Roman Sheets as (Petrov Petrov... (/mob/living/carbon/human) - call stack: -Roman Sheets as (Petrov Petrov... (/mob/living/carbon/human): db click("back", 1) -the back (/obj/screen): attack hand(Roman Sheets as (Petrov Petrov... (/mob/living/carbon/human), 1) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=13;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Earl Swabby (/mob/living/carbon/human) - src: Earl Swabby (/mob/living/carbon/human) - call stack: -Earl Swabby (/mob/living/carbon/human): db click("storage1", null) -the storage1 (/obj/screen): attack hand(Earl Swabby (/mob/living/carbon/human), null) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=5;icon-y=19;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 - usr: Smoke Shaw (/mob/living/carbon/human) - src: Smoke Shaw (/mob/living/carbon/human) - call stack: -Smoke Shaw (/mob/living/carbon/human): db click("mask", 0) -the mask (/obj/screen): attack hand(Smoke Shaw (/mob/living/carbon/human), 0) -the mask (/obj/screen): DblClick(null, null, null) -the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=13;left=1;scr...") -runtime error: Cannot read null.reagents -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,748 - usr: Dutch Hargrave (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Dutch Hargrave (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=11;left=1;scr...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=11;left=1;scr...") -runtime error: Cannot read null.reagents -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,748 - usr: Dutch Hargrave (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Dutch Hargrave (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=6;left=1;scre...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=6;left=1;scre...") -runtime error: Cannot read null.total_volume -proc name: attackby (/obj/machinery/reagentgrinder/attackby) - source file: Chemistry-Machinery.dm,751 - usr: Dutch Hargrave (/mob/living/carbon/human) - src: All-In-One Grinder (/obj/machinery/reagentgrinder) - call stack: -All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Dutch Hargrave (/mob/living/carbon/human)) -All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=20;icon-y=6;left=1;scre...") -All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=20;icon-y=6;left=1;scre...") -Rebooted server. -Running TG Revision Number: 4060. -Sun Jul 15 01:27:07 2012 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Crate (/obj/structure/closet/crate) - call stack: -Crate (/obj/structure/closet/crate): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Crate (/obj/structure/closet/crate) - call stack: -Crate (/obj/structure/closet/crate): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: the emergency closet (/obj/structure/closet/emcloset) - call stack: -the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 - usr: 0 - src: Crate (/obj/structure/closet/crate) - call stack: -Crate (/obj/structure/closet/crate): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) -the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -runtime error: Cannot read null.has_gravity -proc name: throw at (/atom/movable/proc/throw_at) - source file: throwing.dm,194 -Warning: further proc crash messages are being suppressed to prevent overload... -Rebooted server. -Sun Jul 15 02:33:41 2012 -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Earl Swabby (/mob/living/carbon/human) - src: Earl Swabby (/mob/living/carbon/human) - call stack: -Earl Swabby (/mob/living/carbon/human): db click("storage2", null) -the storage2 (/obj/screen): attack hand(Earl Swabby (/mob/living/carbon/human), null) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=23;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1093 - usr: Lord Gwyn (/mob/living/carbon/human) - src: Lord Gwyn (/mob/living/carbon/human) - call stack: -Lord Gwyn (/mob/living/carbon/human): db click("eyes", 1) -the eyes (/obj/screen): attack hand(Lord Gwyn (/mob/living/carbon/human), 1) -the eyes (/obj/screen): DblClick(null, null, null) -the eyes (/obj/screen): Click(null, "mapwindow.map", "icon-x=9;icon-y=18;left=1;scre...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Crunk Junksworth (/mob/living/carbon/human) - src: Crunk Junksworth (/mob/living/carbon/human) - call stack: -Crunk Junksworth (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Crunk Junksworth (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=20;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Crunk Junksworth (/mob/living/carbon/human) - src: Crunk Junksworth (/mob/living/carbon/human) - call stack: -Crunk Junksworth (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Crunk Junksworth (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=19;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -Denied access to 'StevenRodriguez' connecting from 98.64.162.60 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Coyo (/mob/living/carbon/human) - src: Coyo (/mob/living/carbon/human) - call stack: -Coyo (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Coyo (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Yahn Deir (/mob/living/carbon/human) - src: Yahn Deir (/mob/living/carbon/human) - call stack: -Yahn Deir (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Yahn Deir (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=22;icon-y=10;left=1;scr...") -Sun Jul 15 03:41:55 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1048 - usr: Samuel York (/mob/living/carbon/human) - src: Samuel York (/mob/living/carbon/human) - call stack: -Samuel York (/mob/living/carbon/human): db click("o_clothing", 1) -the o_clothing (/obj/screen): attack hand(Samuel York (/mob/living/carbon/human), 1) -the o_clothing (/obj/screen): DblClick(null, null, null) -the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") -### VarEdit by Doohl: /obj/effect/step_trigger/teleporter affect_ghosts=1 -### VarEdit by Doohl: /obj/effect/step_trigger/teleporter teleport_x=239 -### VarEdit by Doohl: /obj/effect/step_trigger/teleporter teleport_x=239 -### VarEdit by Doohl: /obj/effect/step_trigger/teleporter teleport_y=12 -### VarEdit by Doohl: /obj/effect/step_trigger/teleporter teleport_z=2 -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Bubble (/mob/living/carbon/human) - src: Bubble (/mob/living/carbon/human) - call stack: -Bubble (/mob/living/carbon/human): db click("storage2", 0) -the storage2 (/obj/screen): attack hand(Bubble (/mob/living/carbon/human), 0) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=13;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Tyson Roby (/mob/living/carbon/human) - src: Tyson Roby (/mob/living/carbon/human) - call stack: -Tyson Roby (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Tyson Roby (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=17;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Tyson Roby (/mob/living/carbon/human) - src: Tyson Roby (/mob/living/carbon/human) - call stack: -Tyson Roby (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Tyson Roby (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Tyson Roby (/mob/living/carbon/human) - src: Tyson Roby (/mob/living/carbon/human) - call stack: -Tyson Roby (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Tyson Roby (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=16;left=1;scr...") -runtime error: Cannot read null.w_class -proc name: attackby (/obj/item/weapon/storage/attackby) - source file: storage.dm,187 - usr: Samuel York (/mob/living/carbon/human) - src: the backpack (/obj/item/weapon/storage/backpack) - call stack: -the backpack (/obj/item/weapon/storage/backpack): attackby(null, Samuel York (/mob/living/carbon/human)) -the backpack (/obj/item/weapon/storage/backpack): attackby(null, Samuel York (/mob/living/carbon/human)) -the backpack (/obj/item/weapon/storage/backpack): DblClick(null, "mapwindow.map", "icon-x=17;icon-y=18;left=1;scr...") -the backpack (/obj/item/weapon/storage/backpack): Click(null, "mapwindow.map", "icon-x=17;icon-y=18;left=1;scr...") -Rebooted server. -Sun Jul 15 04:43:18 2012 -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=21;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=20;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1035 - usr: Michigan Slim (/mob/living/carbon/human) - src: Michigan Slim (/mob/living/carbon/human) - call stack: -Michigan Slim (/mob/living/carbon/human): db click("back", 0) -the back (/obj/screen): attack hand(Michigan Slim (/mob/living/carbon/human), 0) -the back (/obj/screen): DblClick(null, null, null) -the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=19;left=1;scr...") -Rebooted server. -Sun Jul 15 05:48:07 2012 -Running TG Revision Number: 4060. -### VarEdit by Yinadele: /mob/living/simple_animal/cat/Runtime ckey=bossname -### VarEdit by Yinadele: /mob/living/simple_animal/cat/Runtime universal_speak=0 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1083 - usr: Jimz Ruztlfoot (/mob/living/carbon/human) - src: Jimz Ruztlfoot (/mob/living/carbon/human) - call stack: -Jimz Ruztlfoot (/mob/living/carbon/human): db click("belt", 0) -the belt (/obj/screen): attack hand(Jimz Ruztlfoot (/mob/living/carbon/human), 0) -the belt (/obj/screen): DblClick(null, null, null) -the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=10;left=1;scr...") -Rebooted server. -Sun Jul 15 06:57:20 2012 -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1061 - usr: Quinton Bould (/mob/living/carbon/human) - src: Quinton Bould (/mob/living/carbon/human) - call stack: -Quinton Bould (/mob/living/carbon/human): db click("gloves", 1) -the gloves (/obj/screen): attack hand(Quinton Bould (/mob/living/carbon/human), 1) -the gloves (/obj/screen): DblClick(null, null, null) -the gloves (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1023 - usr: Yahn Deir (/mob/living/carbon/human) - src: Yahn Deir (/mob/living/carbon/human) - call stack: -Yahn Deir (/mob/living/carbon/human): db click("mask", 1) -the mask (/obj/screen): attack hand(Yahn Deir (/mob/living/carbon/human), 1) -the mask (/obj/screen): DblClick(null, null, null) -the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=20;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Dutch Hargrave (/mob/living/carbon/human) - src: Dutch Hargrave (/mob/living/carbon/human) - call stack: -Dutch Hargrave (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Dutch Hargrave (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=16;left=1;scr...") -Sun Jul 15 08:26:36 2012 -runtime error: Cannot read null.nodes -proc name: merge powernets (/datum/powernet/proc/merge_powernets) - source file: power.dm,401 - usr: Stephen Cox (/mob/living/carbon/human) - src: /datum/powernet (/datum/powernet) - call stack: -/datum/powernet (/datum/powernet): merge powernets(null) -the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() -the cable coil (/obj/item/weapon/cable_coil): turf place(the floor (87,115,1) (/turf/simulated/floor), Stephen Cox (/mob/living/carbon/human)) -the floor (87,115,1) (/turf/simulated/floor): attackby(the cable coil (/obj/item/weapon/cable_coil), Stephen Cox (/mob/living/carbon/human)) -the floor (87,115,1) (/turf/simulated/floor): DblClick(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") -the floor (87,115,1) (/turf/simulated/floor): DblClick(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") -the floor (87,115,1) (/turf/simulated/floor): Click(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") -runtime error: Cannot read null.nodes -proc name: merge powernets (/datum/powernet/proc/merge_powernets) - source file: power.dm,401 - usr: Stephen Cox (/mob/living/carbon/human) - src: /datum/powernet (/datum/powernet) - call stack: -/datum/powernet (/datum/powernet): merge powernets(null) -the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() -the cable coil (/obj/item/weapon/cable_coil): turf place(the floor (87,115,1) (/turf/simulated/floor), Stephen Cox (/mob/living/carbon/human)) -the floor (87,115,1) (/turf/simulated/floor): attackby(the cable coil (/obj/item/weapon/cable_coil), Stephen Cox (/mob/living/carbon/human)) -the floor (87,115,1) (/turf/simulated/floor): DblClick(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") -the floor (87,115,1) (/turf/simulated/floor): DblClick(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") -the floor (87,115,1) (/turf/simulated/floor): Click(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1166 - usr: Clarisse Holderbach (/mob/living/carbon/human) - src: Clarisse Holderbach (/mob/living/carbon/human) - call stack: -Clarisse Holderbach (/mob/living/carbon/human): db click("storage2", null) -the storage2 (/obj/screen): attack hand(Clarisse Holderbach (/mob/living/carbon/human), null) -the storage2 (/obj/screen): DblClick(null, null, null) -the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=15;left=1;scr...") -Sun Jul 15 09:41:02 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1071 - usr: Samantha Kendrick (/mob/living/carbon/human) - src: Samantha Kendrick (/mob/living/carbon/human) - call stack: -Samantha Kendrick (/mob/living/carbon/human): db click("shoes", 1) -the shoes (/obj/screen): attack hand(Samantha Kendrick (/mob/living/carbon/human), 1) -the shoes (/obj/screen): DblClick(null, null, null) -the shoes (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") -### VarEdit by Scaredofshadows: /mob/living/simple_animal/corgi/Ian ckey=laharlmontgommery -Sun Jul 15 10:43:09 2012 -### VarEdit by Scaredofshadows: /mob/living/simple_animal/cat/Runtime ckey=drlareku -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1093 - usr: Rowdy Randy (/mob/living/carbon/human) - src: Rowdy Randy (/mob/living/carbon/human) - call stack: -Rowdy Randy (/mob/living/carbon/human): db click("eyes", 0) -the eyes (/obj/screen): attack hand(Rowdy Randy (/mob/living/carbon/human), 0) -the eyes (/obj/screen): DblClick(null, null, null) -the eyes (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=20;left=1;scr...") -### VarEdit by Scaredofshadows: /mob/living/simple_animal/cat/Runtime canmove=1 -### VarEdit by Scaredofshadows: /mob/living/simple_animal/parrot/DrProfessor ckey=kyora473 -runtime error: Cannot read null.amount -proc name: attackby (/obj/machinery/constructable_frame/machine_frame/attackby) - source file: constructable_frame.dm,27 - usr: Quinton Bould (/mob/living/carbon/human) - src: the machine frame (/obj/machinery/constructable_frame/machine_frame) - call stack: -the machine frame (/obj/machinery/constructable_frame/machine_frame): attackby(null, Quinton Bould (/mob/living/carbon/human)) -the machine frame (/obj/machinery/constructable_frame/machine_frame): DblClick(the floor (98,69,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=23;icon-y=16;left=1;scr...") -the machine frame (/obj/machinery/constructable_frame/machine_frame): Click(the floor (98,69,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=23;icon-y=16;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1103 - usr: Quinton Bould (/mob/living/carbon/human) - src: Quinton Bould (/mob/living/carbon/human) - call stack: -Quinton Bould (/mob/living/carbon/human): db click("head", 1) -the head (/obj/screen): attack hand(Quinton Bould (/mob/living/carbon/human), 1) -the head (/obj/screen): DblClick(null, null, null) -the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=28;left=1;scr...") -runtime error: Cannot read null.loc -proc name: Life (/mob/living/simple_animal/corgi/Ian/Life) - source file: corgi.dm,304 - usr: null - src: Captain Ian (/mob/living/simple_animal/corgi/Ian) - call stack: -Captain Ian (/mob/living/simple_animal/corgi/Ian): Life() -/datum/controller/game_control... (/datum/controller/game_controller): process() -### VarEdit by Scaredofshadows: /mob/living/simple_animal/corgi/Ian real_name=Fluffum-Wuffums -Rebooted server. -Running TG Revision Number: 4060. -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Dat Bass (/mob/living/carbon/human) - src: Dat Bass (/mob/living/carbon/human) - call stack: -Dat Bass (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Dat Bass (/mob/living/carbon/human) - src: Dat Bass (/mob/living/carbon/human) - call stack: -Dat Bass (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=15;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Dat Bass (/mob/living/carbon/human) - src: Dat Bass (/mob/living/carbon/human) - call stack: -Dat Bass (/mob/living/carbon/human): db click("storage1", 0) -the storage1 (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), 0) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=15;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=9;left=1;scre...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=9;left=1;scre...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=19;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=19;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=20;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=20;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=12;left=1;scr...") -runtime error: Cannot read null.broadcasting -proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) - source file: robot.dm,857 - usr: Engineering Cyborg -432 (/mob/living/silicon/robot) - src: Engineering Cyborg -432 (/mob/living/silicon/robot) - call stack: -Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() -the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=11;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1061 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1071 -Sun Jul 15 12:02:37 2012 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 -runtime error: Cannot modify null.status. -proc name: death (/mob/living/silicon/robot/death) - source file: death.dm,51 -runtime error: Cannot create objects of type null. -proc name: Topic (/obj/machinery/computer/rdconsole/Topic) - source file: rdconsole.dm,381 -Rebooted server. -Running TG Revision Number: 4060. -Denied access to 'Cronzefir' connecting from 79.113.206.100 -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Zeus Geesenheimer (/mob/living/carbon/human) - src: Zeus Geesenheimer (/mob/living/carbon/human) - call stack: -Zeus Geesenheimer (/mob/living/carbon/human): db click("id", 0) -the id (/obj/screen): attack hand(Zeus Geesenheimer (/mob/living/carbon/human), 0) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=13;left=1;scr...") -Rebooted server. -Running TG Revision Number: 4060. -Sun Jul 15 13:06:40 2012 -runtime error: unexpected stat -proc name: Stat (/mob/living/silicon/robot/Stat) - source file: robot.dm,227 - usr: Bill Kerman (/mob/living/carbon/human) - src: Miner Cyborg -734 (/mob/living/silicon/robot) - call stack: -Miner Cyborg -734 (/mob/living/silicon/robot): Stat() -Mining Borg Jetpack (/obj/item/borg/upgrade/jetpack): action(Miner Cyborg -734 (/mob/living/silicon/robot)) -Mining Borg Jetpack (/obj/item/borg/upgrade/jetpack): action(Miner Cyborg -734 (/mob/living/silicon/robot)) -Miner Cyborg -734 (/mob/living/silicon/robot): attackby(Mining Borg Jetpack (/obj/item/borg/upgrade/jetpack), Bill Kerman (/mob/living/carbon/human)) -Miner Cyborg -734 (/mob/living/silicon/robot): DblClick(the floor (100,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=16;left=1;scr...") -Miner Cyborg -734 (/mob/living/silicon/robot): Click(the floor (100,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=16;left=1;scr...") -runtime error: unexpected stat -proc name: Stat (/mob/living/silicon/robot/Stat) - source file: robot.dm,227 - usr: Bill Kerman (/mob/living/carbon/human) - src: Miner Cyborg -734 (/mob/living/silicon/robot) - call stack: -Miner Cyborg -734 (/mob/living/silicon/robot): Stat() -Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Miner Cyborg -734 (/mob/living/silicon/robot)) -Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Miner Cyborg -734 (/mob/living/silicon/robot)) -Miner Cyborg -734 (/mob/living/silicon/robot): attackby(Borg VTEC Module (/obj/item/borg/upgrade/vtec), Bill Kerman (/mob/living/carbon/human)) -Miner Cyborg -734 (/mob/living/silicon/robot): DblClick(the floor (99,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") -Miner Cyborg -734 (/mob/living/silicon/robot): Click(the floor (99,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1142 - usr: Unknown (/mob/living/carbon/human) - src: Unknown (/mob/living/carbon/human) - call stack: -Unknown (/mob/living/carbon/human): db click("id", 1) -the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 1) -the id (/obj/screen): DblClick(null, null, null) -the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=14;left=1;scr...") -runtime error: Cannot read null.slot_flags -proc name: db click (/mob/living/carbon/human/db_click) - source file: inventory.dm,1154 - usr: Rocky McDoughboy XIV (/mob/living/carbon/human) - src: Rocky McDoughboy XIV (/mob/living/carbon/human) - call stack: -Rocky McDoughboy XIV (/mob/living/carbon/human): db click("storage1", 1) -the storage1 (/obj/screen): attack hand(Rocky McDoughboy XIV (/mob/living/carbon/human), 1) -the storage1 (/obj/screen): DblClick(null, null, null) -the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=14;left=1;scr...") -runtime error: Cannot read null.loc -proc name: Life (/mob/living/simple_animal/corgi/Ian/Life) - source file: corgi.dm,304 - usr: null - src: Ian (/mob/living/simple_animal/corgi/Ian) - call stack: -Ian (/mob/living/simple_animal/corgi/Ian): Life() -/datum/controller/game_control... (/datum/controller/game_controller): process() -Rebooted server. -Running TG Revision Number: 4060. -Stopped server. - -*** End Log: Sun Jul 15 13:55:08 2012 ****** Begin Log: Mon Oct 05 18:58:26 2015 *** -Mon Oct 05 19:01:11 2015 -World opened on network port 49626. -Welcome BYOND! (5.0 Beta Version 508.1294) -Running /tg/ revision: -2015-10-06 -e2ed83b6a595fe0d6078fa75bfde85b3fa6c392b -Path : /obj/machinery/mech_bay_recharge_port -Failures : 1 -Path : /obj/effect/landmark/start -Failures : 2 - -Rebooted server. -Running /tg/ revision: -2015-10-06 -e2ed83b6a595fe0d6078fa75bfde85b3fa6c392b -Path : /obj/effect/landmark/start -Failures : 2 - -Rebooted server. -Running /tg/ revision: -2015-10-06 -e2ed83b6a595fe0d6078fa75bfde85b3fa6c392b -Tue Oct 06 02:28:25 2015 -runtime error: pick() from empty list -proc name: New (/obj/effect/hallucination/whispers/New) - source file: Hallucination.dm,524 - usr: null - src: the whispers (/obj/effect/hallucination/whispers) - call stack: -the whispers (/obj/effect/hallucination/whispers): New(the plating (103,105,1) (/turf/simulated/floor/plating), the monkey (525) (/mob/living/carbon/monkey)) -the monkey (525) (/mob/living/carbon/monkey): hallucinate("whispers") -the monkey (525) (/mob/living/carbon/monkey): handle hallucinations() -the monkey (525) (/mob/living/carbon/monkey): handle status effects() -Tue Oct 06 06:07:02 2015 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 - usr: null - src: the space vines (/obj/effect/spacevine) - call stack: -the space vines (/obj/effect/spacevine): spread() -the spacevine controller (/obj/effect/spacevine_controller): process(20) -Objects (/datum/subsystem/objects): fire() -/datum/controller/game_control... (/datum/controller/game_controller): process() -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -Tue Oct 06 07:07:36 2015 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -runtime error: Cannot execute null.Enter(). -proc name: spread (/obj/effect/spacevine/proc/spread) - source file: spacevine.dm,521 -Warning: further proc crash messages are being suppressed to prevent overload... +*** Begin Log: Fri Jul 13 17:04:27 2012 *** +Fri Jul 13 17:04:49 2012 +World opened on network port 2337. +Welcome BYOND! (4.0 Public Version 495.1136) +Running TG Revision Number: 4060. +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Puke Chunks (/mob/living/carbon/human) + src: Puke Chunks (/mob/living/carbon/human) + call stack: +Puke Chunks (/mob/living/carbon/human): db click("belt", 0) +the belt (/obj/screen): attack hand(Puke Chunks (/mob/living/carbon/human), 0) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=19;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1117 + usr: Lord Gwyn (/mob/living/carbon/human) + src: Lord Gwyn (/mob/living/carbon/human) + call stack: +Lord Gwyn (/mob/living/carbon/human): db click("ears", 0) +the ears (/obj/screen): attack hand(Lord Gwyn (/mob/living/carbon/human), 0) +the ears (/obj/screen): DblClick(null, null, null) +the ears (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=28;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1117 + usr: Lord Gwyn (/mob/living/carbon/human) + src: Lord Gwyn (/mob/living/carbon/human) + call stack: +Lord Gwyn (/mob/living/carbon/human): db click("ears", 0) +the ears (/obj/screen): attack hand(Lord Gwyn (/mob/living/carbon/human), 0) +the ears (/obj/screen): DblClick(null, null, null) +the ears (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1117 + usr: Lord Gwyn (/mob/living/carbon/human) + src: Lord Gwyn (/mob/living/carbon/human) + call stack: +Lord Gwyn (/mob/living/carbon/human): db click("ears", 0) +the ears (/obj/screen): attack hand(Lord Gwyn (/mob/living/carbon/human), 0) +the ears (/obj/screen): DblClick(null, null, null) +the ears (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Blake Sable (/mob/living/carbon/human) + src: Blake Sable (/mob/living/carbon/human) + call stack: +Blake Sable (/mob/living/carbon/human): db click("storage2", 0) +the storage2 (/obj/screen): attack hand(Blake Sable (/mob/living/carbon/human), 0) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=18;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Donald Mckendrick (/mob/living/carbon/human) + src: Donald Mckendrick (/mob/living/carbon/human) + call stack: +Donald Mckendrick (/mob/living/carbon/human): db click("storage2", 1) +the storage2 (/obj/screen): attack hand(Donald Mckendrick (/mob/living/carbon/human), 1) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Blake Sable (/mob/living/carbon/human) + src: Blake Sable (/mob/living/carbon/human) + call stack: +Blake Sable (/mob/living/carbon/human): db click("o_clothing", 0) +the o_clothing (/obj/screen): attack hand(Blake Sable (/mob/living/carbon/human), 0) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=15;left=1;scr...") +Fri Jul 13 18:18:09 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Jeanette DeLisle (/mob/living/carbon/human) + src: Jeanette DeLisle (/mob/living/carbon/human) + call stack: +Jeanette DeLisle (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Jeanette DeLisle (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=21;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=21;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=20;icon-y=15;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=20;icon-y=15;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=18;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=19;icon-y=18;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=19;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=19;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Reese Marcotte (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Reese Marcotte (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=17;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (169,125,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=17;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 +Fri Jul 13 19:18:20 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 +Denied access to 'Rotcod' connecting from 138.210.6.170 +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Jack Kasshu (/mob/living/carbon/human) + src: Jack Kasshu (/mob/living/carbon/human) + call stack: +Jack Kasshu (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Jack Kasshu (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=10;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Slimeria (/mob/living/carbon/human) + src: Slimeria (/mob/living/carbon/human) + call stack: +Slimeria (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Slimeria (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Slimeria (/mob/living/carbon/human) + src: Slimeria (/mob/living/carbon/human) + call stack: +Slimeria (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Slimeria (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=20;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Alberto Ivanov (/mob/living/carbon/human) + src: Alberto Ivanov (/mob/living/carbon/human) + call stack: +Alberto Ivanov (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Alberto Ivanov (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=13;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Mr. English (/mob/living/carbon/human) + src: Mr. English (/mob/living/carbon/human) + call stack: +Mr. English (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Mr. English (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=20;left=1;scr...") +runtime error: undefined variable /client/var/loc +proc name: get turf (/proc/get_turf) + source file: helper_procs.dm,38 + usr: the ghost (/mob/dead/observer) + src: null + call stack: +get turf(Nodka (/client)) +the ghost (/mob/dead/observer): New(Nodka (/client), 0) +Nodka (/client): Ghost() +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Captain Fudge (/mob/living/carbon/human) + src: Captain Fudge (/mob/living/carbon/human) + call stack: +Captain Fudge (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Captain Fudge (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=19;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Ellen Quinn (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Ellen Quinn (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=21;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=21;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Ellen Quinn (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Ellen Quinn (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=14;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=14;left=1;scr...") +Fri Jul 13 20:36:29 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Dawson Gibbens (/mob/living/carbon/human) + src: Dawson Gibbens (/mob/living/carbon/human) + call stack: +Dawson Gibbens (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Dawson Gibbens (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Jack Kasshu (/mob/living/carbon/human) + src: Jack Kasshu (/mob/living/carbon/human) + call stack: +Jack Kasshu (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Jack Kasshu (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=20;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Josh Morris (/mob/living/carbon/human) + src: Josh Morris (/mob/living/carbon/human) + call stack: +Josh Morris (/mob/living/carbon/human): db click("storage2", 0) +the storage2 (/obj/screen): attack hand(Josh Morris (/mob/living/carbon/human), 0) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=14;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Josh Morris (/mob/living/carbon/human) + src: Josh Morris (/mob/living/carbon/human) + call stack: +Josh Morris (/mob/living/carbon/human): db click("storage2", 0) +the storage2 (/obj/screen): attack hand(Josh Morris (/mob/living/carbon/human), 0) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Simon Bonkers (/mob/living/carbon/human) + src: Simon Bonkers (/mob/living/carbon/human) + call stack: +Simon Bonkers (/mob/living/carbon/human): db click("o_clothing", 0) +the o_clothing (/obj/screen): attack hand(Simon Bonkers (/mob/living/carbon/human), 0) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=14;left=1;scr...") +Denied access to 'Deamon_Man16' connecting from 108.200.222.182 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1127 + usr: Dave Oneal (/mob/living/carbon/human) + src: Dave Oneal (/mob/living/carbon/human) + call stack: +Dave Oneal (/mob/living/carbon/human): db click("i_clothing", 1) +the i_clothing (/obj/screen): attack hand(Dave Oneal (/mob/living/carbon/human), 1) +the i_clothing (/obj/screen): DblClick(null, null, null) +the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=13;left=1;scr...") +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Space Retrovirus (/datum/disease/dnaspread) + call stack: +Space Retrovirus (/datum/disease/dnaspread): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Space Retrovirus (/datum/disease/dnaspread) + call stack: +Space Retrovirus (/datum/disease/dnaspread): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Space Retrovirus (/datum/disease/dnaspread) + call stack: +Space Retrovirus (/datum/disease/dnaspread): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Space Retrovirus (/datum/disease/dnaspread) + call stack: +Space Retrovirus (/datum/disease/dnaspread): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: undefined proc or verb /obj/machinery/space_heater/attack(). + +proc name: attackby (/mob/attackby) + source file: items.dm,334 +runtime error: undefined proc or verb /obj/machinery/space_heater/attack(). + +proc name: attackby (/mob/attackby) + source file: items.dm,334 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1071 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +Warning: further proc crash messages are being suppressed to prevent overload... +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Reign Boudash (/mob/living/carbon/human) + src: Reign Boudash (/mob/living/carbon/human) + call stack: +Reign Boudash (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Reign Boudash (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Reign Boudash (/mob/living/carbon/human) + src: Reign Boudash (/mob/living/carbon/human) + call stack: +Reign Boudash (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Reign Boudash (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") +Fri Jul 13 21:43:15 2012 +runtime error: unexpected stat +proc name: Stat (/mob/living/silicon/robot/Stat) + source file: robot.dm,227 + usr: Michigan Slim (/mob/living/carbon/human) + src: Engineering Cyborg -133 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -133 (/mob/living/silicon/robot): Stat() +Borg module reset board (/obj/item/borg/upgrade/reset): action(Engineering Cyborg -133 (/mob/living/silicon/robot)) +Borg module reset board (/obj/item/borg/upgrade/reset): action(Engineering Cyborg -133 (/mob/living/silicon/robot)) +Engineering Cyborg -133 (/mob/living/silicon/robot): attackby(Borg module reset board (/obj/item/borg/upgrade/reset), Michigan Slim (/mob/living/carbon/human)) +Engineering Cyborg -133 (/mob/living/silicon/robot): DblClick(the floor (95,85,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=14;icon-y=18;left=1;scr...") +Engineering Cyborg -133 (/mob/living/silicon/robot): Click(the floor (95,85,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=14;icon-y=18;left=1;scr...") +runtime error: undefined proc or verb /obj/machinery/portable_atmospherics/canister/toxins/attack(). + +proc name: attackby (/mob/attackby) + source file: items.dm,334 + usr: Watt Malker (/mob/living/carbon/human) + src: Black Dick Bishop (/mob/living/carbon/human) + call stack: +Black Dick Bishop (/mob/living/carbon/human): attackby(Canister \[Toxin (Bio)] (OPEN ... (/obj/machinery/portable_atmospherics/canister/toxins), Watt Malker (/mob/living/carbon/human), "chest") +runtime error: undefined variable /datum/preferences/var/fields +proc name: Topic (/obj/machinery/computer/cloning/Topic) + source file: cloning.dm,370 + usr: Logan Graves (/mob/living/carbon/human) + src: Cloning console (/obj/machinery/computer/cloning) + call stack: +Cloning console (/obj/machinery/computer/cloning): Topic("src=\[0x2005ab9];clone=\[0x210...", /list (/list)) +LordGeneralCastor (/client): Topic("src=\[0x2005ab9];clone=\[0x210...", /list (/list), Cloning console (/obj/machinery/computer/cloning)) +runtime error: unexpected stat +proc name: Stat (/mob/living/silicon/robot/Stat) + source file: robot.dm,214 + usr: Kingston Sommer (/mob/living/carbon/human) + src: Miner Cyborg 133 (/mob/living/silicon/robot) + call stack: +Miner Cyborg 133 (/mob/living/silicon/robot): Stat() +Borg module reset board (/obj/item/borg/upgrade/reset): action(Miner Cyborg 133 (/mob/living/silicon/robot)) +Borg module reset board (/obj/item/borg/upgrade/reset): action(Miner Cyborg 133 (/mob/living/silicon/robot)) +Miner Cyborg 133 (/mob/living/silicon/robot): attackby(Borg module reset board (/obj/item/borg/upgrade/reset), Kingston Sommer (/mob/living/carbon/human)) +Miner Cyborg 133 (/mob/living/silicon/robot): DblClick(the floor (97,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=14;icon-y=9;left=1;scre...") +Miner Cyborg 133 (/mob/living/silicon/robot): Click(the floor (97,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=14;icon-y=9;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Reign Boudash (/mob/living/carbon/human) + src: Reign Boudash (/mob/living/carbon/human) + call stack: +Reign Boudash (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Reign Boudash (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=17;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Zevin Thusar (/mob/living/carbon/human) + src: Zevin Thusar (/mob/living/carbon/human) + call stack: +Zevin Thusar (/mob/living/carbon/human): db click("storage2", 1) +the storage2 (/obj/screen): attack hand(Zevin Thusar (/mob/living/carbon/human), 1) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Nick Cave (/mob/living/carbon/human) + src: Nick Cave (/mob/living/carbon/human) + call stack: +Nick Cave (/mob/living/carbon/human): db click("belt", null) +the belt (/obj/screen): attack hand(Nick Cave (/mob/living/carbon/human), null) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=15;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Nick Cave (/mob/living/carbon/human) + src: Nick Cave (/mob/living/carbon/human) + call stack: +Nick Cave (/mob/living/carbon/human): db click("belt", null) +the belt (/obj/screen): attack hand(Nick Cave (/mob/living/carbon/human), null) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=23;icon-y=14;left=1;scr...") +runtime error: undefined variable /turf/simulated/floor/plating/var/mineral +proc name: attackby (/turf/simulated/wall/attackby) + source file: turf.dm,600 + usr: Monte Smail (/mob/living/carbon/human) + src: the plating (176,172,5) (/turf/simulated/floor/plating) + call stack: +the plating (176,172,5) (/turf/simulated/floor/plating): attackby(Diamond Mining Drill (/obj/item/weapon/pickaxe/diamonddrill), Monte Smail (/mob/living/carbon/human)) +the plating (176,172,5) (/turf/simulated/floor/plating): DblClick(the plating (176,172,5) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=15;icon-y=6;left=1;scre...") +the plating (176,172,5) (/turf/simulated/floor/plating): DblClick(the plating (176,172,5) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=15;icon-y=6;left=1;scre...") +the plating (176,172,5) (/turf/simulated/floor/plating): Click(the plating (176,172,5) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=15;icon-y=6;left=1;scre...") +Rebooted server. +Running TG Revision Number: 4060. +Fri Jul 13 22:48:12 2012 +runtime error: Cannot create objects of type null. +proc name: Topic (/obj/machinery/computer/rdconsole/Topic) + source file: rdconsole.dm,381 + usr: Matthew Hoff (/mob/living/carbon/human) + src: Core R&D Console (/obj/machinery/computer/rdconsole/core) + call stack: +Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x20065fa];build=large_G...", /list (/list)) +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Chip Harshman (/mob/living/carbon/human) + src: Chip Harshman (/mob/living/carbon/human) + call stack: +Chip Harshman (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Chip Harshman (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=20;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Chip Harshman (/mob/living/carbon/human) + src: Chip Harshman (/mob/living/carbon/human) + call stack: +Chip Harshman (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Chip Harshman (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=20;left=1;scr...") +runtime error: Cannot read null.blood_DNA +proc name: update inv w uniform (/mob/living/carbon/human/update_inv_w_uniform) + source file: update_icons.dm,372 + usr: null + src: Chip Harshman (/mob/living/carbon/human) + call stack: +Chip Harshman (/mob/living/carbon/human): update inv w uniform(0) +Chip Harshman (/mob/living/carbon/human): handle chemicals in body() +Chip Harshman (/mob/living/carbon/human): Life() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot create objects of type null. +proc name: Topic (/obj/machinery/computer/rdconsole/Topic) + source file: rdconsole.dm,381 + usr: Sprigg Spooly (/mob/living/carbon/human) + src: Core R&D Console (/obj/machinery/computer/rdconsole/core) + call stack: +Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x200ca33];build=large_G...", /list (/list)) +runtime error: Cannot create objects of type null. +proc name: Topic (/obj/machinery/computer/rdconsole/Topic) + source file: rdconsole.dm,381 + usr: Sprigg Spooly (/mob/living/carbon/human) + src: Core R&D Console (/obj/machinery/computer/rdconsole/core) + call stack: +Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x200ca94];build=large_G...", /list (/list)) +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1093 + usr: Jack Kasshu (/mob/living/carbon/human) + src: Jack Kasshu (/mob/living/carbon/human) + call stack: +Jack Kasshu (/mob/living/carbon/human): db click("eyes", 0) +the eyes (/obj/screen): attack hand(Jack Kasshu (/mob/living/carbon/human), 0) +the eyes (/obj/screen): DblClick(null, null, null) +the eyes (/obj/screen): Click(null, "mapwindow.map", "icon-x=29;icon-y=1;left=1;scre...") +Fri Jul 13 23:52:26 2012 +### VarEdit by Yinadele: /datum/reagents maximum_volume=20 +### VarEdit by Yinadele: /datum/reagents total_volume=20 +### VarEdit by Yinadele: /obj/effect/decal name=chemicals (Haunter) +### VarEdit by Yinadele: /obj/effect/decal throwforce=20 +### VarEdit by Yinadele: /obj/effect/decal infra_luminosity=1 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Sergei Vladislanikov (/mob/living/carbon/human) + src: Sergei Vladislanikov (/mob/living/carbon/human) + call stack: +Sergei Vladislanikov (/mob/living/carbon/human): db click("storage2", 1) +the storage2 (/obj/screen): attack hand(Sergei Vladislanikov (/mob/living/carbon/human), 1) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Sprigg Spooly (/mob/living/carbon/human) + src: Sprigg Spooly (/mob/living/carbon/human) + call stack: +Sprigg Spooly (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Sprigg Spooly (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=14;left=1;scr...") +runtime error: unexpected stat +proc name: Stat (/mob/living/silicon/robot/Stat) + source file: robot.dm,227 + usr: Sprigg Spooly as (Matthew Hoff... (/mob/living/carbon/human) + src: Engineering Cyborg -952 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -952 (/mob/living/silicon/robot): Stat() +Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Engineering Cyborg -952 (/mob/living/silicon/robot)) +Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Engineering Cyborg -952 (/mob/living/silicon/robot)) +Engineering Cyborg -952 (/mob/living/silicon/robot): attackby(Borg VTEC Module (/obj/item/borg/upgrade/vtec), Sprigg Spooly as (Matthew Hoff... (/mob/living/carbon/human)) +Engineering Cyborg -952 (/mob/living/silicon/robot): DblClick(the floor (101,86,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=17;left=1;scr...") +Engineering Cyborg -952 (/mob/living/silicon/robot): Click(the floor (101,86,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=17;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Miku Frost (/mob/living/carbon/human) + src: Miku Frost (/mob/living/carbon/human) + call stack: +Miku Frost (/mob/living/carbon/human): db click("belt", 0) +the belt (/obj/screen): attack hand(Miku Frost (/mob/living/carbon/human), 0) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=20;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Miku Frost (/mob/living/carbon/human) + src: Miku Frost (/mob/living/carbon/human) + call stack: +Miku Frost (/mob/living/carbon/human): db click("belt", 0) +the belt (/obj/screen): attack hand(Miku Frost (/mob/living/carbon/human), 0) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Tood Hoawd (/mob/living/carbon/human) + src: Tood Hoawd (/mob/living/carbon/human) + call stack: +Tood Hoawd (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Tood Hoawd (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=17;left=1;scr...") +Sat Jul 14 00:57:29 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: Ellen Quinn (/mob/living/carbon/human) + src: Ellen Quinn (/mob/living/carbon/human) + call stack: +Ellen Quinn (/mob/living/carbon/human): db click("back", 0) +the back (/obj/screen): attack hand(Ellen Quinn (/mob/living/carbon/human), 0) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=25;icon-y=22;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("back", null) +the back (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), null) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=14;left=1;scr...") +### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=commissarzoey +### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=nodrak +### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=dakorok +### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=nickknack +### VarEdit by Yinadele: /mob/living/carbon/metroid/adult ckey=yinadele +### VarEdit by Yinadele: /mob/living/carbon/metroid ckey=emperorofcatkind +runtime error: Cannot read null.current +proc name: check completion (/datum/objective/download/check_completion) + source file: objective.dm,411 + usr: null + src: /datum/objective/download (/datum/objective/download) + call stack: +/datum/objective/download (/datum/objective/download): check completion() +extended (/datum/game_mode/extended): auto declare completion traitor() +/datum/controller/gameticker (/datum/controller/gameticker): declare completion() +/datum/controller/gameticker (/datum/controller/gameticker): process() +runtime error: Cannot read null.current +proc name: check completion (/datum/objective/steal/check_completion) + source file: objective.dm,352 + usr: null + src: /datum/objective/steal (/datum/objective/steal) + call stack: +/datum/objective/steal (/datum/objective/steal): check completion() +extended (/datum/game_mode/extended): auto declare completion traitor() +/datum/controller/gameticker (/datum/controller/gameticker): declare completion() +/datum/controller/gameticker (/datum/controller/gameticker): process() +runtime error: Cannot read null.current +proc name: check completion (/datum/objective/download/check_completion) + source file: objective.dm,411 + usr: null + src: /datum/objective/download (/datum/objective/download) + call stack: +/datum/objective/download (/datum/objective/download): check completion() +extended (/datum/game_mode/extended): auto declare completion traitor() +/datum/controller/gameticker (/datum/controller/gameticker): declare completion() +/datum/controller/gameticker (/datum/controller/gameticker): process() +runtime error: Cannot read null.current +proc name: check completion (/datum/objective/steal/check_completion) + source file: objective.dm,352 + usr: null + src: /datum/objective/steal (/datum/objective/steal) + call stack: +/datum/objective/steal (/datum/objective/steal): check completion() +extended (/datum/game_mode/extended): auto declare completion traitor() +/datum/controller/gameticker (/datum/controller/gameticker): declare completion() +/datum/controller/gameticker (/datum/controller/gameticker): process() +runtime error: Cannot read null.current +proc name: check completion (/datum/objective/download/check_completion) + source file: objective.dm,411 +runtime error: Cannot read null.current +proc name: check completion (/datum/objective/download/check_completion) + source file: objective.dm,411 +runtime error: Cannot read null.current +proc name: check completion (/datum/objective/steal/check_completion) + source file: objective.dm,352 +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Ophelia Braun (/mob/living/carbon/human) + src: Ophelia Braun (/mob/living/carbon/human) + call stack: +Ophelia Braun (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Ophelia Braun (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Helen Lester (/mob/living/carbon/human) + src: Helen Lester (/mob/living/carbon/human) + call stack: +Helen Lester (/mob/living/carbon/human): db click("belt", null) +the belt (/obj/screen): attack hand(Helen Lester (/mob/living/carbon/human), null) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=11;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Dave Oneal (/mob/living/carbon/human) + src: Dave Oneal (/mob/living/carbon/human) + call stack: +Dave Oneal (/mob/living/carbon/human): db click("id", null) +the id (/obj/screen): attack hand(Dave Oneal (/mob/living/carbon/human), null) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") +Sat Jul 14 01:57:46 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1117 + usr: Dat Bass (/mob/living/carbon/human) + src: Dat Bass (/mob/living/carbon/human) + call stack: +Dat Bass (/mob/living/carbon/human): db click("ears", null) +the ears (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), null) +the ears (/obj/screen): DblClick(null, null, null) +the ears (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=15;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Helen Lester (/mob/living/carbon/human) + src: Helen Lester (/mob/living/carbon/human) + call stack: +Helen Lester (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Helen Lester (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=12;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Lila Desminto (/mob/living/carbon/human) + src: Lila Desminto (/mob/living/carbon/human) + call stack: +Lila Desminto (/mob/living/carbon/human): db click("belt", 0) +the belt (/obj/screen): attack hand(Lila Desminto (/mob/living/carbon/human), 0) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=5;left=1;scre...") +### VarEdit by Doohl: /obj/effect/new_year_tree name=BODA TREE +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Penny Merriweather (/mob/living/carbon/human) + src: Penny Merriweather (/mob/living/carbon/human) + call stack: +Penny Merriweather (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Penny Merriweather (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Penny Merriweather (/mob/living/carbon/human) + src: Penny Merriweather (/mob/living/carbon/human) + call stack: +Penny Merriweather (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Penny Merriweather (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Penny Merriweather (/mob/living/carbon/human) + src: Penny Merriweather (/mob/living/carbon/human) + call stack: +Penny Merriweather (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Penny Merriweather (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Magnitis (/datum/disease/magnitis) + call stack: +Magnitis (/datum/disease/magnitis): cure(0) +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +Warning: further proc crash messages are being suppressed to prevent overload... +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Ashley Taylor (/mob/living/carbon/human) + src: Ashley Taylor (/mob/living/carbon/human) + call stack: +Ashley Taylor (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Ashley Taylor (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=15;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +Sat Jul 14 03:10:48 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1103 + usr: Trisha Galade (/mob/living/carbon/human) + src: Trisha Galade (/mob/living/carbon/human) + call stack: +Trisha Galade (/mob/living/carbon/human): db click("head", 0) +the head (/obj/screen): attack hand(Trisha Galade (/mob/living/carbon/human), 0) +the head (/obj/screen): DblClick(null, null, null) +the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=26;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Trisha Galade (/mob/living/carbon/human) + src: Trisha Galade (/mob/living/carbon/human) + call stack: +Trisha Galade (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Trisha Galade (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=9;icon-y=14;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Jack Napier (/mob/living/carbon/human) + src: Jack Napier (/mob/living/carbon/human) + call stack: +Jack Napier (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Jack Napier (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=9;left=1;scre...") +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: Reese Marcotte (/mob/living/carbon/human) + src: Reese Marcotte (/mob/living/carbon/human) + call stack: +Reese Marcotte (/mob/living/carbon/human): throw at(Asteroid (174,85,5) (/turf/simulated/floor/plating/airless/asteroid), 5, 1) +Delivery chute (/obj/machinery/disposal/deliveryChute): expel(null) +Rebooted server. +Running TG Revision Number: 4060. +Sat Jul 14 04:15:01 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1071 + usr: Flint Steel (/mob/living/carbon/human) + src: Flint Steel (/mob/living/carbon/human) + call stack: +Flint Steel (/mob/living/carbon/human): db click("shoes", 1) +the shoes (/obj/screen): attack hand(Flint Steel (/mob/living/carbon/human), 1) +the shoes (/obj/screen): DblClick(null, null, null) +the shoes (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=18;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Flint Steel (/mob/living/carbon/human) + src: Flint Steel (/mob/living/carbon/human) + call stack: +Flint Steel (/mob/living/carbon/human): db click("belt", 1) +the belt (/obj/screen): attack hand(Flint Steel (/mob/living/carbon/human), 1) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=9;icon-y=12;left=1;scre...") +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: The Flu (/datum/disease/flu) + call stack: +The Flu (/datum/disease/flu): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 +Warning: further proc crash messages are being suppressed to prevent overload... +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 + usr: Trisha Galade (/mob/living/carbon/human) + src: Trisha Galade (/mob/living/carbon/human) + call stack: +Trisha Galade (/mob/living/carbon/human): db click("mask", 0) +the mask (/obj/screen): attack hand(Trisha Galade (/mob/living/carbon/human), 0) +the mask (/obj/screen): DblClick(null, null, null) +the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=16;left=1;scr...") +Rebooted server. +Sat Jul 14 05:27:37 2012 +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Ladybug Honks (/mob/living/carbon/human) + src: Ladybug Honks (/mob/living/carbon/human) + call stack: +Ladybug Honks (/mob/living/carbon/human): db click("storage2", 1) +the storage2 (/obj/screen): attack hand(Ladybug Honks (/mob/living/carbon/human), 1) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Ladybug Honks (/mob/living/carbon/human) + src: Ladybug Honks (/mob/living/carbon/human) + call stack: +Ladybug Honks (/mob/living/carbon/human): db click("storage2", 1) +the storage2 (/obj/screen): attack hand(Ladybug Honks (/mob/living/carbon/human), 1) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=12;left=1;scr...") +Sat Jul 14 06:59:16 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=12;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Malik Graham (/mob/living/carbon/human) + src: Malik Graham (/mob/living/carbon/human) + call stack: +Malik Graham (/mob/living/carbon/human): db click("belt", 1) +the belt (/obj/screen): attack hand(Malik Graham (/mob/living/carbon/human), 1) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=19;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Rocky McDoughboy XIV (/mob/living/carbon/human) + src: Rocky McDoughboy XIV (/mob/living/carbon/human) + call stack: +Rocky McDoughboy XIV (/mob/living/carbon/human): db click("o_clothing", 1) +the o_clothing (/obj/screen): attack hand(Rocky McDoughboy XIV (/mob/living/carbon/human), 1) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=11;left=1;scr...") +Sat Jul 14 08:11:16 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Sly (/mob/living/carbon/human) + src: Sly (/mob/living/carbon/human) + call stack: +Sly (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Sly (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=5;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Sly (/mob/living/carbon/human) + src: Sly (/mob/living/carbon/human) + call stack: +Sly (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Sly (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=11;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Sly (/mob/living/carbon/human) + src: Sly (/mob/living/carbon/human) + call stack: +Sly (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Sly (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=14;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Sher Thorun (/mob/living/carbon/human) + src: Sher Thorun (/mob/living/carbon/human) + call stack: +Sher Thorun (/mob/living/carbon/human): db click("storage1", null) +the storage1 (/obj/screen): attack hand(Sher Thorun (/mob/living/carbon/human), null) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=11;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1127 + usr: Jono Lionhurt (/mob/living/carbon/human) + src: Jono Lionhurt (/mob/living/carbon/human) + call stack: +Jono Lionhurt (/mob/living/carbon/human): db click("i_clothing", null) +the i_clothing (/obj/screen): attack hand(Jono Lionhurt (/mob/living/carbon/human), null) +the i_clothing (/obj/screen): DblClick(null, null, null) +the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=12;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): db click("storage2", 0) +the storage2 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 0) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=14;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): db click("storage2", 0) +the storage2 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 0) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=14;left=1;scr...") +Sat Jul 14 09:17:07 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): db click("storage2", 1) +the storage2 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 1) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): db click("storage2", 1) +the storage2 (/obj/screen): attack hand(Hugh Jazz (/mob/living/carbon/human), 1) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1127 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("i_clothing", 1) +the i_clothing (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 1) +the i_clothing (/obj/screen): DblClick(null, null, null) +the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=31;icon-y=21;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1127 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("i_clothing", 1) +the i_clothing (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 1) +the i_clothing (/obj/screen): DblClick(null, null, null) +the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=23;left=1;scr...") +Sat Jul 14 10:37:02 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1103 + usr: Lord Jamlamin (/mob/living/carbon/human) + src: Lord Jamlamin (/mob/living/carbon/human) + call stack: +Lord Jamlamin (/mob/living/carbon/human): db click("head", 0) +the head (/obj/screen): attack hand(Lord Jamlamin (/mob/living/carbon/human), 0) +the head (/obj/screen): DblClick(null, null, null) +the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=18;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 + usr: Jennifer Ketavim (/mob/living/carbon/human) + src: Jennifer Ketavim (/mob/living/carbon/human) + call stack: +Jennifer Ketavim (/mob/living/carbon/human): db click("mask", 1) +the mask (/obj/screen): attack hand(Jennifer Ketavim (/mob/living/carbon/human), 1) +the mask (/obj/screen): DblClick(null, null, null) +the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=10;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("id", null) +the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), null) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=18;left=1;scr...") +runtime error: Cannot execute null.dropped(). +proc name: drop l hand (/mob/proc/drop_l_hand) + source file: inventory.dm,104 + usr: Jeb Stone (/mob/living/carbon/human) + src: Jeb Stone (/mob/living/carbon/human) + call stack: +Jeb Stone (/mob/living/carbon/human): drop l hand(null) +Jeb Stone (/mob/living/carbon/human): drop item(null) +Jeb Stone (/mob/living/carbon/human): drop item v() +the drop (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=16;left=1;scre...") +Sat Jul 14 12:01:13 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1093 + usr: Thomas Hereford (/mob/living/carbon/human) + src: Thomas Hereford (/mob/living/carbon/human) + call stack: +Thomas Hereford (/mob/living/carbon/human): db click("eyes", 1) +the eyes (/obj/screen): attack hand(Thomas Hereford (/mob/living/carbon/human), 1) +the eyes (/obj/screen): DblClick(null, null, null) +the eyes (/obj/screen): Click(null, "mapwindow.map", "icon-x=25;icon-y=27;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: Hugh Jazz (/mob/living/carbon/human) + src: Hugh Jazz (/mob/living/carbon/human) + call stack: +Hugh Jazz (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: Paul Wolfe (/mob/living/carbon/human) + src: Paul Wolfe (/mob/living/carbon/human) + call stack: +Paul Wolfe (/mob/living/carbon/human): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen) + call stack: +Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen) + call stack: +Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen) + call stack: +Canister: \[O2] (/obj/machinery/portable_atmospherics/canister/oxygen): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air) + call stack: +Canister \[Air] (/obj/machinery/portable_atmospherics/canister/air): throw at(the floor (112,99,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (122,99,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +Warning: further proc crash messages are being suppressed to prevent overload... +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1071 + usr: Dat Bass (/mob/living/carbon/human) + src: Dat Bass (/mob/living/carbon/human) + call stack: +Dat Bass (/mob/living/carbon/human): db click("shoes", 1) +the shoes (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), 1) +the shoes (/obj/screen): DblClick(null, null, null) +the shoes (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=17;left=1;scr...") +Sat Jul 14 13:19:34 2012 +runtime error: list index out of bounds +proc name: mergeConnectedNetworksOnTurf (/obj/structure/cable/proc/mergeConnectedNetworksOnTurf) + source file: cable.dm,520 + usr: Josue Sandford (/mob/living/carbon/human) + src: the power cable (/obj/structure/cable) + call stack: +the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() +the cable coil (/obj/item/weapon/cable_coil/orange): turf place(the plating (87,80,1) (/turf/simulated/floor/plating), Josue Sandford (/mob/living/carbon/human)) +the plating (87,80,1) (/turf/simulated/floor/plating): attackby(the cable coil (/obj/item/weapon/cable_coil/orange), Josue Sandford (/mob/living/carbon/human)) +the plating (87,80,1) (/turf/simulated/floor/plating): DblClick(the plating (87,80,1) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=17;icon-y=28;left=1;scr...") +the plating (87,80,1) (/turf/simulated/floor/plating): DblClick(the plating (87,80,1) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=17;icon-y=28;left=1;scr...") +the plating (87,80,1) (/turf/simulated/floor/plating): Click(the plating (87,80,1) (/turf/simulated/floor/plating), "mapwindow.map", "icon-x=17;icon-y=28;left=1;scr...") +runtime error: Cannot read null.nodes +proc name: merge powernets (/datum/powernet/proc/merge_powernets) + source file: power.dm,401 + usr: Darin Keppel (/mob/living/carbon/human) + src: /datum/powernet (/datum/powernet) + call stack: +/datum/powernet (/datum/powernet): merge powernets(null) +the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() +the cable piece (/obj/item/weapon/cable_coil): cable join(the power cable (/obj/structure/cable), Darin Keppel (/mob/living/carbon/human)) +the power cable (/obj/structure/cable): attackby(the cable piece (/obj/item/weapon/cable_coil), Darin Keppel (/mob/living/carbon/human)) +the power cable (/obj/structure/cable): DblClick(the floor (105,80,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=29;icon-y=17;left=1;scr...") +the power cable (/obj/structure/cable): Click(the floor (105,80,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=29;icon-y=17;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Jace Johnson (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=19;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=19;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Jace Johnson (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=19;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=19;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Jace Johnson (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=12;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Travis Robinson (/mob/living/carbon/human) + src: Travis Robinson (/mob/living/carbon/human) + call stack: +Travis Robinson (/mob/living/carbon/human): db click("belt", 0) +the belt (/obj/screen): attack hand(Travis Robinson (/mob/living/carbon/human), 0) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=12;left=1;scre...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Jace Johnson (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=16;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=16;left=1;scr...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Jace Johnson (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Jace Johnson (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=18;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=18;left=1;scr...") +Sat Jul 14 14:26:55 2012 +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Appendicitis (/datum/disease/appendicitis) + call stack: +Appendicitis (/datum/disease/appendicitis): cure(0) +runtime error: Cannot read null.viruses +proc name: cure (/datum/disease/proc/cure) + source file: disease.dm,156 + usr: null + src: Appendicitis (/datum/disease/appendicitis) + call stack: +Appendicitis (/datum/disease/appendicitis): cure(0) +Rebooted server. +Running TG Revision Number: 4060. +Denied access to 'Intigracy' connecting from 76.246.55.198 +Sat Jul 14 15:41:50 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Derrick Cobb (/mob/living/carbon/human) + src: Derrick Cobb (/mob/living/carbon/human) + call stack: +Derrick Cobb (/mob/living/carbon/human): db click("id", null) +the id (/obj/screen): attack hand(Derrick Cobb (/mob/living/carbon/human), null) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=18;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1103 + usr: Derrick Cobb (/mob/living/carbon/human) + src: Derrick Cobb (/mob/living/carbon/human) + call stack: +Derrick Cobb (/mob/living/carbon/human): db click("head", 1) +the head (/obj/screen): attack hand(Derrick Cobb (/mob/living/carbon/human), 1) +the head (/obj/screen): DblClick(null, null, null) +the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=27;icon-y=1;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Aaden Mitchell (/mob/living/carbon/human) + src: Aaden Mitchell (/mob/living/carbon/human) + call stack: +Aaden Mitchell (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Aaden Mitchell (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=17;left=1;scr...") +runtime error: Cannot create objects of type null. +proc name: Topic (/obj/machinery/computer/rdconsole/Topic) + source file: rdconsole.dm,381 + usr: Cletus Powers (/mob/living/carbon/human) + src: Core R&D Console (/obj/machinery/computer/rdconsole/core) + call stack: +Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x2006613];build=large_G...", /list (/list)) +runtime error: Cannot create objects of type null. +proc name: Topic (/obj/machinery/computer/rdconsole/Topic) + source file: rdconsole.dm,381 + usr: Cletus Powers (/mob/living/carbon/human) + src: Core R&D Console (/obj/machinery/computer/rdconsole/core) + call stack: +Core R&D Console (/obj/machinery/computer/rdconsole/core): Topic("src=\[0x2003615];build=large_G...", /list (/list)) +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Simon Bonkers (/mob/living/carbon/human) + src: Simon Bonkers (/mob/living/carbon/human) + call stack: +Simon Bonkers (/mob/living/carbon/human): db click("o_clothing", 0) +the o_clothing (/obj/screen): attack hand(Simon Bonkers (/mob/living/carbon/human), 0) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=23;icon-y=24;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 + usr: Hreri Bright-Eyed (/mob/living/carbon/human) + src: Hreri Bright-Eyed (/mob/living/carbon/human) + call stack: +Hreri Bright-Eyed (/mob/living/carbon/human): db click("mask", 1) +the mask (/obj/screen): attack hand(Hreri Bright-Eyed (/mob/living/carbon/human), 1) +the mask (/obj/screen): DblClick(null, null, null) +the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=20;left=1;scr...") +Rebooted server. +Sat Jul 14 16:52:03 2012 +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Micheal Simpson (/mob/living/carbon/human) + src: Micheal Simpson (/mob/living/carbon/human) + call stack: +Micheal Simpson (/mob/living/carbon/human): db click("o_clothing", null) +the o_clothing (/obj/screen): attack hand(Micheal Simpson (/mob/living/carbon/human), null) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=12;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Micheal Simpson (/mob/living/carbon/human) + src: Micheal Simpson (/mob/living/carbon/human) + call stack: +Micheal Simpson (/mob/living/carbon/human): db click("o_clothing", null) +the o_clothing (/obj/screen): attack hand(Micheal Simpson (/mob/living/carbon/human), null) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=20;left=1;scr...") +runtime error: undefined variable /client/var/loc +proc name: get turf (/proc/get_turf) + source file: helper_procs.dm,38 + usr: the ghost (/mob/dead/observer) + src: null + call stack: +get turf(Akett (/client)) +the ghost (/mob/dead/observer): New(Akett (/client), 0) +Akett (/client): Ghost() +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Jennifer Ketavim (/mob/living/carbon/human) + src: Jennifer Ketavim (/mob/living/carbon/human) + call stack: +Jennifer Ketavim (/mob/living/carbon/human): db click("belt", 1) +the belt (/obj/screen): attack hand(Jennifer Ketavim (/mob/living/carbon/human), 1) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=15;left=1;scr...") +Rebooted server. +Sat Jul 14 17:57:29 2012 +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Bubba Bellton (/mob/living/carbon/human) + src: Bubba Bellton (/mob/living/carbon/human) + call stack: +Bubba Bellton (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Bubba Bellton (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=23;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Bubble (/mob/living/carbon/human) + src: Bubble (/mob/living/carbon/human) + call stack: +Bubble (/mob/living/carbon/human): db click("storage1", null) +the storage1 (/obj/screen): attack hand(Bubble (/mob/living/carbon/human), null) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Bubble (/mob/living/carbon/human) + src: Bubble (/mob/living/carbon/human) + call stack: +Bubble (/mob/living/carbon/human): db click("storage1", null) +the storage1 (/obj/screen): attack hand(Bubble (/mob/living/carbon/human), null) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 + usr: Bartholomew Williams (/mob/living/carbon/human) + src: Bartholomew Williams (/mob/living/carbon/human) + call stack: +Bartholomew Williams (/mob/living/carbon/human): db click("mask", 0) +the mask (/obj/screen): attack hand(Bartholomew Williams (/mob/living/carbon/human), 0) +the mask (/obj/screen): DblClick(null, null, null) +the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=15;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: Rusty Fraser (/mob/living/carbon/human) + src: Rusty Fraser (/mob/living/carbon/human) + call stack: +Rusty Fraser (/mob/living/carbon/human): db click("back", null) +the back (/obj/screen): attack hand(Rusty Fraser (/mob/living/carbon/human), null) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=5;left=1;scre...") +Rebooted server. +Running TG Revision Number: 4060. +Sat Jul 14 19:13:30 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 + usr: Derrick Cobb (/mob/living/carbon/human) + src: Derrick Cobb (/mob/living/carbon/human) + call stack: +Derrick Cobb (/mob/living/carbon/human): db click("mask", 1) +the mask (/obj/screen): attack hand(Derrick Cobb (/mob/living/carbon/human), 1) +the mask (/obj/screen): DblClick(null, null, null) +the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=29;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: HoP Man (/mob/living/carbon/human) + src: HoP Man (/mob/living/carbon/human) + call stack: +HoP Man (/mob/living/carbon/human): db click("back", 0) +the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=4;icon-y=26;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: HoP Man (/mob/living/carbon/human) + src: HoP Man (/mob/living/carbon/human) + call stack: +HoP Man (/mob/living/carbon/human): db click("back", 0) +the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=26;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: HoP Man (/mob/living/carbon/human) + src: HoP Man (/mob/living/carbon/human) + call stack: +HoP Man (/mob/living/carbon/human): db click("back", 0) +the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) +the back (/obj/screen): DblClick(null, "mapwindow.map", "icon-x=8;icon-y=26;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: HoP Man (/mob/living/carbon/human) + src: HoP Man (/mob/living/carbon/human) + call stack: +HoP Man (/mob/living/carbon/human): db click("back", 0) +the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=26;left=1;shif...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: HoP Man (/mob/living/carbon/human) + src: HoP Man (/mob/living/carbon/human) + call stack: +HoP Man (/mob/living/carbon/human): db click("back", 0) +the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=8;icon-y=26;left=1;ctrl...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: HoP Man (/mob/living/carbon/human) + src: HoP Man (/mob/living/carbon/human) + call stack: +HoP Man (/mob/living/carbon/human): db click("back", 0) +the back (/obj/screen): attack hand(HoP Man (/mob/living/carbon/human), 0) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=3;icon-y=17;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Earl Swabby (/mob/living/carbon/human) + src: Earl Swabby (/mob/living/carbon/human) + call stack: +Earl Swabby (/mob/living/carbon/human): db click("belt", 1) +the belt (/obj/screen): attack hand(Earl Swabby (/mob/living/carbon/human), 1) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=12;icon-y=12;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Slimeria (/mob/living/carbon/human) + src: Slimeria (/mob/living/carbon/human) + call stack: +Slimeria (/mob/living/carbon/human): db click("o_clothing", 1) +the o_clothing (/obj/screen): attack hand(Slimeria (/mob/living/carbon/human), 1) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=28;icon-y=9;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: River Pavlov (/mob/living/carbon/human) + src: River Pavlov (/mob/living/carbon/human) + call stack: +River Pavlov (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(River Pavlov (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=15;left=1;scr...") +runtime error: list index out of bounds +proc name: mergeConnectedNetworksOnTurf (/obj/structure/cable/proc/mergeConnectedNetworksOnTurf) + source file: cable.dm,501 + usr: Tyrion Lannister (/mob/living/carbon/human) + src: the power cable (/obj/structure/cable) + call stack: +the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() +the cable piece (/obj/item/weapon/cable_coil): cable join(the power cable (/obj/structure/cable), Tyrion Lannister (/mob/living/carbon/human)) +the power cable (/obj/structure/cable): attackby(the cable piece (/obj/item/weapon/cable_coil), Tyrion Lannister (/mob/living/carbon/human)) +the power cable (/obj/structure/cable): DblClick(the floor (174,138,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=17;left=1;scr...") +the power cable (/obj/structure/cable): Click(the floor (174,138,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=17;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Slimeria (/mob/living/carbon/human) + src: Slimeria (/mob/living/carbon/human) + call stack: +Slimeria (/mob/living/carbon/human): db click("belt", 0) +the belt (/obj/screen): attack hand(Slimeria (/mob/living/carbon/human), 0) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=24;left=1;scr...") +Sat Jul 14 20:29:42 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Urist McAssistant (/mob/living/carbon/human) + src: Urist McAssistant (/mob/living/carbon/human) + call stack: +Urist McAssistant (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Urist McAssistant (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=17;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Jennifer Ketavim (/mob/living/carbon/human) + src: Jennifer Ketavim (/mob/living/carbon/human) + call stack: +Jennifer Ketavim (/mob/living/carbon/human): db click("o_clothing", 0) +the o_clothing (/obj/screen): attack hand(Jennifer Ketavim (/mob/living/carbon/human), 0) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=5;left=1;scre...") +Sat Jul 14 21:49:49 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Jennifer Ketavim (/mob/living/carbon/human) + src: Jennifer Ketavim (/mob/living/carbon/human) + call stack: +Jennifer Ketavim (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Jennifer Ketavim (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=30;icon-y=20;left=1;scr...") +runtime error: Cannot read null.fields +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,263 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(/list (/list), /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.fields +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,263 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(/list (/list), /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Log%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.fields +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,263 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(/list (/list), /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=Return", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=Return", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.fields +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,263 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(/list (/list), /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=New%20...", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=New%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 + usr: Wintermute (/mob/living/silicon/ai) + src: null + call stack: +mergeRecordLists(null, /list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +sortRecord(/list (/list), "name", 1) +Security Records (/obj/machinery/computer/secure_data): attack hand(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): attack ai(Wintermute (/mob/living/silicon/ai)) +Security Records (/obj/machinery/computer/secure_data): updateUsrDialog() +Security Records (/obj/machinery/computer/secure_data): Topic("src=\[0x2008e1c];choice=New%20...", /list (/list)) +Saar Cavaliers (/client): Topic("src=\[0x2008e1c];choice=New%20...", /list (/list), Security Records (/obj/machinery/computer/secure_data)) +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: Cannot read null.flags +proc name: relaymove (/obj/effect/dummy/spell_jaunt/relaymove) + source file: ethereal_jaunt.dm,81 +runtime error: undefined variable /client/var/loc +proc name: get turf (/proc/get_turf) + source file: helper_procs.dm,38 +runtime error: Cannot read null.fields +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,263 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.fields +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,263 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.len +proc name: mergeRecordLists (/proc/mergeRecordLists) + source file: helpers.dm,260 +runtime error: Cannot read null.reagents +proc name: fire syringe (/obj/item/weapon/gun/syringe/proc/fire_syringe) + source file: Chemistry-Tools.dm,462 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Reuben Garratt (/mob/living/carbon/human) + src: Reuben Garratt (/mob/living/carbon/human) + call stack: +Reuben Garratt (/mob/living/carbon/human): db click("id", null) +the id (/obj/screen): attack hand(Reuben Garratt (/mob/living/carbon/human), null) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=15;left=1;scr...") +Sat Jul 14 23:05:19 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Alice Ravensdale (/mob/living/carbon/human) + src: Alice Ravensdale (/mob/living/carbon/human) + call stack: +Alice Ravensdale (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Alice Ravensdale (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=18;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Eric Hunter (/mob/living/carbon/human) + src: Eric Hunter (/mob/living/carbon/human) + call stack: +Eric Hunter (/mob/living/carbon/human): db click("o_clothing", 1) +the o_clothing (/obj/screen): attack hand(Eric Hunter (/mob/living/carbon/human), 1) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=25;icon-y=14;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Samuel York (/mob/living/carbon/human) + src: Samuel York (/mob/living/carbon/human) + call stack: +Samuel York (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Samuel York (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=15;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1103 + usr: MCEyePop (/mob/living/carbon/human) + src: MCEyePop (/mob/living/carbon/human) + call stack: +MCEyePop (/mob/living/carbon/human): db click("head", 1) +the head (/obj/screen): attack hand(MCEyePop (/mob/living/carbon/human), 1) +the head (/obj/screen): DblClick(null, null, null) +the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=25;icon-y=11;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=29;icon-y=17;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=21;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=21;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Alexander Delapour (/mob/living/carbon/human) + src: Alexander Delapour (/mob/living/carbon/human) + call stack: +Alexander Delapour (/mob/living/carbon/human): db click("belt", 1) +the belt (/obj/screen): attack hand(Alexander Delapour (/mob/living/carbon/human), 1) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=23;icon-y=13;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: Roman Sheets (/mob/living/carbon/human) + src: Roman Sheets (/mob/living/carbon/human) + call stack: +Roman Sheets (/mob/living/carbon/human): db click("back", null) +the back (/obj/screen): attack hand(Roman Sheets (/mob/living/carbon/human), null) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=10;icon-y=12;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1127 + usr: Roman Sheets (/mob/living/carbon/human) + src: Roman Sheets (/mob/living/carbon/human) + call stack: +Roman Sheets (/mob/living/carbon/human): db click("i_clothing", 0) +the i_clothing (/obj/screen): attack hand(Roman Sheets (/mob/living/carbon/human), 0) +the i_clothing (/obj/screen): DblClick(null, null, null) +the i_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=18;left=1;scr...") +Sun Jul 15 00:17:04 2012 +runtime error: unexpected stat +proc name: Stat (/mob/living/silicon/robot/Stat) + source file: robot.dm,227 + usr: Iroquois Pliskin (/mob/living/carbon/human) + src: Miner Cyborg -770 (/mob/living/silicon/robot) + call stack: +Miner Cyborg -770 (/mob/living/silicon/robot): Stat() +Borg module reset board (/obj/item/borg/upgrade/reset): action(Miner Cyborg -770 (/mob/living/silicon/robot)) +Borg module reset board (/obj/item/borg/upgrade/reset): action(Miner Cyborg -770 (/mob/living/silicon/robot)) +Miner Cyborg -770 (/mob/living/silicon/robot): attackby(Borg module reset board (/obj/item/borg/upgrade/reset), Iroquois Pliskin (/mob/living/carbon/human)) +Miner Cyborg -770 (/mob/living/silicon/robot): DblClick(the floor (98,88,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=10;left=1;scr...") +Miner Cyborg -770 (/mob/living/silicon/robot): Click(the floor (98,88,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=10;left=1;scr...") +runtime error: unexpected stat +proc name: Stat (/mob/living/silicon/robot/Stat) + source file: robot.dm,227 + usr: Iroquois Pliskin (/mob/living/carbon/human) + src: Engineering Cyborg 770 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg 770 (/mob/living/silicon/robot): Stat() +Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Engineering Cyborg 770 (/mob/living/silicon/robot)) +Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Engineering Cyborg 770 (/mob/living/silicon/robot)) +Engineering Cyborg 770 (/mob/living/silicon/robot): attackby(Borg VTEC Module (/obj/item/borg/upgrade/vtec), Iroquois Pliskin (/mob/living/carbon/human)) +Engineering Cyborg 770 (/mob/living/silicon/robot): DblClick(the floor (98,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=12;left=1;scr...") +Engineering Cyborg 770 (/mob/living/silicon/robot): Click(the floor (98,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=12;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: MCEyePop (/mob/living/carbon/human) + src: MCEyePop (/mob/living/carbon/human) + call stack: +MCEyePop (/mob/living/carbon/human): db click("belt", 1) +the belt (/obj/screen): attack hand(MCEyePop (/mob/living/carbon/human), 1) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: MCEyePop (/mob/living/carbon/human) + src: MCEyePop (/mob/living/carbon/human) + call stack: +MCEyePop (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(MCEyePop (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=13;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: Roman Sheets as (Petrov Petrov... (/mob/living/carbon/human) + src: Roman Sheets as (Petrov Petrov... (/mob/living/carbon/human) + call stack: +Roman Sheets as (Petrov Petrov... (/mob/living/carbon/human): db click("back", 1) +the back (/obj/screen): attack hand(Roman Sheets as (Petrov Petrov... (/mob/living/carbon/human), 1) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=13;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Earl Swabby (/mob/living/carbon/human) + src: Earl Swabby (/mob/living/carbon/human) + call stack: +Earl Swabby (/mob/living/carbon/human): db click("storage1", null) +the storage1 (/obj/screen): attack hand(Earl Swabby (/mob/living/carbon/human), null) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=5;icon-y=19;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 + usr: Smoke Shaw (/mob/living/carbon/human) + src: Smoke Shaw (/mob/living/carbon/human) + call stack: +Smoke Shaw (/mob/living/carbon/human): db click("mask", 0) +the mask (/obj/screen): attack hand(Smoke Shaw (/mob/living/carbon/human), 0) +the mask (/obj/screen): DblClick(null, null, null) +the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=13;left=1;scr...") +runtime error: Cannot read null.reagents +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,748 + usr: Dutch Hargrave (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Dutch Hargrave (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=11;left=1;scr...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=18;icon-y=11;left=1;scr...") +runtime error: Cannot read null.reagents +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,748 + usr: Dutch Hargrave (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Dutch Hargrave (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=6;left=1;scre...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=17;icon-y=6;left=1;scre...") +runtime error: Cannot read null.total_volume +proc name: attackby (/obj/machinery/reagentgrinder/attackby) + source file: Chemistry-Machinery.dm,751 + usr: Dutch Hargrave (/mob/living/carbon/human) + src: All-In-One Grinder (/obj/machinery/reagentgrinder) + call stack: +All-In-One Grinder (/obj/machinery/reagentgrinder): attackby(Plant Bag (/obj/item/weapon/plantbag), Dutch Hargrave (/mob/living/carbon/human)) +All-In-One Grinder (/obj/machinery/reagentgrinder): DblClick(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=20;icon-y=6;left=1;scre...") +All-In-One Grinder (/obj/machinery/reagentgrinder): Click(the floor (154,134,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=20;icon-y=6;left=1;scre...") +Rebooted server. +Running TG Revision Number: 4060. +Sun Jul 15 01:27:07 2012 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Crate (/obj/structure/closet/crate) + call stack: +Crate (/obj/structure/closet/crate): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Crate (/obj/structure/closet/crate) + call stack: +Crate (/obj/structure/closet/crate): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: the emergency closet (/obj/structure/closet/emcloset) + call stack: +the emergency closet (/obj/structure/closet/emcloset): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 + usr: 0 + src: Crate (/obj/structure/closet/crate) + call stack: +Crate (/obj/structure/closet/crate): throw at(the floor (172,138,1) (/turf/simulated/floor), 100, 1) +the disposal pipe (/obj/structure/disposalpipe/trunk): expel(null, the floor (182,138,1) (/turf/simulated/floor), 8) +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +runtime error: Cannot read null.has_gravity +proc name: throw at (/atom/movable/proc/throw_at) + source file: throwing.dm,194 +Warning: further proc crash messages are being suppressed to prevent overload... +Rebooted server. +Sun Jul 15 02:33:41 2012 +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Earl Swabby (/mob/living/carbon/human) + src: Earl Swabby (/mob/living/carbon/human) + call stack: +Earl Swabby (/mob/living/carbon/human): db click("storage2", null) +the storage2 (/obj/screen): attack hand(Earl Swabby (/mob/living/carbon/human), null) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=23;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1093 + usr: Lord Gwyn (/mob/living/carbon/human) + src: Lord Gwyn (/mob/living/carbon/human) + call stack: +Lord Gwyn (/mob/living/carbon/human): db click("eyes", 1) +the eyes (/obj/screen): attack hand(Lord Gwyn (/mob/living/carbon/human), 1) +the eyes (/obj/screen): DblClick(null, null, null) +the eyes (/obj/screen): Click(null, "mapwindow.map", "icon-x=9;icon-y=18;left=1;scre...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Crunk Junksworth (/mob/living/carbon/human) + src: Crunk Junksworth (/mob/living/carbon/human) + call stack: +Crunk Junksworth (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Crunk Junksworth (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=20;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Crunk Junksworth (/mob/living/carbon/human) + src: Crunk Junksworth (/mob/living/carbon/human) + call stack: +Crunk Junksworth (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Crunk Junksworth (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=19;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +Denied access to 'StevenRodriguez' connecting from 98.64.162.60 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Coyo (/mob/living/carbon/human) + src: Coyo (/mob/living/carbon/human) + call stack: +Coyo (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Coyo (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=13;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Yahn Deir (/mob/living/carbon/human) + src: Yahn Deir (/mob/living/carbon/human) + call stack: +Yahn Deir (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Yahn Deir (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=22;icon-y=10;left=1;scr...") +Sun Jul 15 03:41:55 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1048 + usr: Samuel York (/mob/living/carbon/human) + src: Samuel York (/mob/living/carbon/human) + call stack: +Samuel York (/mob/living/carbon/human): db click("o_clothing", 1) +the o_clothing (/obj/screen): attack hand(Samuel York (/mob/living/carbon/human), 1) +the o_clothing (/obj/screen): DblClick(null, null, null) +the o_clothing (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") +### VarEdit by Doohl: /obj/effect/step_trigger/teleporter affect_ghosts=1 +### VarEdit by Doohl: /obj/effect/step_trigger/teleporter teleport_x=239 +### VarEdit by Doohl: /obj/effect/step_trigger/teleporter teleport_x=239 +### VarEdit by Doohl: /obj/effect/step_trigger/teleporter teleport_y=12 +### VarEdit by Doohl: /obj/effect/step_trigger/teleporter teleport_z=2 +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Bubble (/mob/living/carbon/human) + src: Bubble (/mob/living/carbon/human) + call stack: +Bubble (/mob/living/carbon/human): db click("storage2", 0) +the storage2 (/obj/screen): attack hand(Bubble (/mob/living/carbon/human), 0) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=13;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Tyson Roby (/mob/living/carbon/human) + src: Tyson Roby (/mob/living/carbon/human) + call stack: +Tyson Roby (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Tyson Roby (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=17;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Tyson Roby (/mob/living/carbon/human) + src: Tyson Roby (/mob/living/carbon/human) + call stack: +Tyson Roby (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Tyson Roby (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Tyson Roby (/mob/living/carbon/human) + src: Tyson Roby (/mob/living/carbon/human) + call stack: +Tyson Roby (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Tyson Roby (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=16;left=1;scr...") +runtime error: Cannot read null.w_class +proc name: attackby (/obj/item/weapon/storage/attackby) + source file: storage.dm,187 + usr: Samuel York (/mob/living/carbon/human) + src: the backpack (/obj/item/weapon/storage/backpack) + call stack: +the backpack (/obj/item/weapon/storage/backpack): attackby(null, Samuel York (/mob/living/carbon/human)) +the backpack (/obj/item/weapon/storage/backpack): attackby(null, Samuel York (/mob/living/carbon/human)) +the backpack (/obj/item/weapon/storage/backpack): DblClick(null, "mapwindow.map", "icon-x=17;icon-y=18;left=1;scr...") +the backpack (/obj/item/weapon/storage/backpack): Click(null, "mapwindow.map", "icon-x=17;icon-y=18;left=1;scr...") +Rebooted server. +Sun Jul 15 04:43:18 2012 +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=26;icon-y=21;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=20;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1035 + usr: Michigan Slim (/mob/living/carbon/human) + src: Michigan Slim (/mob/living/carbon/human) + call stack: +Michigan Slim (/mob/living/carbon/human): db click("back", 0) +the back (/obj/screen): attack hand(Michigan Slim (/mob/living/carbon/human), 0) +the back (/obj/screen): DblClick(null, null, null) +the back (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=19;left=1;scr...") +Rebooted server. +Sun Jul 15 05:48:07 2012 +Running TG Revision Number: 4060. +### VarEdit by Yinadele: /mob/living/simple_animal/cat/Runtime ckey=bossname +### VarEdit by Yinadele: /mob/living/simple_animal/cat/Runtime universal_speak=0 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1083 + usr: Jimz Ruztlfoot (/mob/living/carbon/human) + src: Jimz Ruztlfoot (/mob/living/carbon/human) + call stack: +Jimz Ruztlfoot (/mob/living/carbon/human): db click("belt", 0) +the belt (/obj/screen): attack hand(Jimz Ruztlfoot (/mob/living/carbon/human), 0) +the belt (/obj/screen): DblClick(null, null, null) +the belt (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=10;left=1;scr...") +Rebooted server. +Sun Jul 15 06:57:20 2012 +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1061 + usr: Quinton Bould (/mob/living/carbon/human) + src: Quinton Bould (/mob/living/carbon/human) + call stack: +Quinton Bould (/mob/living/carbon/human): db click("gloves", 1) +the gloves (/obj/screen): attack hand(Quinton Bould (/mob/living/carbon/human), 1) +the gloves (/obj/screen): DblClick(null, null, null) +the gloves (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=19;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1023 + usr: Yahn Deir (/mob/living/carbon/human) + src: Yahn Deir (/mob/living/carbon/human) + call stack: +Yahn Deir (/mob/living/carbon/human): db click("mask", 1) +the mask (/obj/screen): attack hand(Yahn Deir (/mob/living/carbon/human), 1) +the mask (/obj/screen): DblClick(null, null, null) +the mask (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=20;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Dutch Hargrave (/mob/living/carbon/human) + src: Dutch Hargrave (/mob/living/carbon/human) + call stack: +Dutch Hargrave (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Dutch Hargrave (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=16;left=1;scr...") +Sun Jul 15 08:26:36 2012 +runtime error: Cannot read null.nodes +proc name: merge powernets (/datum/powernet/proc/merge_powernets) + source file: power.dm,401 + usr: Stephen Cox (/mob/living/carbon/human) + src: /datum/powernet (/datum/powernet) + call stack: +/datum/powernet (/datum/powernet): merge powernets(null) +the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() +the cable coil (/obj/item/weapon/cable_coil): turf place(the floor (87,115,1) (/turf/simulated/floor), Stephen Cox (/mob/living/carbon/human)) +the floor (87,115,1) (/turf/simulated/floor): attackby(the cable coil (/obj/item/weapon/cable_coil), Stephen Cox (/mob/living/carbon/human)) +the floor (87,115,1) (/turf/simulated/floor): DblClick(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") +the floor (87,115,1) (/turf/simulated/floor): DblClick(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") +the floor (87,115,1) (/turf/simulated/floor): Click(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") +runtime error: Cannot read null.nodes +proc name: merge powernets (/datum/powernet/proc/merge_powernets) + source file: power.dm,401 + usr: Stephen Cox (/mob/living/carbon/human) + src: /datum/powernet (/datum/powernet) + call stack: +/datum/powernet (/datum/powernet): merge powernets(null) +the power cable (/obj/structure/cable): mergeConnectedNetworksOnTurf() +the cable coil (/obj/item/weapon/cable_coil): turf place(the floor (87,115,1) (/turf/simulated/floor), Stephen Cox (/mob/living/carbon/human)) +the floor (87,115,1) (/turf/simulated/floor): attackby(the cable coil (/obj/item/weapon/cable_coil), Stephen Cox (/mob/living/carbon/human)) +the floor (87,115,1) (/turf/simulated/floor): DblClick(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") +the floor (87,115,1) (/turf/simulated/floor): DblClick(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") +the floor (87,115,1) (/turf/simulated/floor): Click(the floor (87,115,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=26;icon-y=18;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1166 + usr: Clarisse Holderbach (/mob/living/carbon/human) + src: Clarisse Holderbach (/mob/living/carbon/human) + call stack: +Clarisse Holderbach (/mob/living/carbon/human): db click("storage2", null) +the storage2 (/obj/screen): attack hand(Clarisse Holderbach (/mob/living/carbon/human), null) +the storage2 (/obj/screen): DblClick(null, null, null) +the storage2 (/obj/screen): Click(null, "mapwindow.map", "icon-x=17;icon-y=15;left=1;scr...") +Sun Jul 15 09:41:02 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1071 + usr: Samantha Kendrick (/mob/living/carbon/human) + src: Samantha Kendrick (/mob/living/carbon/human) + call stack: +Samantha Kendrick (/mob/living/carbon/human): db click("shoes", 1) +the shoes (/obj/screen): attack hand(Samantha Kendrick (/mob/living/carbon/human), 1) +the shoes (/obj/screen): DblClick(null, null, null) +the shoes (/obj/screen): Click(null, "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") +### VarEdit by Scaredofshadows: /mob/living/simple_animal/corgi/Ian ckey=laharlmontgommery +Sun Jul 15 10:43:09 2012 +### VarEdit by Scaredofshadows: /mob/living/simple_animal/cat/Runtime ckey=drlareku +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1093 + usr: Rowdy Randy (/mob/living/carbon/human) + src: Rowdy Randy (/mob/living/carbon/human) + call stack: +Rowdy Randy (/mob/living/carbon/human): db click("eyes", 0) +the eyes (/obj/screen): attack hand(Rowdy Randy (/mob/living/carbon/human), 0) +the eyes (/obj/screen): DblClick(null, null, null) +the eyes (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=20;left=1;scr...") +### VarEdit by Scaredofshadows: /mob/living/simple_animal/cat/Runtime canmove=1 +### VarEdit by Scaredofshadows: /mob/living/simple_animal/parrot/DrProfessor ckey=kyora473 +runtime error: Cannot read null.amount +proc name: attackby (/obj/machinery/constructable_frame/machine_frame/attackby) + source file: constructable_frame.dm,27 + usr: Quinton Bould (/mob/living/carbon/human) + src: the machine frame (/obj/machinery/constructable_frame/machine_frame) + call stack: +the machine frame (/obj/machinery/constructable_frame/machine_frame): attackby(null, Quinton Bould (/mob/living/carbon/human)) +the machine frame (/obj/machinery/constructable_frame/machine_frame): DblClick(the floor (98,69,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=23;icon-y=16;left=1;scr...") +the machine frame (/obj/machinery/constructable_frame/machine_frame): Click(the floor (98,69,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=23;icon-y=16;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1103 + usr: Quinton Bould (/mob/living/carbon/human) + src: Quinton Bould (/mob/living/carbon/human) + call stack: +Quinton Bould (/mob/living/carbon/human): db click("head", 1) +the head (/obj/screen): attack hand(Quinton Bould (/mob/living/carbon/human), 1) +the head (/obj/screen): DblClick(null, null, null) +the head (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=28;left=1;scr...") +runtime error: Cannot read null.loc +proc name: Life (/mob/living/simple_animal/corgi/Ian/Life) + source file: corgi.dm,304 + usr: null + src: Captain Ian (/mob/living/simple_animal/corgi/Ian) + call stack: +Captain Ian (/mob/living/simple_animal/corgi/Ian): Life() +/datum/controller/game_control... (/datum/controller/game_controller): process() +### VarEdit by Scaredofshadows: /mob/living/simple_animal/corgi/Ian real_name=Fluffum-Wuffums +Rebooted server. +Running TG Revision Number: 4060. +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Dat Bass (/mob/living/carbon/human) + src: Dat Bass (/mob/living/carbon/human) + call stack: +Dat Bass (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Dat Bass (/mob/living/carbon/human) + src: Dat Bass (/mob/living/carbon/human) + call stack: +Dat Bass (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=15;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Dat Bass (/mob/living/carbon/human) + src: Dat Bass (/mob/living/carbon/human) + call stack: +Dat Bass (/mob/living/carbon/human): db click("storage1", 0) +the storage1 (/obj/screen): attack hand(Dat Bass (/mob/living/carbon/human), 0) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=14;icon-y=15;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=18;icon-y=13;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=9;left=1;scre...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=9;left=1;scre...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=21;icon-y=11;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=19;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=19;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=20;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=20;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=15;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=12;left=1;scr...") +runtime error: Cannot read null.broadcasting +proc name: radio menu (/mob/living/silicon/robot/proc/radio_menu) + source file: robot.dm,857 + usr: Engineering Cyborg -432 (/mob/living/silicon/robot) + src: Engineering Cyborg -432 (/mob/living/silicon/robot) + call stack: +Engineering Cyborg -432 (/mob/living/silicon/robot): radio menu() +the radio (/obj/screen): Click(null, "mapwindow.map", "icon-x=19;icon-y=11;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1061 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1071 +Sun Jul 15 12:02:37 2012 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 +runtime error: Cannot modify null.status. +proc name: death (/mob/living/silicon/robot/death) + source file: death.dm,51 +runtime error: Cannot create objects of type null. +proc name: Topic (/obj/machinery/computer/rdconsole/Topic) + source file: rdconsole.dm,381 +Rebooted server. +Running TG Revision Number: 4060. +Denied access to 'Cronzefir' connecting from 79.113.206.100 +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Zeus Geesenheimer (/mob/living/carbon/human) + src: Zeus Geesenheimer (/mob/living/carbon/human) + call stack: +Zeus Geesenheimer (/mob/living/carbon/human): db click("id", 0) +the id (/obj/screen): attack hand(Zeus Geesenheimer (/mob/living/carbon/human), 0) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=20;icon-y=13;left=1;scr...") +Rebooted server. +Running TG Revision Number: 4060. +Sun Jul 15 13:06:40 2012 +runtime error: unexpected stat +proc name: Stat (/mob/living/silicon/robot/Stat) + source file: robot.dm,227 + usr: Bill Kerman (/mob/living/carbon/human) + src: Miner Cyborg -734 (/mob/living/silicon/robot) + call stack: +Miner Cyborg -734 (/mob/living/silicon/robot): Stat() +Mining Borg Jetpack (/obj/item/borg/upgrade/jetpack): action(Miner Cyborg -734 (/mob/living/silicon/robot)) +Mining Borg Jetpack (/obj/item/borg/upgrade/jetpack): action(Miner Cyborg -734 (/mob/living/silicon/robot)) +Miner Cyborg -734 (/mob/living/silicon/robot): attackby(Mining Borg Jetpack (/obj/item/borg/upgrade/jetpack), Bill Kerman (/mob/living/carbon/human)) +Miner Cyborg -734 (/mob/living/silicon/robot): DblClick(the floor (100,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=16;left=1;scr...") +Miner Cyborg -734 (/mob/living/silicon/robot): Click(the floor (100,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=16;left=1;scr...") +runtime error: unexpected stat +proc name: Stat (/mob/living/silicon/robot/Stat) + source file: robot.dm,227 + usr: Bill Kerman (/mob/living/carbon/human) + src: Miner Cyborg -734 (/mob/living/silicon/robot) + call stack: +Miner Cyborg -734 (/mob/living/silicon/robot): Stat() +Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Miner Cyborg -734 (/mob/living/silicon/robot)) +Borg VTEC Module (/obj/item/borg/upgrade/vtec): action(Miner Cyborg -734 (/mob/living/silicon/robot)) +Miner Cyborg -734 (/mob/living/silicon/robot): attackby(Borg VTEC Module (/obj/item/borg/upgrade/vtec), Bill Kerman (/mob/living/carbon/human)) +Miner Cyborg -734 (/mob/living/silicon/robot): DblClick(the floor (99,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") +Miner Cyborg -734 (/mob/living/silicon/robot): Click(the floor (99,87,1) (/turf/simulated/floor), "mapwindow.map", "icon-x=16;icon-y=19;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1142 + usr: Unknown (/mob/living/carbon/human) + src: Unknown (/mob/living/carbon/human) + call stack: +Unknown (/mob/living/carbon/human): db click("id", 1) +the id (/obj/screen): attack hand(Unknown (/mob/living/carbon/human), 1) +the id (/obj/screen): DblClick(null, null, null) +the id (/obj/screen): Click(null, "mapwindow.map", "icon-x=11;icon-y=14;left=1;scr...") +runtime error: Cannot read null.slot_flags +proc name: db click (/mob/living/carbon/human/db_click) + source file: inventory.dm,1154 + usr: Rocky McDoughboy XIV (/mob/living/carbon/human) + src: Rocky McDoughboy XIV (/mob/living/carbon/human) + call stack: +Rocky McDoughboy XIV (/mob/living/carbon/human): db click("storage1", 1) +the storage1 (/obj/screen): attack hand(Rocky McDoughboy XIV (/mob/living/carbon/human), 1) +the storage1 (/obj/screen): DblClick(null, null, null) +the storage1 (/obj/screen): Click(null, "mapwindow.map", "icon-x=15;icon-y=14;left=1;scr...") +runtime error: Cannot read null.loc +proc name: Life (/mob/living/simple_animal/corgi/Ian/Life) + source file: corgi.dm,304 + usr: null + src: Ian (/mob/living/simple_animal/corgi/Ian) + call stack: +Ian (/mob/living/simple_animal/corgi/Ian): Life() +/datum/controller/game_control... (/datum/controller/game_controller): process() +Rebooted server. +Running TG Revision Number: 4060. +Stopped server. + +*** End Log: Sun Jul 15 13:55:08 2012 ****** Begin Log: Mon Oct 05 18:58:26 2015 *** +Mon Oct 05 19:01:11 2015 +World opened on network port 49626. +Welcome BYOND! (5.0 Beta Version 508.1294) +Running /tg/ revision: +2015-10-06 +e2ed83b6a595fe0d6078fa75bfde85b3fa6c392b +Path : /obj/machinery/mech_bay_recharge_port +Failures : 1 +Path : /obj/effect/landmark/start +Failures : 2 + +Rebooted server. +Running /tg/ revision: +2015-10-06 +e2ed83b6a595fe0d6078fa75bfde85b3fa6c392b +Path : /obj/effect/landmark/start +Failures : 2 + +Rebooted server. +Running /tg/ revision: +2015-10-06 +e2ed83b6a595fe0d6078fa75bfde85b3fa6c392b +Tue Oct 06 02:28:25 2015 +runtime error: pick() from empty list +proc name: New (/obj/effect/hallucination/whispers/New) + source file: Hallucination.dm,524 + usr: null + src: the whispers (/obj/effect/hallucination/whispers) + call stack: +the whispers (/obj/effect/hallucination/whispers): New(the plating (103,105,1) (/turf/simulated/floor/plating), the monkey (525) (/mob/living/carbon/monkey)) +the monkey (525) (/mob/living/carbon/monkey): hallucinate("whispers") +the monkey (525) (/mob/living/carbon/monkey): handle hallucinations() +the monkey (525) (/mob/living/carbon/monkey): handle status effects() +Tue Oct 06 06:07:02 2015 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 + usr: null + src: the space vines (/obj/effect/spacevine) + call stack: +the space vines (/obj/effect/spacevine): spread() +the spacevine controller (/obj/effect/spacevine_controller): process(20) +Objects (/datum/subsystem/objects): fire() +/datum/controller/game_control... (/datum/controller/game_controller): process() +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +Tue Oct 06 07:07:36 2015 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +runtime error: Cannot execute null.Enter(). +proc name: spread (/obj/effect/spacevine/proc/spread) + source file: spacevine.dm,521 +Warning: further proc crash messages are being suppressed to prevent overload... diff --git a/tools/Runtime Condenser/Output.txt b/tools/Runtime Condenser/Output.txt index c1a84c8103b4..4d11b75ca3ba 100644 --- a/tools/Runtime Condenser/Output.txt +++ b/tools/Runtime Condenser/Output.txt @@ -1,438 +1,438 @@ -Note: The source file, src and usr are all from the FIRST of the identical runtimes. Everything else is cropped. - -Total unique runtimes: 28 -Total runtimes: 124723 - -Total unique hard deletions: 108 -Total hard deletions: 1080 - -** Runtimes ** - -The following runtime has occurred 123121 time(s). -runtime error: Component is missing a pipenet! Rebuilding... - - -The following runtime has occurred 709 time(s). -runtime error: wrong type of value for list -proc name: updateghostimages (/mob/dead/observer/proc/updateghostimages) - source file: observer.dm,488 - usr: (src) - src: Argon IXV (/mob/dead/observer) - src.loc: the asteroid sand (217,137,8) (/turf/open/floor/plating/asteroid) - - -The following runtime has occurred 696 time(s). -runtime error: Cannot read null.air -proc name: return air (/obj/machinery/atmospherics/pipe/return_air) - source file: pipes.dm,58 - usr: null - src: the air supply pipe (/obj/machinery/atmospherics/pipe/manifold4w/supply/visible) - src.loc: the floor (169,130,1) (/turf/open/floor/plasteel) - - -The following runtime has occurred 48 time(s). -runtime error: bad resource file -proc name: send asset (/proc/send_asset) - source file: asset_cache.dm,44 - usr: Mariabella Pycroft-Crane (/mob/living/carbon/human) - src: null - - -The following runtime has occurred 30 time(s). -runtime error: list index out of bounds -proc name: GetGreaterChild (/Heap/proc/GetGreaterChild) - source file: heap.dm,62 - usr: null - src: /Heap (/Heap) - - -The following runtime has occurred 21 time(s). -runtime error: undefined proc or verb /turf/closed/wall/shuttle/high pressure movements(). - - -The following runtime has occurred 15 time(s). -runtime error: Simple animal being instantiated in nullspace -proc name: stack trace (/proc/stack_trace) - source file: unsorted.dm,1358 - usr: the Securitron (/mob/living/simple_animal/bot/secbot) - src: null - - -The following runtime has occurred 12 time(s). -runtime error: Cannot read null.viewavail -proc name: record (/obj/machinery/computer/monitor/proc/record) - source file: monitor.dm,41 - usr: null - src: Engineering Power Monitoring C... (/obj/machinery/computer/monitor) - src.loc: space (154,151,1) (/turf/open/space) - - -The following runtime has occurred 11 time(s). -runtime error: Cannot read null.selection_color -proc name: SetChoices (/datum/preferences/proc/SetChoices) - source file: preferences.dm,543 - usr: Sweetestbro (/mob/new_player) - src: /datum/preferences (/datum/preferences) - - -The following runtime has occurred 10 time(s). -runtime error: Cannot execute null.add turf(). -proc name: process cell (/turf/open/process_cell) - source file: LINDA_turf_tile.dm,172 - usr: null - src: the plating (102,135,1) (/turf/open/floor/plating) - - -The following runtime has occurred 9 time(s). -runtime error: Cannot read null.thrownby -proc name: hitby (/mob/living/carbon/human/hitby) - source file: human_defense.dm,353 - usr: the monkey (662) (/mob/living/carbon/monkey) - src: Sydney Hujsak (/mob/living/carbon/human) - - -The following runtime has occurred 8 time(s). -runtime error: Cannot execute null.HasProximity(). -proc name: Entered (/turf/Entered) - source file: turf.dm,95 - usr: 0 - src: the floor (99,147,1) (/turf/open/floor/plasteel) - - -The following runtime has occurred 5 time(s). -runtime error: bad client -proc name: open (/datum/browser/proc/open) - source file: browser.dm,106 - usr: Timothy Catleay (/mob/living/carbon/human) - src: /datum/browser (/datum/browser) - - -The following runtime has occurred 4 time(s). -runtime error: return_file_text(): File not found - - -The following runtime has occurred 4 time(s). -runtime error: Cannot execute null.merge(). -proc name: assume air (/turf/open/assume_air) - source file: LINDA_turf_tile.dm,56 - usr: null - src: the engraved floor (95,49,5) (/turf/open/floor/engine/cult) - - -The following runtime has occurred 3 time(s). -runtime error: Cannot read null.gases -proc name: tile graphic (/turf/open/proc/tile_graphic) - source file: LINDA_turf_tile.dm,110 - usr: null - src: the floor (81,52,5) (/turf/open/floor/plasteel/freezer) - - -The following runtime has occurred 3 time(s). -runtime error: undefined proc or verb /turf/closed/wall/high pressure movements(). - - -The following runtime has occurred 2 time(s). -runtime error: Cannot modify null.loc. -proc name: ui act (/obj/machinery/suit_storage_unit/ui_act) - source file: suit_storage_unit.dm,359 - usr: Cy Carter (/mob/living/carbon/human) - src: the suit storage unit (/obj/machinery/suit_storage_unit/standard_unit) - - -The following runtime has occurred 2 time(s). -runtime error: Cannot execute null.remove(). -proc name: remove air (/turf/open/remove_air) - source file: LINDA_turf_tile.dm,62 - usr: null - src: the engraved floor (171,210,5) (/turf/open/floor/engine/cult) - - -The following runtime has occurred 2 time(s). -runtime error: Cannot execute null.return pressure(). -proc name: update pressure (/obj/mecha/working/ripley/proc/update_pressure) - source file: ripley.dm,150 - usr: 0 - src: the APLU \"Ripley\" (/obj/mecha/working/ripley) - src.loc: the floor (88,45,5) (/turf/open/floor/plasteel/freezer) - - -The following runtime has occurred 1 time(s). -runtime error: list index out of bounds -proc name: Topic (/datum/song/Topic) - source file: musician.dm,208 - usr: Francisco Luque (/mob/living/carbon/human) - src: Untitled (/datum/song/handheld) - - -The following runtime has occurred 1 time(s). -runtime error: undefined proc or verb /turf/closed/wall/r_wall/high pressure movements(). - - -The following runtime has occurred 1 time(s). -runtime error: bad client -proc name: show (/datum/html_interface/proc/show) - source file: html_interface.dm,227 - usr: Mariabella Pycroft-Crane (/mob/living/carbon/human) - src: /datum/html_interface/nanotras... (/datum/html_interface/nanotrasen) - - -The following runtime has occurred 1 time(s). -runtime error: Cannot execute null.GetAtmosAdjacentTurfs(). -proc name: spread smoke (/obj/effect/particle_effect/smoke/proc/spread_smoke) - source file: effects_smoke.dm,74 - usr: null - src: the smoke (/obj/effect/particle_effect/smoke) - src.loc: null - - -The following runtime has occurred 1 time(s). -runtime error: Cannot read null.pipe_vision_img -proc name: add ventcrawl (/mob/living/proc/add_ventcrawl) - source file: ventcrawling.dm,94 - usr: (src) - src: the monkey (809) (/mob/living/carbon/monkey) - src.loc: the Cloning Lab vent pump #1 (/obj/machinery/atmospherics/components/unary/vent_pump) - - -The following runtime has occurred 1 time(s). -runtime error: Cannot execute null.s click(). -proc name: Click (/obj/screen/grab/Click) - source file: screen_objects.dm,167 - usr: Hisstian Weston Chandler (/mob/living/carbon/human) - src: the disarm/kill (/obj/screen/grab) - - -The following runtime has occurred 1 time(s). -runtime error: list index out of bounds -proc name: mod list add (/client/proc/mod_list_add) - source file: modifyvariables.dm,161 - usr: the potted plant (/mob/dead/observer) - src: MimicFaux (/client) - - -The following runtime has occurred 1 time(s). -runtime error: Cannot execute null.get reagent amount(). -proc name: get fuel (/obj/item/weapon/weldingtool/proc/get_fuel) - source file: tools.dm,334 - usr: null - src: the welding tool (/obj/item/weapon/weldingtool) - src.loc: null - - - -** Hard deletions ** -/obj/structure/lattice - 499 time(s). - -/mob/new_player - 91 time(s). - -/image - 87 time(s). - -/mob/dead/observer - 42 time(s). - -/datum/mind - 36 time(s). - -/obj/item/radio/integrated/signal - 31 time(s). - -/obj/effect/ebeam - 24 time(s). - -/obj/machinery/camera - 15 time(s). - -/obj/machinery/atmospherics/components/binary/pump - 14 time(s). - -/obj/effect/decal/cleanable/trail_holder - 10 time(s). - -/obj/screen/buildmode/bdir - 10 time(s). - -/obj/screen/buildmode/help - 10 time(s). - -/obj/screen/buildmode/mode - 10 time(s). - -/obj/screen/buildmode/quit - 10 time(s). - -/obj/effect/landmark/start - 8 time(s). - -/obj/machinery/status_display - 7 time(s). - -/obj/item/stack/sheet/cardboard - 7 time(s). - -/datum/gas_mixture - 6 time(s). - -/obj/machinery/requests_console - 6 time(s). - -/obj/machinery/airalarm - 5 time(s). - -/obj/machinery/atmospherics/components/unary/vent_pump - 5 time(s). - -/obj/item/organ/hivelord_core/legion - 5 time(s). - -/obj/item/weapon/electronics/airlock - 5 time(s). - -/obj/effect/mist - 5 time(s). - -/obj/machinery/camera/portable - 5 time(s). - -/obj/machinery/porta_turret - 4 time(s). - -/obj/machinery/atmospherics/components/unary/portables_connector/visible - 4 time(s). - -/obj/effect/decal/cleanable/blood/gibs - 4 time(s). - -/obj/machinery/atmospherics/components/unary/vent_scrubber - 4 time(s). - -/obj/item/weapon/tank/internals/oxygen - 3 time(s). - -/obj/item/assembly/signaler - 3 time(s). - -/datum/event - 3 time(s). - -/obj/effect/decal/cleanable/blood/footprints - 3 time(s). - -/obj/machinery/camera/emp_proof - 3 time(s). - -/obj/item/weapon/tank/internals/plasma - 3 time(s). - -/obj/machinery/camera_assembly - 3 time(s). - -/obj/machinery/camera/motion - 3 time(s). - -/obj/machinery/atmospherics/pipe/simple/supply/hidden - 2 time(s). - -/obj/item/mecha_parts/chassis/ripley - 2 time(s). - -/obj/item/stack/sheet/metal - 2 time(s). - -/obj/machinery/door/airlock/external - 2 time(s). - -/obj/machinery/suit_storage_unit/engine - 2 time(s). - -/obj/item/weapon/stock_parts/cell - 2 time(s). - -/obj/machinery/computer/communications - 2 time(s). - -/obj/item/clothing/tie/petcollar - 2 time(s). - -/obj/machinery/atmospherics/components/binary/passive_gate - 2 time(s). - -/obj/machinery/power/apc - 2 time(s). - -/obj/structure/table/glass - 2 time(s). - -/obj/item/stack/cable_coil - 2 time(s). - -/obj/effect/decal/cleanable/blood/gibs/core - 2 time(s). - -/obj/machinery/atmospherics/components/unary/heat_exchanger - 2 time(s). - -/obj/item/mecha_parts/chassis/firefighter - 2 time(s). - -/obj/machinery/porta_turret/ai - 2 time(s). - -/obj/structure/closet/secure_closet/brig - 1 time(s). - -/obj/effect/landmark/river_waypoint - 1 time(s). - -/obj/item/clothing/head/chaplain_hood - 1 time(s). - -/obj/machinery/door/window/eastleft - 1 time(s). - -/obj/item/weapon/staff/broom - 1 time(s). - -/obj/machinery/door/window - 1 time(s). - -/obj/machinery/computer/pandemic - 1 time(s). - -/obj/machinery/door/airlock/research - 1 time(s). - -/obj/machinery/portable_atmospherics/pump - 1 time(s). - -/obj/machinery/suit_storage_unit/ce - 1 time(s). - -/obj/machinery/power/grounding_rod - 1 time(s). - -/mob/camera/aiEye - 1 time(s). - -/datum/data/record - 1 time(s). - -/obj/machinery/firealarm - 1 time(s). - -/obj/machinery/porta_turret_cover - 1 time(s). - -/obj/mecha/working/ripley - 1 time(s). - -/obj/machinery/portable_atmospherics/scrubber - 1 time(s). - -/obj/effect/decal/cleanable/robot_debris/old - 1 time(s). - -/obj/machinery/biogenerator - 1 time(s). - -/obj/machinery/plantgenes - 1 time(s). - -/datum/reagents - 1 time(s). - -/obj/machinery/vending/hydronutrients - 1 time(s). - -/obj/machinery/recycler - 1 time(s). - -/obj/item/weapon/tank/jetpack/suit - 1 time(s). - -/obj/machinery/vending/hydroseeds - 1 time(s). - -/obj/structure/table/optable - 1 time(s). - -/obj/item/pda/ai/pai - 1 time(s). - -/obj/item/stack/sheet/mineral/plasma - 1 time(s). - -/obj/machinery/chem_dispenser/drinks/beer - 1 time(s). - -/obj/structure/closet/crate - 1 time(s). - -/obj/item/stack/sheet/animalhide/monkey - 1 time(s). - -/obj/effect/hallucination/simple/singularity - 1 time(s). - -/obj - 1 time(s). - -/obj/item/clothing/head/explorer - 1 time(s). - -/obj/screen/grab - 1 time(s). - -/obj/item/weapon/grab - 1 time(s). - -/obj/machinery/atmospherics/components/unary/thermomachine/heater - 1 time(s). - -/obj/machinery/atmospherics/components/unary/thermomachine/freezer - 1 time(s). - -/obj/machinery/door/poddoor - 1 time(s). - -/obj/item/weapon/cartridge/hos - 1 time(s). - -/obj/item/pda/heads/hos - 1 time(s). - -/obj/item/ammo_casing/energy/electrode - 1 time(s). - -/obj/effect/mob_spawn/human/alive/space_bar_patron - 1 time(s). - -/obj/effect/blob/core - 1 time(s). - -/obj/machinery/clonepod - 1 time(s). - -/obj/item/weapon/reagent_containers/spray/mister/janitor - 1 time(s). - -/obj/item/clothing/head/helmet/space/hardsuit/shielded/ctf - 1 time(s). - -/obj/item/weapon/card/id/syndicate - 1 time(s). - -/obj/item/radio - 1 time(s). - -/obj/machinery/r_n_d/circuit_imprinter - 1 time(s). - -/obj/machinery/door/window/brigdoor - 1 time(s). - -/obj/machinery/computer/rdconsole/core - 1 time(s). - -/obj/structure/cable/yellow - 1 time(s). - -/obj/machinery/disposal/bin - 1 time(s). - -/obj/effect/mob_spawn/human/prisoner_transport - 1 time(s). +Note: The source file, src and usr are all from the FIRST of the identical runtimes. Everything else is cropped. + +Total unique runtimes: 28 +Total runtimes: 124723 + +Total unique hard deletions: 108 +Total hard deletions: 1080 + +** Runtimes ** + +The following runtime has occurred 123121 time(s). +runtime error: Component is missing a pipenet! Rebuilding... + + +The following runtime has occurred 709 time(s). +runtime error: wrong type of value for list +proc name: updateghostimages (/mob/dead/observer/proc/updateghostimages) + source file: observer.dm,488 + usr: (src) + src: Argon IXV (/mob/dead/observer) + src.loc: the asteroid sand (217,137,8) (/turf/open/floor/plating/asteroid) + + +The following runtime has occurred 696 time(s). +runtime error: Cannot read null.air +proc name: return air (/obj/machinery/atmospherics/pipe/return_air) + source file: pipes.dm,58 + usr: null + src: the air supply pipe (/obj/machinery/atmospherics/pipe/manifold4w/supply/visible) + src.loc: the floor (169,130,1) (/turf/open/floor/plasteel) + + +The following runtime has occurred 48 time(s). +runtime error: bad resource file +proc name: send asset (/proc/send_asset) + source file: asset_cache.dm,44 + usr: Mariabella Pycroft-Crane (/mob/living/carbon/human) + src: null + + +The following runtime has occurred 30 time(s). +runtime error: list index out of bounds +proc name: GetGreaterChild (/Heap/proc/GetGreaterChild) + source file: heap.dm,62 + usr: null + src: /Heap (/Heap) + + +The following runtime has occurred 21 time(s). +runtime error: undefined proc or verb /turf/closed/wall/shuttle/high pressure movements(). + + +The following runtime has occurred 15 time(s). +runtime error: Simple animal being instantiated in nullspace +proc name: stack trace (/proc/stack_trace) + source file: unsorted.dm,1358 + usr: the Securitron (/mob/living/simple_animal/bot/secbot) + src: null + + +The following runtime has occurred 12 time(s). +runtime error: Cannot read null.viewavail +proc name: record (/obj/machinery/computer/monitor/proc/record) + source file: monitor.dm,41 + usr: null + src: Engineering Power Monitoring C... (/obj/machinery/computer/monitor) + src.loc: space (154,151,1) (/turf/open/space) + + +The following runtime has occurred 11 time(s). +runtime error: Cannot read null.selection_color +proc name: SetChoices (/datum/preferences/proc/SetChoices) + source file: preferences.dm,543 + usr: Sweetestbro (/mob/new_player) + src: /datum/preferences (/datum/preferences) + + +The following runtime has occurred 10 time(s). +runtime error: Cannot execute null.add turf(). +proc name: process cell (/turf/open/process_cell) + source file: LINDA_turf_tile.dm,172 + usr: null + src: the plating (102,135,1) (/turf/open/floor/plating) + + +The following runtime has occurred 9 time(s). +runtime error: Cannot read null.thrownby +proc name: hitby (/mob/living/carbon/human/hitby) + source file: human_defense.dm,353 + usr: the monkey (662) (/mob/living/carbon/monkey) + src: Sydney Hujsak (/mob/living/carbon/human) + + +The following runtime has occurred 8 time(s). +runtime error: Cannot execute null.HasProximity(). +proc name: Entered (/turf/Entered) + source file: turf.dm,95 + usr: 0 + src: the floor (99,147,1) (/turf/open/floor/plasteel) + + +The following runtime has occurred 5 time(s). +runtime error: bad client +proc name: open (/datum/browser/proc/open) + source file: browser.dm,106 + usr: Timothy Catleay (/mob/living/carbon/human) + src: /datum/browser (/datum/browser) + + +The following runtime has occurred 4 time(s). +runtime error: return_file_text(): File not found + + +The following runtime has occurred 4 time(s). +runtime error: Cannot execute null.merge(). +proc name: assume air (/turf/open/assume_air) + source file: LINDA_turf_tile.dm,56 + usr: null + src: the engraved floor (95,49,5) (/turf/open/floor/engine/cult) + + +The following runtime has occurred 3 time(s). +runtime error: Cannot read null.gases +proc name: tile graphic (/turf/open/proc/tile_graphic) + source file: LINDA_turf_tile.dm,110 + usr: null + src: the floor (81,52,5) (/turf/open/floor/plasteel/freezer) + + +The following runtime has occurred 3 time(s). +runtime error: undefined proc or verb /turf/closed/wall/high pressure movements(). + + +The following runtime has occurred 2 time(s). +runtime error: Cannot modify null.loc. +proc name: ui act (/obj/machinery/suit_storage_unit/ui_act) + source file: suit_storage_unit.dm,359 + usr: Cy Carter (/mob/living/carbon/human) + src: the suit storage unit (/obj/machinery/suit_storage_unit/standard_unit) + + +The following runtime has occurred 2 time(s). +runtime error: Cannot execute null.remove(). +proc name: remove air (/turf/open/remove_air) + source file: LINDA_turf_tile.dm,62 + usr: null + src: the engraved floor (171,210,5) (/turf/open/floor/engine/cult) + + +The following runtime has occurred 2 time(s). +runtime error: Cannot execute null.return pressure(). +proc name: update pressure (/obj/mecha/working/ripley/proc/update_pressure) + source file: ripley.dm,150 + usr: 0 + src: the APLU \"Ripley\" (/obj/mecha/working/ripley) + src.loc: the floor (88,45,5) (/turf/open/floor/plasteel/freezer) + + +The following runtime has occurred 1 time(s). +runtime error: list index out of bounds +proc name: Topic (/datum/song/Topic) + source file: musician.dm,208 + usr: Francisco Luque (/mob/living/carbon/human) + src: Untitled (/datum/song/handheld) + + +The following runtime has occurred 1 time(s). +runtime error: undefined proc or verb /turf/closed/wall/r_wall/high pressure movements(). + + +The following runtime has occurred 1 time(s). +runtime error: bad client +proc name: show (/datum/html_interface/proc/show) + source file: html_interface.dm,227 + usr: Mariabella Pycroft-Crane (/mob/living/carbon/human) + src: /datum/html_interface/nanotras... (/datum/html_interface/nanotrasen) + + +The following runtime has occurred 1 time(s). +runtime error: Cannot execute null.GetAtmosAdjacentTurfs(). +proc name: spread smoke (/obj/effect/particle_effect/smoke/proc/spread_smoke) + source file: effects_smoke.dm,74 + usr: null + src: the smoke (/obj/effect/particle_effect/smoke) + src.loc: null + + +The following runtime has occurred 1 time(s). +runtime error: Cannot read null.pipe_vision_img +proc name: add ventcrawl (/mob/living/proc/add_ventcrawl) + source file: ventcrawling.dm,94 + usr: (src) + src: the monkey (809) (/mob/living/carbon/monkey) + src.loc: the Cloning Lab vent pump #1 (/obj/machinery/atmospherics/components/unary/vent_pump) + + +The following runtime has occurred 1 time(s). +runtime error: Cannot execute null.s click(). +proc name: Click (/obj/screen/grab/Click) + source file: screen_objects.dm,167 + usr: Hisstian Weston Chandler (/mob/living/carbon/human) + src: the disarm/kill (/obj/screen/grab) + + +The following runtime has occurred 1 time(s). +runtime error: list index out of bounds +proc name: mod list add (/client/proc/mod_list_add) + source file: modifyvariables.dm,161 + usr: the potted plant (/mob/dead/observer) + src: MimicFaux (/client) + + +The following runtime has occurred 1 time(s). +runtime error: Cannot execute null.get reagent amount(). +proc name: get fuel (/obj/item/weapon/weldingtool/proc/get_fuel) + source file: tools.dm,334 + usr: null + src: the welding tool (/obj/item/weapon/weldingtool) + src.loc: null + + + +** Hard deletions ** +/obj/structure/lattice - 499 time(s). + +/mob/new_player - 91 time(s). + +/image - 87 time(s). + +/mob/dead/observer - 42 time(s). + +/datum/mind - 36 time(s). + +/obj/item/radio/integrated/signal - 31 time(s). + +/obj/effect/ebeam - 24 time(s). + +/obj/machinery/camera - 15 time(s). + +/obj/machinery/atmospherics/components/binary/pump - 14 time(s). + +/obj/effect/decal/cleanable/trail_holder - 10 time(s). + +/obj/screen/buildmode/bdir - 10 time(s). + +/obj/screen/buildmode/help - 10 time(s). + +/obj/screen/buildmode/mode - 10 time(s). + +/obj/screen/buildmode/quit - 10 time(s). + +/obj/effect/landmark/start - 8 time(s). + +/obj/machinery/status_display - 7 time(s). + +/obj/item/stack/sheet/cardboard - 7 time(s). + +/datum/gas_mixture - 6 time(s). + +/obj/machinery/requests_console - 6 time(s). + +/obj/machinery/airalarm - 5 time(s). + +/obj/machinery/atmospherics/components/unary/vent_pump - 5 time(s). + +/obj/item/organ/hivelord_core/legion - 5 time(s). + +/obj/item/weapon/electronics/airlock - 5 time(s). + +/obj/effect/mist - 5 time(s). + +/obj/machinery/camera/portable - 5 time(s). + +/obj/machinery/porta_turret - 4 time(s). + +/obj/machinery/atmospherics/components/unary/portables_connector/visible - 4 time(s). + +/obj/effect/decal/cleanable/blood/gibs - 4 time(s). + +/obj/machinery/atmospherics/components/unary/vent_scrubber - 4 time(s). + +/obj/item/weapon/tank/internals/oxygen - 3 time(s). + +/obj/item/assembly/signaler - 3 time(s). + +/datum/event - 3 time(s). + +/obj/effect/decal/cleanable/blood/footprints - 3 time(s). + +/obj/machinery/camera/emp_proof - 3 time(s). + +/obj/item/weapon/tank/internals/plasma - 3 time(s). + +/obj/machinery/camera_assembly - 3 time(s). + +/obj/machinery/camera/motion - 3 time(s). + +/obj/machinery/atmospherics/pipe/simple/supply/hidden - 2 time(s). + +/obj/item/mecha_parts/chassis/ripley - 2 time(s). + +/obj/item/stack/sheet/metal - 2 time(s). + +/obj/machinery/door/airlock/external - 2 time(s). + +/obj/machinery/suit_storage_unit/engine - 2 time(s). + +/obj/item/weapon/stock_parts/cell - 2 time(s). + +/obj/machinery/computer/communications - 2 time(s). + +/obj/item/clothing/tie/petcollar - 2 time(s). + +/obj/machinery/atmospherics/components/binary/passive_gate - 2 time(s). + +/obj/machinery/power/apc - 2 time(s). + +/obj/structure/table/glass - 2 time(s). + +/obj/item/stack/cable_coil - 2 time(s). + +/obj/effect/decal/cleanable/blood/gibs/core - 2 time(s). + +/obj/machinery/atmospherics/components/unary/heat_exchanger - 2 time(s). + +/obj/item/mecha_parts/chassis/firefighter - 2 time(s). + +/obj/machinery/porta_turret/ai - 2 time(s). + +/obj/structure/closet/secure_closet/brig - 1 time(s). + +/obj/effect/landmark/river_waypoint - 1 time(s). + +/obj/item/clothing/head/chaplain_hood - 1 time(s). + +/obj/machinery/door/window/eastleft - 1 time(s). + +/obj/item/weapon/staff/broom - 1 time(s). + +/obj/machinery/door/window - 1 time(s). + +/obj/machinery/computer/pandemic - 1 time(s). + +/obj/machinery/door/airlock/research - 1 time(s). + +/obj/machinery/portable_atmospherics/pump - 1 time(s). + +/obj/machinery/suit_storage_unit/ce - 1 time(s). + +/obj/machinery/power/grounding_rod - 1 time(s). + +/mob/camera/aiEye - 1 time(s). + +/datum/data/record - 1 time(s). + +/obj/machinery/firealarm - 1 time(s). + +/obj/machinery/porta_turret_cover - 1 time(s). + +/obj/mecha/working/ripley - 1 time(s). + +/obj/machinery/portable_atmospherics/scrubber - 1 time(s). + +/obj/effect/decal/cleanable/robot_debris/old - 1 time(s). + +/obj/machinery/biogenerator - 1 time(s). + +/obj/machinery/plantgenes - 1 time(s). + +/datum/reagents - 1 time(s). + +/obj/machinery/vending/hydronutrients - 1 time(s). + +/obj/machinery/recycler - 1 time(s). + +/obj/item/weapon/tank/jetpack/suit - 1 time(s). + +/obj/machinery/vending/hydroseeds - 1 time(s). + +/obj/structure/table/optable - 1 time(s). + +/obj/item/pda/ai/pai - 1 time(s). + +/obj/item/stack/sheet/mineral/plasma - 1 time(s). + +/obj/machinery/chem_dispenser/drinks/beer - 1 time(s). + +/obj/structure/closet/crate - 1 time(s). + +/obj/item/stack/sheet/animalhide/monkey - 1 time(s). + +/obj/effect/hallucination/simple/singularity - 1 time(s). + +/obj - 1 time(s). + +/obj/item/clothing/head/explorer - 1 time(s). + +/obj/screen/grab - 1 time(s). + +/obj/item/weapon/grab - 1 time(s). + +/obj/machinery/atmospherics/components/unary/thermomachine/heater - 1 time(s). + +/obj/machinery/atmospherics/components/unary/thermomachine/freezer - 1 time(s). + +/obj/machinery/door/poddoor - 1 time(s). + +/obj/item/weapon/cartridge/hos - 1 time(s). + +/obj/item/pda/heads/hos - 1 time(s). + +/obj/item/ammo_casing/energy/electrode - 1 time(s). + +/obj/effect/mob_spawn/human/alive/space_bar_patron - 1 time(s). + +/obj/effect/blob/core - 1 time(s). + +/obj/machinery/clonepod - 1 time(s). + +/obj/item/weapon/reagent_containers/spray/mister/janitor - 1 time(s). + +/obj/item/clothing/head/helmet/space/hardsuit/shielded/ctf - 1 time(s). + +/obj/item/weapon/card/id/syndicate - 1 time(s). + +/obj/item/radio - 1 time(s). + +/obj/machinery/r_n_d/circuit_imprinter - 1 time(s). + +/obj/machinery/door/window/brigdoor - 1 time(s). + +/obj/machinery/computer/rdconsole/core - 1 time(s). + +/obj/structure/cable/yellow - 1 time(s). + +/obj/machinery/disposal/bin - 1 time(s). + +/obj/effect/mob_spawn/human/prisoner_transport - 1 time(s). diff --git a/tools/UnstandardnessTestForDM/UnstandardnessTestForDM/obj/x86/Debug/UnstandardnessTestForDM.csproj.FileListAbsolute.txt b/tools/UnstandardnessTestForDM/UnstandardnessTestForDM/obj/x86/Debug/UnstandardnessTestForDM.csproj.FileListAbsolute.txt index 4ba1e4015f59..1d36d1cd556e 100644 --- a/tools/UnstandardnessTestForDM/UnstandardnessTestForDM/obj/x86/Debug/UnstandardnessTestForDM.csproj.FileListAbsolute.txt +++ b/tools/UnstandardnessTestForDM/UnstandardnessTestForDM/obj/x86/Debug/UnstandardnessTestForDM.csproj.FileListAbsolute.txt @@ -1,18 +1,18 @@ -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.exe -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.pdb -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\ResolveAssemblyReference.cache -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.Form1.resources -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.Properties.Resources.resources -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\GenerateResource.read.1.tlog -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\GenerateResource.write.1.tlog -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.exe -c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.pdb -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.exe -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.pdb -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\ResolveAssemblyReference.cache -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.Form1.resources -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.Properties.Resources.resources -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\GenerateResource.read.1.tlog -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\GenerateResource.write.1.tlog -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.exe -C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.pdb +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.exe +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.pdb +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\ResolveAssemblyReference.cache +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.Form1.resources +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.Properties.Resources.resources +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\GenerateResource.read.1.tlog +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\GenerateResource.write.1.tlog +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.exe +c:\users\baloh\documents\visual studio 2010\Projects\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.pdb +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.exe +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.pdb +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\ResolveAssemblyReference.cache +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.Form1.resources +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.Properties.Resources.resources +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\GenerateResource.read.1.tlog +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\GenerateResource.write.1.tlog +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.exe +C:\Users\Baloh\Desktop\tgs13\tools\UnstandardnessTestForDM\UnstandardnessTestForDM\obj\x86\Debug\UnstandardnessTestForDM.pdb diff --git a/tools/UnstandardnessTestForDM/readme.txt b/tools/UnstandardnessTestForDM/readme.txt index 3fb04ee4dfab..8d3c5168dc54 100644 --- a/tools/UnstandardnessTestForDM/readme.txt +++ b/tools/UnstandardnessTestForDM/readme.txt @@ -1,4 +1,4 @@ -the compiled exe file for the Unstandardness text for DM program is in: -UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.exe - -You have to move it to the root folder (where the dme file is) and run it from there for it to work. +the compiled exe file for the Unstandardness text for DM program is in: +UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.exe + +You have to move it to the root folder (where the dme file is) and run it from there for it to work. diff --git a/tools/travis/travis_config.txt b/tools/travis/travis_config.txt index e59e4ed4ca23..ff6bf9a793e6 100644 --- a/tools/travis/travis_config.txt +++ b/tools/travis/travis_config.txt @@ -1,9 +1,9 @@ -SQL_ENABLED -ADDRESS 127.0.0.1 -PORT 3306 -FEEDBACK_DATABASE tg_travis -FEEDBACK_TABLEPREFIX -FEEDBACK_LOGIN root -FEEDBACK_PASSWORD -LAVALAND_BUDGET 0 -SPACE_BUDGET 0 +SQL_ENABLED +ADDRESS 127.0.0.1 +PORT 3306 +FEEDBACK_DATABASE tg_travis +FEEDBACK_TABLEPREFIX +FEEDBACK_LOGIN root +FEEDBACK_PASSWORD +LAVALAND_BUDGET 0 +SPACE_BUDGET 0 diff --git a/yogstation/code/datums/diseases/advance/symptoms/confusion.dm b/yogstation/code/datums/diseases/advance/symptoms/confusion.dm index f75ff265ac66..b8fefed67ad7 100644 --- a/yogstation/code/datums/diseases/advance/symptoms/confusion.dm +++ b/yogstation/code/datums/diseases/advance/symptoms/confusion.dm @@ -1,53 +1,53 @@ -/datum/symptom/numb - - name = "Analgesia" - desc = "The Virus attacks pain receptors in the host making them unable to feel injuries." - stealth = 2 - resistance = -2 - stage_speed = 0 - transmittable = -2 - level = 5 - severity = 2 - base_message_chance = 15 - symptom_delay_min = 5 //quick because it needs to reduce stun times - symptom_delay_max = 10 - var/stun_reduce = -15 - var/stamina_regen = FALSE - threshold_desc = "Resistance 8: Increases stun resistance.
              \ - Transmission 6: Gives stamina regen.
              \ - Stealth 4: The symptom remains hidden until active." - -/datum/symptom/numb/Start(datum/disease/advance/A) //ADD Stamina reg, and a stun resist - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["resistance"] >= 8) - stun_reduce = -25 - if(A.properties["transmission"] >= 7) - stamina_regen = TRUE - -/datum/symptom/numb/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - if(A.stage < 5) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You feel better.")]") - else - M.AdjustStun(stun_reduce, 0) - M.set_screwyhud(SCREWYHUD_HEALTHY) - if(stamina_regen) - M.adjustStaminaLoss(-2, 0) - M.updatehealth() - - return - -/datum/symptom/numb/End(datum/disease/advance/A) - var/mob/living/carbon/M = A.affected_mob - if(!..()) - return - else - M.set_screwyhud(SCREWYHUD_NONE) - M.updatehealth() - return +/datum/symptom/numb + + name = "Analgesia" + desc = "The Virus attacks pain receptors in the host making them unable to feel injuries." + stealth = 2 + resistance = -2 + stage_speed = 0 + transmittable = -2 + level = 5 + severity = 2 + base_message_chance = 15 + symptom_delay_min = 5 //quick because it needs to reduce stun times + symptom_delay_max = 10 + var/stun_reduce = -15 + var/stamina_regen = FALSE + threshold_desc = "Resistance 8: Increases stun resistance.
              \ + Transmission 6: Gives stamina regen.
              \ + Stealth 4: The symptom remains hidden until active." + +/datum/symptom/numb/Start(datum/disease/advance/A) //ADD Stamina reg, and a stun resist + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["resistance"] >= 8) + stun_reduce = -25 + if(A.properties["transmission"] >= 7) + stamina_regen = TRUE + +/datum/symptom/numb/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + if(A.stage < 5) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You feel better.")]") + else + M.AdjustStun(stun_reduce, 0) + M.set_screwyhud(SCREWYHUD_HEALTHY) + if(stamina_regen) + M.adjustStaminaLoss(-2, 0) + M.updatehealth() + + return + +/datum/symptom/numb/End(datum/disease/advance/A) + var/mob/living/carbon/M = A.affected_mob + if(!..()) + return + else + M.set_screwyhud(SCREWYHUD_NONE) + M.updatehealth() + return diff --git a/yogstation/code/datums/diseases/advance/symptoms/heal.dm b/yogstation/code/datums/diseases/advance/symptoms/heal.dm index d1e5852d9261..d09cfd159776 100644 --- a/yogstation/code/datums/diseases/advance/symptoms/heal.dm +++ b/yogstation/code/datums/diseases/advance/symptoms/heal.dm @@ -1,65 +1,65 @@ -/datum/symptom/heal/conversion - name = "Rapid Protein Synthesis" - desc = "The virus rapidly consumes nutrients in blood to heal wounds." - stealth = 0 - resistance = -2 - stage_speed = 2 - transmittable = -2 - level = 6 - passive_message = "You feel tough." - var/Toxin_damage = FALSE - var/Hunger_reduction = 8 - threshold_desc = "Resistance 9: No blood loss.
              \ - Stage Speed 7: Reduces nutrients used." - -/datum/symptom/heal/conversion/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 7) - Hunger_reduction = 4 - if(A.properties["resistance"] >= 9) - Toxin_damage = TRUE - -/datum/symptom/heal/conversion/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - switch(M.nutrition) - if(0) - return FALSE - if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_STARVING) - return 0.5 - if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) - return 1 - if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) - return 1.5 - else - return 2 - -/datum/symptom/heal/conversion/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = actual_power - var/Hunger_multi = actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1) - - if(!parts.len) - return - - M.nutrition = max(M.nutrition - (Hunger_reduction * Hunger_multi), 0) // So heal to nutrient ratio doesnt change - - if(M.nutrition <= NUTRITION_LEVEL_STARVING && !Toxin_damage) - M.blood_volume -= 10 - if(prob(45)) - to_chat(M, "You feel like you are wasting away!") - - if(Toxin_damage) - M.adjustToxLoss(2) - if(prob(45)) - to_chat(M, "You dont feel so well.") - - if(prob(25)) - to_chat(M, "You feel your wounds getting warm.") - - 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 TRUE +/datum/symptom/heal/conversion + name = "Rapid Protein Synthesis" + desc = "The virus rapidly consumes nutrients in blood to heal wounds." + stealth = 0 + resistance = -2 + stage_speed = 2 + transmittable = -2 + level = 6 + passive_message = "You feel tough." + var/Toxin_damage = FALSE + var/Hunger_reduction = 8 + threshold_desc = "Resistance 9: No blood loss.
              \ + Stage Speed 7: Reduces nutrients used." + +/datum/symptom/heal/conversion/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 7) + Hunger_reduction = 4 + if(A.properties["resistance"] >= 9) + Toxin_damage = TRUE + +/datum/symptom/heal/conversion/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + switch(M.nutrition) + if(0) + return FALSE + if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_STARVING) + return 0.5 + if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) + return 1 + if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) + return 1.5 + else + return 2 + +/datum/symptom/heal/conversion/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = actual_power + var/Hunger_multi = actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1) + + if(!parts.len) + return + + M.nutrition = max(M.nutrition - (Hunger_reduction * Hunger_multi), 0) // So heal to nutrient ratio doesnt change + + if(M.nutrition <= NUTRITION_LEVEL_STARVING && !Toxin_damage) + M.blood_volume -= 10 + if(prob(45)) + to_chat(M, "You feel like you are wasting away!") + + if(Toxin_damage) + M.adjustToxLoss(2) + if(prob(45)) + to_chat(M, "You dont feel so well.") + + if(prob(25)) + to_chat(M, "You feel your wounds getting warm.") + + 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 TRUE diff --git a/yogstation/code/game/machinery/computer/atmos_sim.dm b/yogstation/code/game/machinery/computer/atmos_sim.dm index 24b684c60fd0..3422e3fe2673 100644 --- a/yogstation/code/game/machinery/computer/atmos_sim.dm +++ b/yogstation/code/game/machinery/computer/atmos_sim.dm @@ -1,188 +1,188 @@ -#define ATMOS_SIM_MODE_BOMB 1 -#define ATMOS_SIM_MODE_TANK 2 -#define ATMOS_SIM_MODE_COUNT 2 - -/obj/machinery/computer/atmos_sim - name = "atmospherics simulator" - desc = "Used to simulate various atmospherics events" - icon_screen = "tank" - icon_keyboard = "atmos_key" - circuit = /obj/item/circuitboard/computer/atmos_sim - var/mode = 2 - var/datum/gas_mixture/tank_mix = new - var/datum/gas_mixture/bomb_1 = new - var/datum/gas_mixture/bomb_2 = new - var/datum/gas_mixture/bomb_result = new - var/list/bomb_explosion_size = null - -/obj/machinery/computer/atmos_sim/Initialize() - tank_mix.temperature = T20C - tank_mix.volume = CELL_VOLUME - bomb_1.temperature = T20C - bomb_1.volume = 70 - bomb_2.temperature = T20C - bomb_2.volume = 70 - ..() - -/obj/machinery/computer/atmos_sim/proc/simulate_bomb() - bomb_explosion_size = null - bomb_result = new /datum/gas_mixture() - bomb_result.volume = bomb_1.volume + bomb_2.volume - bomb_result.merge(bomb_1) - bomb_result.merge(bomb_2) - for(var/I in 1 to 10) - bomb_result.react() - if(bomb_result.return_pressure() >= TANK_FRAGMENT_PRESSURE) - // Explosion! - bomb_result.react() - bomb_result.react() - bomb_result.react() - var/range = (bomb_result.return_pressure()-TANK_FRAGMENT_PRESSURE)/TANK_FRAGMENT_SCALE - bomb_explosion_size = list(round(range*0.25), round(range*0.5), round(range)) - break - -/obj/machinery/computer/atmos_sim/ui_interact(mob/living/user) - . = ..() - var/dat - dat += "Mode:" - for(var/i in 1 to ATMOS_SIM_MODE_COUNT) - var/mode_name = null - switch(i) - if(1) - mode_name = "TTV Bomb" - if(2) - mode_name = "Gas Reactions" - dat += "[mode_name]" - dat += "
              " - switch(mode) - if(ATMOS_SIM_MODE_BOMB) - dat += "
              " - dat += gas_html(bomb_1, "Left Tank", "bomb_1") - dat += "" - dat += gas_html(bomb_2, "Right Tank", "bomb_2") - dat += "
              " - if(bomb_explosion_size) - dat += "
              Theoretical Explosion Size: ([bomb_explosion_size[1]]/[bomb_explosion_size[2]]/[bomb_explosion_size[3]])
              " - dat += gas_html(bomb_result, "Result") - if(ATMOS_SIM_MODE_TANK) - dat += gas_html(tank_mix, "Tank Contents", "tank_mix") - dat += "" - - var/datum/browser/popup = new(user, "atmos_sim", name, 700, 550) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/computer/atmos_sim/proc/gas_html(datum/gas_mixture/mix, name, mix_id) - var/dat = "" - dat += "" - if(mix_id) - dat += "" - else - dat += "" - for(var/id in mix.gases) - var/list/gas = mix.gases[id] - dat += "" - if(mix_id) - dat += "" - dat += "" - dat += "" - dat += "" - else - dat += "" - dat += "" - dat += "" - dat += "" - if(mix_id) - dat += "" - dat += "" - dat += "
              [name]
              Volume: [mix.volume] LTemp: [mix.temperature] K
              Volume: [mix.volume] LTemp: [mix.temperature] K
              X[gas[GAS_META][META_GAS_NAME]][gas[MOLES]] moles[gas[MOLES] * R_IDEAL_GAS_EQUATION * mix.temperature / mix.volume] kPa[gas[GAS_META][META_GAS_NAME]][gas[MOLES]] moles[gas[MOLES] * R_IDEAL_GAS_EQUATION * mix.temperature / mix.volume] kPa
              Add Gas
              TOTAL[mix.total_moles()] moles[mix.return_pressure()] kPa
              " - return dat - -/obj/machinery/computer/atmos_sim/proc/gas_topic(datum/gas_mixture/mix, href_list) - if(href_list["delete_gas"]) - mix.gases -= text2path(href_list["delete_gas"]) - if(href_list["change_moles"]) - var/id = text2path(href_list["change_moles"]) - if(mix.gases[id]) - var/new_moles = input(usr, "Enter a new mole count for [mix.gases[id][GAS_META][META_GAS_NAME]]", name) as null|num - if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || new_moles == null) - return - mix.gases[id][MOLES] = new_moles - if(href_list["change_pressure"]) - var/id = text2path(href_list["change_pressure"]) - if(mix.gases[id]) - var/new_pressure = input(usr, "Enter a new pressure for [mix.gases[id][GAS_META][META_GAS_NAME]]", name) as null|num - if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || new_pressure == null) - return - mix.gases[id][MOLES] = new_pressure / R_IDEAL_GAS_EQUATION / mix.temperature * mix.volume - if(href_list["add_gas"]) - var/list/valid_gas_types = subtypesof(/datum/gas) - for(var/id in mix.gases) - valid_gas_types -= id - var/list/gas_types_map = list() - for(var/id in valid_gas_types) - var/datum/gas/gas_type = id - gas_types_map[initial(gas_type.name)] = id - var/gas_type = input(usr, "Select a gas type", name) as null|anything in gas_types_map - if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || gas_type == null) - return - mix.assert_gas(gas_types_map[gas_type]) - if(href_list["change_volume"]) - var/volume_type = input(usr, "Select a container type", name) as null|anything in list("Custom", "Floor Tile", "Canister", "Portable Tank") - if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || volume_type == null) - return - var/desired_volume - switch(volume_type) - if("Custom") - desired_volume = input(usr, "Enter a new volume", name) as null|num - if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || desired_volume == null) - return - if("Floor Tile") - desired_volume = CELL_VOLUME - if("Canister") - desired_volume = 1000 - if("Portable Tank") - desired_volume = 70 - mix.volume = desired_volume - if(href_list["change_temperature"]) - var/new_temp = input(usr, "Enter a new temperature (0 degrees C = [T0C] K)", name) as null|num - if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || new_temp == null) - return - new_temp = max(TCMB, new_temp) - var/temp_ratio = mix.temperature / new_temp - for(var/gas_id in mix.gases) - mix.gases[gas_id][MOLES] *= temp_ratio // Preserve the pressure - mix.temperature = new_temp - -/obj/machinery/computer/atmos_sim/Topic(href, href_list) - if(..()) - return - if(!usr || !usr.canUseTopic(src) || stat || QDELETED(src)) - return - if(href_list["mode"]) - mode = text2num(href_list["mode"]) - if(href_list["mix"]) - var/mix_id = href_list["mix"] - var/datum/gas_mixture/mix - var/do_simulate_bomb = FALSE - switch(mix_id) - if("tank_mix") - mix = tank_mix - if("bomb_1") - mix = bomb_1 - do_simulate_bomb = TRUE - if("bomb_2") - mix = bomb_2 - do_simulate_bomb = TRUE - if(mix) - gas_topic(mix, href_list) - if(do_simulate_bomb) - simulate_bomb() - if(href_list["react_tank"]) - tank_mix.react() - ui_interact(usr) - -#undef ATMOS_SIM_MODE_BOMB -#undef ATMOS_SIM_MODE_TANK -#undef ATMOS_SIM_MODE_COUNT +#define ATMOS_SIM_MODE_BOMB 1 +#define ATMOS_SIM_MODE_TANK 2 +#define ATMOS_SIM_MODE_COUNT 2 + +/obj/machinery/computer/atmos_sim + name = "atmospherics simulator" + desc = "Used to simulate various atmospherics events" + icon_screen = "tank" + icon_keyboard = "atmos_key" + circuit = /obj/item/circuitboard/computer/atmos_sim + var/mode = 2 + var/datum/gas_mixture/tank_mix = new + var/datum/gas_mixture/bomb_1 = new + var/datum/gas_mixture/bomb_2 = new + var/datum/gas_mixture/bomb_result = new + var/list/bomb_explosion_size = null + +/obj/machinery/computer/atmos_sim/Initialize() + tank_mix.temperature = T20C + tank_mix.volume = CELL_VOLUME + bomb_1.temperature = T20C + bomb_1.volume = 70 + bomb_2.temperature = T20C + bomb_2.volume = 70 + ..() + +/obj/machinery/computer/atmos_sim/proc/simulate_bomb() + bomb_explosion_size = null + bomb_result = new /datum/gas_mixture() + bomb_result.volume = bomb_1.volume + bomb_2.volume + bomb_result.merge(bomb_1) + bomb_result.merge(bomb_2) + for(var/I in 1 to 10) + bomb_result.react() + if(bomb_result.return_pressure() >= TANK_FRAGMENT_PRESSURE) + // Explosion! + bomb_result.react() + bomb_result.react() + bomb_result.react() + var/range = (bomb_result.return_pressure()-TANK_FRAGMENT_PRESSURE)/TANK_FRAGMENT_SCALE + bomb_explosion_size = list(round(range*0.25), round(range*0.5), round(range)) + break + +/obj/machinery/computer/atmos_sim/ui_interact(mob/living/user) + . = ..() + var/dat + dat += "Mode:" + for(var/i in 1 to ATMOS_SIM_MODE_COUNT) + var/mode_name = null + switch(i) + if(1) + mode_name = "TTV Bomb" + if(2) + mode_name = "Gas Reactions" + dat += "[mode_name]" + dat += "
              " + switch(mode) + if(ATMOS_SIM_MODE_BOMB) + dat += "
              " + dat += gas_html(bomb_1, "Left Tank", "bomb_1") + dat += "" + dat += gas_html(bomb_2, "Right Tank", "bomb_2") + dat += "
              " + if(bomb_explosion_size) + dat += "
              Theoretical Explosion Size: ([bomb_explosion_size[1]]/[bomb_explosion_size[2]]/[bomb_explosion_size[3]])
              " + dat += gas_html(bomb_result, "Result") + if(ATMOS_SIM_MODE_TANK) + dat += gas_html(tank_mix, "Tank Contents", "tank_mix") + dat += "" + + var/datum/browser/popup = new(user, "atmos_sim", name, 700, 550) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/computer/atmos_sim/proc/gas_html(datum/gas_mixture/mix, name, mix_id) + var/dat = "" + dat += "" + if(mix_id) + dat += "" + else + dat += "" + for(var/id in mix.gases) + var/list/gas = mix.gases[id] + dat += "" + if(mix_id) + dat += "" + dat += "" + dat += "" + dat += "" + else + dat += "" + dat += "" + dat += "" + dat += "" + if(mix_id) + dat += "" + dat += "" + dat += "
              [name]
              Volume: [mix.volume] LTemp: [mix.temperature] K
              Volume: [mix.volume] LTemp: [mix.temperature] K
              X[gas[GAS_META][META_GAS_NAME]][gas[MOLES]] moles[gas[MOLES] * R_IDEAL_GAS_EQUATION * mix.temperature / mix.volume] kPa[gas[GAS_META][META_GAS_NAME]][gas[MOLES]] moles[gas[MOLES] * R_IDEAL_GAS_EQUATION * mix.temperature / mix.volume] kPa
              Add Gas
              TOTAL[mix.total_moles()] moles[mix.return_pressure()] kPa
              " + return dat + +/obj/machinery/computer/atmos_sim/proc/gas_topic(datum/gas_mixture/mix, href_list) + if(href_list["delete_gas"]) + mix.gases -= text2path(href_list["delete_gas"]) + if(href_list["change_moles"]) + var/id = text2path(href_list["change_moles"]) + if(mix.gases[id]) + var/new_moles = input(usr, "Enter a new mole count for [mix.gases[id][GAS_META][META_GAS_NAME]]", name) as null|num + if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || new_moles == null) + return + mix.gases[id][MOLES] = new_moles + if(href_list["change_pressure"]) + var/id = text2path(href_list["change_pressure"]) + if(mix.gases[id]) + var/new_pressure = input(usr, "Enter a new pressure for [mix.gases[id][GAS_META][META_GAS_NAME]]", name) as null|num + if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || new_pressure == null) + return + mix.gases[id][MOLES] = new_pressure / R_IDEAL_GAS_EQUATION / mix.temperature * mix.volume + if(href_list["add_gas"]) + var/list/valid_gas_types = subtypesof(/datum/gas) + for(var/id in mix.gases) + valid_gas_types -= id + var/list/gas_types_map = list() + for(var/id in valid_gas_types) + var/datum/gas/gas_type = id + gas_types_map[initial(gas_type.name)] = id + var/gas_type = input(usr, "Select a gas type", name) as null|anything in gas_types_map + if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || gas_type == null) + return + mix.assert_gas(gas_types_map[gas_type]) + if(href_list["change_volume"]) + var/volume_type = input(usr, "Select a container type", name) as null|anything in list("Custom", "Floor Tile", "Canister", "Portable Tank") + if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || volume_type == null) + return + var/desired_volume + switch(volume_type) + if("Custom") + desired_volume = input(usr, "Enter a new volume", name) as null|num + if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || desired_volume == null) + return + if("Floor Tile") + desired_volume = CELL_VOLUME + if("Canister") + desired_volume = 1000 + if("Portable Tank") + desired_volume = 70 + mix.volume = desired_volume + if(href_list["change_temperature"]) + var/new_temp = input(usr, "Enter a new temperature (0 degrees C = [T0C] K)", name) as null|num + if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src) || new_temp == null) + return + new_temp = max(TCMB, new_temp) + var/temp_ratio = mix.temperature / new_temp + for(var/gas_id in mix.gases) + mix.gases[gas_id][MOLES] *= temp_ratio // Preserve the pressure + mix.temperature = new_temp + +/obj/machinery/computer/atmos_sim/Topic(href, href_list) + if(..()) + return + if(!usr || !usr.canUseTopic(src) || stat || QDELETED(src)) + return + if(href_list["mode"]) + mode = text2num(href_list["mode"]) + if(href_list["mix"]) + var/mix_id = href_list["mix"] + var/datum/gas_mixture/mix + var/do_simulate_bomb = FALSE + switch(mix_id) + if("tank_mix") + mix = tank_mix + if("bomb_1") + mix = bomb_1 + do_simulate_bomb = TRUE + if("bomb_2") + mix = bomb_2 + do_simulate_bomb = TRUE + if(mix) + gas_topic(mix, href_list) + if(do_simulate_bomb) + simulate_bomb() + if(href_list["react_tank"]) + tank_mix.react() + ui_interact(usr) + +#undef ATMOS_SIM_MODE_BOMB +#undef ATMOS_SIM_MODE_TANK +#undef ATMOS_SIM_MODE_COUNT diff --git a/yogstation/code/game/objects/items/airlock_scaner.dm b/yogstation/code/game/objects/items/airlock_scaner.dm index 72aa5cfa38d7..ccdc34c3349c 100644 --- a/yogstation/code/game/objects/items/airlock_scaner.dm +++ b/yogstation/code/game/objects/items/airlock_scaner.dm @@ -1,49 +1,49 @@ -/obj/item/airlock_scanner - name = "access scanner" - desc = "A tool used to idetifying access requiremnts of various machinery. Make sure airlocks id scan routines were activated at least once before the scan" - icon = 'icons/obj/objects.dmi' - icon_state = "airlock_scanner" - - 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 - -/obj/item/airlock_scanner/proc/show_access(obj/target, mob/user) - var/t1 = "" - var/list/accses_list - if(length(target.req_one_access)) - t1 += "Restriction Type: At least one access required
              " - accses_list = target.req_one_access - else - t1 += "Restriction Type: All accesses required
              " - accses_list = target.req_access - - var/accesses = "" - accesses += "
              Access
              " - accesses += "" - accesses += "" - for(var/i = 1; i <= 7; i++) - accesses += "" - accesses += "" - for(var/i = 1; i <= 7; i++) - accesses += "" - accesses += "
              [get_region_accesses_name(i)]:
              " - for(var/A in get_region_accesses(i)) - if(A in accses_list) - accesses += "[replacetext(get_access_desc(A), " ", " ")] " - else - accesses += "[replacetext(get_access_desc(A), " ", " ")] " - accesses += "
              " - accesses += "
              " - t1 += "[accesses]" - - t1 += "

              Close

              \n" - - var/datum/browser/popup = new(user, "airlock_scan", "Access Scan", 900, 500) - popup.set_content(t1) - popup.open() +/obj/item/airlock_scanner + name = "access scanner" + desc = "A tool used to idetifying access requiremnts of various machinery. Make sure airlocks id scan routines were activated at least once before the scan" + icon = 'icons/obj/objects.dmi' + icon_state = "airlock_scanner" + + 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 + +/obj/item/airlock_scanner/proc/show_access(obj/target, mob/user) + var/t1 = "" + var/list/accses_list + if(length(target.req_one_access)) + t1 += "Restriction Type: At least one access required
              " + accses_list = target.req_one_access + else + t1 += "Restriction Type: All accesses required
              " + accses_list = target.req_access + + var/accesses = "" + accesses += "
              Access
              " + accesses += "" + accesses += "" + for(var/i = 1; i <= 7; i++) + accesses += "" + accesses += "" + for(var/i = 1; i <= 7; i++) + accesses += "" + accesses += "
              [get_region_accesses_name(i)]:
              " + for(var/A in get_region_accesses(i)) + if(A in accses_list) + accesses += "[replacetext(get_access_desc(A), " ", " ")] " + else + accesses += "[replacetext(get_access_desc(A), " ", " ")] " + accesses += "
              " + accesses += "
              " + t1 += "[accesses]" + + t1 += "

              Close

              \n" + + var/datum/browser/popup = new(user, "airlock_scan", "Access Scan", 900, 500) + popup.set_content(t1) + popup.open() onclose(user, "airlock_scan") \ No newline at end of file diff --git a/yogstation/code/modules/admin/topic.dm b/yogstation/code/modules/admin/topic.dm index f0ddb6e7f8a2..205e74d97c9e 100644 --- a/yogstation/code/modules/admin/topic.dm +++ b/yogstation/code/modules/admin/topic.dm @@ -1,100 +1,100 @@ -/datum/admins/proc/hippie_makeVampire(datum/admins/sr) - if(sr.makeVampire()) - message_admins("[key_name(usr)] created a vampire.") - log_admin("[key_name(usr)] created a vampire.") - else - message_admins("[key_name_admin(usr)] tried to create a vampire. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a vampire.") -//why is this called hippie stop it thats bad - -/datum/admins/proc/checkMentorEditList(ckey) - var/sql_key = sanitizeSQL("[ckey]") - var/datum/DBQuery/query_memoedits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("mentor_memo")] WHERE (ckey = '[sql_key]')") - if(!query_memoedits.warn_execute()) - qdel(query_memoedits) - return - - if(query_memoedits.NextRow()) - var/edit_log = query_memoedits.item[1] - usr << browse(edit_log,"window=mentormemoeditlist") - qdel(query_memoedits) - -/datum/admins/proc/makeMentor(ckey) - if(!usr.client) - return - - if (!check_rights(R_EVERYTHING)) - return - - if(!ckey) - return - - var/client/C = GLOB.directory[ckey] - if(C) - if(check_rights_for(C, R_ADMIN,0)) - to_chat(usr, "The client chosen is an admin! Cannot mentorize.") - return - - new /datum/mentors(ckey) - - if(SSdbcore.Connect()) - var/datum/DBQuery/query_get_mentor = SSdbcore.NewQuery("SELECT id FROM `[format_table_name("mentor")]` WHERE `ckey` = '[ckey]'") - query_get_mentor.warn_execute() - if(query_get_mentor.NextRow()) - to_chat(usr, "[ckey] is already a mentor.") - qdel(query_get_mentor) - return - qdel(query_get_mentor) - - var/datum/DBQuery/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')") - if(!query_add_mentor.warn_execute()) - qdel(query_add_mentor) - return - qdel(query_add_mentor) - - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');") - if(!query_add_admin_log.warn_execute()) - qdel(query_add_admin_log) - return - qdel(query_add_admin_log) - - webhook_send_mchange(owner.ckey, C.ckey, "add") - - else - to_chat(usr, "Failed to establish database connection. The changes will last only for the current round.") - - message_admins("[key_name_admin(usr)] added new mentor: [ckey]") - log_admin("[key_name(usr)] added new mentor: [ckey]") - -/datum/admins/proc/removeMentor(ckey) - if(!usr.client) - return - - if (!check_rights(R_EVERYTHING)) - return - - if(!ckey) - return - - var/client/C = GLOB.directory[ckey] - if(C) - if(check_rights_for(C, R_ADMIN,0)) - to_chat(usr, "The client chosen is an admin, not a mentor! Cannot de-mentorize.") - return - - C.remove_mentor_verbs() - C.mentor_datum = null - GLOB.mentors -= C - - if(SSdbcore.Connect()) - var/datum/DBQuery/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM `[format_table_name("mentor")]` WHERE `ckey` = '[ckey]'") - query_remove_mentor.warn_execute() - qdel(query_remove_mentor) - - webhook_send_mchange(owner.ckey, C.ckey, "remove") - - else - to_chat(usr, "Failed to establish database connection. The changes will last only for the current round.") - - message_admins("[key_name_admin(usr)] removed mentor: [ckey]") - log_admin("[key_name(usr)] removed mentor: [ckey]") +/datum/admins/proc/hippie_makeVampire(datum/admins/sr) + if(sr.makeVampire()) + message_admins("[key_name(usr)] created a vampire.") + log_admin("[key_name(usr)] created a vampire.") + else + message_admins("[key_name_admin(usr)] tried to create a vampire. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a vampire.") +//why is this called hippie stop it thats bad + +/datum/admins/proc/checkMentorEditList(ckey) + var/sql_key = sanitizeSQL("[ckey]") + var/datum/DBQuery/query_memoedits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("mentor_memo")] WHERE (ckey = '[sql_key]')") + if(!query_memoedits.warn_execute()) + qdel(query_memoedits) + return + + if(query_memoedits.NextRow()) + var/edit_log = query_memoedits.item[1] + usr << browse(edit_log,"window=mentormemoeditlist") + qdel(query_memoedits) + +/datum/admins/proc/makeMentor(ckey) + if(!usr.client) + return + + if (!check_rights(R_EVERYTHING)) + return + + if(!ckey) + return + + var/client/C = GLOB.directory[ckey] + if(C) + if(check_rights_for(C, R_ADMIN,0)) + to_chat(usr, "The client chosen is an admin! Cannot mentorize.") + return + + new /datum/mentors(ckey) + + if(SSdbcore.Connect()) + var/datum/DBQuery/query_get_mentor = SSdbcore.NewQuery("SELECT id FROM `[format_table_name("mentor")]` WHERE `ckey` = '[ckey]'") + query_get_mentor.warn_execute() + if(query_get_mentor.NextRow()) + to_chat(usr, "[ckey] is already a mentor.") + qdel(query_get_mentor) + return + qdel(query_get_mentor) + + var/datum/DBQuery/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')") + if(!query_add_mentor.warn_execute()) + qdel(query_add_mentor) + return + qdel(query_add_mentor) + + var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');") + if(!query_add_admin_log.warn_execute()) + qdel(query_add_admin_log) + return + qdel(query_add_admin_log) + + webhook_send_mchange(owner.ckey, C.ckey, "add") + + else + to_chat(usr, "Failed to establish database connection. The changes will last only for the current round.") + + message_admins("[key_name_admin(usr)] added new mentor: [ckey]") + log_admin("[key_name(usr)] added new mentor: [ckey]") + +/datum/admins/proc/removeMentor(ckey) + if(!usr.client) + return + + if (!check_rights(R_EVERYTHING)) + return + + if(!ckey) + return + + var/client/C = GLOB.directory[ckey] + if(C) + if(check_rights_for(C, R_ADMIN,0)) + to_chat(usr, "The client chosen is an admin, not a mentor! Cannot de-mentorize.") + return + + C.remove_mentor_verbs() + C.mentor_datum = null + GLOB.mentors -= C + + if(SSdbcore.Connect()) + var/datum/DBQuery/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM `[format_table_name("mentor")]` WHERE `ckey` = '[ckey]'") + query_remove_mentor.warn_execute() + qdel(query_remove_mentor) + + webhook_send_mchange(owner.ckey, C.ckey, "remove") + + else + to_chat(usr, "Failed to establish database connection. The changes will last only for the current round.") + + message_admins("[key_name_admin(usr)] removed mentor: [ckey]") + log_admin("[key_name(usr)] removed mentor: [ckey]") diff --git a/yogstation/code/modules/clothing/glasses/_glasses.dm b/yogstation/code/modules/clothing/glasses/_glasses.dm index 92c05bab5f84..d79a523f5a98 100644 --- a/yogstation/code/modules/clothing/glasses/_glasses.dm +++ b/yogstation/code/modules/clothing/glasses/_glasses.dm @@ -1,28 +1,28 @@ -/obj/item/clothing/glasses/sunglasses/cheap - name = "cheap sunglasses" - desc = "Made in china" - icon_state = "sun" - item_state = "sunglasses" - darkness_view = 1 - flash_protect = 0 - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/gray - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/glasses/yogs/threed - name = "3D glasses" - desc = "A pair of glasses used to watch films in red-cyan anaglyph 3D." - icon_state = "threed" - item_state = "threed" - -/obj/item/clothing/glasses/yogs/hypno - name = "hypno-spectacles" - desc = "A pair of glasses which claim to have the ability to hypnotize people." - icon_state = "hypnospecs" - item_state = "hypnospecs" - -/obj/item/clothing/glasses/yogs/eyepatch - name = "eye patch" - desc = "Arrrggg now you too can be a real pirate matey." - icon_state = "eyepatch" +/obj/item/clothing/glasses/sunglasses/cheap + name = "cheap sunglasses" + desc = "Made in china" + icon_state = "sun" + item_state = "sunglasses" + darkness_view = 1 + flash_protect = 0 + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/gray + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/glasses/yogs/threed + name = "3D glasses" + desc = "A pair of glasses used to watch films in red-cyan anaglyph 3D." + icon_state = "threed" + item_state = "threed" + +/obj/item/clothing/glasses/yogs/hypno + name = "hypno-spectacles" + desc = "A pair of glasses which claim to have the ability to hypnotize people." + icon_state = "hypnospecs" + item_state = "hypnospecs" + +/obj/item/clothing/glasses/yogs/eyepatch + name = "eye patch" + desc = "Arrrggg now you too can be a real pirate matey." + icon_state = "eyepatch" item_state = "eyepatch" \ No newline at end of file diff --git a/yogstation/code/modules/surgery/organs/lungs.dm b/yogstation/code/modules/surgery/organs/lungs.dm index c318772c7f09..b8162dee52f0 100644 --- a/yogstation/code/modules/surgery/organs/lungs.dm +++ b/yogstation/code/modules/surgery/organs/lungs.dm @@ -1,14 +1,14 @@ -/obj/item/organ/lungs/ashwalker - name = "grey lungs" - desc = "The grey-ish lungs of an ashwalker." - icon = 'yogstation/icons/obj/surgery.dmi' - icon_state = "lungs-ash" - - safe_oxygen_min = 0 - safe_nitro_min = 16 - safe_toxins_max = 0.05 - safe_co2_max = 20 - SA_para_min = 1 - SA_sleep_min = 5 - BZ_trip_balls_min = 1 - gas_stimulation_min = 0.002 +/obj/item/organ/lungs/ashwalker + name = "grey lungs" + desc = "The grey-ish lungs of an ashwalker." + icon = 'yogstation/icons/obj/surgery.dmi' + icon_state = "lungs-ash" + + safe_oxygen_min = 0 + safe_nitro_min = 16 + safe_toxins_max = 0.05 + safe_co2_max = 20 + SA_para_min = 1 + SA_sleep_min = 5 + BZ_trip_balls_min = 1 + gas_stimulation_min = 0.002 diff --git a/yogstation/code/modules/xenoarch/loot/guns.dm b/yogstation/code/modules/xenoarch/loot/guns.dm index ae6f56805d6b..23865696a6e2 100644 --- a/yogstation/code/modules/xenoarch/loot/guns.dm +++ b/yogstation/code/modules/xenoarch/loot/guns.dm @@ -1,176 +1,176 @@ -/obj/item/gun/energy/polarstar - name = "Polar Star" - desc = "Despite being incomplete, the severe wear on this gun shows to which extent it's been used already." - icon = 'yogstation/icons/obj/xenoarch/guns.dmi' - lefthand_file = 'yogstation/icons/mob/inhands/weapons/xenoarch_lefthand.dmi' - righthand_file = 'yogstation/icons/mob/inhands/weapons/xenoarch_righthand.dmi' - icon_state = "polarstar" - item_state = "polarstar" - slot_flags = SLOT_BELT - fire_delay = 1 - recoil = 1 - cell_type = /obj/item/stock_parts/cell - ammo_type = list(/obj/item/ammo_casing/energy/polarstar) - var/chargesound - -/obj/item/gun/energy/polarstar/New(loc, ...) - . = ..() - playsound(src, 'yogstation/sound/weapons/spur_spawn.ogg') - -/obj/item/gun/energy/polarstar/update_icon(force_update) - var/maxcharge = cell.maxcharge - var/charge = cell.charge - - var/oldsound = chargesound - var/obj/item/ammo_casing/energy/AC = ammo_type[select] //shouldnt be anything other than the normal but eh,adminbus resistance doesnt hurt - if(charge >= ((maxcharge/3) * 2)) // 2 third charged - chargesound = 'yogstation/sound/weapons/spur_chargehigh.ogg' - recoil = 1 - fire_sound = 'yogstation/sound/weapons/spur_high.ogg' - else if(charge >= ((maxcharge/3) * 1)) // 1 third charged - chargesound = 'yogstation/sound/weapons/spur_chargemed.ogg' - recoil = 0 - fire_sound = 'yogstation/sound/weapons/spur_medium.ogg' - else if(charge >= AC.e_cost) // less than that - chargesound = 'yogstation/sound/weapons/spur_chargehigh.ogg' - recoil = 0 - fire_sound = 'yogstation/sound/weapons/spur_low.ogg' - else - chargesound = null - recoil = 0 - fire_sound = 'yogstation/sound/weapons/spur_low.ogg' - - if(chargesound != oldsound) - playsound(src, chargesound, 100) - sleep(1) - playsound(src, chargesound, 75) - return - -/obj/item/gun/energy/polarstar/spur - name = "Spur" - desc = "A masterpiece crafted by the legendary gunsmith of a far-away planet." - icon_state = "spur" - item_state = "spur" - fire_delay = 0 //pewpew - ammo_type = list(/obj/item/ammo_casing/energy/polarstar/spur) - selfcharge = TRUE - - - - - - - - -//#################// -//###PROJECTILES###// -//#################// - -/obj/item/ammo_casing/energy/polarstar - projectile_type = /obj/item/projectile/bullet/polarstar - select_name = "polar star lens" - e_cost = 100 - fire_sound = null - harmful = TRUE - - -/obj/item/projectile/bullet/polarstar - name = "polar star bullet" - range = 20 - damage = 20 - damage_type = BRUTE - icon = 'yogstation/icons/obj/xenoarch/guns.dmi' - icon_state = "spur_high" - var/skip = FALSE //this is the hackiest thing ive ever done but i dont know any other solution other than deparent the spur projectile - -/obj/item/projectile/bullet/polarstar/fire(angle, atom/direct_target) - if(!fired_from || !istype(fired_from,/obj/item/gun/energy) || skip) - return ..() - - var/obj/item/gun/energy/polarstar/fired_gun = fired_from - var/maxcharge = fired_gun.cell.maxcharge - var/charge = fired_gun.cell.charge - - if(charge >= ((maxcharge/3) * 2)) // 2 third charged - icon_state = "spur_high" - damage = 20 - range = 20 - else if(charge >= ((maxcharge/3) * 1)) // 1 third charged - icon_state = "spur_medium" - damage = 15 - range = 13 - else - icon_state = "spur_low" - damage = 10 - range = 7 - ..() - -/obj/item/projectile/bullet/polarstar/on_range() - if(!loc) - return - var/turf/T = loc - var/image/impact = image('yogstation/icons/obj/xenoarch/guns.dmi',T,"spur_range") - impact.layer = ABOVE_MOB_LAYER - T.overlays += impact - sleep(3) - T.overlays -= impact - qdel(impact) - ..() - -/obj/item/projectile/bullet/polarstar/on_hit(atom/target, blocked) - . = ..() - var/impact_icon = null - var/impact_sound = null - - if(ismob(target)) - impact_icon = "spur_hitmob" - impact_sound = 'yogstation/sound/weapons/spur_hitmob.ogg' - else - impact_icon = "spur_hitwall" - impact_sound = 'yogstation/sound/weapons/spur_hitwall.ogg' - - var/image/impact = image('yogstation/icons/obj/xenoarch/guns.dmi',target,impact_icon) - target.overlays += impact - spawn(30) - target.overlays -= impact - playsound(loc, impact_sound, 30) - if(istype(target,/turf/closed/mineral)) - var/turf/closed/mineral/M = target - M.gets_drilled() - ..() - -/obj/item/ammo_casing/energy/polarstar/spur - projectile_type = /obj/item/projectile/bullet/polarstar/spur - select_name = "spur lens" - - -/obj/item/projectile/bullet/polarstar/spur - name = "spur bullet" - range = 20 - damage = 40 - damage_type = BRUTE - icon = 'yogstation/icons/obj/xenoarch/guns.dmi' - icon_state = "spur_high" - skip = TRUE - -/obj/item/projectile/bullet/polarstar/spur/fire(angle, atom/direct_target) - if(!fired_from || !istype(fired_from,/obj/item/gun/energy)) - return ..() - - var/obj/item/gun/energy/polarstar/fired_gun = fired_from - var/maxcharge = fired_gun.cell.maxcharge - var/charge = fired_gun.cell.charge - - if(charge >= ((maxcharge/3) * 2)) // 2 third charged - icon_state = "spur_high" - damage = 40 - range = 20 - else if(charge >= ((maxcharge/3) * 1)) // 1 third charged - icon_state = "spur_medium" - damage = 30 - range = 13 - else - icon_state = "spur_low" - damage = 20 - range = 7 +/obj/item/gun/energy/polarstar + name = "Polar Star" + desc = "Despite being incomplete, the severe wear on this gun shows to which extent it's been used already." + icon = 'yogstation/icons/obj/xenoarch/guns.dmi' + lefthand_file = 'yogstation/icons/mob/inhands/weapons/xenoarch_lefthand.dmi' + righthand_file = 'yogstation/icons/mob/inhands/weapons/xenoarch_righthand.dmi' + icon_state = "polarstar" + item_state = "polarstar" + slot_flags = SLOT_BELT + fire_delay = 1 + recoil = 1 + cell_type = /obj/item/stock_parts/cell + ammo_type = list(/obj/item/ammo_casing/energy/polarstar) + var/chargesound + +/obj/item/gun/energy/polarstar/New(loc, ...) + . = ..() + playsound(src, 'yogstation/sound/weapons/spur_spawn.ogg') + +/obj/item/gun/energy/polarstar/update_icon(force_update) + var/maxcharge = cell.maxcharge + var/charge = cell.charge + + var/oldsound = chargesound + var/obj/item/ammo_casing/energy/AC = ammo_type[select] //shouldnt be anything other than the normal but eh,adminbus resistance doesnt hurt + if(charge >= ((maxcharge/3) * 2)) // 2 third charged + chargesound = 'yogstation/sound/weapons/spur_chargehigh.ogg' + recoil = 1 + fire_sound = 'yogstation/sound/weapons/spur_high.ogg' + else if(charge >= ((maxcharge/3) * 1)) // 1 third charged + chargesound = 'yogstation/sound/weapons/spur_chargemed.ogg' + recoil = 0 + fire_sound = 'yogstation/sound/weapons/spur_medium.ogg' + else if(charge >= AC.e_cost) // less than that + chargesound = 'yogstation/sound/weapons/spur_chargehigh.ogg' + recoil = 0 + fire_sound = 'yogstation/sound/weapons/spur_low.ogg' + else + chargesound = null + recoil = 0 + fire_sound = 'yogstation/sound/weapons/spur_low.ogg' + + if(chargesound != oldsound) + playsound(src, chargesound, 100) + sleep(1) + playsound(src, chargesound, 75) + return + +/obj/item/gun/energy/polarstar/spur + name = "Spur" + desc = "A masterpiece crafted by the legendary gunsmith of a far-away planet." + icon_state = "spur" + item_state = "spur" + fire_delay = 0 //pewpew + ammo_type = list(/obj/item/ammo_casing/energy/polarstar/spur) + selfcharge = TRUE + + + + + + + + +//#################// +//###PROJECTILES###// +//#################// + +/obj/item/ammo_casing/energy/polarstar + projectile_type = /obj/item/projectile/bullet/polarstar + select_name = "polar star lens" + e_cost = 100 + fire_sound = null + harmful = TRUE + + +/obj/item/projectile/bullet/polarstar + name = "polar star bullet" + range = 20 + damage = 20 + damage_type = BRUTE + icon = 'yogstation/icons/obj/xenoarch/guns.dmi' + icon_state = "spur_high" + var/skip = FALSE //this is the hackiest thing ive ever done but i dont know any other solution other than deparent the spur projectile + +/obj/item/projectile/bullet/polarstar/fire(angle, atom/direct_target) + if(!fired_from || !istype(fired_from,/obj/item/gun/energy) || skip) + return ..() + + var/obj/item/gun/energy/polarstar/fired_gun = fired_from + var/maxcharge = fired_gun.cell.maxcharge + var/charge = fired_gun.cell.charge + + if(charge >= ((maxcharge/3) * 2)) // 2 third charged + icon_state = "spur_high" + damage = 20 + range = 20 + else if(charge >= ((maxcharge/3) * 1)) // 1 third charged + icon_state = "spur_medium" + damage = 15 + range = 13 + else + icon_state = "spur_low" + damage = 10 + range = 7 + ..() + +/obj/item/projectile/bullet/polarstar/on_range() + if(!loc) + return + var/turf/T = loc + var/image/impact = image('yogstation/icons/obj/xenoarch/guns.dmi',T,"spur_range") + impact.layer = ABOVE_MOB_LAYER + T.overlays += impact + sleep(3) + T.overlays -= impact + qdel(impact) + ..() + +/obj/item/projectile/bullet/polarstar/on_hit(atom/target, blocked) + . = ..() + var/impact_icon = null + var/impact_sound = null + + if(ismob(target)) + impact_icon = "spur_hitmob" + impact_sound = 'yogstation/sound/weapons/spur_hitmob.ogg' + else + impact_icon = "spur_hitwall" + impact_sound = 'yogstation/sound/weapons/spur_hitwall.ogg' + + var/image/impact = image('yogstation/icons/obj/xenoarch/guns.dmi',target,impact_icon) + target.overlays += impact + spawn(30) + target.overlays -= impact + playsound(loc, impact_sound, 30) + if(istype(target,/turf/closed/mineral)) + var/turf/closed/mineral/M = target + M.gets_drilled() + ..() + +/obj/item/ammo_casing/energy/polarstar/spur + projectile_type = /obj/item/projectile/bullet/polarstar/spur + select_name = "spur lens" + + +/obj/item/projectile/bullet/polarstar/spur + name = "spur bullet" + range = 20 + damage = 40 + damage_type = BRUTE + icon = 'yogstation/icons/obj/xenoarch/guns.dmi' + icon_state = "spur_high" + skip = TRUE + +/obj/item/projectile/bullet/polarstar/spur/fire(angle, atom/direct_target) + if(!fired_from || !istype(fired_from,/obj/item/gun/energy)) + return ..() + + var/obj/item/gun/energy/polarstar/fired_gun = fired_from + var/maxcharge = fired_gun.cell.maxcharge + var/charge = fired_gun.cell.charge + + if(charge >= ((maxcharge/3) * 2)) // 2 third charged + icon_state = "spur_high" + damage = 40 + range = 20 + else if(charge >= ((maxcharge/3) * 1)) // 1 third charged + icon_state = "spur_medium" + damage = 30 + range = 13 + else + icon_state = "spur_low" + damage = 20 + range = 7 ..() \ No newline at end of file