From 686079fd7649c4c7ccdfd90e2bd69281a877ab43 Mon Sep 17 00:00:00 2001 From: joker Date: Tue, 27 Oct 2026 02:29:42 +0100 Subject: [PATCH 01/11] =?UTF-8?q?Wirusologia=20disease2=20z=20Monkestation?= =?UTF-8?q?=20(port=20modu=C5=82u=20virology)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port modułu monkestation/code/modules/virology (autor Dwasint, system disease2 z /vg/station): antygeny, siła i wytrzymałość patogenu, jeden objaw na stopień, układ odpornościowy na mobie, chmury patogenów, sterylność ubioru, maszyny wirusologa (izolator, inkubator, centryfuga, analizator, splicer, konsola bazy patogenów), szalki, dyski GNA, symptom cracker, okulary patologa, myszy i szczury zarazy, paczki cargo i zlecenia, obwody i projekty R&D. Objawy disease2 siedzą na /datum/symptom/acute, żeby nie mieszać się z naszymi objawami advance (22 nazwy typów się pokrywały). Stary system (advance, PANDEMIC, wirus-antagonista, katalog objawów w PDA) zostaje nietknięty. Rdzeń ruszony minimalnie: lista objawów advance w SSdisease i debugowej szafie bez gałęzi acute, ROLE_PLAGUERAT w rolach duchów, var/list/symptoms przeniesiony z advance na /datum/disease. Reszta to nadpisania w aquila/ (opisane w aquila/LEGACY_OVERRIDES.md). --- aquila/LEGACY_OVERRIDES.md | 10 + aquila/aquila.dm | 170 ++++++ aquila/code/__DEFINES/virology.dm | 120 ++++ .../modules/unit_tests/aquila_pathology.dm | 93 +++ aquila/code/modules/virology/__base_procs.dm | 8 + aquila/code/modules/virology/__effects.dm | 31 + aquila/code/modules/virology/_core/compat.dm | 72 +++ aquila/code/modules/virology/_core/helpers.dm | 57 ++ aquila/code/modules/virology/_core/hooks.dm | 56 ++ .../code/modules/virology/_core/observer.dm | 47 ++ .../_core/pathogen_clouds_subsystem.dm | 74 +++ .../modules/virology/_core/virus_viewer.dm | 57 ++ .../code/modules/virology/cargo/bounties.dm | 37 ++ .../code/modules/virology/cargo/pathology.dm | 27 + .../disease/base_disease_folder/_base.dm | 320 +++++++++++ .../disease/base_disease_folder/admin.dm | 266 +++++++++ .../disease/base_disease_folder/base_types.dm | 35 ++ .../disease_augmentation.dm | 40 ++ .../disease_modification.dm | 81 +++ .../base_disease_folder/disease_strings.dm | 123 ++++ .../base_disease_folder/general_procs.dm | 122 ++++ .../disease/base_disease_folder/logging.dm | 50 ++ .../virology/disease/plague_rat/antagonist.dm | 40 ++ .../virology/disease/plague_rat/event.dm | 44 ++ .../virology/disease/plague_rat/objective.dm | 26 + .../virology/disease/plague_rat/plague_rat.dm | 42 ++ .../virology/disease/plague_rat/team.dm | 103 ++++ .../disease/premades/_base_premades.dm | 25 + .../disease/premades/adrenal_crisis.dm | 15 + .../virology/disease/premades/anxiety.dm | 15 + .../disease/premades/death_sandwich.dm | 26 + .../disease/premades/debug_disease.dm | 57 ++ .../virology/disease/premades/decloning.dm | 16 + .../modules/virology/disease/premades/flu.dm | 16 + .../virology/disease/premades/fungal_tb.dm | 31 + .../virology/disease/premades/heart_attack.dm | 29 + .../disease/premades/transformations.dm | 154 +++++ .../disease/symptom_cracker/cracker_puzzle.dm | 173 ++++++ .../symptom_cracker/symptom_component.dm | 163 ++++++ .../virology/disease/symptom_varients/base.dm | 57 ++ .../disease/symptom_varients/bluespace.dm | 35 ++ .../disease/symptom_varients/recursive.dm | 25 + .../disease/symptom_varients/reflexive.dm | 24 + .../disease/symptom_varients/routine.dm | 24 + .../disease/symptom_varients/strengthen.dm | 20 + .../virology/disease/symtoms/_symptom.dm | 116 ++++ .../disease/symtoms/animation_procs.dm | 109 ++++ .../disease/symtoms/annoying/bee_vomit.dm | 45 ++ .../disease/symtoms/annoying/bloodynose.dm | 17 + .../disease/symtoms/annoying/chicken_pox.dm | 15 + .../disease/symtoms/annoying/cough.dm | 25 + .../disease/symtoms/annoying/cult_syndrome.dm | 75 +++ .../disease/symtoms/annoying/cyborg_vomit.dm | 33 ++ .../disease/symtoms/annoying/dizzy.dm | 36 ++ .../annoying/eldritch_hallucinations.dm | 38 ++ .../symtoms/annoying/fullness_syndrome.dm | 21 + .../disease/symtoms/annoying/headache.dm | 34 ++ .../disease/symtoms/annoying/itching.dm | 30 + .../disease/symtoms/annoying/sneeze.dm | 23 + .../disease/symtoms/annoying/vitreous.dm | 39 ++ .../virology/disease/symtoms/deadly/alkali.dm | 66 +++ .../disease/symtoms/deadly/asphyxiation.dm | 19 + .../virology/disease/symtoms/deadly/fire.dm | 44 ++ .../disease/symtoms/deadly/flesh_death.dm | 48 ++ .../disease/symtoms/deadly/flesh_eating.dm | 42 ++ .../disease/symtoms/deadly/killer_dna.dm | 14 + .../disease/symtoms/deadly/killer_toxins.dm | 12 + .../disease/symtoms/deadly/rhumba_beat.dm | 41 ++ .../virology/disease/symtoms/flavor/beard.dm | 27 + .../disease/symtoms/flavor/disfigured.dm | 14 + .../disease/symtoms/flavor/drained.dm | 9 + .../disease/symtoms/flavor/drolling.dm | 10 + .../disease/symtoms/flavor/eyewater.dm | 10 + .../virology/disease/symtoms/flavor/fizzle.dm | 9 + .../virology/disease/symtoms/flavor/fridge.dm | 21 + .../virology/disease/symtoms/flavor/giggle.dm | 9 + .../virology/disease/symtoms/flavor/groan.dm | 9 + .../virology/disease/symtoms/flavor/gunk.dm | 9 + .../disease/symtoms/flavor/hair_growth.dm | 21 + .../disease/symtoms/flavor/polyvitiligo.dm | 20 + .../disease/symtoms/flavor/soreness.dm | 14 + .../symtoms/flavor/special_adaptation.dm | 28 + .../disease/symtoms/flavor/telepathy.dm | 32 ++ .../virology/disease/symtoms/flavor/twitch.dm | 10 + .../virology/disease/symtoms/flavor/wheeze.dm | 9 + .../disease/symtoms/harmful/blindness.dm | 42 ++ .../disease/symtoms/harmful/butterfly_skin.dm | 41 ++ .../disease/symtoms/harmful/dna_spread.dm | 44 ++ .../virology/disease/symtoms/harmful/drunk.dm | 15 + .../disease/symtoms/harmful/lazy_mind.dm | 13 + .../disease/symtoms/harmful/mint_toxin.dm | 11 + .../symtoms/harmful/mob_spawn_symptoms.dm | 48 ++ .../disease/symtoms/harmful/retro_virus.dm | 49 ++ .../symtoms/harmful/species_modification.dm | 33 ++ .../disease/symtoms/harmful/teratoma.dm | 65 +++ .../disease/symtoms/harmful/toxins.dm | 10 + .../disease/symtoms/harmful/wizarditis.dm | 93 +++ .../disease/symtoms/helpful/antitoxin.dm | 15 + .../disease/symtoms/helpful/clockwork.dm | 204 +++++++ .../virology/disease/symtoms/helpful/coma.dm | 92 +++ .../disease/symtoms/helpful/damge_to_toxin.dm | 20 + .../disease/symtoms/helpful/darkness.dm | 41 ++ .../disease/symtoms/helpful/delightful.dm | 16 + .../disease/symtoms/helpful/invisible.dm | 9 + .../disease/symtoms/helpful/lantern.dm | 40 ++ .../disease/symtoms/helpful/longevity.dm | 34 ++ .../symtoms/helpful/metabolism_boost.dm | 18 + .../symtoms/helpful/mind_restoration.dm | 20 + .../disease/symtoms/helpful/nanites.dm | 73 +++ .../disease/symtoms/helpful/plasma_heal.dm | 113 ++++ .../symtoms/helpful/self_respiration.dm | 25 + .../symtoms/helpful/sensory_restoration.dm | 50 ++ .../disease/symtoms/helpful/space_adapt.dm | 23 + .../disease/symtoms/helpful/stimulant.dm | 20 + .../disease/symtoms/helpful/thick_blood.dm | 13 + .../disease/symtoms/helpful/water_heal.dm | 50 ++ .../disease/symtoms/hinderance/choking.dm | 15 + .../disease/symtoms/hinderance/confusion.dm | 13 + .../disease/symtoms/hinderance/cult_vomit.dm | 24 + .../disease/symtoms/hinderance/deafness.dm | 26 + .../disease/symtoms/hinderance/drowsness.dm | 12 + .../disease/symtoms/hinderance/elvis.dm | 62 ++ .../disease/symtoms/hinderance/famine.dm | 20 + .../symtoms/hinderance/fragile_bones.dm | 19 + .../disease/symtoms/hinderance/gregarious.dm | 28 + .../symtoms/hinderance/hallucination.dm | 9 + .../symtoms/hinderance/horse_throat.dm | 27 + .../symtoms/hinderance/pierotts_throat.dm | 66 +++ .../disease/symtoms/hinderance/shaking.dm | 10 + .../disease/symtoms/hinderance/spiky_skin.dm | 58 ++ .../disease/symtoms/hinderance/sweat.dm | 12 + .../symtoms/hinderance/wendigo_vomit.dm | 15 + .../disease/symtoms/restricted/stage1.dm | 204 +++++++ .../disease/symtoms/restricted/stage2.dm | 222 +++++++ .../disease/symtoms/restricted/stage3.dm | 73 +++ .../disease/symtoms/restricted/stage4.dm | 170 ++++++ .../modules/virology/effects/cleanables.dm | 51 ++ .../virology/effects/pathogen_cloud.dm | 133 +++++ aquila/code/modules/virology/fullscreen.dm | 4 + .../virology/immune_systems/_immune_system.dm | 220 +++++++ .../virology/immune_systems/job_boosting.dm | 26 + .../virology/items/_base_item_additions.dm | 95 +++ .../modules/virology/items/antibodyscanner.dm | 163 ++++++ .../virology/items/bio_hazard_deployable.dm | 23 + .../modules/virology/items/clothing/mask.dm | 2 + .../modules/virology/items/clothing/shoes.dm | 7 + .../modules/virology/items/disease_disk.dm | 19 + .../modules/virology/items/extrapolator.dm | 114 ++++ .../code/modules/virology/items/mousecubes.dm | 10 + .../virology/items/pathology_glasses.dm | 80 +++ .../code/modules/virology/items/virusdish.dm | 333 +++++++++++ .../virology/living/infection_procs.dm | 115 ++++ aquila/code/modules/virology/living/mouse.dm | 70 +++ .../modules/virology/living/spread_disease.dm | 100 ++++ .../modules/virology/living/sterile_procs.dm | 66 +++ .../modules/virology/machines/analyzer.dm | 270 +++++++++ .../modules/virology/machines/centrifuge.dm | 542 ++++++++++++++++++ .../code/modules/virology/machines/curer.dm | 105 ++++ .../virology/machines/disease_records.dm | 71 +++ .../virology/machines/floor_health_scanner.dm | 83 +++ .../modules/virology/machines/incubator.dm | 492 ++++++++++++++++ .../modules/virology/machines/isolator.dm | 114 ++++ .../code/modules/virology/machines/splicer.dm | 265 +++++++++ .../virology/reagents/_stage_mutations.dm | 35 ++ .../virology/reagents/antipathenogenics.dm | 37 ++ .../virology/reagents/immune_healers.dm | 65 +++ .../modules/virology/reagents/incubation.dm | 46 ++ .../virology/reagents/symptom_reagents.dm | 38 ++ .../code/modules/virology/reagents/vaccine.dm | 31 + .../virology/research/circuitboards.dm | 61 ++ .../modules/virology/research/research.dm | 57 ++ aquila/icons/virology/96x96.dmi | Bin 0 -> 1567 bytes aquila/icons/virology/animal.dmi | Bin 0 -> 2833 bytes aquila/icons/virology/biohazard.dmi | Bin 0 -> 2533 bytes aquila/icons/virology/deityrunes.dmi | Bin 0 -> 28470 bytes aquila/icons/virology/effects.dmi | Bin 0 -> 4880 bytes aquila/icons/virology/items.dmi | Bin 0 -> 2472 bytes aquila/icons/virology/role_HUD_icons.dmi | Bin 0 -> 247 bytes aquila/icons/virology/shoes.dmi | Bin 0 -> 421 bytes aquila/icons/virology/shoes_worn.dmi | Bin 0 -> 367 bytes aquila/icons/virology/virology.dmi | Bin 0 -> 17111 bytes aquila/icons/virology/virology_bg.dmi | Bin 0 -> 849 bytes aquila/sound/virology/behind_you1.ogg | Bin 0 -> 70085 bytes aquila/sound/virology/behind_you2.ogg | Bin 0 -> 84020 bytes aquila/sound/virology/ear_ring_single.ogg | Bin 0 -> 36320 bytes aquila/sound/virology/far_noise.ogg | Bin 0 -> 66168 bytes aquila/sound/virology/ghost.ogg | Bin 0 -> 29637 bytes aquila/sound/virology/ghost2.ogg | Bin 0 -> 11359 bytes aquila/sound/virology/growl1.ogg | Bin 0 -> 40715 bytes aquila/sound/virology/growl2.ogg | Bin 0 -> 40955 bytes aquila/sound/virology/growl3.ogg | Bin 0 -> 40815 bytes aquila/sound/virology/heart_beat_single.ogg | Bin 0 -> 167466 bytes aquila/sound/virology/i_see_you1.ogg | Bin 0 -> 104313 bytes aquila/sound/virology/i_see_you2.ogg | Bin 0 -> 100263 bytes aquila/sound/virology/im_here1.ogg | Bin 0 -> 79600 bytes aquila/sound/virology/im_here2.ogg | Bin 0 -> 68005 bytes aquila/sound/virology/look_up1.ogg | Bin 0 -> 52748 bytes aquila/sound/virology/look_up2.ogg | Bin 0 -> 36980 bytes aquila/sound/virology/over_here1.ogg | Bin 0 -> 66452 bytes aquila/sound/virology/over_here2.ogg | Bin 0 -> 72148 bytes aquila/sound/virology/over_here3.ogg | Bin 0 -> 58634 bytes aquila/sound/virology/scary.ogg | Bin 0 -> 72096 bytes aquila/sound/virology/screech.ogg | Bin 0 -> 15453 bytes aquila/sound/virology/turn_around1.ogg | Bin 0 -> 63423 bytes aquila/sound/virology/turn_around2.ogg | Bin 0 -> 60415 bytes aquila/sound/virology/veryfar_noise.ogg | Bin 0 -> 68803 bytes aquila/sound/virology/wail.ogg | Bin 0 -> 75480 bytes code/__DEFINES/role_preferences.dm | 2 + code/controllers/subsystem/disease.dm | 2 +- code/datums/diseases/advance/advance.dm | 2 +- .../kitchen_machinery/smartfridge.dm | 2 +- .../tgui/interfaces/CrackerPuzzle.jsx | 116 ++++ .../tgui/interfaces/DiseaseIncubator.jsx | 170 ++++++ .../tgui/interfaces/DiseaseSplicer.tsx | 119 ++++ .../packages/tgui/interfaces/Extrapolator.tsx | 78 +++ .../PathologyRecords/RecordTabs.tsx | 73 +++ .../PathologyRecords/RecordView.tsx | 149 +++++ .../interfaces/PathologyRecords/helpers.ts | 24 + .../interfaces/PathologyRecords/index.tsx | 82 +++ .../tgui/interfaces/PathologyRecords/types.ts | 33 ++ 220 files changed, 11476 insertions(+), 3 deletions(-) create mode 100644 aquila/code/__DEFINES/virology.dm create mode 100644 aquila/code/modules/unit_tests/aquila_pathology.dm create mode 100644 aquila/code/modules/virology/__base_procs.dm create mode 100644 aquila/code/modules/virology/__effects.dm create mode 100644 aquila/code/modules/virology/_core/compat.dm create mode 100644 aquila/code/modules/virology/_core/helpers.dm create mode 100644 aquila/code/modules/virology/_core/hooks.dm create mode 100644 aquila/code/modules/virology/_core/observer.dm create mode 100644 aquila/code/modules/virology/_core/pathogen_clouds_subsystem.dm create mode 100644 aquila/code/modules/virology/_core/virus_viewer.dm create mode 100644 aquila/code/modules/virology/cargo/bounties.dm create mode 100644 aquila/code/modules/virology/cargo/pathology.dm create mode 100644 aquila/code/modules/virology/disease/base_disease_folder/_base.dm create mode 100644 aquila/code/modules/virology/disease/base_disease_folder/admin.dm create mode 100644 aquila/code/modules/virology/disease/base_disease_folder/base_types.dm create mode 100644 aquila/code/modules/virology/disease/base_disease_folder/disease_augmentation.dm create mode 100644 aquila/code/modules/virology/disease/base_disease_folder/disease_modification.dm create mode 100644 aquila/code/modules/virology/disease/base_disease_folder/disease_strings.dm create mode 100644 aquila/code/modules/virology/disease/base_disease_folder/general_procs.dm create mode 100644 aquila/code/modules/virology/disease/base_disease_folder/logging.dm create mode 100644 aquila/code/modules/virology/disease/plague_rat/antagonist.dm create mode 100644 aquila/code/modules/virology/disease/plague_rat/event.dm create mode 100644 aquila/code/modules/virology/disease/plague_rat/objective.dm create mode 100644 aquila/code/modules/virology/disease/plague_rat/plague_rat.dm create mode 100644 aquila/code/modules/virology/disease/plague_rat/team.dm create mode 100644 aquila/code/modules/virology/disease/premades/_base_premades.dm create mode 100644 aquila/code/modules/virology/disease/premades/adrenal_crisis.dm create mode 100644 aquila/code/modules/virology/disease/premades/anxiety.dm create mode 100644 aquila/code/modules/virology/disease/premades/death_sandwich.dm create mode 100644 aquila/code/modules/virology/disease/premades/debug_disease.dm create mode 100644 aquila/code/modules/virology/disease/premades/decloning.dm create mode 100644 aquila/code/modules/virology/disease/premades/flu.dm create mode 100644 aquila/code/modules/virology/disease/premades/fungal_tb.dm create mode 100644 aquila/code/modules/virology/disease/premades/heart_attack.dm create mode 100644 aquila/code/modules/virology/disease/premades/transformations.dm create mode 100644 aquila/code/modules/virology/disease/symptom_cracker/cracker_puzzle.dm create mode 100644 aquila/code/modules/virology/disease/symptom_cracker/symptom_component.dm create mode 100644 aquila/code/modules/virology/disease/symptom_varients/base.dm create mode 100644 aquila/code/modules/virology/disease/symptom_varients/bluespace.dm create mode 100644 aquila/code/modules/virology/disease/symptom_varients/recursive.dm create mode 100644 aquila/code/modules/virology/disease/symptom_varients/reflexive.dm create mode 100644 aquila/code/modules/virology/disease/symptom_varients/routine.dm create mode 100644 aquila/code/modules/virology/disease/symptom_varients/strengthen.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/_symptom.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/animation_procs.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/bee_vomit.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/bloodynose.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/chicken_pox.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/cough.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/cult_syndrome.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/cyborg_vomit.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/dizzy.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/eldritch_hallucinations.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/fullness_syndrome.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/headache.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/itching.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/sneeze.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/annoying/vitreous.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/deadly/alkali.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/deadly/asphyxiation.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/deadly/fire.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/deadly/flesh_death.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/deadly/flesh_eating.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/deadly/killer_dna.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/deadly/killer_toxins.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/deadly/rhumba_beat.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/beard.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/disfigured.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/drained.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/drolling.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/eyewater.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/fizzle.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/fridge.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/giggle.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/groan.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/gunk.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/hair_growth.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/polyvitiligo.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/soreness.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/special_adaptation.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/telepathy.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/twitch.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/flavor/wheeze.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/blindness.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/butterfly_skin.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/dna_spread.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/drunk.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/lazy_mind.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/mint_toxin.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/mob_spawn_symptoms.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/retro_virus.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/species_modification.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/teratoma.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/toxins.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/harmful/wizarditis.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/antitoxin.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/clockwork.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/coma.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/damge_to_toxin.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/darkness.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/delightful.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/invisible.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/lantern.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/longevity.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/metabolism_boost.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/mind_restoration.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/nanites.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/plasma_heal.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/self_respiration.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/sensory_restoration.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/space_adapt.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/stimulant.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/thick_blood.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/helpful/water_heal.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/choking.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/confusion.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/cult_vomit.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/deafness.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/drowsness.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/elvis.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/famine.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/fragile_bones.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/gregarious.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/hallucination.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/horse_throat.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/pierotts_throat.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/shaking.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/spiky_skin.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/sweat.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/hinderance/wendigo_vomit.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/restricted/stage1.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/restricted/stage2.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/restricted/stage3.dm create mode 100644 aquila/code/modules/virology/disease/symtoms/restricted/stage4.dm create mode 100644 aquila/code/modules/virology/effects/cleanables.dm create mode 100644 aquila/code/modules/virology/effects/pathogen_cloud.dm create mode 100644 aquila/code/modules/virology/fullscreen.dm create mode 100644 aquila/code/modules/virology/immune_systems/_immune_system.dm create mode 100644 aquila/code/modules/virology/immune_systems/job_boosting.dm create mode 100644 aquila/code/modules/virology/items/_base_item_additions.dm create mode 100644 aquila/code/modules/virology/items/antibodyscanner.dm create mode 100644 aquila/code/modules/virology/items/bio_hazard_deployable.dm create mode 100644 aquila/code/modules/virology/items/clothing/mask.dm create mode 100644 aquila/code/modules/virology/items/clothing/shoes.dm create mode 100644 aquila/code/modules/virology/items/disease_disk.dm create mode 100644 aquila/code/modules/virology/items/extrapolator.dm create mode 100644 aquila/code/modules/virology/items/mousecubes.dm create mode 100644 aquila/code/modules/virology/items/pathology_glasses.dm create mode 100644 aquila/code/modules/virology/items/virusdish.dm create mode 100644 aquila/code/modules/virology/living/infection_procs.dm create mode 100644 aquila/code/modules/virology/living/mouse.dm create mode 100644 aquila/code/modules/virology/living/spread_disease.dm create mode 100644 aquila/code/modules/virology/living/sterile_procs.dm create mode 100644 aquila/code/modules/virology/machines/analyzer.dm create mode 100644 aquila/code/modules/virology/machines/centrifuge.dm create mode 100644 aquila/code/modules/virology/machines/curer.dm create mode 100644 aquila/code/modules/virology/machines/disease_records.dm create mode 100644 aquila/code/modules/virology/machines/floor_health_scanner.dm create mode 100644 aquila/code/modules/virology/machines/incubator.dm create mode 100644 aquila/code/modules/virology/machines/isolator.dm create mode 100644 aquila/code/modules/virology/machines/splicer.dm create mode 100644 aquila/code/modules/virology/reagents/_stage_mutations.dm create mode 100644 aquila/code/modules/virology/reagents/antipathenogenics.dm create mode 100644 aquila/code/modules/virology/reagents/immune_healers.dm create mode 100644 aquila/code/modules/virology/reagents/incubation.dm create mode 100644 aquila/code/modules/virology/reagents/symptom_reagents.dm create mode 100644 aquila/code/modules/virology/reagents/vaccine.dm create mode 100644 aquila/code/modules/virology/research/circuitboards.dm create mode 100644 aquila/code/modules/virology/research/research.dm create mode 100644 aquila/icons/virology/96x96.dmi create mode 100644 aquila/icons/virology/animal.dmi create mode 100644 aquila/icons/virology/biohazard.dmi create mode 100644 aquila/icons/virology/deityrunes.dmi create mode 100644 aquila/icons/virology/effects.dmi create mode 100644 aquila/icons/virology/items.dmi create mode 100644 aquila/icons/virology/role_HUD_icons.dmi create mode 100644 aquila/icons/virology/shoes.dmi create mode 100644 aquila/icons/virology/shoes_worn.dmi create mode 100644 aquila/icons/virology/virology.dmi create mode 100644 aquila/icons/virology/virology_bg.dmi create mode 100644 aquila/sound/virology/behind_you1.ogg create mode 100644 aquila/sound/virology/behind_you2.ogg create mode 100644 aquila/sound/virology/ear_ring_single.ogg create mode 100644 aquila/sound/virology/far_noise.ogg create mode 100644 aquila/sound/virology/ghost.ogg create mode 100644 aquila/sound/virology/ghost2.ogg create mode 100644 aquila/sound/virology/growl1.ogg create mode 100644 aquila/sound/virology/growl2.ogg create mode 100644 aquila/sound/virology/growl3.ogg create mode 100644 aquila/sound/virology/heart_beat_single.ogg create mode 100644 aquila/sound/virology/i_see_you1.ogg create mode 100644 aquila/sound/virology/i_see_you2.ogg create mode 100644 aquila/sound/virology/im_here1.ogg create mode 100644 aquila/sound/virology/im_here2.ogg create mode 100644 aquila/sound/virology/look_up1.ogg create mode 100644 aquila/sound/virology/look_up2.ogg create mode 100644 aquila/sound/virology/over_here1.ogg create mode 100644 aquila/sound/virology/over_here2.ogg create mode 100644 aquila/sound/virology/over_here3.ogg create mode 100644 aquila/sound/virology/scary.ogg create mode 100644 aquila/sound/virology/screech.ogg create mode 100644 aquila/sound/virology/turn_around1.ogg create mode 100644 aquila/sound/virology/turn_around2.ogg create mode 100644 aquila/sound/virology/veryfar_noise.ogg create mode 100644 aquila/sound/virology/wail.ogg create mode 100644 tgui/packages/tgui/interfaces/CrackerPuzzle.jsx create mode 100644 tgui/packages/tgui/interfaces/DiseaseIncubator.jsx create mode 100644 tgui/packages/tgui/interfaces/DiseaseSplicer.tsx create mode 100644 tgui/packages/tgui/interfaces/Extrapolator.tsx create mode 100644 tgui/packages/tgui/interfaces/PathologyRecords/RecordTabs.tsx create mode 100644 tgui/packages/tgui/interfaces/PathologyRecords/RecordView.tsx create mode 100644 tgui/packages/tgui/interfaces/PathologyRecords/helpers.ts create mode 100644 tgui/packages/tgui/interfaces/PathologyRecords/index.tsx create mode 100644 tgui/packages/tgui/interfaces/PathologyRecords/types.ts diff --git a/aquila/LEGACY_OVERRIDES.md b/aquila/LEGACY_OVERRIDES.md index bce686c3fc1..e9c76a807da 100644 --- a/aquila/LEGACY_OVERRIDES.md +++ b/aquila/LEGACY_OVERRIDES.md @@ -41,6 +41,9 @@ The other ~60 same-type redefinitions in `aquila/` call `..()`, so they are alre | Hook | Location | Feature | |---|---|---| +| `SSdisease.list_symptoms` bez `typesof(/datum/symptom/acute)`, ten sam filtr w debugowej szafie wirusologii | `code/controllers/subsystem/disease.dm`, `code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm` | Objawy disease2 (`aquila/code/modules/virology`) nie mogą trafiać do losowania objawów advance | +| `#define ROLE_PLAGUERAT` + wpis na liście ról duchów | `code/__DEFINES/role_preferences.dm` | Rola szczura zarazy (event wirusologii) musi być znana rdzeniowi przed `aquila/` | +| `var/list/symptoms` przeniesione z `/datum/disease/advance` na `/datum/disease` | `code/datums/diseases/advance/advance.dm` (deklaracja w `aquila/.../_base.dm`) | Patogeny disease2 też trzymają objawy, a maszyny czytają je z bazowego typu | | `icon = 'icons/obj/tools.dmi'`, `item_state`/`belt_icon_state` per colour | `code/game/objects/items/tools/wirecutters.dm` (`Initialize()`) | The Paradise `pcutters_*` states exist only in core `tools.dmi`, while `aquila/` points wirecutters at `aquila/icons/obj/tools.dmi`; inhand/belt states exist only for `cutters_red`/`cutters_yellow` (same pattern as the screwdriver) | | `icon_exists()` check before the shield overlay | `code/datums/components/shielded.dm` (`on_update_overlays()`) | `icons/effects/effects.dmi` has no `broken` state; a depleted shield drew a missing state, which showed a random sprite with the walk animation on 516 | | `initialized = INITIALIZATION_INNEW_REGULAR` before the round start `InitializeAtoms()` instead of `INITIALIZATION_INNEW_MAPLOAD` | `code/controllers/subsystem/atoms.dm` (`Initialize()`) | `InitializeAtoms()` switches to MAPLOAD itself and restores the previous state; starting from MAPLOAD made that the restored state, map loads during the late loaders (ship interiors) copied it and it stuck for the round, so `LateInitialize()` of runtime atoms (cables laid by players) never ran. Test: `atoms_init_state` | @@ -718,3 +721,10 @@ Of the logic files, 182 contain an AQ/AQUILA marker or an `aquila/` resource; th - `nsv13/code/modules/research/astrometrics.dm` + +### Wirusologia disease2 (port Monkestation2.0, F) + +- **Pełne nadpisania w `aquila/code/modules/virology/_core/hooks.dm`** (klasa C, bez `..()`): `/mob/living/carbon/handle_diseases()` — nasza wersja przewijała tylko stopnie chorób advance, patogeny mają własną pętlę `activate()`, a chmury i zakażone plamy działają w `handle_virus_updates()`. Zwykłe choroby dalej rozsiewa `spread()`, patogeny robią to chmurami `/obj/effect/pathogen_cloud`. +- **Nadpisania z `..()`**: `handle_breathing` (wdychanie chmur), `attack_hand` i `Bump` (zakażenie przez dotyk, liczone sterylnością ubioru i krwawieniem), `AttemptLateSpawn` (`DiseaseCarrierCheck`, 10% dołączających dostaje łagodny patogen), `/obj/effect/decal/cleanable/Initialize` (plama zapamiętuje patogeny; nasz rdzeń listy nie trzymał). +- **Warstwa zgodności** `aquila/code/modules/virology/_core/compat.dm` + `aquila/code/__DEFINES/virology.dm`: flagi `DISEASE_DORMANT/COPYSTAGE/ANALYZED`, strefy `BODY_ZONE_EVERYTHING/ARMS/LEGS`, `ROUND_TIME()`, probówka `/obj/item/reagent_containers/glass/tube`, `GLOB.inspectable_diseases`, `create_symptoms()`. +- **Objawy disease2 siedzą na `/datum/symptom/acute`**, nie na `/datum/symptom`, żeby nie mieszać się z naszymi 45 objawami advance (nazwy typów się pokrywały w 22 miejscach). diff --git a/aquila/aquila.dm b/aquila/aquila.dm index 8b06d7e7358..6a9fb1f4c54 100644 --- a/aquila/aquila.dm +++ b/aquila/aquila.dm @@ -40,6 +40,7 @@ #include "code\__DEFINES\tutorial.dm" #include "code\__DEFINES\uplink.dm" #include "code\__DEFINES\vampires.dm" +#include "code\__DEFINES\virology.dm" #include "code\__DEFINES\wires.dm" #include "code\__DEFINES\wounds.dm" #include "code\__HELPERS\names.dm" @@ -527,6 +528,174 @@ #include "code\modules\explorer_drone\research_cargo.dm" #include "code\modules\explorer_drone\scanner_array.dm" #include "code\modules\language\vampiric.dm" +// Wirusologia disease2 (port Monkestation2.0) +#include "code\modules\virology\_core\compat.dm" +#include "code\modules\virology\_core\helpers.dm" +#include "code\modules\virology\_core\hooks.dm" +#include "code\modules\virology\_core\observer.dm" +#include "code\modules\virology\_core\pathogen_clouds_subsystem.dm" +#include "code\modules\virology\_core\virus_viewer.dm" +#include "code\modules\virology\__base_procs.dm" +#include "code\modules\virology\__effects.dm" +#include "code\modules\virology\disease\base_disease_folder\_base.dm" +#include "code\modules\virology\disease\base_disease_folder\admin.dm" +#include "code\modules\virology\disease\base_disease_folder\base_types.dm" +#include "code\modules\virology\disease\base_disease_folder\disease_augmentation.dm" +#include "code\modules\virology\disease\base_disease_folder\disease_modification.dm" +#include "code\modules\virology\disease\base_disease_folder\disease_strings.dm" +#include "code\modules\virology\disease\base_disease_folder\general_procs.dm" +#include "code\modules\virology\disease\base_disease_folder\logging.dm" +#include "code\modules\virology\cargo\bounties.dm" +#include "code\modules\virology\cargo\pathology.dm" +#include "code\modules\virology\disease\plague_rat\antagonist.dm" +#include "code\modules\virology\disease\plague_rat\event.dm" +#include "code\modules\virology\disease\plague_rat\objective.dm" +#include "code\modules\virology\disease\plague_rat\plague_rat.dm" +#include "code\modules\virology\disease\plague_rat\team.dm" +#include "code\modules\virology\disease\premades\_base_premades.dm" +#include "code\modules\virology\disease\premades\adrenal_crisis.dm" +#include "code\modules\virology\disease\premades\anxiety.dm" +#include "code\modules\virology\disease\premades\death_sandwich.dm" +#include "code\modules\virology\disease\premades\debug_disease.dm" +#include "code\modules\virology\disease\premades\decloning.dm" +#include "code\modules\virology\disease\premades\flu.dm" +#include "code\modules\virology\disease\premades\fungal_tb.dm" +#include "code\modules\virology\disease\premades\heart_attack.dm" +#include "code\modules\virology\disease\premades\transformations.dm" +#include "code\modules\virology\disease\symptom_cracker\cracker_puzzle.dm" +#include "code\modules\virology\disease\symptom_cracker\symptom_component.dm" +#include "code\modules\virology\disease\symptom_varients\base.dm" +#include "code\modules\virology\disease\symptom_varients\bluespace.dm" +#include "code\modules\virology\disease\symptom_varients\recursive.dm" +#include "code\modules\virology\disease\symptom_varients\reflexive.dm" +#include "code\modules\virology\disease\symptom_varients\routine.dm" +#include "code\modules\virology\disease\symptom_varients\strengthen.dm" +#include "code\modules\virology\disease\symtoms\_symptom.dm" +#include "code\modules\virology\disease\symtoms\animation_procs.dm" +#include "code\modules\virology\disease\symtoms\annoying\bee_vomit.dm" +#include "code\modules\virology\disease\symtoms\annoying\bloodynose.dm" +#include "code\modules\virology\disease\symtoms\annoying\chicken_pox.dm" +#include "code\modules\virology\disease\symtoms\annoying\cough.dm" +#include "code\modules\virology\disease\symtoms\annoying\cult_syndrome.dm" +#include "code\modules\virology\disease\symtoms\annoying\cyborg_vomit.dm" +#include "code\modules\virology\disease\symtoms\annoying\dizzy.dm" +#include "code\modules\virology\disease\symtoms\annoying\eldritch_hallucinations.dm" +#include "code\modules\virology\disease\symtoms\annoying\fullness_syndrome.dm" +#include "code\modules\virology\disease\symtoms\annoying\headache.dm" +#include "code\modules\virology\disease\symtoms\annoying\itching.dm" +#include "code\modules\virology\disease\symtoms\annoying\sneeze.dm" +#include "code\modules\virology\disease\symtoms\annoying\vitreous.dm" +#include "code\modules\virology\disease\symtoms\deadly\alkali.dm" +#include "code\modules\virology\disease\symtoms\deadly\asphyxiation.dm" +#include "code\modules\virology\disease\symtoms\deadly\fire.dm" +#include "code\modules\virology\disease\symtoms\deadly\flesh_death.dm" +#include "code\modules\virology\disease\symtoms\deadly\flesh_eating.dm" +#include "code\modules\virology\disease\symtoms\deadly\killer_dna.dm" +#include "code\modules\virology\disease\symtoms\deadly\killer_toxins.dm" +#include "code\modules\virology\disease\symtoms\deadly\rhumba_beat.dm" +#include "code\modules\virology\disease\symtoms\flavor\beard.dm" +#include "code\modules\virology\disease\symtoms\flavor\disfigured.dm" +#include "code\modules\virology\disease\symtoms\flavor\drained.dm" +#include "code\modules\virology\disease\symtoms\flavor\drolling.dm" +#include "code\modules\virology\disease\symtoms\flavor\eyewater.dm" +#include "code\modules\virology\disease\symtoms\flavor\fizzle.dm" +#include "code\modules\virology\disease\symtoms\flavor\fridge.dm" +#include "code\modules\virology\disease\symtoms\flavor\giggle.dm" +#include "code\modules\virology\disease\symtoms\flavor\groan.dm" +#include "code\modules\virology\disease\symtoms\flavor\gunk.dm" +#include "code\modules\virology\disease\symtoms\flavor\hair_growth.dm" +#include "code\modules\virology\disease\symtoms\flavor\polyvitiligo.dm" +#include "code\modules\virology\disease\symtoms\flavor\soreness.dm" +#include "code\modules\virology\disease\symtoms\flavor\special_adaptation.dm" +#include "code\modules\virology\disease\symtoms\flavor\telepathy.dm" +#include "code\modules\virology\disease\symtoms\flavor\twitch.dm" +#include "code\modules\virology\disease\symtoms\flavor\wheeze.dm" +#include "code\modules\virology\disease\symtoms\harmful\blindness.dm" +#include "code\modules\virology\disease\symtoms\harmful\butterfly_skin.dm" +#include "code\modules\virology\disease\symtoms\harmful\dna_spread.dm" +#include "code\modules\virology\disease\symtoms\harmful\drunk.dm" +#include "code\modules\virology\disease\symtoms\harmful\lazy_mind.dm" +#include "code\modules\virology\disease\symtoms\harmful\mint_toxin.dm" +#include "code\modules\virology\disease\symtoms\harmful\mob_spawn_symptoms.dm" +#include "code\modules\virology\disease\symtoms\harmful\retro_virus.dm" +#include "code\modules\virology\disease\symtoms\harmful\species_modification.dm" +#include "code\modules\virology\disease\symtoms\harmful\teratoma.dm" +#include "code\modules\virology\disease\symtoms\harmful\toxins.dm" +#include "code\modules\virology\disease\symtoms\harmful\wizarditis.dm" +#include "code\modules\virology\disease\symtoms\helpful\antitoxin.dm" +#include "code\modules\virology\disease\symtoms\helpful\clockwork.dm" +#include "code\modules\virology\disease\symtoms\helpful\coma.dm" +#include "code\modules\virology\disease\symtoms\helpful\damge_to_toxin.dm" +#include "code\modules\virology\disease\symtoms\helpful\darkness.dm" +#include "code\modules\virology\disease\symtoms\helpful\delightful.dm" +#include "code\modules\virology\disease\symtoms\helpful\invisible.dm" +#include "code\modules\virology\disease\symtoms\helpful\lantern.dm" +#include "code\modules\virology\disease\symtoms\helpful\longevity.dm" +#include "code\modules\virology\disease\symtoms\helpful\metabolism_boost.dm" +#include "code\modules\virology\disease\symtoms\helpful\mind_restoration.dm" +#include "code\modules\virology\disease\symtoms\helpful\nanites.dm" +#include "code\modules\virology\disease\symtoms\helpful\plasma_heal.dm" +#include "code\modules\virology\disease\symtoms\helpful\self_respiration.dm" +#include "code\modules\virology\disease\symtoms\helpful\sensory_restoration.dm" +#include "code\modules\virology\disease\symtoms\helpful\space_adapt.dm" +#include "code\modules\virology\disease\symtoms\helpful\stimulant.dm" +#include "code\modules\virology\disease\symtoms\helpful\thick_blood.dm" +#include "code\modules\virology\disease\symtoms\helpful\water_heal.dm" +#include "code\modules\virology\disease\symtoms\hinderance\choking.dm" +#include "code\modules\virology\disease\symtoms\hinderance\confusion.dm" +#include "code\modules\virology\disease\symtoms\hinderance\cult_vomit.dm" +#include "code\modules\virology\disease\symtoms\hinderance\deafness.dm" +#include "code\modules\virology\disease\symtoms\hinderance\drowsness.dm" +#include "code\modules\virology\disease\symtoms\hinderance\elvis.dm" +#include "code\modules\virology\disease\symtoms\hinderance\famine.dm" +#include "code\modules\virology\disease\symtoms\hinderance\fragile_bones.dm" +#include "code\modules\virology\disease\symtoms\hinderance\gregarious.dm" +#include "code\modules\virology\disease\symtoms\hinderance\hallucination.dm" +#include "code\modules\virology\disease\symtoms\hinderance\horse_throat.dm" +#include "code\modules\virology\disease\symtoms\hinderance\pierotts_throat.dm" +#include "code\modules\virology\disease\symtoms\hinderance\shaking.dm" +#include "code\modules\virology\disease\symtoms\hinderance\spiky_skin.dm" +#include "code\modules\virology\disease\symtoms\hinderance\sweat.dm" +#include "code\modules\virology\disease\symtoms\hinderance\wendigo_vomit.dm" +#include "code\modules\virology\disease\symtoms\restricted\stage1.dm" +#include "code\modules\virology\disease\symtoms\restricted\stage2.dm" +#include "code\modules\virology\disease\symtoms\restricted\stage3.dm" +#include "code\modules\virology\disease\symtoms\restricted\stage4.dm" +#include "code\modules\virology\effects\cleanables.dm" +#include "code\modules\virology\effects\pathogen_cloud.dm" +#include "code\modules\virology\fullscreen.dm" +#include "code\modules\virology\immune_systems\_immune_system.dm" +#include "code\modules\virology\immune_systems\job_boosting.dm" +#include "code\modules\virology\items\_base_item_additions.dm" +#include "code\modules\virology\items\antibodyscanner.dm" +#include "code\modules\virology\items\bio_hazard_deployable.dm" +#include "code\modules\virology\items\clothing\mask.dm" +#include "code\modules\virology\items\clothing\shoes.dm" +#include "code\modules\virology\items\disease_disk.dm" +#include "code\modules\virology\items\extrapolator.dm" +#include "code\modules\virology\items\mousecubes.dm" +#include "code\modules\virology\items\pathology_glasses.dm" +#include "code\modules\virology\items\virusdish.dm" +#include "code\modules\virology\living\infection_procs.dm" +#include "code\modules\virology\living\mouse.dm" +#include "code\modules\virology\living\spread_disease.dm" +#include "code\modules\virology\living\sterile_procs.dm" +#include "code\modules\virology\machines\analyzer.dm" +#include "code\modules\virology\machines\centrifuge.dm" +#include "code\modules\virology\machines\curer.dm" +#include "code\modules\virology\machines\disease_records.dm" +#include "code\modules\virology\machines\floor_health_scanner.dm" +#include "code\modules\virology\machines\incubator.dm" +#include "code\modules\virology\machines\isolator.dm" +#include "code\modules\virology\machines\splicer.dm" +#include "code\modules\virology\reagents\_stage_mutations.dm" +#include "code\modules\virology\reagents\antipathenogenics.dm" +#include "code\modules\virology\reagents\immune_healers.dm" +#include "code\modules\virology\reagents\incubation.dm" +#include "code\modules\virology\reagents\symptom_reagents.dm" +#include "code\modules\virology\reagents\vaccine.dm" +#include "code\modules\virology\research\circuitboards.dm" +#include "code\modules\virology\research\research.dm" #include "code\modules\roundend\roundend_ui.dm" #include "code\modules\research\designs\bounty_market_designs.dm" #include "code\modules\cargo\exports\large_objects.dm" @@ -1420,6 +1589,7 @@ #include "code\modules\unit_tests\aquila_stationarybike.dm" #include "code\modules\unit_tests\aquila_surgery_radial.dm" #include "code\modules\unit_tests\aquila_tutorials.dm" +#include "code\modules\unit_tests\aquila_pathology.dm" #include "code\modules\unit_tests\aquila_viruses.dm" #include "code\modules\unit_tests\aquila_vitals_reader.dm" #include "code\modules\unit_tests\aquila_wounds.dm" diff --git a/aquila/code/__DEFINES/virology.dm b/aquila/code/__DEFINES/virology.dm new file mode 100644 index 00000000000..e91ba3e2778 --- /dev/null +++ b/aquila/code/__DEFINES/virology.dm @@ -0,0 +1,120 @@ +#define EFFECT_DANGER_HELPFUL "0" +#define EFFECT_DANGER_FLAVOR "1" +#define EFFECT_DANGER_ANNOYING "2" +#define EFFECT_DANGER_HINDRANCE "3" +#define EFFECT_DANGER_HARMFUL "4" +#define EFFECT_DANGER_DEADLY "5" + +#define ANTIGEN_BLOOD "blood" +#define ANTIGEN_COMMON "common" +#define ANTIGEN_RARE "rare" +#define ANTIGEN_ALIEN "alien" +#define ANTIGEN_UNIQUE "unique" + +//blood antigens +#define ANTIGEN_O "O" +#define ANTIGEN_A "A" +#define ANTIGEN_B "B" +#define ANTIGEN_RH "Rh" +//common antigens +#define ANTIGEN_Q "Q" +#define ANTIGEN_U "U" +#define ANTIGEN_V "V" +//rare antigens +#define ANTIGEN_M "M" +#define ANTIGEN_N "N" +#define ANTIGEN_P "P" +//alien antigens +#define ANTIGEN_X "X" +#define ANTIGEN_Y "Y" +#define ANTIGEN_Z "Z" +//Unique antigens +#define ANTIGEN_IG "Ig" //For TB + + +#define DISEASE_BUMP "bump" +#define DISEASE_TOUCH "touch" + +#define DISEASE_NORMAL "normal" +#define DISEASE_TRANSFORMATION "transformation" +#define DISEASE_GONDOLA "gondola" +#define DISEASE_GONDOLA_DIGITAL "digital_gondola" +#define DISEASE_XENO "xeno" +#define DISEASE_CORGI "corgi" +#define DISEASE_SLIME "slime" +#define DISEASE_MORPH "morph" +#define DISEASE_ROBOT "robot" +#define DISEASE_MONKEY "monkey" +#define DISEASE_COLD "cold" +#define DISEASE_HEART "heart" +#define DISEASE_SANDWICH "sandwich" +#define DISEASE_TRAUMA "trauma" +#define DISEASE_DECLONING "decloning" +#define DISEASE_ANXIETY "anxiety" +#define DISEASE_SHOCK "shock" +#define DISEASE_FUNGUS "fungal_spores" +#define DISEASE_DEBUG "debug" + +#define COMSIG_CRACKER_PUZZLE_FAILURE "cracker_puzzle_failure" +#define COMSIG_CRACKER_PUZZLE_SUCCESS "cracker_puzzle_success" + +#define COMSIG_SYMPTOM_ATTACH "symptom_attach" +#define COMSIG_SYMPTOM_DETACH "symptom_detach" +#define COMSIG_SYMPTOM_TRIGGER "symptom_trigger" + +#define COMSIG_CARBON_TAKE_BRUTE_DAMAGE "carbon_take_brute" + +// --------------------------------------------------------------------------- +// AQ: definicje z tg/Monke potrzebne wirusologii, których nasza baza nie ma. +// --------------------------------------------------------------------------- + +// Flagi chorób (Monke code/__DEFINES/diseases.dm). Nasze CURABLE/CAN_CARRY/CAN_RESIST zajmują bity 0-2. +#define DISEASE_DORMANT (1<<3) +#define DISEASE_COPYSTAGE (1<<4) +#define DISEASE_ANALYZED (1<<5) + +// Nazwy stopni groźności z tg odpowiadają naszym DISEASE_* (zmienna `danger`). +#define DISEASE_SEVERITY_POSITIVE DISEASE_POSITIVE +#define DISEASE_SEVERITY_NONTHREAT DISEASE_NONTHREAT +#define DISEASE_SEVERITY_MINOR DISEASE_MINOR +#define DISEASE_SEVERITY_MEDIUM DISEASE_MEDIUM +#define DISEASE_SEVERITY_HARMFUL DISEASE_HARMFUL +#define DISEASE_SEVERITY_DANGEROUS DISEASE_DANGEROUS +#define DISEASE_SEVERITY_BIOHAZARD DISEASE_BIOHAZARD + +/// Każdy podtyp patogenu poza gotowcami. +#define WILD_ACUTE_DISEASES (subtypesof(/datum/disease/acute) - typesof(/datum/disease/acute/premade)) + +// Strefy ciała do liczenia sterylności. +#define BODY_ZONE_EVERYTHING "everything" +#define BODY_ZONE_ARMS "arms" +#define BODY_ZONE_LEGS "legs" + +/// Czas od startu rundy (Monke liczy go od STATION_TIME_PASSED, którego nie mamy). +#define ROUND_TIME(...) gameTimestamp("hh:mm:ss", world.time - SSticker.round_start_time) + +#ifndef TRAIT_IMMUNODEFICIENCY +#define TRAIT_IMMUNODEFICIENCY "immunodeficiency" +#endif +#define TRAIT_VIRUS_SCANNER "virus_scanner" +#define TRAIT_NEARSIGHTED_CORRECTED "fixes_nearsighted" +#define TRAIT_PLASMA_LOVER_METABOLISM "plasma_lover_metabolism" + +#define POLL_IGNORE_PLAGUERAT "plague_rat" + +#define isvirusdish(A) (istype(A, /obj/item/weapon/virusdish)) +#define is_reagent_container(O) (istype(O, /obj/item/reagent_containers)) + +#define CIRCUIT_COLOR_MEDICAL "#00CCFF" +#define FIRE_PRIORITY_PATHOGEN 31 + +#ifndef TRAIT_VIRUS_RESISTANCE +#define TRAIT_VIRUS_RESISTANCE "virus_resistance" +#endif + +// Zużycie prądu maszyn (tg code/__DEFINES/machines.dm). +#define BASE_MACHINE_IDLE_CONSUMPTION 10 +#define BASE_MACHINE_ACTIVE_CONSUMPTION (BASE_MACHINE_IDLE_CONSUMPTION * 10) + +/// Bazowa cena paczki cargo (tg CARGO_CRATE_VALUE). +#define CARGO_CRATE_VALUE 200 diff --git a/aquila/code/modules/unit_tests/aquila_pathology.dm b/aquila/code/modules/unit_tests/aquila_pathology.dm new file mode 100644 index 00000000000..9ad69a9645a --- /dev/null +++ b/aquila/code/modules/unit_tests/aquila_pathology.dm @@ -0,0 +1,93 @@ +// AQUILA - wirusologia disease2 (port Monkestation2.0): antygeny, przeciwciała, zakażenie, szalki. +// Makra TEST_ASSERT* z rdzenia są #undef'owane na końcu code/modules/unit_tests/_unit_tests.dm, +// więc ten test modularny ma własne kopie (wzór: aquila_viruses.dm). +#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]") } + +#define TEST_ASSERT_EQUAL(a, b, message) do { \ + var/lhs = ##a; \ + var/rhs = ##b; \ + if (lhs != rhs) { \ + return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]"); \ + } \ +} while (FALSE) + +/// Patogen ma antygeny, objaw na każdy stopień i zaraża nosiciela bez przeciwciał. +/datum/unit_test/pathology_infection + +/datum/unit_test/pathology_infection/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human) + TEST_ASSERT(patient.immune_system, "Człowiek nie ma układu odpornościowego") + + var/datum/disease/acute/virus/pathogen = new + pathogen.makerandom(list(60, 60), list(50, 50), list(ANTIGEN_COMMON = 1), list(EFFECT_DANGER_FLAVOR = 1), null) + + TEST_ASSERT(length(pathogen.antigen), "Patogen nie dostał antygenów") + TEST_ASSERT_EQUAL(pathogen.strength, 60, "Siła wylosowana poza zadanym zakresem") + TEST_ASSERT_EQUAL(pathogen.robustness, 50, "Wytrzymałość wylosowana poza zadanym zakresem") + TEST_ASSERT(length(pathogen.symptoms), "Patogen nie dostał objawów") + + // przeciwciała poniżej siły patogenu nie chronią + for(var/antigen in pathogen.antigen) + patient.immune_system.antibodies[antigen] = 0 + TEST_ASSERT(patient.immune_system.CanInfect(pathogen), "Układ odpornościowy bez przeciwciał zablokował zakażenie") + + patient.infect_disease(pathogen, forced = TRUE, notes = "(test jednostkowy)") + var/datum/disease/acute/carried = locate(/datum/disease/acute) in patient.diseases + TEST_ASSERT(carried, "Nosiciel nie został zakażony") + TEST_ASSERT_EQUAL(carried.affected_mob, patient, "Patogen nie zna swojego nosiciela") + + // kopia zachowuje pola disease2 (nasz /datum/disease/Copy() ich nie zna) + TEST_ASSERT_EQUAL(carried.strength, pathogen.strength, "Kopia zgubiła siłę") + TEST_ASSERT_EQUAL(length(carried.antigen), length(pathogen.antigen), "Kopia zgubiła antygeny") + TEST_ASSERT(length(carried.symptoms), "Kopia zgubiła objawy") + + // wyleczenie podnosi przeciwciała na antygen patogenu + carried.cure(add_resistance = TRUE, target = patient) + TEST_ASSERT(!(carried in patient.diseases), "Patogen został w nosicielu po wyleczeniu") + var/boosted = FALSE + for(var/antigen in carried.antigen) + if(patient.immune_system.antibodies[antigen] >= carried.strength) + boosted = TRUE + TEST_ASSERT(boosted, "Wyleczenie nie podniosło przeciwciał") + +/// Przeciwciała na poziomie siły patogenu nie wpuszczają go do ciała. +/datum/unit_test/pathology_immunity + +/datum/unit_test/pathology_immunity/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human) + var/datum/disease/acute/virus/pathogen = new + pathogen.makerandom(list(50, 50), list(50, 50), list(ANTIGEN_COMMON = 1), list(EFFECT_DANGER_FLAVOR = 1), null) + + for(var/antigen in pathogen.antigen) + patient.immune_system.antibodies[antigen] = pathogen.strength + 10 + + TEST_ASSERT(!patient.immune_system.CanInfect(pathogen), "Przeciwciała nie zablokowały zakażenia") + patient.infect_disease(pathogen, forced = TRUE, notes = "(test jednostkowy)") + TEST_ASSERT(!length(patient.diseases), "Odporny nosiciel i tak się zaraził") + +/// Szczepionka z przeciwciał podnosi je na wskazane antygeny. +/datum/unit_test/pathology_vaccine + +/datum/unit_test/pathology_vaccine/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human) + patient.immune_system.antibodies[ANTIGEN_Q] = 0 + patient.immune_system.ApplyVaccine(list(ANTIGEN_Q), 5, 1 MINUTES) + TEST_ASSERT(patient.immune_system.antibodies[ANTIGEN_Q] > 0, "Szczepionka nie podniosła przeciwciał") + +/// Szalka trzyma patogen i rośnie w inkubatorze. +/datum/unit_test/pathology_dish + +/datum/unit_test/pathology_dish/Run() + var/obj/item/weapon/virusdish/dish = allocate(/obj/item/weapon/virusdish) + var/datum/disease/acute/virus/pathogen = new + pathogen.makerandom(list(50, 50), list(50, 50), list(ANTIGEN_COMMON = 1), list(EFFECT_DANGER_FLAVOR = 1), null) + dish.contained_virus = pathogen + dish.growth = 0 + + dish.reagents.add_reagent(/datum/reagent/consumable/virus_food, 5) + dish.incubate(0, 3) + TEST_ASSERT(dish.growth > 0, "Szalka nie rosła na pożywce") + TEST_ASSERT_EQUAL(dish.contained_virus, pathogen, "Szalka zgubiła patogen") + +#undef TEST_ASSERT +#undef TEST_ASSERT_EQUAL diff --git a/aquila/code/modules/virology/__base_procs.dm b/aquila/code/modules/virology/__base_procs.dm new file mode 100644 index 00000000000..4d6bae392b6 --- /dev/null +++ b/aquila/code/modules/virology/__base_procs.dm @@ -0,0 +1,8 @@ +/atom/proc/infection_attempt(mob/living/perp, datum/disease/D, bodypart = null) + return FALSE + +/atom/proc/infect_disease(datum/disease/disease, forced = FALSE, notes = "", decay = TRUE) + return FALSE + +/atom/proc/try_infect_with_mobs_diseases(mob/living/carbon/infectee) + return FALSE diff --git a/aquila/code/modules/virology/__effects.dm b/aquila/code/modules/virology/__effects.dm new file mode 100644 index 00000000000..06a46065287 --- /dev/null +++ b/aquila/code/modules/virology/__effects.dm @@ -0,0 +1,31 @@ +/obj/effect/decal/cleanable/virusdish + name = "broken virus containment dish" + icon = 'aquila/icons/virology/virology.dmi' + icon_state = "brokendish-outline" + density = 0 + anchored = 1 + mouse_opacity = 1 + layer = OBJ_LAYER + var/last_openner + var/datum/disease/acute/contained_virus + +/obj/effect/decal/cleanable/virusdish/Entered(mob/living/perp) + ..() + infection_attempt(perp) + +/obj/effect/decal/cleanable/virusdish/infection_attempt(mob/living/perp) + //Now if your feet aren't well protected, or are bleeding, you might get infected. + var/block = 0 + var/bleeding = 0 + if (perp.lying & LYING_DOWN) + block = perp.check_contact_sterility(BODY_ZONE_EVERYTHING) + bleeding = perp.check_bodypart_bleeding(BODY_ZONE_EVERYTHING) + else + block = perp.check_contact_sterility(BODY_ZONE_LEGS) + bleeding = perp.check_bodypart_bleeding(BODY_ZONE_LEGS) + + + if (!block && contained_virus.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + perp.infect_disease(contained_virus, notes="(Contact, from [(perp.lying & LYING_DOWN)?"lying":"standing"] over a broken virus dish[last_openner ? " broken by [last_openner]" : ""])") + else if (bleeding && (contained_virus.spread_flags & DISEASE_SPREAD_BLOOD)) + perp.infect_disease(contained_virus, notes="(Blood, from [(perp.lying & LYING_DOWN)?"lying":"standing"] over a broken virus dish[last_openner ? " broken by [last_openner]" : ""])") diff --git a/aquila/code/modules/virology/_core/compat.dm b/aquila/code/modules/virology/_core/compat.dm new file mode 100644 index 00000000000..35c3554a158 --- /dev/null +++ b/aquila/code/modules/virology/_core/compat.dm @@ -0,0 +1,72 @@ +// Warstwa zgodności portu wirusologii (Monkestation2.0 -> Aquila). +// Monke stoi na nowym /tg/, my na Bee/NSV13, więc tu siedzą brakujące globalne listy, +// zmienne i drobne odpowiedniki procek, żeby nie dłubać w rdzeniu. + +/// Patogeny widoczne w panelu admina; w Monke leży to w code/datums/diseases/_disease.dm. +GLOBAL_LIST_EMPTY(inspectable_diseases) + +/obj/effect/decal/cleanable + /// Patogeny w tej plamie. Nasz rdzeń przyjmuje je w Initialize, ale nie trzyma listy. + var/list/diseases + +// Probówka do centryfugi. W Monke to /obj/item/reagent_containers/glass/tube, +// u nas rodzina pojemników nazywa się glass. +/obj/item/reagent_containers/glass/tube + name = "probówka" + desc = "Mała szklana probówka. Pasuje do centryfugi." + icon = 'aquila/icons/virology/items.dmi' + icon_state = "tube" + volume = 30 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5, 10, 15, 20, 25, 30) + w_class = WEIGHT_CLASS_TINY + spillable = TRUE + +/obj/item/reagent_containers/glass/tube/Initialize(mapload) + . = ..() + update_icon() + +// --- drobne odpowiedniki definicji z tg --------------------------------------- + +/// tg trzyma docelową temperaturę ciała na mobie; u nas to stała. +#define standard_body_temperature_compat BODYTEMP_NORMAL + +/// tg: czy turf przepuszcza widok w dół. +#define istransparentturf(A) (istype(A, /turf/open/openspace)) +/// tg: turf nad danym. +#define GET_TURF_ABOVE(T) (locate(T.x, T.y, T.z + 1)) + +#define COLOR_MATRIX_GRAYSCALE list(0.33,0.33,0.33,0, 0.33,0.33,0.33,0, 0.33,0.33,0.33,0, 0,0,0,1, 0,0,0,0) +#define BLOOD_TYPE_O_MINUS "O-" +#define TRAIT_NO_ZOMBIFY "no_zombify" +#define TRAUMA_SPECIAL_CURE_PROOF TRAUMA_RESILIENCE_ABSOLUTE +#define MUTATION_SOURCE_GENE_SYMPTOM "gene symptom" +#define VV_HK_VIEW_DISEASE_DATA "view_disease_data" +#define AWAY_LATHE PROTOLATHE + +/// tg oznacza organy robotyczne flagą, my mamy status ORGAN_SYNTHETIC. +#define IS_ROBOTIC_ORGAN(organ) (organ.status & ORGAN_SYNTHETIC) + +/// Ludzie w Monke mają listę wylosowanych genów symptomów (symptom cracker). +/mob/living/carbon/human + var/created_symptoms = FALSE + +/obj/item/bodypart + /// tg: czytelna nazwa strefy ciała. + var/plaintext_zone + +/// Litery i cyfry do minigry symptom crackera (tg GLOB.alphabet_upper / GLOB.numerals). +GLOBAL_LIST_INIT(alphabet_upper, 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(numerals, list("0","1","2","3","4","5","6","7","8","9")) + +/// Nastrój od objawu „rozkoszny” (Monke: /datum/mood_event/delightful_symptom). +/datum/mood_event/delightful_symptom + description = "Czuję się wyjątkowo rozkosznie!" + mood_change = 4 + timeout = 2 MINUTES + +/// Losowe geny objawów dla symptom crackera (Monke: code/modules/mob/living/carbon/human/human.dm). +/mob/living/carbon/human/proc/create_symptoms() + if(!dna?.species) + return + AddComponent(/datum/component/symptom_genes, dna.species, 3) diff --git a/aquila/code/modules/virology/_core/helpers.dm b/aquila/code/modules/virology/_core/helpers.dm new file mode 100644 index 00000000000..0cfcaf174f8 --- /dev/null +++ b/aquila/code/modules/virology/_core/helpers.dm @@ -0,0 +1,57 @@ +GLOBAL_LIST_INIT(all_antigens, list( + ANTIGEN_O, + ANTIGEN_A, + ANTIGEN_B, + ANTIGEN_RH, + ANTIGEN_Q, + ANTIGEN_U, + ANTIGEN_V, + ANTIGEN_M, + ANTIGEN_N, + ANTIGEN_P, + ANTIGEN_X, + ANTIGEN_Y, + ANTIGEN_Z, + )) + +GLOBAL_LIST_INIT(blood_antigens, list( + ANTIGEN_O, + ANTIGEN_A, + ANTIGEN_B, + ANTIGEN_RH, +)) + +GLOBAL_LIST_INIT(common_antigens, list( + ANTIGEN_Q, + ANTIGEN_U, + ANTIGEN_V, +)) + +GLOBAL_LIST_INIT(rare_antigens, list( + ANTIGEN_M, + ANTIGEN_N, + ANTIGEN_P, +)) + +GLOBAL_LIST_INIT(alien_antigens, list( + ANTIGEN_X, + ANTIGEN_Y, + ANTIGEN_Z, +)) + +GLOBAL_LIST_INIT(unique_antigens, list( + ANTIGEN_IG, +)) + +/proc/antigen_family(id) + switch(id) + if (ANTIGEN_BLOOD) + return GLOB.blood_antigens + if (ANTIGEN_COMMON) + return GLOB.common_antigens + if (ANTIGEN_RARE) + return GLOB.rare_antigens + if (ANTIGEN_ALIEN) + return GLOB.alien_antigens + if (ANTIGEN_UNIQUE) + return GLOB.unique_antigens diff --git a/aquila/code/modules/virology/_core/hooks.dm b/aquila/code/modules/virology/_core/hooks.dm new file mode 100644 index 00000000000..c7c0fe5baf6 --- /dev/null +++ b/aquila/code/modules/virology/_core/hooks.dm @@ -0,0 +1,56 @@ +// Podłączenie wirusologii disease2 do naszego rdzenia. +// Monke robi to edycjami w core (readme modułu wymienia: carbon/life.dm, _disease.dm, +// decals/cleanable.dm, atoms.dm, human_defense.dm). U nas prawie wszystko da się zrobić +// nadpisaniem w aquila/, więc rdzeń ruszamy tylko tam, gdzie nie ma czego nadpisać. + +// --- tick życia: patogeny, chmury i kontakt z plamami ------------------------- +// Nasz /mob/living/carbon/handle_diseases() tylko przewija stopnie chorób advance. +// Patogeny disease2 mają własną pętlę (activate), a chmury i plamy zarażają w handle_virus_updates(). +/mob/living/carbon/handle_diseases() + handle_virus_updates() + if(QDELETED(src)) + return + // powietrze: zwykłe choroby rozsiewa nasz spread(), patogeny robią to chmurami + for(var/datum/disease/disease as anything in diseases) + if(istype(disease, /datum/disease/acute)) + continue + if(prob(disease.infectivity)) + disease.spread() + +/mob/living/carbon/human/handle_breathing(times_fired) + . = ..() + if(stat != DEAD && !HAS_TRAIT(src, TRAIT_VIRUSIMMUNE)) + breath_airborne_diseases() + +/mob/living/simple_animal/mouse/Life(seconds, times_fired) + . = ..() + if(stat != DEAD) + handle_virus_updates() + +// --- kontakt: dotyk i wpadanie na siebie -------------------------------------- +/mob/living/carbon/human/attack_hand(mob/user, modifiers) + . = ..() + if(!isliving(user) || user == src) + return + var/mob/living/toucher = user + var/block = toucher.check_contact_sterility(BODY_ZONE_ARMS) || check_contact_sterility(BODY_ZONE_EVERYTHING) + var/bleeding = toucher.check_bodypart_bleeding(BODY_ZONE_ARMS) && check_bodypart_bleeding(BODY_ZONE_EVERYTHING) + share_contact_diseases(toucher, block, bleeding) + +/mob/living/Bump(atom/bumped_atom) + . = ..() + if(!isliving(bumped_atom)) + return + var/mob/living/other = bumped_atom + if(!length(diseases) && !length(other.diseases)) + return + var/block = check_contact_sterility(BODY_ZONE_EVERYTHING) || other.check_contact_sterility(BODY_ZONE_EVERYTHING) + var/bleeding = check_bodypart_bleeding(BODY_ZONE_EVERYTHING) && other.check_bodypart_bleeding(BODY_ZONE_EVERYTHING) + share_contact_diseases(other, block, bleeding) + +// --- latejoin: część dołączających ma łagodny patogen ------------------------- +/mob/dead/new_player/AttemptLateSpawn(rank) + . = ..() + var/mob/living/carbon/human/character = mind?.current + if(ishuman(character)) + DiseaseCarrierCheck(character) diff --git a/aquila/code/modules/virology/_core/observer.dm b/aquila/code/modules/virology/_core/observer.dm new file mode 100644 index 00000000000..63a0ef1cb14 --- /dev/null +++ b/aquila/code/modules/virology/_core/observer.dm @@ -0,0 +1,47 @@ +// Podgląd chorób dla duchów i adminów (Monkestation2.0: observer.dm toggle_disease_view, ADMIN_VERB disease_view/diseases_panel). +// U nas nie ma makr GAME_VERB_DESC/ADMIN_VERB, więc to zwykłe czasowniki. + +/mob/dead/observer + /// Czy duch widzi wektory zakażeń (chmury, zakażone przedmioty i osoby). + var/disease_view = FALSE + +/mob/dead/observer/verb/toggle_disease_view() + set name = "Toggle Disease View" + set desc = "Pokazuje lub ukrywa wektory zakażeń (uśpione, krew, kontakt, powietrze)." + set category = "Ghost" + + if(disease_view) + RemoveElement(/datum/element/virus_viewer) + to_chat(src, "Podgląd chorób wyłączony.") + else + AddElement(/datum/element/virus_viewer) + to_chat(src, "Podgląd chorób włączony.") + + disease_view = !disease_view + +/client/proc/disease_view() + set name = "Disease View" + set desc = "Pokazuje wizualizację chorób." + set category = "Debug" + + if(!check_rights(R_ADMIN)) + return + if(isobserver(mob)) + var/mob/dead/observer/observer = mob + observer.toggle_disease_view() // Na duchu cecha nie działa, więc idzie przez czasownik ducha. + return + if(HAS_TRAIT_FROM(mob, TRAIT_VIRUS_SCANNER, ADMIN_TRAIT)) + REMOVE_TRAIT(mob, TRAIT_VIRUS_SCANNER, ADMIN_TRAIT) + to_chat(src, "Adminowski podgląd chorób wyłączony.") + else + ADD_TRAIT(mob, TRAIT_VIRUS_SCANNER, ADMIN_TRAIT) + to_chat(src, "Adminowski podgląd chorób włączony.") + +/client/proc/diseases_panel() + set name = "Disease Panel" + set desc = "Lista patogenów w rundzie i ich logi." + set category = "Logs" + + if(!check_rights(R_ADMIN)) + return + holder.diseases_panel() diff --git a/aquila/code/modules/virology/_core/pathogen_clouds_subsystem.dm b/aquila/code/modules/virology/_core/pathogen_clouds_subsystem.dm new file mode 100644 index 00000000000..d8160cdc579 --- /dev/null +++ b/aquila/code/modules/virology/_core/pathogen_clouds_subsystem.dm @@ -0,0 +1,74 @@ +#define RUN_TYPE_CLOUDS 1 +#define RUN_TYPE_CORES 2 + +SUBSYSTEM_DEF(pathogen_clouds) + name = "Pathogen Clouds" + priority = FIRE_PRIORITY_PATHOGEN + wait = 1 SECONDS + flags = SS_BACKGROUND | SS_NO_INIT + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/current_run_cores = list() + var/list/current_run_clouds = list() + var/list/cores = list() + var/list/clouds = list() + var/current_run_level = RUN_TYPE_CLOUDS + +// AQ: nasz MC nie ma hibernacji podsystemow (hibernate_checks z tg), wiec PreInit nie jest potrzebny + +/datum/controller/subsystem/pathogen_clouds/stat_entry(msg) + msg += "Run Cores:[length(current_run_cores)]" + msg += "|Cores:[length(cores)]" + msg += "|Run Clouds:[length(current_run_clouds)]" + msg += "|Clouds:[length(clouds)]" + return ..() + +/datum/controller/subsystem/pathogen_clouds/Recover() + cores = SSpathogen_clouds.cores.Copy() + clouds = SSpathogen_clouds.clouds.Copy() + +/datum/controller/subsystem/pathogen_clouds/fire(resumed = FALSE) + if(!resumed) + current_run_cores = cores.Copy() + current_run_clouds = clouds.Copy() + + var/list/currentrun + if(current_run_level == RUN_TYPE_CLOUDS) + currentrun = current_run_clouds + while(length(currentrun)) + var/obj/effect/pathogen_cloud/cloud = currentrun[length(currentrun)] + currentrun.len-- + if(QDELETED(cloud)) + clouds -= cloud + continue + //If we exist ontop of a core transfer viruses and die unless parent this means something moved back. + //This should prevent mobs breathing in hundreds of clouds at once + for(var/obj/effect/pathogen_cloud/core/core in cloud.loc) + core.add_virus(cloud.viruses) + qdel(cloud) + if(MC_TICK_CHECK) + return + current_run_level = RUN_TYPE_CORES + + if(current_run_level == RUN_TYPE_CORES) + currentrun = current_run_cores + while(length(currentrun)) + var/obj/effect/pathogen_cloud/core = currentrun[length(currentrun)] + currentrun.len-- + if(QDELETED(core)) + cores -= core + continue + + if(!core.moving || core.target == get_turf(core)) + for(var/obj/effect/pathogen_cloud/core/other_core in core.loc) + if(other_core == core || other_core.moving) + continue + core.add_virus(other_core.viruses) + qdel(other_core) + core.moving = FALSE + if(MC_TICK_CHECK) + return + current_run_level = RUN_TYPE_CLOUDS + +#undef RUN_TYPE_CORES +#undef RUN_TYPE_CLOUDS diff --git a/aquila/code/modules/virology/_core/virus_viewer.dm b/aquila/code/modules/virology/_core/virus_viewer.dm new file mode 100644 index 00000000000..aa83191d2a8 --- /dev/null +++ b/aquila/code/modules/virology/_core/virus_viewer.dm @@ -0,0 +1,57 @@ +/datum/element/virus_viewer + element_flags = ELEMENT_DETACH + +/datum/element/virus_viewer/Attach(mob/user) + . = ..() + if(!ismob(user)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(user, COMSIG_MOB_LOGIN, PROC_REF(enable_virus_view)) + RegisterSignal(user, COMSIG_MOB_LOGOUT, PROC_REF(disable_virus_view)) + + enable_virus_view(user) + +/datum/element/virus_viewer/Detach(mob/user) + . = ..() + + UnregisterSignal(user, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT)) + + disable_virus_view(user) + +/datum/element/virus_viewer/proc/enable_virus_view(mob/user) + SIGNAL_HANDLER + + if(!user.client) + return + + GLOB.virus_viewers += user + + for(var/obj/item/item in GLOB.infected_items) + if(item.pathogen) + user.client.images |= item.pathogen + for(var/mob/living/living_mob in GLOB.infected_contact_mobs) + if(living_mob.pathogen) + user.client.images |= living_mob.pathogen + for(var/obj/effect/pathogen_cloud/cloud as anything in GLOB.pathogen_clouds) + if(cloud.pathogen) + user.client.images |= cloud.pathogen + for(var/obj/effect/decal/cleanable/cleanable in GLOB.infected_cleanables) + if(cleanable.pathogen) + user.client.images |= cleanable.pathogen + +/datum/element/virus_viewer/proc/disable_virus_view(mob/user) + SIGNAL_HANDLER + + GLOB.virus_viewers -= user // Even if the user doesn't have a client anymore we need to make sure they're out of the list. + + if(!user.client) + return + + for(var/obj/item/item in GLOB.infected_items) + user.client.images -= item.pathogen + for(var/mob/living/living_mob in GLOB.infected_contact_mobs) + user.client.images -= living_mob.pathogen + for(var/obj/effect/pathogen_cloud/cloud as anything in GLOB.pathogen_clouds) + user.client.images -= cloud.pathogen + for(var/obj/effect/decal/cleanable/cleanable in GLOB.infected_cleanables) + user.client.images -= cleanable.pathogen diff --git a/aquila/code/modules/virology/cargo/bounties.dm b/aquila/code/modules/virology/cargo/bounties.dm new file mode 100644 index 00000000000..1df9ff4fdbf --- /dev/null +++ b/aquila/code/modules/virology/cargo/bounties.dm @@ -0,0 +1,37 @@ +/datum/bounty/item/virus + reward = CARGO_CRATE_VALUE * 10 + var/datum/symptom/acute/requested_symptom + +/datum/bounty/item/virus/New() + ..() + requested_symptom = randomize_symptom() + name = "Virus ([requested_symptom.name])" + description = "Nanotrasen is interested in a virus with [requested_symptom.name] as a symptom. Central Command will pay handsomely for such a virus dish." + reward += rand(0, 4) * CARGO_CRATE_VALUE + +/datum/bounty/item/virus/proc/randomize_symptom() + var/datum/symptom/acute/symptom = pick(subtypesof(/datum/symptom/acute)) + if(symptom.restricted) + symptom = randomize_symptom() + return symptom + +/datum/bounty/item/virus/applies_to(obj/O) + if(O.flags_1 & HOLOGRAM_1) + return FALSE + if(isvirusdish(O)) + var/obj/item/weapon/virusdish/dish = O + if(accepts_virus(dish.contained_virus)) + return TRUE + return FALSE + +/datum/bounty/item/virus/ship(obj/O) + if(!applies_to(O)) + return + shipped_count += 1 + +/datum/bounty/item/virus/proc/accepts_virus(V) + var/datum/disease/advance/A = V + for(var/datum/symptom/acute/symptom in A.symptoms) + if(symptom.name == requested_symptom.name) + return TRUE + return FALSE diff --git a/aquila/code/modules/virology/cargo/pathology.dm b/aquila/code/modules/virology/cargo/pathology.dm new file mode 100644 index 00000000000..cf6cada470a --- /dev/null +++ b/aquila/code/modules/virology/cargo/pathology.dm @@ -0,0 +1,27 @@ +/datum/supply_pack/medical/growth_dishes + name = "Random Virus Samples" + desc = "A pack of 5 random virus samples" + cost = CARGO_CRATE_VALUE * 10 + access = ACCESS_VIROLOGY + contains = list( + /obj/item/weapon/virusdish/random, + /obj/item/weapon/virusdish/random, + /obj/item/weapon/virusdish/random, + /obj/item/weapon/virusdish/random, + /obj/item/weapon/virusdish/random, + ) + crate_name = "virus samples crates" + +/datum/supply_pack/medical/rat_cubes + name = "Crate of Rat Cube Boxes" + desc = "A pack of 5 boxes of rat-cubes" + cost = CARGO_CRATE_VALUE * 4 + contains = list( + /obj/item/storage/box/monkeycubes/mousecubes, + /obj/item/storage/box/monkeycubes/mousecubes, + /obj/item/storage/box/monkeycubes/mousecubes, + /obj/item/storage/box/monkeycubes/mousecubes, + /obj/item/storage/box/monkeycubes/mousecubes, + ) + crate_name = "rat cube crates" + crate_type = /obj/structure/closet/crate diff --git a/aquila/code/modules/virology/disease/base_disease_folder/_base.dm b/aquila/code/modules/virology/disease/base_disease_folder/_base.dm new file mode 100644 index 00000000000..984331dce3d --- /dev/null +++ b/aquila/code/modules/virology/disease/base_disease_folder/_base.dm @@ -0,0 +1,320 @@ +GLOBAL_LIST_INIT(infected_contact_mobs, list()) +GLOBAL_LIST_INIT(virusDB, list()) + +/datum/disease + /// Symptomy choroby. AQ: w Monke ta zmienna jest w code/datums/diseases/_disease.dm, u nas wcześniej była tylko na advance. + var/list/symptoms = list() + //the disease's antigens, that the body's immune_system will read to produce corresponding antibodies. Without antigens, a disease cannot be cured. + var/list/antigen = list() + //alters a pathogen's propensity to mutate. Set to FALSE to forbid a pathogen from ever mutating. + var/mutation_modifier = TRUE + //the antibody concentration at which the disease will fully exit the body + var/strength = 100 + //the percentage of the strength at which effects will start getting disabled by antibodies. + var/robustness = 100 + //chance to cure the disease at every proc when the body is getting cooked alive. + var/max_bodytemperature = T0C+100 + //very low temperatures will stop the disease from activating/progressing + var/min_bodytemperature = 120 + ///split category used for predefined diseases atm + var/category = DISEASE_NORMAL + + //logging + var/log = "" + var/origin = "Unknown" + var/logged_virusfood = FALSE + var/fever_warning = FALSE + + //cosmetic + var/color + var/pattern = 1 + var/pattern_color + + ///pathogenic warfare - If you have a second disease of a form name in the list they will start fighting. + var/list/can_kill = list("Bacteria") + + //When an opportunity for the disease to spread_flags to a mob arrives, runs this percentage through prob() + //Ignored if infected materials are ingested (injected with infected blood, eating infected meat) + var/infectionchance = 20 + var/infectionchance_base = 20 + + //ticks increases by [speed] every time the disease activates. Drinking Virus Food also accelerates the process by 10. + var/ticks = 0 + // AQ: var/speed siedzi na /datum/disease/acute (nasz advance ma wlasne pole speed) + + var/stageprob = 25 + + //when spreading to another mob, that new carrier has the disease's stage reduced by stage_variance + var/stage_variance = -1 + + var/uniqueID = 0// 0000 to 9999, set when the pathogen gets initially created + var/subID = 0// 000 to 9999, set if the pathogen underwent effect or antigen mutation + var/childID = 0// 01 to 99, incremented as the pathogen gets analyzed after a mutation + //bitflag showing which transmission types are allowed for this disease + var/allowed_transmission = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_AIRBORNE + +/datum/disease/proc/roll_antigen(list/factors = list()) + if (factors.len <= 0) + antigen = list(pick(GLOB.all_antigens)) + antigen |= pick(GLOB.all_antigens) + else + var/selected_first_antigen = pick( + factors[ANTIGEN_BLOOD];ANTIGEN_BLOOD, + factors[ANTIGEN_COMMON];ANTIGEN_COMMON, + factors[ANTIGEN_RARE];ANTIGEN_RARE, + factors[ANTIGEN_ALIEN];ANTIGEN_ALIEN, + ) + + antigen = list(pick(antigen_family(selected_first_antigen))) + + var/selected_second_antigen = pick( + factors[ANTIGEN_BLOOD];ANTIGEN_BLOOD, + factors[ANTIGEN_COMMON];ANTIGEN_COMMON, + factors[ANTIGEN_RARE];ANTIGEN_RARE, + factors[ANTIGEN_ALIEN];ANTIGEN_ALIEN, + ) + + antigen |= pick(antigen_family(selected_second_antigen)) + +/datum/disease/proc/get_effect(index) + if(!index) + return pick(symptoms) + return symptoms[clamp(index,0,symptoms.len)] + +/datum/disease/proc/GetImmuneData(mob/living/mob) + var/lowest_stage = stage + var/highest_concentration = 0 + + if (mob.immune_system) + var/immune_system = mob.immune_system.GetImmunity() + var/list/antibodies = immune_system[2] + var/subdivision = (strength - ((robustness * strength) / 100)) / max_stages + //for each antigen, we measure the corresponding antibody concentration in the carrier's immune system + //the less robust the pathogen, the more likely that further stages' effects won't activate at a given concentration + for (var/A in antigen) + var/concentration = antibodies[A] + highest_concentration = max(highest_concentration,concentration) + var/i = lowest_stage + while (i > 0) + if (concentration > (strength - i * subdivision)) + lowest_stage = i-1 + i-- + + return list(lowest_stage,highest_concentration) + +/datum/disease/acute/cure(add_resistance = TRUE, mob/living/carbon/target, safe = FALSE) + target = target || affected_mob || usr + if(!istype(affected_mob) || QDELING(affected_mob)) + return + for(var/datum/symptom/acute/symptom in symptoms) + symptom.disable_effect(target, src, safe) + target.diseases -= src + target.med_hud_set_status() + log_virus("[affected_mob.name] was cured of virus [real_name()] at [loc_name(affected_mob.loc)]", list("disease_data" = admin_details(), "location" = loc_name(affected_mob.loc))) + //--Plague Stuff-- + /* + var/datum/faction/plague_mice/plague = find_active_faction_by_type(/datum/faction/plague_mice) + if (plague && ("[uniqueID]-[subID]" == plague.diseaseID)) + plague.update_hud_icons() + */ + //---------------- + var/list/pathogen_info = filter_disease_by_spread(affected_mob.diseases, required = DISEASE_SPREAD_CONTACT_SKIN) + if(!length(pathogen_info)) + GLOB.infected_contact_mobs -= affected_mob + if(affected_mob.pathogen) + for(var/mob/living/goggle_wearer in GLOB.virus_viewers) + goggle_wearer.client?.images -= affected_mob.pathogen + + // Add resistance by boosting whichever antigen is needed + if(add_resistance && target.immune_system) + var/boosted_antigen + var/boosted_antigen_level + for(var/antigen in src.antigen) + var/level = target.immune_system.antibodies[antigen] + if(level >= strength) + return + else if(!boosted_antigen || (boosted_antigen_level > level)) + boosted_antigen = antigen + boosted_antigen_level = level + if(boosted_antigen) + target.immune_system.antibodies[boosted_antigen] = max(strength + 10, boosted_antigen_level) + + +/datum/disease/acute/proc/activate(mob/living/mob, starved = FALSE, seconds_per_tick) + if(!affected_mob) + return_parent() + if((mob.stat == DEAD) && !process_dead) + return + + //Searing body temperatures cure diseases, on top of killing you. + if(mob.bodytemperature > max_bodytemperature) + cure(add_resistance = FALSE, target = mob) + return + + if(disease_flags & DISEASE_DORMANT) + return + + if(!(mob.mob_biotypes in infectable_biotypes)) + return + + if(mob.immune_system) + if(prob(10 - (robustness * 0.1))) //100 robustness don't auto cure + mob.immune_system.NaturalImmune() + + if(!mob.immune_system.CanInfect(src)) + cure(target = mob) + return + + //Freezing body temperatures halt diseases completely + if(mob.bodytemperature < min_bodytemperature) + return + + //Virus food speeds up disease progress + if(!ismouse(mob)) + if(mob.reagents?.has_reagent(/datum/reagent/consumable/virus_food)) + mob.reagents.remove_reagent(/datum/reagent/consumable/virus_food, 0.1) + if(!logged_virusfood) + log += "
[ROUND_TIME()] Virus Fed ([mob.reagents.get_reagent_amount(/datum/reagent/consumable/virus_food)]U)" + logged_virusfood=1 + ticks += 10 + else + logged_virusfood=0 + if(prob(strength * 0.1)) + incubate(mob, 1) + + //Moving to the next stage + if(ticks > stage*100 && prob(stageprob)) + incubate(mob, 1) + if(stage < max_stages) + log += "
[ROUND_TIME()] NEXT STAGE ([stage])" + stage++ + ticks = 0 + + //Pathogen killing each others + for (var/datum/disease/acute/enemy_pathogen as anything in mob.diseases) + if(enemy_pathogen == src) + continue + + if ((enemy_pathogen.form in can_kill) && strength > enemy_pathogen.strength) + log += "
[ROUND_TIME()] destroyed enemy [enemy_pathogen.form] #[enemy_pathogen.uniqueID]-[enemy_pathogen.subID] ([strength] > [enemy_pathogen.strength])" + enemy_pathogen.cure(target = mob) + + // This makes it so that only ever gets affected by the equivalent of one virus so antags don't just stack a bunch + if(starved) + return + + var/list/immune_data = GetImmuneData(mob) + + if(!istype(mob, /mob/living/simple_animal/mouse/plague) && (!carrier)) //plague mice don't trigger effects to not kill em + for(var/datum/symptom/acute/e in symptoms) + if (e.can_run_effect(immune_data[1], seconds_per_tick)) + e.run_effect(mob, src) + + //fever is a reaction of the body's immune system to the infection. The higher the antibody concentration (and the disease still not cured), the higher the fever + if (mob.bodytemperature < BODYTEMP_HEAT_DAMAGE_LIMIT - 15)//but we won't go all the way to burning up just because of a fever, probably + var/fever = round((robustness / 100) * (immune_data[2] / 10) * (stage / max_stages)) + switch (mob.mob_size) + if (MOB_SIZE_TINY) + mob.bodytemperature += fever*0.2 + if (MOB_SIZE_SMALL) + mob.bodytemperature += fever*0.5 + if (MOB_SIZE_HUMAN) + mob.bodytemperature += fever + if (MOB_SIZE_LARGE) + mob.bodytemperature += fever*1.5 + if (MOB_SIZE_HUGE) + mob.bodytemperature += fever*2 + + if (fever > 0 && prob(3)) + switch (fever_warning) + if (0) + to_chat(mob, span_warning("You feel a fever coming on, your body warms up and your head hurts a bit.")) + fever_warning++ + if (1) + if (mob.bodytemperature > 320) + to_chat(mob, span_warning("Your palms are sweaty.")) + fever_warning++ + if (2) + if (mob.bodytemperature > 335) + to_chat(mob, span_warning("Your knees are weak.")) + fever_warning++ + if (3) + if (mob.bodytemperature > 350) + to_chat(mob, span_warning("Your arms are heavy.")) + fever_warning++ + + + ticks += speed + +//horrible, awful, stolen code from disease/advance. But it WORKS +/datum/disease/acute + var/list/properties = list() + /// Licznik tempa (w Monke stoi na /datum/disease, u nas advance ma juz wlasne `speed`). + var/speed = 1 + +/// Nasz /datum/disease/Copy() kopiuje tylko pola z Bee, wiec pola disease2 dopisujemy tutaj. +/datum/disease/acute/Copy() + var/datum/disease/acute/copy = ..() + if(!istype(copy)) + return copy + var/static/list/acute_vars = list( + "antigen", "mutation_modifier", "strength", "robustness", + "max_bodytemperature", "min_bodytemperature", "category", + "log", "origin", "logged_virusfood", "fever_warning", + "color", "pattern", "pattern_color", "can_kill", + "infectionchance", "infectionchance_base", "ticks", "speed", + "stageprob", "stage_variance", "uniqueID", "subID", "childID", + "allowed_transmission", "symptoms", + ) + for(var/field in acute_vars) + var/value = vars[field] + if(islist(value)) + var/list/listed = value + value = listed.Copy() + copy.vars[field] = value + var/list/copied_symptoms = list() + for(var/datum/symptom/acute/symptom as anything in symptoms) + var/datum/symptom/acute/copied = symptom.Copy() + copied_symptoms += copied + SEND_SIGNAL(copied, COMSIG_SYMPTOM_ATTACH, copy) + copy.symptoms = copied_symptoms + return copy + +/// Calls on GenerateProperties and AssignProperties to set a disease severity. From `disease/advance` +/datum/disease/acute/proc/Refresh_Acute(new_name = FALSE) + GenerateProperties_Acute() + assign_properties_Acute() + +/// Generates the list for the severity with severity defined at 0, then calls on symtomps severity for final. +/datum/disease/acute/proc/GenerateProperties_Acute() + properties = list("severity" = 0) + for(var/datum/symptom/acute/S in symptoms) + if(!S.neutered) + properties["severity"] = max(properties["severity"], S.severity) // severity is based on the highest severity non-neutered symptom + +/datum/disease/acute/proc/assign_properties_Acute() + if(length(properties)) + set_severity_Acute(properties["severity"]) + else + CRASH("Our properties were empty or null!") + +///sets a serverity level based on the properties["severity"] value of the disease +/datum/disease/acute/proc/set_severity_Acute(level_sev) + switch(level_sev) + + if(-INFINITY to 0) + danger = DISEASE_SEVERITY_POSITIVE + if(1) + danger = DISEASE_SEVERITY_NONTHREAT + if(2) + danger = DISEASE_SEVERITY_MINOR + if(3) + danger = DISEASE_SEVERITY_MEDIUM + if(4) + danger = DISEASE_SEVERITY_HARMFUL + if(5) + danger = DISEASE_SEVERITY_DANGEROUS + if(6 to INFINITY) + danger = DISEASE_SEVERITY_BIOHAZARD + else + danger = "Unknown" diff --git a/aquila/code/modules/virology/disease/base_disease_folder/admin.dm b/aquila/code/modules/virology/disease/base_disease_folder/admin.dm new file mode 100644 index 00000000000..f7c4f6f12dc --- /dev/null +++ b/aquila/code/modules/virology/disease/base_disease_folder/admin.dm @@ -0,0 +1,266 @@ +/proc/make_custom_virus(client/C, mob/living/infectedMob) + if(!istype(C) || !C.holder) + return 0 + + var/datum/disease/acute/D = new /datum/disease/acute() + D.origin = "Badmin" + + var/list/known_forms = list() + for (var/disease_type in subtypesof(/datum/disease/acute)) + var/datum/disease/acute/d_type = disease_type + known_forms[initial(d_type.form)] = d_type + + known_forms += "custom" + + /* + if (islist(GLOB.inspectable_diseases) && GLOB.inspectable_diseases.len > 0) + known_forms += "infect with an already existing pathogen" + */ + + var/chosen_form = input(C, "Choose a form for your pathogen", "Choose a form") as null | anything in known_forms + if (!chosen_form) + qdel(D) + return + + if (chosen_form == "infect with an already existing pathogen") + var/list/existing_pathogen = list() + for(var/datum/disease/acute/dis as anything in GLOB.inspectable_diseases) + existing_pathogen += dis + var/chosen_pathogen = input(C, "Choose a pathogen", "Choose a pathogen") as null | anything in existing_pathogen + if (!chosen_pathogen) + qdel(D) + return + var/datum/disease/acute/dis = chosen_pathogen + D = dis.Copy() + D.origin = "[D.origin] (Badmin)" + else + if (chosen_form == "custom") + var/form_name = copytext(sanitize(input(C, "Give your custom form a name", "Name your form", "Pathogen") as null | text),1,MAX_NAME_LEN) + if (!form_name) + qdel(D) + return + D.form = form_name + D.max_stages = input(C, "How many stages will your pathogen have?", "Custom Pathogen", D.max_stages) as num + D.max_stages = clamp(D.max_stages,1,99) + D.infectionchance = input(C, "What will be your pathogen's infection chance?", "Custom Pathogen", D.infectionchance) as num + D.infectionchance = clamp(D.infectionchance,0,100) + D.infectionchance_base = D.infectionchance + D.stageprob = input(C, "What will be your pathogen's progression speed?", "Custom Pathogen", D.stageprob) as num + D.stageprob = clamp(D.stageprob,0,100) + D.stage_variance = input(C, "What will be your pathogen's stage variance?", "Custom Pathogen", D.stage_variance) as num + D.stageprob = clamp(D.stageprob,-1*D.max_stages,0) + //D.can_kill = something something a while loop but probably not worth the effort. If you need it for your bus code it yourself. + else + var/d_type = known_forms[chosen_form] + var/datum/disease/acute/d_inst = new d_type + D.form = chosen_form + D.max_stages = d_inst.max_stages + D.infectionchance = d_inst.infectionchance + D.stageprob = d_inst.stageprob + D.stage_variance = d_inst.stage_variance + D.can_kill = d_inst.can_kill.Copy() + qdel(d_inst) + + D.strength = input(C, "What will be your pathogen's strength? (1-50 is trivial to cure. 50-100 requires a bit more effort)", "Pathogen Strength", D.infectionchance) as num + D.strength = clamp(D.strength,0,100) + + D.robustness = input(C, "What will be your pathogen's robustness? (1-100) Lower values mean that infected can carry the pathogen without getting affected by its symptoms.", "Pathogen Robustness", D.infectionchance) as num + D.robustness = clamp(D.strength,0,100) + + D.uniqueID = clamp(input(C, "You can specify the 4 number ID for your Pathogen, or just use this randomly generated one.", "Pick a unique ID", rand(0,9999)) as num, 0, 9999) + + D.subID = rand(0,9999) + D.childID = 0 + + for(var/i = 1; i <= D.max_stages; i++) // run through this loop until everything is set + var/datum/symptom/acute/symptom = input(C, "Choose a symptom for your disease's stage [i] (out of [D.max_stages])", "Choose a Symptom") as null | anything in (subtypesof(/datum/symptom/acute)) + if (!symptom) + return 0 + + var/datum/symptom/acute/e = new symptom(D) + e.stage = i + e.max_chance = input(C, "Choose the maximum chance for this effect to activate", "Effect", e.max_chance) as null | num + e.max_chance = clamp(e.max_chance,0,100) + e.chance = input(C, "Choose the default chance for this effect to activate", "Effect", e.chance) as null | num + e.chance = clamp(e.chance,0,100) + e.max_multiplier = input(C, "Choose the maximum strength for this effect", "Effect", e.max_multiplier) as null | num + e.max_multiplier = clamp(e.max_multiplier,0,100) + e.multiplier = input(C, "Choose the default strength for this effect", "Effect", e.multiplier) as null | num + e.multiplier = clamp(e.multiplier,0,100) + + D.log += "Added [e.name] at [e.chance]% chance and [e.multiplier] strength
" + D.symptoms += e + + if (alert("Do you want to specify which antigen are selected?","Choose your Antigen","Yes","No") == "Yes") + D.antigen = list(input(C, "Choose your first antigen", "Choose your Antigen") as null | anything in GLOB.all_antigens) + if (!D.antigen) + D.antigen = list(input(C, "Choose your second antigen", "Choose your Antigen") as null | anything in GLOB.all_antigens) + else + D.antigen |= input(C, "Choose your second antigen", "Choose your Antigen") as null | anything in GLOB.all_antigens + if (!D.antigen) + if (alert("Beware, your disease having no antigen means that it's incurable. We can still roll some random antigen for you. Are you sure you want your pathogen to have no antigen anyway?","Choose your Antigen","Yes","No") == "No") + D.roll_antigen() + else + D.antigen = list() + else + D.roll_antigen() + + var/list/randomhexes = list("8","9","a","b","c","d","e") + D.color = "#[pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)]" + D.pattern = rand(1,6) + D.pattern_color = "#[pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)]" + if (alert("Do you want to specify the appearance of your pathogen in a petri dish?","Choose your appearance","Yes","No") == "Yes") + D.color = tgui_color_picker(C, "Choose the color of the dish", "Cosmetic") + D.pattern = input(C, "Choose the shape of the pattern inside the dish (1 to 6)", "Cosmetic",rand(1,6)) as num + D.pattern = clamp(D.pattern,1,6) + D.pattern_color = tgui_color_picker(C, "Choose the color of the pattern", "Cosmetic") + + D.spread_flags = 0 + if (alert("Can this virus spread_flags into blood? (warning! if choosing No, this virus will be impossible to sample and analyse!)","Spreading Vectors","Yes","No") == "Yes") + D.spread_flags |= DISEASE_SPREAD_BLOOD + if(D.allowed_transmission & DISEASE_SPREAD_CONTACT_SKIN) + if (alert("Can this virus spread_flags by contact, and on items?","Spreading Vectors","Yes","No") == "Yes") + D.spread_flags |= DISEASE_SPREAD_CONTACT_SKIN + if(D.allowed_transmission & DISEASE_SPREAD_AIRBORNE) + if (alert("Can this virus spread_flags through the air?","Spreading Vectors","Yes","No") == "Yes") + D.spread_flags |= DISEASE_SPREAD_AIRBORNE + /* + if(D.allowed_transmission & SPREAD_COLONY) + if (alert("Does this fungus prefer suits? Exclusive with contact/air.","Spreading Vectors","Yes","No") == "Yes") + D.spread_flags |= SPREAD_COLONY + D.spread_flags &= ~(SPREAD_BLOOD|SPREAD_AIRBORNE) + if(D.allowed_transmission & SPREAD_MEMETIC) + if (alert("Can this virus spread_flags through words?","Spreading Vectors","Yes","No") == "Yes") + D.spread_flags |= SPREAD_MEMETIC + */ + GLOB.inspectable_diseases -= "[D.uniqueID]-[D.subID]"//little odds of this happening thanks to subID but who knows + D.update_global_log() + + if (alert("Lastly, do you want this pathogen to be added to the station's Database? (allows medical HUDs to locate infected mobs, among other things)","Pathogen Database","Yes","No") == "Yes") + D.addToDB() + + if (istype(infectedMob)) + D.log += "
[ROUND_TIME()] Infected [key_name(infectedMob)]" + if(!length(infectedMob.diseases)) + infectedMob.diseases = list() + infectedMob.diseases += D + var/nickname = "" + if ("[D.uniqueID]-[D.subID]" in GLOB.virusDB) + var/datum/data/record/v = GLOB.virusDB["[D.uniqueID]-[D.subID]"] + nickname = v.fields["nickname"] ? " \"[v.fields["nickname"]]\"" : "" + log_admin("[infectedMob] was infected with [D.form] #[D.uniqueID]-[D.subID][nickname] by [C.ckey]") + message_admins("[infectedMob] was infected with [D.form] #["[D.uniqueID]"]-["[D.subID]"][nickname] by [C.ckey]") + D.AddToGoggleView(infectedMob) + else + var/obj/item/weapon/virusdish/dish = new(C.mob.loc) + dish.contained_virus = D + dish.growth = rand(5, 50) + dish.name = "growth dish (Unknown [D.form])" + if ("[D.uniqueID]-[D.subID]" in GLOB.virusDB) + dish.name = "growth dish ([D.name(TRUE)])" + dish.update_icon() + + return 1 + +/datum/admins/var/viewingID + +/datum/admins/proc/diseases_panel() + if (!GLOB.inspectable_diseases || !length(GLOB.inspectable_diseases)) + alert("There are no pathogen in the round currently!") + return + var/list/logs = list() + var/dat = {" + + + + +

Disease Panel

+ + + + + + + + + + "} + + for (var/ID in GLOB.inspectable_diseases) + var/infctd_mobs = 0 + var/infctd_mobs_dead = 0 + var/infctd_items = 0 + var/dishes = 0 + for (var/mob/living/L in GLOB.mob_list) + for(var/datum/disease/acute/D as anything in L.diseases) + if (ID == "[D.uniqueID]-[D.subID]") + infctd_mobs++ + if (L.stat == DEAD) + infctd_mobs_dead++ + if(!length(logs["[ID]"])) + logs["[ID]"]= list() + logs["[ID]"] += "[L]" + logs["[ID]"]["[L]"] = D.log + + for (var/obj/item/I in GLOB.infected_items) + for(var/datum/disease/acute/D as anything in I.viruses) + if (ID == "[D.uniqueID]-[D.subID]") + infctd_items++ + if(!length(logs["[ID]"])) + logs["[ID]"] = list() + logs["[ID]"] += "[I]" + logs["[ID]"]["[I]"] = D.log + for (var/obj/item/weapon/virusdish/dish in GLOB.virusdishes) + if (dish.contained_virus) + if (ID == "[dish.contained_virus.uniqueID]-[dish.contained_virus.subID]") + dishes++ + if(!length(logs["[ID]"])) + logs["[ID]"] = list() + logs["[ID]"] += "[dish]" + logs["[ID]"]["[dish]"] = dish.contained_virus.log + + var/datum/disease/acute/D = GLOB.inspectable_diseases[ID] + dat += {" + + + + + + + + "} + + dat += {"
Disease IDOriginin Database?Infected PeopleInfected Itemsin Growth Dishes
[D.form] #["[D.uniqueID]"]-["[D.subID]"][D.origin][(ID in GLOB.virusDB) ? "Yes" : "No"][infctd_mobs][infctd_mobs_dead ? " (including [infctd_mobs_dead] dead)" : "" ][infctd_items][dishes]
+ "} + dat += {" + + + "} + for(var/item in logs[viewingID]) + dat += {" + + "} + dat += {"
Disease Logs
[item] - [viewingID]
[logs[viewingID][item]] +
+ + + "} + usr << browse(dat, "window=diseasespanel;size=705x450") + +/datum/admins/Topic(href, href_list) + . = ..() + if(href_list["diseasepanel_examine"]) + viewingID = href_list["diseasepanel_examine"] + diseases_panel() diff --git a/aquila/code/modules/virology/disease/base_disease_folder/base_types.dm b/aquila/code/modules/virology/disease/base_disease_folder/base_types.dm new file mode 100644 index 00000000000..131d9a4b8dc --- /dev/null +++ b/aquila/code/modules/virology/disease/base_disease_folder/base_types.dm @@ -0,0 +1,35 @@ +/datum/disease/acute/virus + form = "Virus" + max_stages = 4 + infectionchance = 20 + infectionchance_base = 20 + stageprob = 10 + stage_variance = -1 + can_kill = list("Bacteria") + disease_flags = parent_type::disease_flags | DISEASE_COPYSTAGE + +/datum/disease/acute/bacteria//faster spread_flags and progression, but only 3 stages max, and reset to stage 1 on every spread_flags + form = "Bacteria" + max_stages = 3 + infectionchance = 30 + infectionchance_base = 30 + stageprob = 30 + stage_variance = -4 + can_kill = list("Parasite") + +/datum/disease/acute/parasite//slower spread_flags. stage preserved on spread_flags + form = "Parasite" + infectionchance = 15 + infectionchance_base = 15 + stageprob = 10 + stage_variance = 0 + can_kill = list("Virus") + disease_flags = parent_type::disease_flags | DISEASE_COPYSTAGE + +/datum/disease/acute/prion//very fast progression, but very slow spread_flags and resets to stage 1. + form = "Prion" + infectionchance = 3 + infectionchance_base = 3 + stageprob = 80 + stage_variance = -10 + can_kill = list() diff --git a/aquila/code/modules/virology/disease/base_disease_folder/disease_augmentation.dm b/aquila/code/modules/virology/disease/base_disease_folder/disease_augmentation.dm new file mode 100644 index 00000000000..c4dfcf6f949 --- /dev/null +++ b/aquila/code/modules/virology/disease/base_disease_folder/disease_augmentation.dm @@ -0,0 +1,40 @@ +/datum/disease/proc/new_effect(badness = 2, stage = 0) + var/list/datum/symptom/acute/list = list() + var/list/to_choose = subtypesof(/datum/symptom/acute) + for(var/e in to_choose) + var/datum/symptom/acute/f = new e + if(!f.restricted && f.stage == stage && text2num(f.badness) == badness) + list += f + if (list.len <= 0) + return new_random_effect(badness+1,badness-1,stage) + else + var/datum/symptom/acute/e = pick(list) + e.chance = rand(1, e.max_chance) + return e + +/datum/disease/proc/new_random_effect(max_badness = 5, min_badness = 0, stage = 0, old_effect) + var/list/datum/symptom/acute/list = list() + var/list/to_choose = subtypesof(/datum/symptom/acute) + if(old_effect) //So it doesn't just evolve right back into the previous virus type + to_choose.Remove(old_effect) + for(var/e in to_choose) + var/datum/symptom/acute/f = new e + if(!f.restricted && f.stage == stage && text2num(f.badness) <= max_badness && text2num(f.badness) >= min_badness) + list += f + if (list.len <= 0) + return new_random_effect(min(max_badness+1,5),max(0,min_badness-1),stage) + else + var/datum/symptom/acute/e = pick(list) + e.chance = rand(1, e.max_chance) + return e + +/datum/disease/proc/randomize_spread() + spread_flags = DISEASE_SPREAD_BLOOD //without blood spread_flags, the disease cannot be extracted or cured, we don't want that for regular diseases + if (prob(5)) //5% chance of spreading through both contact and the air. + spread_flags |= DISEASE_SPREAD_CONTACT_SKIN + spread_flags |= DISEASE_SPREAD_AIRBORNE + else if (prob(40)) //38% chance of spreading through the air only. + spread_flags |= DISEASE_SPREAD_AIRBORNE + else if (prob(60)) //34,2% chance of spreading through contact only. + spread_flags |= DISEASE_SPREAD_CONTACT_SKIN + //22,8% chance of staying in blood diff --git a/aquila/code/modules/virology/disease/base_disease_folder/disease_modification.dm b/aquila/code/modules/virology/disease/base_disease_folder/disease_modification.dm new file mode 100644 index 00000000000..0f5982ca55d --- /dev/null +++ b/aquila/code/modules/virology/disease/base_disease_folder/disease_modification.dm @@ -0,0 +1,81 @@ +/datum/disease/proc/minormutate(index) + var/datum/symptom/acute/e = get_effect(index) + e.minormutate() + infectionchance = min(50,infectionchance + rand(0,10)) + log += "
[ROUND_TIME()] Infection chance now [infectionchance]%" + +/datum/disease/proc/minorstrength(index) + var/datum/symptom/acute/e = get_effect(index) + e.multiplier_tweak(0.1) + +/datum/disease/proc/minorweak(index) + var/datum/symptom/acute/e = get_effect(index) + e.multiplier_tweak(-0.1) + +//Major Mutations +/datum/disease/proc/effectmutate(inBody=FALSE, datum/symptom/acute/symptom) + clean_global_log() + subID = rand(0,9999) + + ///this should be done better but I can't figure out how to return the index value of something from a list + var/i = 0 + for(var/datum/symptom/acute/s as anything in symptoms) + i++ + if(s == symptom) + break + + var/list/randomhexes = list("7","8","9","a","b","c","d","e") + var/colormix = "#[pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)]" + color = BlendRGB(color,colormix,0.25) + var/datum/symptom/acute/f + if (inBody)//mutations that occur directly in a body don't cause helpful symptoms to become deadly instantly. + f = new_random_effect(min(5,text2num(symptom.badness)+1), max(0,text2num(symptom.badness)-1), symptom.stage, symptom.type) + else + f = new_random_effect(min(5,text2num(symptom.badness)+2), max(0,text2num(symptom.badness)-3), symptom.stage, symptom.type)//badness is slightly more likely to go down than up. + var/datum/symptom/acute/old = symptoms[i] + SEND_SIGNAL(old, COMSIG_SYMPTOM_DETACH) + symptoms[i] = f + log += "
[ROUND_TIME()] Mutated effect [symptom.name] [symptom.chance]% into [f.name] [f.chance]%." + update_global_log() + +/datum/disease/proc/antigenmutate() + clean_global_log() + subID = rand(0,9999) + var/old_dat = get_antigen_string() + roll_antigen() + log += "
[ROUND_TIME()] Mutated antigen [old_dat] into [get_antigen_string()]." + update_global_log() + +/datum/disease/proc/incubate(atom/movable/incubator, mutatechance = 1, specified_stage) + if(!mutatechance) + return + mutatechance *= mutation_modifier + + var/datum/reagents/reagents = incubator.reagents + if(!reagents) + return + + var/obj/machinery/disease2/incubator/machine + if(istype(incubator.loc, /obj/machinery/disease2/incubator)) + machine = incubator.loc + + + if(specified_stage) + stage_incubation(specified_stage, mutatechance, reagents, machine, incubator) + + for(var/datum/reagent/reagent as anything in reagents.reagent_list) + if(prob(mutatechance)) + reagent.disease_incubate(incubator, src, machine) + reagents.remove_reagent(reagent.type, (reagent.volume * 0.1)) + +/datum/disease/proc/stage_incubation(specified_stage, mutatechance, datum/reagents/reagents, obj/machinery/disease2/incubator/machine, atom/movable/incubator) + var/list/symptoms_at_stage = list() + for(var/datum/symptom/acute/e as anything in symptoms) + if(e.stage != specified_stage) + continue + symptoms_at_stage |= e + + for(var/datum/reagent/reagent as anything in reagents.reagent_list) + if(prob(mutatechance)) + reagent.stage_disease_incubate(incubator, src, symptoms_at_stage, machine) + reagents.remove_reagent(reagent.type, (reagent.volume * 0.1)) diff --git a/aquila/code/modules/virology/disease/base_disease_folder/disease_strings.dm b/aquila/code/modules/virology/disease/base_disease_folder/disease_strings.dm new file mode 100644 index 00000000000..1385a0eb2a5 --- /dev/null +++ b/aquila/code/modules/virology/disease/base_disease_folder/disease_strings.dm @@ -0,0 +1,123 @@ +/datum/disease/proc/get_antigen_string() + var/dat = "" + for (var/A in antigen) + dat += "[A]" + return dat + +/datum/disease/proc/name(override=FALSE) + if(disease_flags & DISEASE_DORMANT) + .= "DORMANT - [form] #["[uniqueID]"][childID ? "-["[childID]"]" : ""]" + else + .= "[form] #["[uniqueID]"][childID ? "-["[childID]"]" : ""]" + + if (!override && ("[uniqueID]-[subID]" in GLOB.virusDB)) + var/datum/data/record/V = GLOB.virusDB["[uniqueID]-[subID]"] + .= V.fields["name"] + +/datum/disease/proc/real_name() + if(disease_flags & DISEASE_DORMANT) + .= "DORMANT - [form] #["[uniqueID]"]-["[subID]"]" + else + .= "[form] #["[uniqueID]"]-["[subID]"]" + if ("[uniqueID]-[subID]" in GLOB.virusDB) + var/datum/data/record/v = GLOB.virusDB["[uniqueID]-[subID]"] + var/nickname = v.fields["nickname"] ? " \"[v.fields["nickname"]]\"" : "" + . += nickname + +/datum/disease/proc/get_subdivisions_string() + var/subdivision = (strength - ((robustness * strength) / 100)) / max_stages + var/dat = "(" + for (var/i = 1 to max_stages) + dat += "[round(strength - i * subdivision)]" + if (i < max_stages) + dat += ", " + dat += ")" + return dat + +/datum/disease/proc/get_info() + var/r = "GNAv3 [name()]" + r += "
Strength / Robustness : [strength]% / [robustness]% - [get_subdivisions_string()]" + r += "
Infectability : [infectionchance]%" + r += "
Spread forms : [get_spread_string()]" + r += "
Progress Speed : [stageprob]%" + r += "
" + for(var/datum/symptom/acute/e in symptoms) + r += "
● Stage [e.stage] - [e.name] (Danger: [e.badness]). Strength: [e.multiplier]. Occurrence: [e.chance]%.
" + r += "
[e.desc]
" + r += "
" + r += "
Antigen pattern: [get_antigen_string()]" + r += "
last analyzed at: [worldtime2text()]" + return r + +/datum/disease/proc/get_spread_string() + if(spread_text) + return spread_text + var/dat = "" + var/check = 0 + if (spread_flags & DISEASE_SPREAD_BLOOD) + dat += "Blood" + check += DISEASE_SPREAD_BLOOD + if (spread_flags > check) + dat += ", " + if (spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + dat += "Skin Contact" + check += DISEASE_SPREAD_CONTACT_SKIN + if (spread_flags > check) + dat += ", " + if (spread_flags & DISEASE_SPREAD_AIRBORNE) + dat += "Airborne" + check += DISEASE_SPREAD_AIRBORNE + if (spread_flags > check) + dat += ", " + if (spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS) + dat += "Fluid Contact" + check += DISEASE_SPREAD_CONTACT_FLUIDS + if(spread_flags > check) + dat += ", " + if (spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS) + dat += "Non Contagious" + check += DISEASE_SPREAD_NON_CONTAGIOUS + if(spread_flags > check) + dat += ", " + if (spread_flags & DISEASE_SPREAD_SPECIAL) + dat += "UNKNOWN SPREAD" + check += DISEASE_SPREAD_SPECIAL + if(spread_flags > check) + dat += ", " + /* + if (spread_flags & SPREAD_COLONY) + dat += "Colonizing" + check += SPREAD_COLONY + if (spread_flags > check) + dat += ", " + if (spread_flags & SPREAD_MEMETIC) + dat += "Memetic" + check += SPREAD_MEMETIC + if (spread_flags > check) + dat += ", " + */ + return dat + +/datum/disease/proc/addToDB() + if ("[uniqueID]-[subID]" in GLOB.virusDB) + return 0 + childID = 0 + for (var/virus_file in GLOB.virusDB) + var/datum/data/record/v = GLOB.virusDB[virus_file] + if (v.fields["id"] == uniqueID) + childID++ + var/datum/data/record/v = new() + v.fields["id"] = uniqueID + v.fields["sub"] = subID + v.fields["child"] = childID + v.fields["form"] = form + v.fields["name"] = name() + v.fields["nickname"] = "" + v.fields["description"] = get_info() + v.fields["description_hidden"] = get_info(TRUE) + v.fields["custom_desc"] = "No comments yet." + v.fields["antigen"] = get_antigen_string() + v.fields["spread_flags_type"] = get_spread_string() + v.fields["danger"] = "Undetermined" + GLOB.virusDB["[uniqueID]-[subID]"] = v + return 1 diff --git a/aquila/code/modules/virology/disease/base_disease_folder/general_procs.dm b/aquila/code/modules/virology/disease/base_disease_folder/general_procs.dm new file mode 100644 index 00000000000..f16f0375b5d --- /dev/null +++ b/aquila/code/modules/virology/disease/base_disease_folder/general_procs.dm @@ -0,0 +1,122 @@ +/proc/filter_disease_by_spread(list/diseases, required = NONE) + if(!length(diseases)) + return list() + + var/list/viable = list() + for(var/datum/disease/acute/disease as anything in diseases) + if(!(disease.spread_flags & required)) + continue + viable += disease + return viable + +/proc/virus_copylist(list/list) + if(!length(list)) + return list() + var/list/L = list() + for(var/datum/disease/acute/D as anything in list) + L += D.Copy() + return L + +/datum/disease/proc/makerandom(list/str = list(), list/rob = list(), list/anti = list(), list/bad = list(), atom/source = null) + //ID + uniqueID = rand(0,9999) + subID = rand(0,9999) + + //base stats + strength = rand(str[1],str[2]) + robustness = rand(rob[1],rob[2]) + roll_antigen(anti) + + //effects + for(var/i = 1; i <= max_stages; i++) + var/selected_badness = pick( + bad[EFFECT_DANGER_HELPFUL];EFFECT_DANGER_HELPFUL, + bad[EFFECT_DANGER_FLAVOR];EFFECT_DANGER_FLAVOR, + bad[EFFECT_DANGER_ANNOYING];EFFECT_DANGER_ANNOYING, + bad[EFFECT_DANGER_HINDRANCE];EFFECT_DANGER_HINDRANCE, + bad[EFFECT_DANGER_HARMFUL];EFFECT_DANGER_HARMFUL, + bad[EFFECT_DANGER_DEADLY];EFFECT_DANGER_DEADLY, + ) + var/datum/symptom/acute/e = new_effect(text2num(selected_badness), i) + symptoms += e + SEND_SIGNAL(e, COMSIG_SYMPTOM_ATTACH, src) + log += "
[ROUND_TIME()] Added effect [e.name] ([e.chance]% Occurence)." + + //slightly randomized infection chance + var/variance = initial(infectionchance)/10 + infectionchance = rand(initial(infectionchance)-variance,initial(infectionchance)+variance) + infectionchance_base = infectionchance + + //cosmetic petri dish stuff - if set beforehand, will not be randomized + if (!color) + var/list/randomhexes = list("8","9","a","b","c","d","e") + color = "#[pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)]" + pattern = rand(1,6) + pattern_color = "#[pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)]" + + //spreading vectors - if set beforehand, will not be randomized + if (!spread_flags) + randomize_spread() + + //logging + log += "
[ROUND_TIME()] Created and Randomized
" + + //admin panel + if (origin == "Unknown") + if (isvirusdish(source)) + if (isturf(source.loc)) + var/turf/T = source.loc + if (istype(T.loc,/area/centcom)) + origin = "Centcom" + else if (istype(T.loc,/area/medical/virology)) + origin = "Pathology" + update_global_log() + +/datum/disease/proc/AddToGoggleView(mob/living/infectedMob) + if (spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + GLOB.infected_contact_mobs |= infectedMob + if (!infectedMob.pathogen) + infectedMob.pathogen = image('aquila/icons/virology/effects.dmi',infectedMob,"pathogen_contact") + infectedMob.pathogen.plane = HUD_PLANE + infectedMob.pathogen.appearance_flags = RESET_COLOR|RESET_ALPHA + for (var/mob/living/L in GLOB.virus_viewers) + if (L.client) + L.client.images |= infectedMob.pathogen + return + + if (spread_flags & DISEASE_SPREAD_BLOOD) + GLOB.infected_contact_mobs |= infectedMob + if (!infectedMob.pathogen) + infectedMob.pathogen = image('aquila/icons/virology/effects.dmi',infectedMob,"pathogen_blood") + infectedMob.pathogen.plane = HUD_PLANE + infectedMob.pathogen.appearance_flags = RESET_COLOR|RESET_ALPHA + for (var/mob/living/L in GLOB.virus_viewers) + if (L.client) + L.client.images |= infectedMob.pathogen + return + + if(disease_flags & DISEASE_DORMANT) + GLOB.infected_contact_mobs |= infectedMob + if (!infectedMob.pathogen) + infectedMob.pathogen = image('aquila/icons/virology/effects.dmi',infectedMob,"pathogen_blood-old2") + infectedMob.pathogen.plane = HUD_PLANE + infectedMob.pathogen.appearance_flags = RESET_COLOR|RESET_ALPHA + for (var/mob/living/L in GLOB.virus_viewers) + if (L.client) + L.client.images |= infectedMob.pathogen + return + + +///why do we do essentially a lazy fetching of parent? +///No real reason. This is already being set in spread and infect, however incase affected_mob isn't present this fixes. +///better to have as its not needed for mainline disease processing as mob is passed from the mob itself. +/datum/disease/proc/return_parent() + if(!affected_mob) + for(var/mob/living/mob in GLOB.infected_contact_mobs) + for(var/datum/disease/disease as anything in mob.diseases) + if(disease != src) + continue + affected_mob = mob + return mob + return null + return affected_mob diff --git a/aquila/code/modules/virology/disease/base_disease_folder/logging.dm b/aquila/code/modules/virology/disease/base_disease_folder/logging.dm new file mode 100644 index 00000000000..1d5a128c3c0 --- /dev/null +++ b/aquila/code/modules/virology/disease/base_disease_folder/logging.dm @@ -0,0 +1,50 @@ +/datum/disease/proc/update_global_log() + if ("[uniqueID]-[subID]" in GLOB.inspectable_diseases) + return + GLOB.inspectable_diseases["[uniqueID]-[subID]"] = Copy() + +/datum/disease/proc/clean_global_log() + var/ID = "[uniqueID]-[subID]" + if (ID in GLOB.virusDB) + return + + for (var/mob/living/L in GLOB.mob_list) + if(!length(L.diseases)) + continue + for(var/datum/disease/acute/D as anything in L.diseases) + if (ID == "[D.uniqueID]-[D.subID]") + return + + for (var/obj/item/I in GLOB.infected_items) + for(var/datum/disease/acute/D as anything in I.viruses) + if (ID == "[D.uniqueID]-[D.subID]") + return + + var/dishes = 0 + for (var/obj/item/weapon/virusdish/dish in GLOB.virusdishes) + if (dish.contained_virus) + if (ID == "[dish.contained_virus.uniqueID]-[dish.contained_virus.subID]") + dishes++ + if (dishes > 1)//counting the dish we're in currently + return + //If a pathogen that isn't in the database mutates, we check whether it infected anything, and remove it from the disease list if it didn't + //so we don't clog up the Diseases Panel with irrelevant mutations + GLOB.inspectable_diseases -= ID + +/datum/disease/acute/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION("","------") + VV_DROPDOWN_OPTION(VV_HK_VIEW_DISEASE_DATA, "View Disease Data") + +/datum/disease/acute/vv_do_topic(list/href_list) + . = ..() + if(href_list[VV_HK_VIEW_DISEASE_DATA]) + create_disease_info_pane(usr) + +/datum/disease/acute/proc/create_disease_info_pane(mob/user) + var/datum/browser/popup = new(user, "\ref[src]", "GNAv3 [form] #[uniqueID]-[subID]", 600, 500, src) + var/content = get_info() + content += "
LOGS
" + content += log + popup.set_content(content) + popup.open() diff --git a/aquila/code/modules/virology/disease/plague_rat/antagonist.dm b/aquila/code/modules/virology/disease/plague_rat/antagonist.dm new file mode 100644 index 00000000000..76e6820f545 --- /dev/null +++ b/aquila/code/modules/virology/disease/plague_rat/antagonist.dm @@ -0,0 +1,40 @@ +/datum/antagonist/plague_rat + name = "Plague Rat" + // AQ: nasz /datum/antagonist nie ma job_rank/antag_hud_name/antag_count_points z tg + roundend_category = "szczury zarazy" + antagpanel_category = "Szczur zarazy" + banning_key = ROLE_PLAGUERAT + show_in_antagpanel = TRUE + + var/static/datum/team/plague_rat/rats_rats_we_are_the_rats + var/disease_id + var/datum/disease/acute/bacteria/plague + var/turf/invasion + +// AQ: nasz panel antagów nie pokazuje ikon podglądu (get_preview_icon z tg), więc pomijamy + +/datum/antagonist/plague_rat/on_gain() + if(!rats_rats_we_are_the_rats) + rats_rats_we_are_the_rats = new + rats_rats_we_are_the_rats.setup_diseases() + + rats_rats_we_are_the_rats.add_member(owner) + + disease_id = rats_rats_we_are_the_rats.disease_id + plague = rats_rats_we_are_the_rats.plague + invasion = rats_rats_we_are_the_rats.invasion + + if (invasion) + for(var/datum/mind/M in rats_rats_we_are_the_rats.members) + M.current.forceMove(invasion) + owner.current.infect_disease(plague,1, "Plague Mice") + ADD_TRAIT(owner.current, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + + return ..() + +/datum/antagonist/plague_rat/greet() + . = ..() + to_chat(owner.current, span_warning("You are a [name]! Carrier of a dangerous Bacteria!
Try and spread your contagion across the station!
Eating some food can help heal your wounds, always keep an eye out for cheese.")) + +/datum/antagonist/plague_rat/get_team() + return rats_rats_we_are_the_rats diff --git a/aquila/code/modules/virology/disease/plague_rat/event.dm b/aquila/code/modules/virology/disease/plague_rat/event.dm new file mode 100644 index 00000000000..84e60c16f61 --- /dev/null +++ b/aquila/code/modules/virology/disease/plague_rat/event.dm @@ -0,0 +1,44 @@ +// Event „szczury zarazy” — port z Monkestation2.0 (disease/plague_rat/event.dm). +// Monke stoi na nowym SSevents (track/category/tags) i SSpolling; u nas event i ankieta +// duchów wyglądają jak w pozostałych portach (wzór: aquila/code/modules/events/blood_worm.dm). + +/datum/round_event_control/plague_rat + name = "Plague Rats" + typepath = /datum/round_event/ghost_role/plague_rat + weight = 2 + max_occurrences = 1 + min_players = 35 + earliest_start = 55 MINUTES + dynamic_should_hijack = TRUE + cannot_spawn_after_shuttlecall = TRUE + +/datum/round_event/ghost_role/plague_rat + minimum_required = 1 + role_name = "szczur zarazy" + +/datum/round_event/ghost_role/plague_rat/spawn_role() + var/list/candidates = get_candidates(ROLE_PLAGUERAT, /datum/role_preference/midround_ghost/plague_rat, POLL_IGNORE_PLAGUERAT) + if(!length(candidates)) + return NOT_ENOUGH_PLAYERS + + var/spawncount = min(4, length(candidates)) + while(spawncount > 0 && length(candidates)) + var/mob/dead/selected = pick_n_take(candidates) + var/mob/living/simple_animal/mouse/plague/rat = new + var/datum/mind/player_mind = new /datum/mind(selected.key) + player_mind.active = TRUE + player_mind.transfer_to(rat) + player_mind.assigned_role = ROLE_PLAGUERAT + player_mind.special_role = ROLE_PLAGUERAT + player_mind.add_antag_datum(/datum/antagonist/plague_rat) + playsound(rat, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) + message_admins("[ADMIN_LOOKUPFLW(rat)] został szczurem zarazy przez event.") + log_game("[key_name(rat)] was spawned as a plague rat by an event.") + spawned_mobs += rat + spawncount-- + + return SUCCESSFUL_SPAWN + +/datum/role_preference/midround_ghost/plague_rat + name = "Szczur zarazy" + antag_datum = /datum/antagonist/plague_rat diff --git a/aquila/code/modules/virology/disease/plague_rat/objective.dm b/aquila/code/modules/virology/disease/plague_rat/objective.dm new file mode 100644 index 00000000000..d7fa059c0f0 --- /dev/null +++ b/aquila/code/modules/virology/disease/plague_rat/objective.dm @@ -0,0 +1,26 @@ +/datum/objective/plague + name = "Spread your disease." + explanation_text = "Spread your disease among the station's inhabitants." + var/disease_id = "" + var/total_infections = 0 + +/datum/objective/plague/update_explanation_text() + var/current_infections = 0 + for (var/mob/living/L in GLOB.mob_living_list) + if (length(L.diseases)) + for(var/datum/disease/disease in L.diseases) + if("[disease.uniqueID]-[disease.subID]" != disease_id) + continue + current_infections++ + explanation_text = "Spread your disease among the station's inhabitants. ([total_infections] infections caused in total. [current_infections] infected individuals remaining.)" + +/datum/objective/plague/check_completion() + if (..()) + return TRUE + + if (total_infections > 1) + for (var/mob/living/L in GLOB.mob_living_list) + if (disease_id in L.diseases) + return TRUE//if we infected at least one individual, and there is still an infected individual alive, that's good enough. + + return FALSE diff --git a/aquila/code/modules/virology/disease/plague_rat/plague_rat.dm b/aquila/code/modules/virology/disease/plague_rat/plague_rat.dm new file mode 100644 index 00000000000..d68652dcc2b --- /dev/null +++ b/aquila/code/modules/virology/disease/plague_rat/plague_rat.dm @@ -0,0 +1,42 @@ +/mob/living/simple_animal/mouse/plague + name = "plague rat" + + icon = 'aquila/icons/virology/animal.dmi' + icon_state = "mouse_plague" + icon_living = "mouse_plague" + icon_dead = "mouse_plague_dead" + + maxHealth = 50 + health = 50 + + // AQ: nasze /mob/living/simple_animal/mouse nie ma melee_damage_lower/upper (to pola mobow hostile), + // wiec szczur zarazy zaraza przez dotyk, a nie przez obrazenia + // AQ: nasze myszy nie losuja koloru przez chooses_bodycolor + pass_flags = PASSTABLE|PASSGRILLE|PASSMOB + // AQ: nasze myszy nie maja pola hungry + +/mob/living/simple_animal/mouse/plague/Initialize(mapload, tame, new_body_color) + . = ..() + speed = -1 // AQ: szybszy od zwyklej myszy (w Monke robi to movespeed_modifier) + + +/mob/living/simple_animal/mouse/plague/attack_hand(mob/living/carbon/human/user, list/modifiers) + . = ..() + if (ishuman(user)||ismonkey(user)) + var/block = user.check_contact_sterility(HANDS) + var/bleeding = user.check_bodypart_bleeding(HANDS) + share_contact_diseases(user ,block,bleeding) + +/mob/living/simple_animal/mouse/plague/UnarmedAttack(atom/target, proximity, list/modifiers) + . = ..() + if (ishuman(target)||ismonkey(target)) + var/mob/living/user = target + var/block = user.check_contact_sterility(HANDS) + var/bleeding = user.check_bodypart_bleeding(HANDS) + share_contact_diseases(target ,block,bleeding) + +/mob/living/simple_animal/mouse/attackby(obj/item/attacking_item, mob/living/user, params) + . = ..() + attacking_item.disease_contact(src, BODY_ZONE_CHEST) + +// AQ: nie portujemy /datum/movespeed_modifier/plague_rat — nasze simple_animal maja pole `speed` diff --git a/aquila/code/modules/virology/disease/plague_rat/team.dm b/aquila/code/modules/virology/disease/plague_rat/team.dm new file mode 100644 index 00000000000..3f06cea20f3 --- /dev/null +++ b/aquila/code/modules/virology/disease/plague_rat/team.dm @@ -0,0 +1,103 @@ +GLOBAL_VAR_INIT(static_plague_team, null) + +/datum/team/plague_rat + name = "Plague Rats" + member_name = "plague rat" + + var/disease_id = "" + var/datum/disease/acute/bacteria/plague + var/turf/invasion + var/list/hud_icons = list("plague-logo") + var/logo_state = "plague-logo" + +/datum/team/plague_rat/proc/setup_diseases() + GLOB.static_plague_team = src + if (!plague) + plague = new + + var/list/anti = list( + ANTIGEN_BLOOD = 0, + ANTIGEN_COMMON = 0, + ANTIGEN_RARE = 1, + ANTIGEN_ALIEN = 2, + ) + var/list/bad = list( + EFFECT_DANGER_HELPFUL = 0, + EFFECT_DANGER_FLAVOR = 0, + EFFECT_DANGER_ANNOYING = 1, + EFFECT_DANGER_HINDRANCE = 1, + EFFECT_DANGER_HARMFUL = 3, + EFFECT_DANGER_DEADLY = 5, + ) + if(prob(2)) //Dan's Discount products are notoriously bad + plague.origin = "Discount Dan's Gas Station Sushi" + else if(prob(3)) + plague.origin = pick("Nurgle's Cauldron", "Public Bathroom", "Thrax", + "A spaceman got a mouse disease, this is what happened to his body") + else + plague.origin = pick("Black Plague", "Javorian Pox", "Gray Death", "Doom of Pandyssia", "Thrassian Plague", + "Redlight", "Khaara Bacterium", "MEV-1") + + plague.spread_flags = DISEASE_SPREAD_BLOOD|DISEASE_SPREAD_CONTACT_FLUIDS|DISEASE_SPREAD_CONTACT_SKIN|DISEASE_SPREAD_AIRBORNE //gotta ensure that our mice can spread that disease + + plague.infectivity = 75 + plague.color = "#ADAEAA" + plague.pattern = 3 + plague.pattern_color = "#EE9A9C" + plague.max_stages = 4 //4 stages, unlocks the really dangerous symptoms rather than just DNA Degradation + plague.speed = 2 //Takes about 100 seconds to advance to the next stage, max stage in 5 minutes + + plague.makerandom(list(90,100),list(40,75),anti,bad,null) + plague.Refresh_Acute() + for(var/datum/symptom/acute/e in plague.symptoms) + e.chance *= 2 //More likely to trigger symptoms per tick + + disease_id = "[plague.uniqueID]-[plague.subID]" + + if (!invasion) + var/list/found_vents = list() + // AQ: nasz SSmachines nie ma get_machines_by_type_and_subtypes, lecimy po GLOB.machines + for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) + var/turf/scrubber_turf = get_turf(temp_vent) + if(!is_station_level(scrubber_turf.z)) + continue + found_vents.Add(temp_vent) + if(length(found_vents)) + invasion = get_turf(pick(found_vents)) + else + var/area/kitchen = locate(/area/crew_quarters/kitchen) + var/list/turf/open/floors = list() + for(var/turf/open/floor/F in kitchen) + floors += F + invasion = pick(floors)//or any floor really. And if your station has no kitchen then you don't deserve those mice. + + var/datum/objective/plague/new_plague = new + new_plague.disease_id = disease_id + objectives += new_plague + + +/datum/team/plague_rat/proc/update_hud_icons(offset = 0, factions_with_icons = 0) + //let's remove every icons + for(var/datum/mind/R in members) + if(R.current && R.current.client) + for(var/image/I in R.current.client.images) + if(I.icon_state in hud_icons) + R.current.client.images -= I + + //then re-add them + for(var/datum/mind/R in members) + if(R.current && R.current && R.current.client) + for (var/mob/living/L in GLOB.mob_living_list)//except instead of just tracking our fellow plague mice, let's track everyone that's been infected with our plague + if (length(L.diseases)) + for(var/datum/disease/disease in L.diseases) + if("[disease.uniqueID]-[disease.subID]" != disease_id) + continue + var/imageloc = L + if(istype(L.loc,/obj/mecha)) + imageloc = L.loc + var/image/I = image('aquila/icons/virology/role_HUD_icons.dmi', loc = imageloc, icon_state = logo_state) + I.pixel_x = 20 + I.pixel_y = 20 + I.plane = HUD_PLANE + I.appearance_flags |= RESET_COLOR|RESET_ALPHA + R.current.client.images += I diff --git a/aquila/code/modules/virology/disease/premades/_base_premades.dm b/aquila/code/modules/virology/disease/premades/_base_premades.dm new file mode 100644 index 00000000000..3a0661c83fc --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/_base_premades.dm @@ -0,0 +1,25 @@ +//Pre-Defined Global Diseases for use by nanites/wendigo/xenomicrobes/ect. +GLOBAL_LIST_INIT(global_diseases, list()) + +/proc/create_global_diseases() + for(var/disease_type in subtypesof(/datum/disease/acute/premade)) + var/datum/disease/acute/premade/D = new disease_type + GLOB.global_diseases[D.category] = D + D.update_global_log() + +/datum/disease/acute/premade/New() + . = ..() + antigen = list(pick(antigen_family(ANTIGEN_RARE))) + antigen |= pick(antigen_family(ANTIGEN_RARE)) + uniqueID = rand(0,9999) + subID = rand(0,9999) + strength = rand(70,100) + Refresh_Acute() + +/datum/disease/acute/premade/makerandom(list/str = list(), list/rob = list(), list/anti = list(), list/bad = list(), atom/source = null) + return + +/mob/living/proc/infect_disease_predefined(category, forced, notes) + if(!GLOB.global_diseases[category]) + return + infect_disease(GLOB.global_diseases[category], forced, notes) diff --git a/aquila/code/modules/virology/disease/premades/adrenal_crisis.dm b/aquila/code/modules/virology/disease/premades/adrenal_crisis.dm new file mode 100644 index 00000000000..2e512d72e77 --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/adrenal_crisis.dm @@ -0,0 +1,15 @@ +/datum/disease/acute/premade/adrenal_crisis + name = "Adrenal Crisis" + origin = "Trauma" + category = DISEASE_TRAUMA + + symptoms = list( + new /datum/symptom/acute/bad_adrenaline + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 90 + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + diff --git a/aquila/code/modules/virology/disease/premades/anxiety.dm b/aquila/code/modules/virology/disease/premades/anxiety.dm new file mode 100644 index 00000000000..f74055f054f --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/anxiety.dm @@ -0,0 +1,15 @@ +/datum/disease/acute/premade/decloning + name = "Severe Anxiety" + form = "Infection" + origin = "Social Settings" + category = DISEASE_ANXIETY + + symptoms = list( + new /datum/symptom/acute/anxiety + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 90 + strength = 100 + + infectionchance = 0 + infectionchance_base = 0 diff --git a/aquila/code/modules/virology/disease/premades/death_sandwich.dm b/aquila/code/modules/virology/disease/premades/death_sandwich.dm new file mode 100644 index 00000000000..2ebdd530477 --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/death_sandwich.dm @@ -0,0 +1,26 @@ +/datum/disease/acute/premade/death_sandwich_poisoning + name = "Death Sandwich Poisoning" + form = "Condition" + origin = "Death Sandwich" + category = DISEASE_SANDWICH + + symptoms = list( + new /datum/symptom/acute/death_sandwich, + ) + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + robustness = 100 + strength = 100 + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + +/datum/disease/acute/premade/death_sandwich_poisoning/after_add() + . = ..() + antigen = null + stage = 3 + +/datum/disease/acute/premade/death_sandwich_poisoning/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/toxin/anacea, 1)) //anacea is still the cure, i dunno + cure() diff --git a/aquila/code/modules/virology/disease/premades/debug_disease.dm b/aquila/code/modules/virology/disease/premades/debug_disease.dm new file mode 100644 index 00000000000..d4c723ecc36 --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/debug_disease.dm @@ -0,0 +1,57 @@ +/datum/disease/acute/premade/disease_debug + name = "Debug Virus" + form = "Infective code" + origin = "Rogue Code" + category = DISEASE_DEBUG + + symptoms = list( + new /datum/symptom/acute/coma, + ) + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_AIRBORNE + strength = 100 + robustness = 100 + + infectionchance = 100 + infectionchance_base = 100 + can_kill = list() + viable_mobtypes = list(/mob/living/carbon/human) + +/datum/disease/acute/premade/disease_debug/after_add() + . = ..() + antigen = null + stage = 4 + +/obj/item/reagent_containers/medipen/tuberculosiscure/debug + name = "Debug Vaccine autoinjector" + desc = "An autoinjector to cure the Debug disease, which is otherwise incurable. Has 10 uses. Inject when infected." + volume = 100 + amount_per_transfer_from_this = 10 + list_reagents = list(/datum/reagent/vaccine/debug = 100) + +/datum/reagent/vaccine/debug + name = "Vaccine (Debug)" + +/datum/reagent/vaccine/debug/New(data) + . = ..() + var/list/cached_data + if(!data) + cached_data = list() + else + cached_data = data + cached_data |= "[/datum/disease/acute/premade/disease_debug]" + src.data = cached_data + +/obj/item/storage/box/debugbox/tools + name = "box of a debug disease" + icon_state = "syndiebox" + +/obj/item/storage/box/debugbox/disease/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/medipen/tuberculosiscure/debug(src) + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/glass/bottle/disease_debug(src) + +/obj/item/reagent_containers/glass/bottle/disease_debug + name = "Disease Debug culture bottle" + desc = "A small bottle, contains a uncurable disease with whatever symtomp the debuggers are feelings at the time." + spawned_disease = /datum/disease/acute/premade/disease_debug diff --git a/aquila/code/modules/virology/disease/premades/decloning.dm b/aquila/code/modules/virology/disease/premades/decloning.dm new file mode 100644 index 00000000000..aa43ed8a20a --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/decloning.dm @@ -0,0 +1,16 @@ +/datum/disease/acute/premade/decloning + name = "Cellular Degeneration" + form = "Virus" + origin = "Instability" + category = DISEASE_DECLONING + + symptoms = list( + new /datum/symptom/acute/mutation + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 90 + strength = 100 + + infectionchance = 0 + infectionchance_base = 0 + can_kill = list("Bacteria") diff --git a/aquila/code/modules/virology/disease/premades/flu.dm b/aquila/code/modules/virology/disease/premades/flu.dm new file mode 100644 index 00000000000..f4c33f42999 --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/flu.dm @@ -0,0 +1,16 @@ +/datum/disease/acute/premade/cold + name = "Common Cold" + form = "Viral Infection" + category = DISEASE_COLD + + symptoms = list( + new /datum/symptom/acute/cough, + new /datum/symptom/acute/sneeze, + new /datum/symptom/acute/fridge, + ) + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + robustness = 45 + + infectionchance = 70 + infectionchance_base = 86 + can_kill = list("Bacteria") diff --git a/aquila/code/modules/virology/disease/premades/fungal_tb.dm b/aquila/code/modules/virology/disease/premades/fungal_tb.dm new file mode 100644 index 00000000000..1679097cb8b --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/fungal_tb.dm @@ -0,0 +1,31 @@ +/datum/disease/acute/premade/fungal_tb + name = "Tubercle Bacillus Cosmosis Microbes" + form = "Fungal Spores" + origin = "Active fungal spores" + category = DISEASE_FUNGUS + + symptoms = list( + new /datum/symptom/acute/fungal_tb, + ) + spread_flags = DISEASE_SPREAD_BLOOD|DISEASE_SPREAD_CONTACT_FLUIDS|DISEASE_SPREAD_AIRBORNE + robustness = 100 + strength = 100 + + infectionchance = 75 + infectionchance_base = 75 + danger = DISEASE_SEVERITY_BIOHAZARD + required_organs = list(ORGAN_SLOT_LUNGS) + bypasses_immunity = TRUE // TB primarily impacts the lungs; it's also bacterial or fungal in nature; viral immunity should do nothing. + viable_mobtypes = list(/mob/living/carbon/human) + +/datum/disease/acute/premade/fungal_tb/after_add() + . = ..() + antigen = list(ANTIGEN_IG) + stage = 4 + +/datum/disease/acute/premade/fungal_tb/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/medicine/antipathogenic/spaceacillin, 1)) + if(mob.reagents?.has_reagent(/datum/reagent/medicine/salbutamol, 1)) + if(prob(5)) + cure(add_resistance = FALSE) diff --git a/aquila/code/modules/virology/disease/premades/heart_attack.dm b/aquila/code/modules/virology/disease/premades/heart_attack.dm new file mode 100644 index 00000000000..185d281d1d1 --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/heart_attack.dm @@ -0,0 +1,29 @@ +/datum/disease/acute/premade/heart_failure + name = "Heart Eating Worms" + form = "Worms" + origin = "Heart Worms" + category = DISEASE_HEART + + symptoms = list( + new /datum/symptom/acute/heart_failure + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 75 + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + danger = DISEASE_SEVERITY_DANGEROUS + +/datum/disease/acute/premade/heart_failure/activate(mob/living/mob, starved, seconds_per_tick) + if(iscarbon(mob)) + var/mob/living/carbon/carbon_mob = mob + if(!carbon_mob.can_heartattack()) + cure(target = mob) + return + return ..() + +/datum/disease/acute/premade/heart_failure/cure(add_resistance, mob/living/carbon/target, safe = FALSE) + if(iscarbon(target)) + target.set_heartattack(FALSE) + return ..() diff --git a/aquila/code/modules/virology/disease/premades/transformations.dm b/aquila/code/modules/virology/disease/premades/transformations.dm new file mode 100644 index 00000000000..cc3c37c6a14 --- /dev/null +++ b/aquila/code/modules/virology/disease/premades/transformations.dm @@ -0,0 +1,154 @@ +/datum/disease/acute/premade/gondola + name = "Gondola Transformation" + form = "Gondola Cells" + origin = "Gondola Toxins" + category = DISEASE_GONDOLA + + symptoms = list( + new /datum/symptom/acute/transformation/gondola + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 75 + + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + +/datum/disease/acute/premade/gondola/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/consumable/condensedcapsaicin, 1)) //Pepperspray can ruin anyones peaceful existancce. + cure() + +/datum/disease/acute/premade/gondola/digital + category = DISEASE_GONDOLA_DIGITAL + + symptoms = list( + new /datum/symptom/acute/transformation/gondola/digital + ) + +/datum/disease/acute/premade/xeno + name = "Xenomorph Transformation" + form = "Foreign Cells" + origin = "UNKNOWN" + category = DISEASE_XENO + + symptoms = list( + new /datum/symptom/acute/transformation/xeno + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 75 + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + +/datum/disease/acute/premade/xeno/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/phlogiston, 1)) //Fire + cure() + +/datum/disease/acute/premade/corgi + name = "Puppification" + form = "Puppy Cells" + origin = "Ian" + category = DISEASE_CORGI + + symptoms = list( + new /datum/symptom/acute/transformation/corgi + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 75 + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + +/datum/disease/acute/premade/corgi/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/consumable/cocoa, 1)) //Feed Ian chocolatebars + cure() + +/datum/disease/acute/premade/slime + name = "Slime Syndrome" + form = "Simplified Cells" + origin = "Slime Colonies" + category = DISEASE_SLIME + + symptoms = list( + new /datum/symptom/acute/transformation/slime + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 75 + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + +/datum/disease/acute/premade/slime/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/water, 1)) //Water is effective against slimes + cure() + +/datum/disease/acute/premade/morph + name = "Gluttony" + form = "Hungering Cells" + origin = "The Hivemind" + category = DISEASE_MORPH + + symptoms = list( + new /datum/symptom/acute/transformation/morph + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 75 + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + +/datum/disease/acute/premade/morph/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/toxin/lipolicide, 1)) //Empties the hunger + cure() + +/datum/disease/acute/premade/robot + name = "Nanite Conversion" + form = "Nanites" + origin = "Swarmers" + category = DISEASE_ROBOT + + symptoms = list( + new /datum/symptom/acute/transformation/robot + ) + spread_flags = DISEASE_SPREAD_BLOOD + robustness = 75 + + infectionchance = 0 + infectionchance_base = 0 + stage_variance = 0 + +/datum/disease/acute/premade/robot/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/medicine/system_cleaner, 1)) + cure() + +/datum/disease/acute/premade/jungle_fever + name = "Jungle Fever" + form = "Ape Cells" + origin = "Ape Colonies" + category = DISEASE_MONKEY + + symptoms = list( + new /datum/symptom/acute/transformation/jungle_fever + ) + spread_flags = DISEASE_SPREAD_CONTACT_SKIN + robustness = 75 + + infectionchance = 20 + infectionchance_base = 20 + stage_variance = 0 + +/datum/disease/acute/premade/jungle_fever/activate(mob/living/mob, starved, seconds_per_tick) + . = ..() + if(mob.reagents?.has_reagent(/datum/reagent/consumable/banana, 1)) // mmmmh banana + cure() diff --git a/aquila/code/modules/virology/disease/symptom_cracker/cracker_puzzle.dm b/aquila/code/modules/virology/disease/symptom_cracker/cracker_puzzle.dm new file mode 100644 index 00000000000..2744d61c6ba --- /dev/null +++ b/aquila/code/modules/virology/disease/symptom_cracker/cracker_puzzle.dm @@ -0,0 +1,173 @@ +/datum/cracker_puzzle + var/grid = list() + var/grid_size = 5 //this generates squares + ///our sequence we need + var/sequence = list() + ///our current buffer + var/list/buffer = list() + ///our current timer + var/current_timer_id = FALSE + ///our difficulty + var/difficulty = 1 + ///our letters + var/list/combinations = list("A", "B", "C", "D", "E", "F", "G") + var/is_vertical = FALSE + ///blocking message + var/blocking_message = null + + var/datum/parent + + var/list/plotted_points = list() + +/datum/cracker_puzzle/New(grid_size = 5, difficulty = 1, datum/parent) + src.grid_size = grid_size + src.difficulty = difficulty + src.parent = parent + + //generate_combination_strings() + generate_sequence() + +/datum/cracker_puzzle/Destroy(force) + parent = null + return ..() + +/datum/cracker_puzzle/proc/generate_combination_strings() + combinations = list() + for(var/i = 1 to 7) + combinations += "[pick(GLOB.alphabet_upper)][pick(GLOB.numerals)]" + +/datum/cracker_puzzle/proc/generate_sequence() + for(var/i = 1 to grid_size) + var/list/choices = list() + for(var/b = 1 to grid_size) + choices += pick(combinations) + grid += list(choices) + + generate_crack() + +/datum/cracker_puzzle/proc/generate_crack() + + var/sequence_length = 4 + difficulty + + var/last_sequence_spot = rand(1, grid_size) + var/last_vertical_sequence_spot = rand(1, grid_size) + var/vertical = FALSE + for(var/i = 1 to sequence_length) + if(vertical) + var/vertical_spot = rand(1, grid_size) // Random row (y-coordinate) + var/list/horizontal_cut_vert = grid[vertical_spot] // Get the row corresponding to vertical_spot + sequence += horizontal_cut_vert[last_sequence_spot] // Use the last x-coordinate to select the letter + last_vertical_sequence_spot = vertical_spot // Update the y-coordinate + plotted_points += "[last_sequence_spot], [last_vertical_sequence_spot]" // Log the coordinate pair + vertical = FALSE // Switch to horizontal for the next iteration + else + var/horizontal_spot = rand(1, grid_size) // Random column (x-coordinate) + sequence += grid[last_vertical_sequence_spot][horizontal_spot] // Access the column in the current row + last_sequence_spot = horizontal_spot // Update the x-coordinate + plotted_points += "[last_sequence_spot], [last_vertical_sequence_spot]" // Log the coordinate pair + vertical = TRUE // Switch to vertical for the next iteration + + +/datum/cracker_puzzle/proc/check_press(x, y) + buffer += list(list(x,y)) + if(!vertical_check()) + failure() + is_vertical = !is_vertical + + if(!check_sequence()) + failure() + if(check_finished()) + success() + + +/datum/cracker_puzzle/proc/check_finished() + if(length(buffer) == length(sequence)) + return TRUE + return FALSE + +/datum/cracker_puzzle/proc/success() + blocking_message = "Succeeded" + SEND_SIGNAL(parent, COMSIG_CRACKER_PUZZLE_SUCCESS) + +/datum/cracker_puzzle/proc/vertical_check() + if(length(buffer) <= 1) + return TRUE + + var/list/last_coords = list() + last_coords = buffer[length(buffer) - 1] + + var/list/coords = list() + coords = buffer[length(buffer)] + + if(is_vertical) + if(last_coords[1] != coords[1]) + return FALSE + else + if(last_coords[2] != coords[2]) + return FALSE + + return TRUE + +/datum/cracker_puzzle/proc/check_sequence() + for(var/i = 1 to length(buffer)) + var/list/coords = buffer[i] + + var/value = grid[coords[2]][coords[1]] + + if(sequence[i] != value) + return FALSE + return TRUE + +/datum/cracker_puzzle/proc/failure() + buffer = list() + grid = list() + sequence = list() + is_vertical = FALSE + + blocking_message = "Failed" + addtimer(CALLBACK(src, PROC_REF(clear_block), 3 SECONDS)) + SEND_SIGNAL(parent, COMSIG_CRACKER_PUZZLE_FAILURE) + + generate_sequence() + +/datum/cracker_puzzle/proc/clear_block() + blocking_message = null + + +/datum/cracker_puzzle/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CrackerPuzzle", "Genetic Sequence") + ui.open() + +/datum/cracker_puzzle/ui_state() + return GLOB.always_state + +/datum/cracker_puzzle/ui_data(mob/user) + var/list/data = list() + + var/horiztonal = 0 + if(length(buffer)) + horiztonal = buffer[length(buffer)][1] + + var/vertical = 0 + if(length(buffer)) + vertical = buffer[length(buffer)][2] + + data["grid"] = grid + data["buffer"] = buffer + data["sequence"] = sequence + data["is_vertical"] = is_vertical + data["horizontal_loc"] = horiztonal + data["vertical_loc"] = vertical + data["blocking_message"] = blocking_message + + return data + +/datum/cracker_puzzle/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + + switch(action) + if("press_button") + check_press(params["x"], params["y"]) diff --git a/aquila/code/modules/virology/disease/symptom_cracker/symptom_component.dm b/aquila/code/modules/virology/disease/symptom_cracker/symptom_component.dm new file mode 100644 index 00000000000..4c21db55c1d --- /dev/null +++ b/aquila/code/modules/virology/disease/symptom_cracker/symptom_component.dm @@ -0,0 +1,163 @@ +/datum/component/symptom_genes + ///a typepath list of symptoms we can get from this mob + var/list/symptom_types = list() + ///name to type list + var/list/symptom_type_name = list() + ///our current puzzle + var/datum/cracker_puzzle/puzzle + + var/static/list/species_given = list() + + var/static/list/species_specifc_symptoms = list( + ) + + var/static/list/built_symptoms = list() + + var/datum/symptom/acute/current_choice + + var/mob/current_user + + var/obj/item/extrapolator/current_extrapolator + +/datum/component/symptom_genes/Initialize(datum/species/host_species, symptom_count = 3) + . = ..() + if(!ishuman(parent)) + return COMPONENT_INCOMPATIBLE + + add_symptoms(host_species, symptom_count) + + +/datum/component/symptom_genes/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_ATOM_EXTRAPOLATOR_ACT, PROC_REF(on_extrapolate)) + + +/datum/component/symptom_genes/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, COMSIG_ATOM_EXTRAPOLATOR_ACT) + +/datum/component/symptom_genes/Destroy(force) + current_user = null + current_extrapolator = null + cleanup_puzzle() + return ..() + +/datum/component/symptom_genes/proc/cleanup_puzzle() + if(QDELETED(puzzle)) + return + SStgui.close_uis(puzzle) + UnregisterSignal(puzzle, list(COMSIG_CRACKER_PUZZLE_FAILURE, COMSIG_CRACKER_PUZZLE_SUCCESS)) + QDEL_NULL(puzzle) + +/datum/component/symptom_genes/proc/add_symptoms(datum/species/host_species, symptom_count) + + if(!(host_species.type in species_given)) + if(add_species_symptom(host_species)) + symptom_count-- + + while(symptom_count > 0) + if(add_new_symptom(host_species)) + symptom_count-- + +/datum/component/symptom_genes/proc/add_species_symptom(datum/species/host_species) + species_given |= host_species.type + + if(!(host_species.type in species_specifc_symptoms)) + return FALSE + + var/datum/symptom/acute/new_symptom = pick(species_specifc_symptoms[host_species.type]) + symptom_types |= new_symptom + symptom_type_name[initial(new_symptom.name)] = new_symptom + return TRUE + +/datum/component/symptom_genes/proc/add_new_symptom(datum/species/host_species) + if(!length(built_symptoms)) + for(var/datum/symptom/acute/symptom as anything in subtypesof(/datum/symptom/acute)) + if(symptom.restricted) + continue + built_symptoms |= symptom + + var/datum/symptom/acute/new_symptom = pick(built_symptoms) + if(prob(5)) + new_symptom = pick(species_specifc_symptoms[host_species.type]) + if(new_symptom in symptom_types) + return FALSE + + symptom_types |= new_symptom + symptom_type_name[initial(new_symptom.name)] = new_symptom + return TRUE + +/datum/component/symptom_genes/proc/on_extrapolate(datum/source, mob/user, obj/item/extrapolator/extrapolator) + SIGNAL_HANDLER + + if(!user) + return FALSE + + if(!user.can_interact_with(parent)) + return FALSE + + INVOKE_ASYNC(src, PROC_REF(try_extrapolate), user, extrapolator) + return TRUE + +/datum/component/symptom_genes/proc/try_extrapolate(mob/user, obj/item/extrapolator/extrapolator) + var/symptom_choice = tgui_input_list(user, "Choose a Symptom", "Genetic Symptoms", symptom_type_name) + if(!symptom_choice) + return + current_choice = symptom_type_name[symptom_choice] + current_user = user + + current_extrapolator = extrapolator + + puzzle = new(difficulty = text2num(initial(current_choice.badness)) + 1, parent = src) + + RegisterSignal(src, COMSIG_CRACKER_PUZZLE_SUCCESS, PROC_REF(on_puzzle_success), override = TRUE) + RegisterSignal(src, COMSIG_CRACKER_PUZZLE_FAILURE, PROC_REF(on_puzzle_fail), override = TRUE) + puzzle.ui_interact(user) + + +/datum/component/symptom_genes/proc/on_puzzle_fail() + playsound(current_user, 'sound/machines/defib_failed.ogg', 50, FALSE) + current_choice = null + current_user = null + current_extrapolator = null + cleanup_puzzle() + +/datum/component/symptom_genes/proc/on_puzzle_success() + playsound(current_user, 'sound/machines/defib_success.ogg', 50, FALSE) + var/mob/living/carbon/human/human = parent + var/uncapped = FALSE + if(human.mind) + uncapped = TRUE + + var/datum/symptom/acute/created_symptom = new current_choice + + if(uncapped) + created_symptom.chance = rand(created_symptom.chance, created_symptom.max_chance * 4) + created_symptom.chance = min(created_symptom.chance, 100) + + created_symptom.max_chance = max(created_symptom.chance, created_symptom.max_chance) + + else + created_symptom.chance = rand(created_symptom.chance, created_symptom.max_chance) + created_symptom.chance = min(created_symptom.chance, 100) + + created_symptom.multiplier = rand(created_symptom.multiplier, created_symptom.max_multiplier) + + + var/obj/item/disk/disease/d = new /obj/item/disk/disease(get_turf(current_user)) + current_user.put_in_hands(d) + + d.analyzed = TRUE + d.effect = created_symptom + d.update_desc() + d.name = "\improper [created_symptom.name] GNA disk" + + current_user = null + symptom_types -= current_choice + symptom_type_name -= initial(current_choice.name) + current_choice = null +// if(prob(text2num(created_symptom.badness) * (15 * current_extrapolator.scanner.rating))) +// current_extrapolator.generate_varient() + current_extrapolator = null + + cleanup_puzzle() diff --git a/aquila/code/modules/virology/disease/symptom_varients/base.dm b/aquila/code/modules/virology/disease/symptom_varients/base.dm new file mode 100644 index 00000000000..4f0786bedfe --- /dev/null +++ b/aquila/code/modules/virology/disease/symptom_varients/base.dm @@ -0,0 +1,57 @@ +/datum/symptom_varient + var/name = "Generic Varient" + var/desc = "An amalgamation of genes." + + var/weight = 0 // Set to 0 to be unobtainable till someone balances, still in code commented as heavily tied into extrapolator and easier to readd in future + + var/datum/symptom/acute/host_symptom + var/datum/disease/host_disease + var/cooldown_time = 5 SECONDS + COOLDOWN_DECLARE(host_cooldown) + +/datum/symptom_varient/New(datum/symptom/host, datum/disease/disease) + . = ..() + host_symptom = host + if(disease) + set_disease_parent(disease) + else + RegisterSignal(host_symptom, COMSIG_SYMPTOM_ATTACH, PROC_REF(set_disease_parent)) + + setup_varient() + host_symptom.update_name() + +/datum/symptom_varient/Destroy(force) + if(host_symptom) + UnregisterSignal(host_symptom, list(COMSIG_SYMPTOM_ATTACH, COMSIG_SYMPTOM_DETACH, COMSIG_SYMPTOM_TRIGGER)) + host_symptom = null + host_disease = null + return ..() + +/datum/symptom_varient/proc/set_disease_parent(datum/source, datum/disease/attached) + SIGNAL_HANDLER + + UnregisterSignal(host_symptom, COMSIG_SYMPTOM_ATTACH) + RegisterSignal(host_symptom, COMSIG_SYMPTOM_DETACH, PROC_REF(clear_disease_parent)) + host_disease = attached + +/datum/symptom_varient/proc/clear_disease_parent() + SIGNAL_HANDLER + + host_disease = null + UnregisterSignal(host_symptom, COMSIG_SYMPTOM_DETACH) + RegisterSignal(host_symptom, COMSIG_SYMPTOM_ATTACH, PROC_REF(set_disease_parent)) + +/datum/symptom_varient/proc/setup_varient() + return TRUE + +/datum/symptom_varient/proc/trigger_symptom() + if(QDELETED(host_disease) || QDELETED(host_disease.affected_mob)) + return FALSE + if(!COOLDOWN_FINISHED(src, host_cooldown)) + return FALSE + host_symptom.run_effect(host_disease.affected_mob, host_disease) + COOLDOWN_START(src, host_cooldown, cooldown_time) + return TRUE + +/datum/symptom_varient/proc/Copy(datum/symptom/new_symp) + return new type(new_symp) diff --git a/aquila/code/modules/virology/disease/symptom_varients/bluespace.dm b/aquila/code/modules/virology/disease/symptom_varients/bluespace.dm new file mode 100644 index 00000000000..d314602d303 --- /dev/null +++ b/aquila/code/modules/virology/disease/symptom_varients/bluespace.dm @@ -0,0 +1,35 @@ +GLOBAL_LIST_EMPTY_TYPED(bluespace_varient_list, /datum/symptom_varient/bluespace) + +/datum/symptom_varient/bluespace + name = "Quantumly Entangled" + desc = "The cloning process seems to have caused genes to communicate through hosts." + cooldown_time = 30 SECONDS + + weight = 0 // 2 was original, leaving for future ref + + var/bluespace_id = 0 + var/static/last_bluespace_id = 0 + +/datum/symptom_varient/bluespace/New(datum/symptom/host, bluespace_id) + . = ..() + GLOB.bluespace_varient_list += src + if(isnull(bluespace_id)) + bluespace_id = last_bluespace_id++ + src.bluespace_id = bluespace_id + +/datum/symptom_varient/bluespace/Destroy(force) + GLOB.bluespace_varient_list -= src + return ..() + +/datum/symptom_varient/bluespace/setup_varient() + . = ..() + RegisterSignal(host_symptom, COMSIG_SYMPTOM_TRIGGER, PROC_REF(propagate)) + +/datum/symptom_varient/bluespace/Copy(datum/symptom/new_symp) + return new /datum/symptom_varient/bluespace(new_symp, bluespace_id) + +/datum/symptom_varient/bluespace/proc/propagate() + for(var/datum/symptom_varient/bluespace/bluespace as anything in GLOB.bluespace_varient_list) + if(QDELETED(bluespace) || bluespace_id != bluespace.bluespace_id) + continue + bluespace.trigger_symptom() diff --git a/aquila/code/modules/virology/disease/symptom_varients/recursive.dm b/aquila/code/modules/virology/disease/symptom_varients/recursive.dm new file mode 100644 index 00000000000..8964be62b11 --- /dev/null +++ b/aquila/code/modules/virology/disease/symptom_varients/recursive.dm @@ -0,0 +1,25 @@ +/datum/symptom_varient/recursive + name = "Recursive" + desc = "Triggers a symptom 3 extra times 2 seconds apart from eachother." + + cooldown_time = 15 SECONDS + +/datum/symptom_varient/recursive/Destroy(force) + UnregisterSignal(host_symptom, COMSIG_SYMPTOM_TRIGGER) + return ..() + +/datum/symptom_varient/recursive/set_disease_parent(datum/source, datum/disease/attached) + . = ..() + RegisterSignal(host_symptom, COMSIG_SYMPTOM_TRIGGER, PROC_REF(start_chain)) + +/datum/symptom_varient/recursive/clear_disease_parent() + . = ..() + UnregisterSignal(host_symptom, COMSIG_SYMPTOM_TRIGGER) + +/datum/symptom_varient/recursive/proc/start_chain() + if(trigger_symptom()) + addtimer(CALLBACK(src, PROC_REF(trigger)), 2 SECONDS, TIMER_DELETE_ME) + addtimer(CALLBACK(src, PROC_REF(trigger)), 4 SECONDS, TIMER_DELETE_ME) + +/datum/symptom_varient/recursive/proc/trigger() + host_symptom.run_effect(host_disease.affected_mob, host_disease) diff --git a/aquila/code/modules/virology/disease/symptom_varients/reflexive.dm b/aquila/code/modules/virology/disease/symptom_varients/reflexive.dm new file mode 100644 index 00000000000..5299b80586e --- /dev/null +++ b/aquila/code/modules/virology/disease/symptom_varients/reflexive.dm @@ -0,0 +1,24 @@ +/datum/symptom_varient/reflexive + name = "Reflexive" + desc = "Automatically takes effect when the host is damaged." + + cooldown_time = 15 SECONDS + + var/mob/living/host_mob + +/datum/symptom_varient/reflexive/Destroy(force) + if(host_mob) + UnregisterSignal(host_mob, COMSIG_CARBON_TAKE_BRUTE_DAMAGE) + host_mob = null + return ..() + +/datum/symptom_varient/reflexive/set_disease_parent(datum/source, datum/disease/attached) + . = ..() + if(attached.return_parent()) + host_mob = attached.return_parent() + RegisterSignal(host_mob, COMSIG_CARBON_TAKE_BRUTE_DAMAGE, PROC_REF(trigger_symptom)) + +/datum/symptom_varient/reflexive/clear_disease_parent() + . = ..() + UnregisterSignal(host_mob, COMSIG_CARBON_TAKE_BRUTE_DAMAGE) + host_mob = null diff --git a/aquila/code/modules/virology/disease/symptom_varients/routine.dm b/aquila/code/modules/virology/disease/symptom_varients/routine.dm new file mode 100644 index 00000000000..6ed36f33477 --- /dev/null +++ b/aquila/code/modules/virology/disease/symptom_varients/routine.dm @@ -0,0 +1,24 @@ +/datum/symptom_varient/routine + name = "Clockwork" + desc = "Like clockwork this symptom triggers every 10 seconds" + + cooldown_time = 5 SECONDS + COOLDOWN_DECLARE(next_process) + +/datum/symptom_varient/routine/Destroy(force) + . = ..() + STOP_PROCESSING(SSobj, src) + +/datum/symptom_varient/routine/set_disease_parent(datum/source, datum/disease/attached) + . = ..() + START_PROCESSING(SSobj, src) + +/datum/symptom_varient/routine/clear_disease_parent() + . = ..() + STOP_PROCESSING(SSobj, src) + +/datum/symptom_varient/routine/process(seconds_per_tick) + if(!COOLDOWN_FINISHED(src, next_process)) + return + COOLDOWN_START(src, next_process, 10 SECONDS) + trigger_symptom() diff --git a/aquila/code/modules/virology/disease/symptom_varients/strengthen.dm b/aquila/code/modules/virology/disease/symptom_varients/strengthen.dm new file mode 100644 index 00000000000..1d06ed4e71d --- /dev/null +++ b/aquila/code/modules/virology/disease/symptom_varients/strengthen.dm @@ -0,0 +1,20 @@ +/datum/symptom_varient/strength + name = "Strengthen" + desc = "Raises a symptoms strength." + + cooldown_time = 15 SECONDS + +/datum/symptom_varient/strength/Destroy(force) + . = ..() + UnregisterSignal(host_symptom, COMSIG_SYMPTOM_TRIGGER) + +/datum/symptom_varient/strength/set_disease_parent(datum/source, datum/disease/attached) + . = ..() + RegisterSignal(host_symptom, COMSIG_SYMPTOM_TRIGGER, PROC_REF(increase_strength)) + +/datum/symptom_varient/strength/clear_disease_parent() + . = ..() + UnregisterSignal(host_symptom, COMSIG_SYMPTOM_TRIGGER) + +/datum/symptom_varient/strength/proc/increase_strength() + host_symptom.multiplier = min(host_symptom.multiplier++, host_symptom.max_multiplier) diff --git a/aquila/code/modules/virology/disease/symtoms/_symptom.dm b/aquila/code/modules/virology/disease/symtoms/_symptom.dm new file mode 100644 index 00000000000..9959f7fb6d5 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/_symptom.dm @@ -0,0 +1,116 @@ +/datum/symptom/acute + // How dangerous the symptom is. + // 0 = generally helpful (ex: full glass syndrome) + // 1 = neutral, just flavor text (ex: headache) + // 2 = minor inconvenience (ex: tourettes) + // 3 = severe inconvenience (ex: random tripping) + // 4 = likely to indirectly lead to death (ex: Harlequin Ichthyosis) + // 5 = will definitely kill you (ex: gibbingtons/necrosis) + var/badness = EFFECT_DANGER_ANNOYING + ///are we a restricted type + var/restricted = FALSE + var/encyclopedia = "" + var/stage = -1 + // Diseases start at stage 1. They slowly and cumulatively proceed their way up. + // Try to keep more severe effects in the later stages. + + var/chance = 3 + // Under normal conditions, the percentage chance per tick to activate. + var/max_chance = 9 + // Maximum percentage chance per tick. + + var/multiplier = 1 + // How strong the effects are. Use this in activate(). + var/max_multiplier = 1 + // Maximum multiplier. + + var/count = 0 + // How many times the effect has activated so far. + var/max_count = -1 + // How many times the effect should be allowed to activate. If -1, always activate. + + var/datum/symptom_varient/attached_varient + // This is our attached varient used for updating desc and Symptom copy code. + +/datum/symptom/acute/Destroy(force) + QDEL_NULL(attached_varient) + return ..() + +/datum/symptom/acute/proc/minormutate() + if (prob(20)) + chance = rand(initial(chance), max_chance) + +/datum/symptom/acute/proc/multiplier_tweak(tweak) + multiplier = clamp(multiplier+tweak,1,max_multiplier) + +/datum/symptom/acute/proc/can_run_effect(active_stage = -1, seconds_per_tick) + if((count < max_count || max_count == -1) && (stage <= active_stage || active_stage == -1) && prob(min(chance * seconds_per_tick, max_chance))) + return TRUE + return FALSE + +/datum/symptom/acute/proc/run_effect(mob/living/carbon/mob, datum/disease/acute/disease) + if(isnull(mob)) + CRASH("run_effect called without a valid mob!") + if(count < 1) + first_activate(mob, disease) + activate(mob, disease) + SEND_SIGNAL(src, COMSIG_SYMPTOM_TRIGGER) + count += 1 + +///this runs the first time its activated +/datum/symptom/acute/proc/first_activate(mob/living/carbon/mob, datum/disease/acute/disease) + +// The actual guts of the effect. Has a prob(chance)% to get called per tick. +/datum/symptom/acute/proc/activate(mob/living/carbon/mob, datum/disease/acute/disease) + +// If activation makes any permanent changes to the effect, this is where you undo them. +// Will not get called if the virus has never been activated. +/datum/symptom/acute/proc/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + +/datum/symptom/acute/proc/on_touch(mob/living/carbon/mob, toucher, touched, touch_type) + // Called when the sufferer of the symptom bumps, is bumped, or is touched by hand. +/datum/symptom/acute/proc/on_death(mob/living/carbon/mob) + // Called when the sufferer of the symptom dies +/datum/symptom/acute/proc/side_effect(mob/living/mob) + // Called on every Life() while the body is alive +///called before speech goes out, returns FALSE if we stop, otherwise returns Edited Message +/datum/symptom/acute/proc/on_speech(mob/living/mob) + + +/datum/symptom/acute/proc/disable_effect(mob/living/mob, datum/disease/acute/disease, safe = FALSE) + if (count > 0) + deactivate(mob, disease, safe) + + +/datum/symptom/acute/proc/update_name() + var/name_string = "" + if(attached_varient) + name_string += "[attached_varient.name] " + name_string += initial(name) + + name = name_string + +// AQ: u nas symptomy advance zostają w /datum/symptom, więc symptomy disease2 mają własną gałąź /datum/symptom/acute. +// Poniżej to, co w Monke siedziało we wspólnym code/datums/diseases/advance/symptoms/symptoms.dm. +/datum/symptom/acute + /// Nazwa choroby, którą symptom „udaje” (flavor). + var/illness = "Unidentified" + +/// Nasz /datum/symptom/New() szuka ID w liście symptomów advance i rzuca CRASH, a disease2 go nie potrzebuje. +/datum/symptom/acute/New() + return + +/datum/symptom/acute/Copy() + var/datum/symptom/acute/new_symp = new type + new_symp.name = name + new_symp.id = id + new_symp.neutered = neutered + new_symp.multiplier = multiplier + new_symp.chance = chance + new_symp.max_chance = max_chance + new_symp.max_multiplier = max_multiplier + + if(attached_varient) + var/datum/symptom_varient/varient = attached_varient.Copy(new_symp) + new_symp.attached_varient = varient + return new_symp diff --git a/aquila/code/modules/virology/disease/symtoms/animation_procs.dm b/aquila/code/modules/virology/disease/symtoms/animation_procs.dm new file mode 100644 index 00000000000..e5565558759 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/animation_procs.dm @@ -0,0 +1,109 @@ +#define COLOR_MARTIX_BASE list( 1.00, 0.00, 0.00, 0.00,\ + 0.00, 1.00, 0.00, 0.00,\ + 0.00, 0.00, 1.00, 0.00,\ + 0.00, 0.00, 0.00, 1.00,\ + 0.00, 0.00, 0.00, 0.00) + +/atom/proc/fade_matrix(time = 1 SECONDS, matrix = COLOR_MATRIX_GRAYSCALE) + color = COLOR_MARTIX_BASE + animate(src, color=matrix, time=time, easing=SINE_EASING) + +/atom/proc/fade_from_matrix(time = 1 SECONDS, matrix = COLOR_MATRIX_GRAYSCALE) + color = matrix + animate(src, color=COLOR_MARTIX_BASE, time=time, easing=SINE_EASING) + +//generalized version of lavadrakes Lavaswoop +/atom/proc/fading_leap_up() + var/matrix/M = matrix() + var/loop_count = 15 + while(loop_count > 0) + loop_count-- + animate(src, transform = M, pixel_z = src.pixel_z + 12, alpha = src.alpha - 17, time = 1, loop = 1, easing = LINEAR_EASING) + M.Scale(1.2,1.2) + sleep(0.1 SECONDS) + alpha = 0 + return TRUE + +//inverse of above +/atom/proc/fading_leap_down() + var/matrix/M = matrix() + var/loop_count = 12 + M.Scale(15,15) + while(loop_count > 0) + loop_count-- + animate(src, transform = M, pixel_z = src.pixel_z - 12, alpha = src.alpha + 17, time = 1, loop = 1, easing = LINEAR_EASING) + M.Scale(0.8,0.8) + sleep(0.1 SECONDS) + animate(src, transform = M, pixel_z = 0, alpha = 255, time = 1, loop = 1, easing = LINEAR_EASING) + M.Scale(1,1) + + +///😰😰😰😰😰😰 +/atom/proc/bananeer(dir=null, total_time = 0.5 SECONDS, height = 16, stun_duration = 1 SECONDS, flip_count = 1) + animate(src) // cleanse animations as funny as a ton of stacked flips would be it would be an eye sore + var/matrix/M = transform + var/turn = 90 + if(isnull(dir)) + if(dir == EAST) + turn = 90 + else if(dir == WEST) + turn = -90 + else + if(prob(50)) + turn = -90 + + + var/flip_anim_step_time = total_time / (1 + 4 * flip_count) + animate(src, transform = matrix(M, turn, MATRIX_ROTATE | MATRIX_MODIFY), time = flip_anim_step_time, flags = ANIMATION_PARALLEL) + for(var/i in 1 to flip_count) + animate(transform = matrix(M, turn, MATRIX_ROTATE | MATRIX_MODIFY), time = flip_anim_step_time) + animate(transform = matrix(M, turn, MATRIX_ROTATE | MATRIX_MODIFY), time = flip_anim_step_time) + animate(transform = matrix(M, turn, MATRIX_ROTATE | MATRIX_MODIFY), time = flip_anim_step_time) + animate(transform = matrix(M, turn, MATRIX_ROTATE | MATRIX_MODIFY), time = flip_anim_step_time) + var/matrix/M2 = transform + animate(transform = matrix(M, 1.2, 0.7, MATRIX_SCALE | MATRIX_MODIFY), time = total_time * 0.125) + animate(transform = M2, time = total_time * 0.125) + + animate(src, pixel_y=height, time= total_time * 0.5, flags=ANIMATION_PARALLEL) + animate(pixel_y=-4, time= total_time * 0.5) + + if(isliving(src)) + var/mob/living/living = src + living.Knockdown(stun_duration) + animate(src, pixel_x = 0, pixel_y = 0, transform = src.transform.Turn(-turn), time = 0.3 SECONDS, easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) + +#undef COLOR_MARTIX_BASE +#undef COLOR_MATRIX_GRAYSCALE + + +/atom/movable/proc/mods_send_them_to_hell(size = 1) + ADD_TRAIT(src, TRAIT_IMMOBILIZED, ADMIN_TRAIT) + + var/matrix/matrix_one = matrix() + matrix_one.Scale(0,0) + + var/list/created_hell = list() + + var/turf/our_turf = get_turf(src) + var/list/turfs = block(our_turf, 2+size) + + for(var/turf/turf as anything in turfs) + created_hell += new /obj/effect/hell(turf) + + animate(src, transform = matrix_one, time = 2 SECONDS) + sleep(2 SECONDS) + for(var/obj/effect/hell as anything in created_hell) ///probably some really smart way to animate this into the center point using its offset from the center and animates but eh + qdel(hell) + if(isliving(src)) + var/mob/living/living = src + living.gib(FALSE) + else + qdel(src) + +/obj/effect/hell + icon = 'icons/turf/floors.dmi' + icon_state = "lava" + name = "Portal to Hell" + plane = GAME_PLANE + +// AQ: nie mamy adminowskich /datum/smite z tg, wiec smite „Portal to Hell” pominiety diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/bee_vomit.dm b/aquila/code/modules/virology/disease/symtoms/annoying/bee_vomit.dm new file mode 100644 index 00000000000..207d7fd804d --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/bee_vomit.dm @@ -0,0 +1,45 @@ +/datum/symptom/acute/bee_vomit + name = "Melisso-Emeto Syndrome" + desc = "Converts the lungs of the infected into a bee-hive." + encyclopedia = "Giving the infected a steady drip of honey in exchange of coughing up a bee every so often. The higher the symptom strength, the more honey is generated, and the more bees will be coughed up and more often as well. While Honey is a great healing reagent, it is also high on nutrients. Expect to become fat quickly.." + stage = 1 + badness = EFFECT_DANGER_ANNOYING + severity = 2 + max_multiplier = 4 + +/datum/symptom/acute/bee_vomit/activate(mob/living/mob) + if(!ismouse(mob)) + if ((mob.reagents?.get_reagent_amount(/datum/reagent/consumable/sugar) < 5 + multiplier * 0.5) && prob(multiplier * 8)) //honey quickly decays into sugar + mob.reagents.add_reagent(/datum/reagent/consumable/honey, multiplier) + if(prob(25)) + to_chat(mob, span_notice("You taste someting sweet")) + + if(prob(20 + (20 * multiplier))) + to_chat(mob, span_warning("You feel a buzzing in your throat")) + + addtimer(CALLBACK(src, PROC_REF(spawn_bee), WEAKREF(mob)), 5 SECONDS) + +/datum/symptom/acute/bee_vomit/proc/kill_bee(datum/weakref/bee_ref) + var/mob/living/simple_animal/hostile/poison/bees/bee = bee_ref.resolve() + if(!bee) + return + + bee.visible_message(span_warning("The bee falls apart!"), span_warning("You fall apart")) + bee.death() + QDEL_IN(bee, 0.1 SECONDS) + +/datum/symptom/acute/bee_vomit/proc/spawn_bee(datum/weakref/mob_ref) + var/mob/living/mob = mob_ref.resolve() + if(!mob) + return + var/turf/open/T = get_turf(mob) + if(prob(40 + 10 * multiplier)) + mob.visible_message(span_warning("[mob] coughs out a bee!"),span_danger("You cough up a bee!")) + var/bee_type = pick( + 100;/mob/living/simple_animal/hostile/poison/bees, + 10;/mob/living/simple_animal/hostile/poison/bees, + 5;/mob/living/simple_animal/hostile/poison/bees/toxin, + ) + var/mob/living/simple_animal/hostile/poison/bees/bee = new bee_type(T) + if(multiplier < 4) + addtimer(CALLBACK(src, PROC_REF(kill_bee), WEAKREF(bee)), 20 SECONDS * multiplier) diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/bloodynose.dm b/aquila/code/modules/virology/disease/symtoms/annoying/bloodynose.dm new file mode 100644 index 00000000000..cf9c04ec502 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/bloodynose.dm @@ -0,0 +1,17 @@ +/datum/symptom/acute/bloodynose + name = "Intranasal Hemorrhage" + desc = "Causes the infected's nasal pathways to hemorrhage, causing a nosebleed, potentially carrying the pathogen." + stage = 2 + badness = EFFECT_DANGER_ANNOYING + severity = 2 + +/datum/symptom/acute/bloodynose/activate(mob/living/mob) + if (prob(30)) + if (ishuman(mob)) + var/mob/living/carbon/human/victim = mob + if (!(TRAIT_NO_BLOOD in victim.dna.species.inherent_traits)) + victim.add_splatter_floor(get_turf(mob), 1) + else + var/obj/effect/decal/cleanable/blood/blood= locate(/obj/effect/decal/cleanable/blood) in get_turf(mob) + if(blood==null) + blood = new /obj/effect/decal/cleanable/blood(get_turf(mob), mob.get_static_viruses(), mob.get_blood_dna_list()) diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/chicken_pox.dm b/aquila/code/modules/virology/disease/symtoms/annoying/chicken_pox.dm new file mode 100644 index 00000000000..4024d86a5a9 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/chicken_pox.dm @@ -0,0 +1,15 @@ +/datum/symptom/acute/chickenpox + name = "Chicken Pox" + desc = "Causes the infected to begin coughing up eggs of the poultry variety." + stage = 3 + badness = EFFECT_DANGER_ANNOYING + severity = 2 + var/eggspawn = /obj/item/food/egg + +/datum/symptom/acute/chickenpox/activate(mob/living/carbon/mob) + if (prob(30)) + mob.say(pick("BAWWWK!", "BAAAWWK!", "CLUCK!", "CLUUUCK!", "BAAAAWWWK!")) + if (prob(15)) + mob.emote("me",1,"vomits up a chicken egg!") + playsound(mob.loc, 'sound/effects/splat.ogg', 50, 1) + new eggspawn(get_turf(mob)) diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/cough.dm b/aquila/code/modules/virology/disease/symtoms/annoying/cough.dm new file mode 100644 index 00000000000..d7db2c35ad2 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/cough.dm @@ -0,0 +1,25 @@ +/datum/symptom/acute/cough//creates pathogenic clouds that may contain even non-airborne viruses. + name = "Anima Syndrome" + desc = "Causes the infected to cough rapidly, releasing pathogenic clouds." + stage = 2 + badness = EFFECT_DANGER_ANNOYING + severity = 2 + max_chance = 10 + +/datum/symptom/acute/cough/activate(mob/living/mob) + if(HAS_TRAIT(mob, TRAIT_NOBREATH)) + return + mob.emote("cough") + var/mob/living/carbon/human/victim = mob + // AQ: bez organów zewnętrznych z tg; reszta warunków jak w Monke + if(!ishuman(victim) || !isturf(victim.loc) || !QDELETED(victim.internal) || victim.is_mouth_covered() || victim.has_smoke_protection() || victim.check_airborne_sterility()) + return + var/strength = 0 + for(var/datum/disease/acute/virus in victim.diseases) + strength += virus.infectionchance + strength = round(strength / length(victim.diseases)) + var/i = 1 + while (strength > 0 && i < 10) //stronger viruses create more clouds at once, max limit of 10 clouds + new /obj/effect/pathogen_cloud/core(get_turf(mob), victim, virus_copylist(victim.diseases)) // We need to call where the mob is, not where the source is as the source has no idea where the mob is + strength -= 30 + i++ diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/cult_syndrome.dm b/aquila/code/modules/virology/disease/symtoms/annoying/cult_syndrome.dm new file mode 100644 index 00000000000..e2a185451af --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/cult_syndrome.dm @@ -0,0 +1,75 @@ +/datum/symptom/acute/cult_hallucination + name = "Visions of the End-Times" + desc = "UNKNOWN" + stage = 1 + badness = EFFECT_DANGER_ANNOYING + severity = 2 + max_multiplier = 2.5 + +/datum/symptom/acute/cult_hallucination/activate(mob/living/mob) + var/turf/mob_turf = get_turf(mob) + if(!mob_turf || IS_CULTIST(mob) || istype(get_area(mob), /area/chapel/main) || !mob?.client) + return + mob.whisper("...[pick("ire","ego","nahlizet","certum","veri","jatkaa","mgar","balaq", "karazet", "geeri")]...", forced = "[type]") + + var/list/turf_list = list() + turf_loop: + for(var/turf/open/turf in RANGE_TURFS(10, mob_turf)) + if(!prob(2 * multiplier)) + continue + if(istype(get_area(turf), /area/chapel/main)) + continue + for(var/atom/movable/atom_content as anything in turf.contents) + if(isobj(atom_content) && atom_content.density) + continue turf_loop + turf_list += turf + for(var/turf/open/turf as anything in turf_list) + addtimer(CALLBACK(src, PROC_REF(start_rune_fade_in), mob, turf), rand(0, 5 SECONDS)) + +/datum/symptom/acute/cult_hallucination/proc/start_rune_fade_in(mob/living/victim, turf/turf) + if(QDELETED(victim) || !victim.client || QDELETED(src)) + return + var/client/victim_client = victim.client + var/runenum = rand(1, 2) + var/image/rune_holder = image('aquila/icons/virology/deityrunes.dmi', turf, "") + var/image/rune_render = image('aquila/icons/virology/deityrunes.dmi', turf, "fullrune-[runenum]") + rune_render.color = LIGHT_COLOR_BLOOD_MAGIC + + victim_client.images += rune_holder + + addtimer(CALLBACK(src, PROC_REF(finish_rune_fade_in), victim, rune_holder, rune_render), 3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(rune_fade_out), victim, rune_holder, rune_render), rand(20 SECONDS, 40 SECONDS)) + +/datum/symptom/acute/cult_hallucination/proc/finish_rune_fade_in(mob/living/victim, image/rune_holder, image/rune_render) + rune_holder.overlays += rune_render + AnimateFakeRune(rune_holder) + +/datum/symptom/acute/cult_hallucination/proc/rune_fade_out(mob/living/victim, image/rune_holder, image/rune_render) + var/client/victim_client = victim?.client + if(!victim_client) + return + rune_holder.overlays -= rune_render + addtimer(CALLBACK(src, PROC_REF(finish_rune_fade_out), victim, rune_holder), 1.2 SECONDS) + +/datum/symptom/acute/cult_hallucination/proc/finish_rune_fade_out(mob/living/victim, image/rune_holder) + var/client/victim_client = victim?.client + if(victim_client) + victim_client.images -= rune_holder + +/datum/symptom/acute/cult_hallucination/proc/AnimateFakeRune(image/rune) + animate(rune, color = list(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0), time = 10, loop = -1)//1 + animate(color = list(1.125,0.06,0,0,0,1.125,0.06,0,0.06,0,1.125,0,0,0,0,1,0,0,0,0), time = 2)//2 + animate(color = list(1.25,0.12,0,0,0,1.25,0.12,0,0.12,0,1.25,0,0,0,0,1,0,0,0,0), time = 2)//3 + animate(color = list(1.375,0.19,0,0,0,1.375,0.19,0,0.19,0,1.375,0,0,0,0,1,0,0,0,0), time = 1.5)//4 + animate(color = list(1.5,0.27,0,0,0,1.5,0.27,0,0.27,0,1.5,0,0,0,0,1,0,0,0,0), time = 1.5)//5 + animate(color = list(1.625,0.35,0.06,0,0.06,1.625,0.35,0,0.35,0.06,1.625,0,0,0,0,1,0,0,0,0), time = 1)//6 + animate(color = list(1.75,0.45,0.12,0,0.12,1.75,0.45,0,0.45,0.12,1.75,0,0,0,0,1,0,0,0,0), time = 1)//7 + animate(color = list(1.875,0.56,0.19,0,0.19,1.875,0.56,0,0.56,0.19,1.875,0,0,0,0,1,0,0,0,0), time = 1)//8 + animate(color = list(2,0.67,0.27,0,0.27,2,0.67,0,0.67,0.27,2,0,0,0,0,1,0,0,0,0), time = 5)//9 + animate(color = list(1.875,0.56,0.19,0,0.19,1.875,0.56,0,0.56,0.19,1.875,0,0,0,0,1,0,0,0,0), time = 1)//8 + animate(color = list(1.75,0.45,0.12,0,0.12,1.75,0.45,0,0.45,0.12,1.75,0,0,0,0,1,0,0,0,0), time = 1)//7 + animate(color = list(1.625,0.35,0.06,0,0.06,1.625,0.35,0,0.35,0.06,1.625,0,0,0,0,1,0,0,0,0), time = 1)//6 + animate(color = list(1.5,0.27,0,0,0,1.5,0.27,0,0.27,0,1.5,0,0,0,0,1,0,0,0,0), time = 1)//5 + animate(color = list(1.375,0.19,0,0,0,1.375,0.19,0,0.19,0,1.375,0,0,0,0,1,0,0,0,0), time = 1)//4 + animate(color = list(1.25,0.12,0,0,0,1.25,0.12,0,0.12,0,1.25,0,0,0,0,1,0,0,0,0), time = 1)//3 + animate(color = list(1.125,0.06,0,0,0,1.125,0.06,0,0.06,0,1.125,0,0,0,0,1,0,0,0,0), time = 1)//2 diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/cyborg_vomit.dm b/aquila/code/modules/virology/disease/symtoms/annoying/cyborg_vomit.dm new file mode 100644 index 00000000000..d5878f6dd80 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/cyborg_vomit.dm @@ -0,0 +1,33 @@ +/datum/symptom/acute/cyborg_vomit + name = "Oleum Syndrome" + desc = "Causes the infected to internally synthesize oil and other inorganic material." + stage = 2 + badness = EFFECT_DANGER_ANNOYING + severity = 3 + +/datum/symptom/acute/cyborg_vomit/activate(mob/living/mob) + if(HAS_TRAIT(mob, TRAIT_NOHUNGER) || !mob.has_mouth()) + return + if(prob(90)) //90% chance for just oil + mob.visible_message(span_danger("[mob.name] vomits up some oil!")) + mob.adjustToxLoss(-3) + var/obj/effect/decal/cleanable/oil/oil = new /obj/effect/decal/cleanable/oil(get_turf(mob)) + playsound(oil, 'sound/effects/splat.ogg', 50, 1) + mob.Stun(0.5 SECONDS) + else //10% chance for a random bot! + to_chat(mob, span_danger("You feel like something's about to burst out of you!")) + sleep(100) + var/list/possible_bots = list( + /mob/living/simple_animal/bot/medbot, + /mob/living/simple_animal/bot/secbot, + /mob/living/simple_animal/bot/floorbot, + /mob/living/simple_animal/bot/buttbot + ) + var/chosen_bot = pick(possible_bots) + var/mob/living/newbot = new chosen_bot(get_turf(mob)) + new /obj/effect/decal/cleanable/blood(get_turf(mob)) + mob.visible_message("A [newbot.name] bursts out of [mob.name]'s mouth!") + playsound(newbot, 'sound/effects/splat.ogg', 50, 1) + mob.emote("scream") + mob.adjustBruteLoss(15) + mob.Stun(1 SECONDS) diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/dizzy.dm b/aquila/code/modules/virology/disease/symtoms/annoying/dizzy.dm new file mode 100644 index 00000000000..f5cee697319 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/dizzy.dm @@ -0,0 +1,36 @@ +/**Dizziness + * Increases stealth + * Lowers resistance + * Decreases stage speed considerably + * Slightly reduces transmissibility + * Intense Level + * Bonus: Shakes the affected mob's screen for short periods. + */ + +/datum/symptom/acute/dizzy // Not the egg + + name = "Dizziness" + desc = "The virus causes inflammation of the vestibular system, leading to bouts of dizziness." + illness = "Motion Sickness" + level = 4 + severity = 2 + badness = EFFECT_DANGER_ANNOYING + base_message_chance = 50 + max_multiplier = 3 + + +/datum/symptom/acute/dizzy/Start(datum/disease/acute/A) + . = ..() + if(!.) + return + +/datum/symptom/acute/dizzy/activate(mob/living/carbon/mob, datum/disease/acute/disease) + switch(round(multiplier)) + if(1) + if(prob(base_message_chance) && !suppress_warning) + to_chat(mob, span_warning("[pick("You feel dizzy.", "Your head spins.")]")) + if(2 to 3) + to_chat(mob, span_userdanger("A wave of dizziness washes over you!")) + mob.Dizzy(1 MINUTES) + if(round(multiplier) == 3) + mob.set_drugginess(80 SECONDS) diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/eldritch_hallucinations.dm b/aquila/code/modules/virology/disease/symtoms/annoying/eldritch_hallucinations.dm new file mode 100644 index 00000000000..1572fa1aae6 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/eldritch_hallucinations.dm @@ -0,0 +1,38 @@ +/datum/symptom/acute/wendigo_hallucination + name = "Eldritch Mind Syndrome" + desc = "UNKNOWN" + badness = EFFECT_DANGER_ANNOYING + severity = 2 + stage = 3 + + +/datum/symptom/acute/wendigo_hallucination/first_activate(mob/living/carbon/mob) + RegisterSignal(mob, COMSIG_MOB_SAY, PROC_REF(handle_speech)) + +/datum/symptom/acute/wendigo_hallucination/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + UnregisterSignal(mob, COMSIG_MOB_SAY) + +/datum/symptom/acute/wendigo_hallucination/activate(mob/living/carbon/mob) + if(!ishuman(mob)) + return + var/mob/living/carbon/human/H = mob + H.Jitter(10 SECONDS) + + //creepy sounds copypasted from hallucination code + var/list/possible_sounds = list( + 'aquila/sound/virology/ghost.ogg', 'aquila/sound/virology/ghost2.ogg', 'aquila/sound/virology/heart_beat_single.ogg', 'aquila/sound/virology/ear_ring_single.ogg', 'aquila/sound/virology/screech.ogg',\ + 'aquila/sound/virology/behind_you1.ogg', 'aquila/sound/virology/behind_you2.ogg', 'aquila/sound/virology/far_noise.ogg', 'aquila/sound/virology/growl1.ogg', 'aquila/sound/virology/growl2.ogg',\ + 'aquila/sound/virology/growl3.ogg', 'aquila/sound/virology/im_here1.ogg', 'aquila/sound/virology/im_here2.ogg', 'aquila/sound/virology/i_see_you1.ogg', 'aquila/sound/virology/i_see_you2.ogg',\ + 'aquila/sound/virology/look_up1.ogg', 'aquila/sound/virology/look_up2.ogg', 'aquila/sound/virology/over_here1.ogg', 'aquila/sound/virology/over_here2.ogg', 'aquila/sound/virology/over_here3.ogg',\ + 'aquila/sound/virology/turn_around1.ogg', 'aquila/sound/virology/turn_around2.ogg', 'aquila/sound/virology/veryfar_noise.ogg', 'aquila/sound/virology/wail.ogg') + mob.playsound_local(mob.loc, pick(possible_sounds)) + + + +/datum/symptom/acute/wendigo_hallucination/proc/handle_speech(datum/source, list/speech_args) + SIGNAL_HANDLER + + var/message = speech_args[SPEECH_MESSAGE] + message = replacetext(message,"I","we") + message = replacetext(message,"me","us") + speech_args[SPEECH_MESSAGE] = message diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/fullness_syndrome.dm b/aquila/code/modules/virology/disease/symtoms/annoying/fullness_syndrome.dm new file mode 100644 index 00000000000..5ef3ca90a1c --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/fullness_syndrome.dm @@ -0,0 +1,21 @@ +/datum/symptom/acute/wendigo_warning + name = "Fullness Syndrome" + desc = "An unsual symptom that causes the infected to feel hungry, even after eating." + stage = 1 + badness = EFFECT_DANGER_ANNOYING + severity = 3 + var/list/host_messages = list( + "Your stomach grumbles.", + "You feel peckish.", + "So hungry...", + "Your stomach feels empty.", + "Hunger...", + "Who are we...?", + "Our mind hurts...", + "You feel... different...", + "There's something wrong." + ) + +/datum/symptom/acute/wendigo_warning/activate(mob/living/mob) + to_chat(mob, span_warning("[pick(host_messages)]")) + mob.adjust_nutrition(-10) diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/headache.dm b/aquila/code/modules/virology/disease/symtoms/annoying/headache.dm new file mode 100644 index 00000000000..8b3e5a59715 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/headache.dm @@ -0,0 +1,34 @@ +/*Headache + * Slightly reduces stealth + * Increases resistance tremendously + * Increases stage speed + * No change to transmissibility + * Low level + * Bonus: Displays an annoying message! Should be used for buffing your disease. +*/ +/datum/symptom/acute/headache + name = "Headache" + desc = "The virus causes inflammation inside the brain, causing constant headaches." + illness = "Brain Freeze" + severity = 1 + max_multiplier = 2 + badness = EFFECT_DANGER_ANNOYING + base_message_chance = 100 + max_chance = 20 + /// Cooldown between stun headaches effects + COOLDOWN_DECLARE(effect_cooldown) + +/datum/symptom/acute/headache/activate(mob/living/carbon/mob) + // AQ: nie mamy cechy TRAIT_SOOTHED_HEADACHE (ibuprofen w tg), więc nic nie wycisza bólu głowy + if(round(multiplier) == 2 & prob(50)) + if(prob(50) & COOLDOWN_FINISHED(src, effect_cooldown)) + to_chat(mob, span_userdanger("[pick("Your head hurts!", "You feel a burning knife inside your brain!", "A wave of pain fills your head!")]")) + mob.Stun(35) + COOLDOWN_START(src, effect_cooldown, rand(10 SECONDS, 30 SECONDS)) + else + to_chat(mob, span_warning("[pick("Your head hurts a lot.", "Your head pounds incessantly.")]")) + mob.adjustStaminaLoss(25) + else + if(prob(base_message_chance)) + to_chat(mob, span_warning("[pick("Your head hurts.", "Your head pounds.")]")) + diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/itching.dm b/aquila/code/modules/virology/disease/symtoms/annoying/itching.dm new file mode 100644 index 00000000000..3e6a9ce47c7 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/itching.dm @@ -0,0 +1,30 @@ +/datum/symptom/acute/itching + name = "Itching" + desc = "Makes you Itch!" + stage = 1 + badness = EFFECT_DANGER_ANNOYING + severity = 2 + var/scratch = FALSE + ///emote cooldowns + COOLDOWN_DECLARE(itching_cooldown) + ///if FALSE, there is a percentage chance that the mob will emote scratching while itching_cooldown is on cooldown. If TRUE, won't emote again until after the off cooldown scratch occurs. + var/off_cooldown_scratched = FALSE + +/datum/symptom/acute/itching/activate(mob/living/mob) + if(!iscarbon(mob)) + return + var/mob/living/carbon/affected_mob = mob + var/obj/item/bodypart/bodypart = affected_mob.get_bodypart(ran_zone()) + // AQ: nie mamy BODYPART_PSEUDOPART, zostaje sam test organicznej kończyny + if(bodypart && IS_ORGANIC_LIMB(bodypart)) + var/can_scratch = scratch && !affected_mob.incapacitated() + if(can_scratch) + bodypart.receive_damage(0.5) + //below handles emotes, limiting the emote of emotes passed to chat + if(COOLDOWN_FINISHED(src, itching_cooldown) || !COOLDOWN_FINISHED(src, itching_cooldown) && prob(60) && !off_cooldown_scratched) + affected_mob.visible_message("[can_scratch ? span_warning("[affected_mob] scratches [affected_mob.p_their()] [parse_zone(bodypart.body_zone)].") : ""]", span_warning("Your [parse_zone(bodypart.body_zone)] itches. [can_scratch ? " You scratch it." : ""]")) + COOLDOWN_START(src, itching_cooldown, 5 SECONDS) + if(!off_cooldown_scratched && !COOLDOWN_FINISHED(src, itching_cooldown)) + off_cooldown_scratched = TRUE + else + off_cooldown_scratched = FALSE diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/sneeze.dm b/aquila/code/modules/virology/disease/symtoms/annoying/sneeze.dm new file mode 100644 index 00000000000..03900a52c2f --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/sneeze.dm @@ -0,0 +1,23 @@ +/datum/symptom/acute/sneeze + name = "Coldingtons Effect" + desc = "Makes the infected sneeze every so often, leaving some infected mucus on the floor." + stage = 1 + badness = EFFECT_DANGER_ANNOYING + severity = 2 + +/datum/symptom/acute/sneeze/activate(mob/living/mob) + mob.emote("sneeze") + if(!ishuman(mob)) + return + var/mob/living/carbon/human/host = mob + if (prob(50) && isturf(mob.loc)) + if(istype(host.wear_mask, /obj/item/clothing/mask/cigarette)) + var/obj/item/clothing/mask/cigarette/ciggie = host.get_item_by_slot(ITEM_SLOT_MASK) + if(prob(20)) + var/turf/startLocation = get_turf(mob) + var/turf/endLocation + var/spitForce = pick(0,1,2,3) + endLocation = get_ranged_target_turf(startLocation, mob.dir, spitForce) + to_chat(mob, "You sneezed \the [host.wear_mask] out of your mouth!") + host.dropItemToGround(ciggie) + ciggie.throw_at(endLocation,spitForce,1) diff --git a/aquila/code/modules/virology/disease/symtoms/annoying/vitreous.dm b/aquila/code/modules/virology/disease/symtoms/annoying/vitreous.dm new file mode 100644 index 00000000000..3daa4a6d415 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/annoying/vitreous.dm @@ -0,0 +1,39 @@ +/datum/symptom/acute/vitreous + name = "Vitreous resonance" + desc = "Causes the infected to shake uncontrollably, at the same frequency that is required to break glass." + stage = 2 + chance = 25 + max_chance = 75 + max_multiplier = 2 + badness = EFFECT_DANGER_ANNOYING + severity = 2 + +/datum/symptom/acute/vitreous/activate(mob/living/carbon/human/victim) + victim.Shake(3, 3, 3 SECONDS) + if(ishuman(victim)) + addtimer(CALLBACK(src, PROC_REF(shatter), victim), 0.5 SECONDS) + +/datum/symptom/acute/vitreous/proc/shatter(mob/living/carbon/human/victim) + var/obj/item/reagent_containers/glass_to_shatter = victim.get_active_held_item() + var/obj/item/bodypart/check_arm = victim.get_active_hand() + if(!glass_to_shatter) + return + if (is_type_in_list(glass_to_shatter, list(/obj/item/reagent_containers/food/drinks/drinkingglass))) + to_chat(victim, span_warning("Your [check_arm] resonates with the glass in \the [glass_to_shatter], shattering it to bits!")) + glass_to_shatter.reagents?.expose(victim, TOUCH) + new/obj/effect/decal/cleanable/generic(get_turf(victim)) + playsound(victim, 'sound/effects/glassbr1.ogg', vol = 25, vary = TRUE) + addtimer(CALLBACK(src, PROC_REF(deresonate), victim, check_arm), 1 SECONDS) + qdel(glass_to_shatter) + else if (prob(1)) + to_chat(victim, span_notice("Your [check_arm] aches for the cold, smooth feel of container-grade glass...")) + +/datum/symptom/acute/vitreous/proc/deresonate(mob/living/carbon/human/victim, obj/item/bodypart/arm) + if(QDELETED(src) || QDELETED(victim) || QDELETED(arm)) + return + if(prob(50 * multiplier)) + to_chat(victim, span_notice("Your [arm] deresonates, healing completely!")) + arm.heal_damage(brute = arm.max_damage, burn = arm.max_damage) // full heal + else + to_chat(victim, span_warning("Your [arm] deresonates, sustaining burns!")) + arm.receive_damage(burn = 15 * multiplier) diff --git a/aquila/code/modules/virology/disease/symtoms/deadly/alkali.dm b/aquila/code/modules/virology/disease/symtoms/deadly/alkali.dm new file mode 100644 index 00000000000..479545e3556 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/deadly/alkali.dm @@ -0,0 +1,66 @@ +/* +Alkali perspiration + Hidden. + Lowers resistance. + Decreases stage speed. + Decreases transmissibility. + Fatal Level. +Bonus + Ignites infected mob. + Explodes mob on contact with water. +*/ + +/datum/symptom/acute/alkali + + name = "Alkali perspiration" + desc = "The diserase attaches to sudoriparous glands, synthesizing a chemical that bursts into flames when reacting with water, leading to self-immolation." + illness = "Crispy Skin" + stage = 1 + severity = 6 + badness = EFFECT_DANGER_DEADLY + base_message_chance = 100 + var/chems = FALSE + var/explosion_power = 1 + max_multiplier = 2 + +/datum/symptom/acute/alkali/first_activate(mob/living/carbon/mob, datum/disease/acute/disease) + if(disease.robustness >= 60) + chems = TRUE + if(disease.infectivity >= 40) + explosion_power = 2 + +/datum/symptom/acute/alkali/activate(mob/living/carbon/mob, datum/disease/acute/disease) + switch(disease.stage) + if(1 to 2) + if(prob(base_message_chance)) + to_chat(mob, span_warning("[pick("Your veins boil.", "You feel hot.", "You smell meat cooking.")]")) + if(3) + if(mob.fire_stacks < 0) + mob.visible_message(span_warning("[mob]'s sweat sizzles and pops on contact with water!")) + explosion(mob, devastation_range = -1, heavy_impact_range = (-1 + explosion_power), light_impact_range = (2 * explosion_power), explosion_cause = src) + Alkali_fire_stage_4(mob, disease) + mob.ignite_mob() + to_chat(mob, span_userdanger("Your sweat bursts into flames!")) + mob.emote("scream") + if(4) + if(mob.fire_stacks < 0) + mob.visible_message(span_warning("[mob]'s sweat sizzles and pops on contact with water!")) + explosion(mob, devastation_range = -1, heavy_impact_range = (-1 + explosion_power), light_impact_range = (2 * explosion_power), explosion_cause = src) + Alkali_fire_stage_5(mob, disease) + mob.ignite_mob() + to_chat(mob, span_userdanger("Your skin erupts into an inferno!")) + mob.emote("scream") + +/datum/symptom/acute/alkali/proc/Alkali_fire_stage_4(mob/living/carbon/mob, datum/disease/acute/disease) + var/get_stacks = 3 * multiplier + mob.adjust_fire_stacks(get_stacks) + mob.take_overall_damage(burn = get_stacks / 2) + if(chems) + mob.reagents?.add_reagent(/datum/reagent/clf3, 2 * multiplier) + +/datum/symptom/acute/alkali/proc/Alkali_fire_stage_5(mob/living/carbon/mob, datum/disease/acute/disease) + var/get_stacks = 5 * multiplier + mob.adjust_fire_stacks(get_stacks) + mob.take_overall_damage(burn = get_stacks) + if(chems) + mob.reagents?.add_reagent_list(list(/datum/reagent/napalm = 4 * multiplier, /datum/reagent/clf3 = 4 * multiplier)) diff --git a/aquila/code/modules/virology/disease/symtoms/deadly/asphyxiation.dm b/aquila/code/modules/virology/disease/symtoms/deadly/asphyxiation.dm new file mode 100644 index 00000000000..8c942c17c9f --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/deadly/asphyxiation.dm @@ -0,0 +1,19 @@ +/datum/symptom/acute/asphyxiation + name = "Acute respiratory distress syndrome" + desc = "The virus causes shrinking of the host's lungs, causing severe asphyxiation. May also lead to brain damage in critical patients." + badness = EFFECT_DANGER_DEADLY + severity = 5 + max_chance = 5 + max_multiplier = 5 + stage = 3 + +/datum/symptom/acute/asphyxiation/activate(mob/living/carbon/mob) + if(HAS_TRAIT(mob, TRAIT_NOBREATH)) + return + mob.emote("gasp") + if(prob(20) && multiplier >= 4 && iscarbon(mob)) + mob.reagents.add_reagent_list(list(/datum/reagent/toxin/pancuronium = 3, /datum/reagent/toxin/sodium_thiopental = 3)) + mob.adjustOxyLoss(rand(5,15) * multiplier) + if(mob.getOxyLoss() >= 120 && multiplier == 5) + mob.adjustOxyLoss(rand(5,7) * multiplier) + mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, multiplier) diff --git a/aquila/code/modules/virology/disease/symtoms/deadly/fire.dm b/aquila/code/modules/virology/disease/symtoms/deadly/fire.dm new file mode 100644 index 00000000000..767efd314ea --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/deadly/fire.dm @@ -0,0 +1,44 @@ +/**Spontaneous Combustion + * Slightly hidden. + * Lowers resistance tremendously. + * Decreases stage speed tremendously. + * Decreases transmittablity tremendously. + * Fatal level + * Bonus: Ignites infected mob. + */ + +/datum/symptom/acute/fire + name = "Spontaneous Combustion" + desc = "The virus turns fat into an extremely flammable compound, and raises the body's temperature, making the host burst into flames spontaneously." + illness = "Spontaneous Combustion" + badness = EFFECT_DANGER_DEADLY + severity = 5 + base_message_chance = 20 + stage = 1 + max_multiplier = 5 + +/datum/symptom/acute/fire/activate(mob/living/carbon/mob, datum/disease/acute/disease) + . = ..() + switch(round(multiplier)) + if(1 to 2) + return + if(3) + if(prob(base_message_chance)) + warn_mob(mob) + else + mob.adjust_fire_stacks(disease.stage * multiplier) + mob.take_overall_damage(burn = ((disease.stage) * multiplier)) + mob.ignite_mob(silent = TRUE) + if(mob.on_fire) //check to make sure they actually caught on fire, or if it was prevented cause they were wet. + mob.visible_message(span_warning("[mob] catches fire!"), ignored_mobs = mob) + to_chat(mob, span_userdanger((disease.stage ? "Your skin erupts into an inferno!" : "Your skin bursts into flames!"))) + mob.emote("scream") + else + warn_mob(mob) + multiplier_tweak(0.1) + +/datum/symptom/acute/fire/proc/warn_mob(mob/living/mob) + if(prob(33.33)) + mob.audible_message(self_message = "You hear a crackling noise.") + else + to_chat(mob, span_warning("[pick("You feel hot.", "You smell smoke.")]")) diff --git a/aquila/code/modules/virology/disease/symtoms/deadly/flesh_death.dm b/aquila/code/modules/virology/disease/symtoms/deadly/flesh_death.dm new file mode 100644 index 00000000000..2b6d819c5c1 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/deadly/flesh_death.dm @@ -0,0 +1,48 @@ +/* +////////////////////////////////////// + +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/acute/flesh_death + name = "Autophagocytosis Necrosis" + desc = "The virus rapidly consumes infected cells, leading to heavy and widespread damage." + illness = "Premature Mummification" + severity = 6 + max_multiplier = 2 + base_message_chance = 50 + max_chance = 20 + badness = EFFECT_DANGER_DEADLY + var/zombie = FALSE + +/datum/symptom/acute/flesh_death/activate(mob/living/carbon/mob, datum/disease/acute/disease) + if(HAS_TRAIT(mob, TRAIT_NO_ZOMBIFY)) //These are the zombie symtomps. Best not to kill the zombies with them + return + switch(disease.stage) + if(1,2) + if(prob(base_message_chance)) + to_chat(mob, span_warning("[pick("You feel your body break apart.", "Your skin rubs off like dust.")]")) + if(3,4) + if(prob(base_message_chance / 2)) //reduce spam + to_chat(mob, span_userdanger("[pick("You feel your muscles weakening.", "Some of your skin detaches itself.", "You feel sandy.")]")) + Flesh_death(mob, disease) + +/datum/symptom/acute/flesh_death/proc/Flesh_death(mob/living/mob, datum/disease/acute/disease) + var/get_damage = rand(6,10) + mob.take_overall_damage(brute = get_damage) + if(round(multiplier) == 2 && mob.reagents) + mob.reagents.add_reagent_list(list(/datum/reagent/toxin/heparin = 2, /datum/reagent/toxin/lipolicide = 2)) + if(zombie) + mob.reagents.add_reagent(/datum/reagent/romerol, 1) + return 1 diff --git a/aquila/code/modules/virology/disease/symtoms/deadly/flesh_eating.dm b/aquila/code/modules/virology/disease/symtoms/deadly/flesh_eating.dm new file mode 100644 index 00000000000..7c2b496df63 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/deadly/flesh_eating.dm @@ -0,0 +1,42 @@ +/* +Necrotizing Fasciitis (AKA Flesh-Eating Disease) + Very very noticable. + Lowers resistance tremendously. + No changes to stage speed. + Decreases transmissibility tremendously. + Fatal Level. +Bonus + Deals brute damage over time. +*/ +/datum/symptom/acute/flesh_eating + name = "Necrotizing Fasciitis" + desc = "The virus aggressively attacks bone cells, causing excessive wobbliness and falling down a lot." + illness = "Jellyitis" + severity = 5 + badness = EFFECT_DANGER_DEADLY + stage = 3 + max_multiplier = 2 + base_message_chance = 50 + symptom_delay_min = 15 + symptom_delay_max = 60 + +/datum/symptom/acute/flesh_eating/activate(mob/living/carbon/mob, datum/disease/acute/disease) + if(HAS_TRAIT(mob, TRAIT_NO_ZOMBIFY)) //These are the zombie symtomps. Best not to kill the zombies with them + return + switch(disease.stage) + if(2) + if(prob(base_message_chance)) + to_chat(mob, span_warning("[pick("You feel a sudden pain across your body.", "Drops of blood appear suddenly on your skin.")]")) + if(3, 4) + to_chat(mob, span_userdanger("[pick("You cringe as a violent pain takes over your body.", "It feels like your body is eating itself inside out.", "IT HURTS.")]")) + Flesheat(mob, disease) + +/datum/symptom/acute/flesh_eating/proc/Flesheat(mob/living/mob, datum/disease/acute/disease) + var/get_damage = rand(15,25) * multiplier + mob.take_overall_damage(brute = get_damage) + if(round(multiplier) == 2) + mob.adjustStaminaLoss(get_damage) + var/mob/living/carbon/human/H = mob + var/obj/item/bodypart/random_part = pick(H.bodyparts) + random_part.owner?.bleed(5 * multiplier) + return 1 diff --git a/aquila/code/modules/virology/disease/symtoms/deadly/killer_dna.dm b/aquila/code/modules/virology/disease/symtoms/deadly/killer_dna.dm new file mode 100644 index 00000000000..da13f62b9f7 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/deadly/killer_dna.dm @@ -0,0 +1,14 @@ +/datum/symptom/acute/dna + name = "Reverse Pattern Syndrome" + desc = "Attacks the infected's DNA, causing rapid spontaneous mutation, and inhibits the ability for the infected to be affected by cryogenics." + stage = 4 + badness = EFFECT_DANGER_DEADLY + severity = 5 + +/datum/symptom/acute/dna/activate(mob/living/carbon/mob) + if(!iscarbon(mob)) + return + mob.bodytemperature = max(mob.bodytemperature, 350) + scramble_dna(mob, TRUE, TRUE, TRUE, rand(15,45)) + if(mob.toxloss <= 50) + mob.adjustToxLoss(10) diff --git a/aquila/code/modules/virology/disease/symtoms/deadly/killer_toxins.dm b/aquila/code/modules/virology/disease/symtoms/deadly/killer_toxins.dm new file mode 100644 index 00000000000..a0e0728f91f --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/deadly/killer_toxins.dm @@ -0,0 +1,12 @@ + +/datum/symptom/acute/killertoxins + name = "Toxification Syndrome" + desc = "A more advanced version of Hyperacidity, causing the infected to rapidly generate toxins." + stage = 4 + badness = EFFECT_DANGER_DEADLY + severity = 5 + multiplier = 3 + max_multiplier = 5 + +/datum/symptom/acute/killertoxins/activate(mob/living/carbon/mob) + mob.adjustToxLoss(5 * multiplier) diff --git a/aquila/code/modules/virology/disease/symtoms/deadly/rhumba_beat.dm b/aquila/code/modules/virology/disease/symtoms/deadly/rhumba_beat.dm new file mode 100644 index 00000000000..904bff07be0 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/deadly/rhumba_beat.dm @@ -0,0 +1,41 @@ + +/datum/symptom/acute/rhumba_beat + name = "The Rhumba Beat" + desc = "Chick Chicky Boom!" + max_multiplier = 5 + stage = 4 + badness = EFFECT_DANGER_DEADLY + severity = 5 + +/datum/symptom/acute/rhumba_beat/activate(mob/living/carbon/affected_mob) + if(ismouse(affected_mob)) + affected_mob.gib() + return + multiplier_tweak(0.1) + + switch(round(multiplier)) + if(2) + if(prob(26)) + affected_mob.take_overall_damage(burn = 5) + if(prob(0.5)) + to_chat(affected_mob, span_danger("You feel strange...")) + if(3) + if(prob(2.5)) + to_chat(affected_mob, span_danger("You feel the urge to dance...")) + else if(prob(2.5)) + affected_mob.emote("gasp") + else if(prob(5)) + to_chat(affected_mob, span_danger("You feel the need to chick chicky boom...")) + if(4) + if(prob(10)) + if(prob(50)) + affected_mob.adjust_fire_stacks(2) + affected_mob.ignite_mob() + else + affected_mob.emote("gasp") + to_chat(affected_mob, span_danger("You feel a burning beat inside...")) + if(5) + to_chat(affected_mob, span_danger("Your body is unable to contain the Rhumba Beat...")) + if(prob(29)) + explosion(affected_mob, devastation_range = -1, light_impact_range = 2, flame_range = 2, flash_range = 3, adminlog = FALSE, explosion_cause = src) // This is equivalent to a lvl 1 fireball + multiplier -= 3 diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/beard.dm b/aquila/code/modules/virology/disease/symtoms/flavor/beard.dm new file mode 100644 index 00000000000..8b833869f30 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/beard.dm @@ -0,0 +1,27 @@ +/datum/symptom/acute/beard + name = "Facial Hypertrichosis" + desc = "Causes the infected to spontaneously grow a beard, regardless of gender. Only affects humans." + stage = 2 + max_multiplier = 5 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/beard/activate(mob/living/mob) + if(ishuman(mob)) + addtimer(CALLBACK(src, PROC_REF(give_beard), mob), 5 SECONDS, TIMER_UNIQUE) + +/datum/symptom/acute/beard/proc/give_beard(mob/living/carbon/human/victim) + if(QDELETED(src) || QDELETED(victim)) + return + var/beard_name = "" + if(multiplier >= 1 && multiplier < 2) + beard_name = "Beard (Jensen)" + if(multiplier >= 2 && multiplier < 3) + beard_name = "Beard (Full)" + if(multiplier >= 3 && multiplier < 4) + beard_name = "Beard (Very Long)" + if(multiplier >= 4) + beard_name = "Beard (Dwarf)" + if(beard_name != "" && victim.facial_hair_style != beard_name) + victim.facial_hair_style = beard_name + to_chat(victim, span_warning("Your chin itches.")) diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/disfigured.dm b/aquila/code/modules/virology/disease/symtoms/flavor/disfigured.dm new file mode 100644 index 00000000000..a4d5d852fb7 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/disfigured.dm @@ -0,0 +1,14 @@ +/datum/symptom/acute/disfiguration + name = "Disfiguration" + desc = "The virus liquefies facial muscles, disfiguring the host." + max_count = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + stage = 2 + +/datum/symptom/acute/disfiguration/activate(mob/living/carbon/mob) + ADD_TRAIT(mob, TRAIT_DISFIGURED, type) + mob.visible_message(span_warning("[mob]'s face appears to cave in!"), span_notice("You feel your face crumple and cave in!")) + +/datum/symptom/acute/disfiguration/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + REMOVE_TRAIT(mob, TRAIT_DISFIGURED, type) diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/drained.dm b/aquila/code/modules/virology/disease/symtoms/flavor/drained.dm new file mode 100644 index 00000000000..6d59be9996f --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/drained.dm @@ -0,0 +1,9 @@ +/datum/symptom/acute/drained + name = "Drained Feeling" + desc = "Gives the infected a drained sensation." + stage = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/drained/activate(mob/living/mob) + to_chat(mob, span_warning("You feel drained.")) diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/drolling.dm b/aquila/code/modules/virology/disease/symtoms/flavor/drolling.dm new file mode 100644 index 00000000000..f68248f126a --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/drolling.dm @@ -0,0 +1,10 @@ +/datum/symptom/acute/drool + name = "Saliva Effect" + desc = "Causes the infected to drool." + stage = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/drool/activate(mob/living/mob) + mob.emote("drool") + diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/eyewater.dm b/aquila/code/modules/virology/disease/symtoms/flavor/eyewater.dm new file mode 100644 index 00000000000..bdd3c35da9d --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/eyewater.dm @@ -0,0 +1,10 @@ +/datum/symptom/acute/eyewater + name = "Watery Eyes" + desc = "Causes the infected's tear ducts to overact." + stage = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/eyewater/activate(mob/living/mob) + to_chat(mob, span_warning("Your eyes sting and water!")) + mob.emote("cry") diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/fizzle.dm b/aquila/code/modules/virology/disease/symtoms/flavor/fizzle.dm new file mode 100644 index 00000000000..b2d7db7b87f --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/fizzle.dm @@ -0,0 +1,9 @@ +/datum/symptom/acute/fizzle + name = "Fizzle Effect" + desc = "Causes an ill, though harmless, sensation in the infected's throat." + stage = 4 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/fizzle/activate(mob/living/carbon/mob) + mob.emote("me", 1, pick("sniffles...", "clears their throat...")) diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/fridge.dm b/aquila/code/modules/virology/disease/symtoms/flavor/fridge.dm new file mode 100644 index 00000000000..814a7b09257 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/fridge.dm @@ -0,0 +1,21 @@ +/datum/symptom/acute/fridge + name = "Refridgerator Syndrome" + desc = "Causes the infected to shiver at random." + encyclopedia = "No matter whether the room is cold or hot. This has no effect on their body temperature." + stage = 2 + max_multiplier = 4 + multiplier = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/fridge/activate(mob/living/mob) + to_chat(mob, span_warning("[pick("You feel cold.", "You shiver.")]")) + mob.emote("shiver") + set_body_temp(mob) + +/datum/symptom/acute/fridge/proc/set_body_temp(mob/living/mob) + // AQ: nie mamy homeostazy z tg, więc dreszcze po prostu schładzają ciało + if(multiplier >= 3) // when unsafe the shivers can cause cold damage + mob.adjust_bodytemperature(-6 * power) + else + mob.adjust_bodytemperature(-6 * power, BODYTEMP_COLD_DAMAGE_LIMIT + 5) diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/giggle.dm b/aquila/code/modules/virology/disease/symtoms/flavor/giggle.dm new file mode 100644 index 00000000000..4f26cfed898 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/giggle.dm @@ -0,0 +1,9 @@ +/datum/symptom/acute/giggle + name = "Uncontrolled Laughter Effect" + desc = "Gives the infected a sense of humor." + stage = 3 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/giggle/activate(mob/living/carbon/mob) + mob.emote("giggle") diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/groan.dm b/aquila/code/modules/virology/disease/symtoms/flavor/groan.dm new file mode 100644 index 00000000000..a5bdf6dbeab --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/groan.dm @@ -0,0 +1,9 @@ +/datum/symptom/acute/groan + name = "Groaning Syndrome" + desc = "Causes the infected to groan randomly." + stage = 3 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/groan/activate(mob/living/carbon/mob) + mob.emote("groan") diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/gunk.dm b/aquila/code/modules/virology/disease/symtoms/flavor/gunk.dm new file mode 100644 index 00000000000..3f2557c1a2d --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/gunk.dm @@ -0,0 +1,9 @@ +/datum/symptom/acute/gunck + name = "Flemmingtons" + desc = "Causes a sensation of mucous running down the infected's throat." + stage = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/gunck/activate(mob/living/mob) + to_chat(mob, span_notice("Mucus runs down the back of your throat")) diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/hair_growth.dm b/aquila/code/modules/virology/disease/symtoms/flavor/hair_growth.dm new file mode 100644 index 00000000000..b87ad018cf0 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/hair_growth.dm @@ -0,0 +1,21 @@ +/datum/symptom/acute/hair + name = "Hair Loss" + desc = "Causes rapid hairloss in the infected." + stage = 2 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + multiplier = 1 + max_multiplier = 5 + +/datum/symptom/acute/hair/activate(mob/living/mob) + if(ishuman(mob)) + var/mob/living/carbon/human/victim = mob + if(victim.hair_style != "Bald") + if (victim.hair_style != "Balding Hair") + to_chat(victim, span_danger("Your hair starts to fall out in clumps...")) + if (prob(multiplier * power * 20)) + victim.hair_style = "Balding Hair" + else + to_chat(victim, span_danger("You have almost no hair left...")) + if (prob(multiplier * power * 20)) + victim.hair_style = "Bald" diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/polyvitiligo.dm b/aquila/code/modules/virology/disease/symtoms/flavor/polyvitiligo.dm new file mode 100644 index 00000000000..0e4dbbeef8d --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/polyvitiligo.dm @@ -0,0 +1,20 @@ +/datum/symptom/acute/polyvitiligo + name = "Chroma Imbalance" + desc = "The virus replaces the melanin in the skin with reactive pigment." + stage = 3 + max_multiplier = 6 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/polyvitiligo/activate(mob/living/carbon/mob) + if(!iscarbon(mob) || !mob.reagents) + return + switch(round(multiplier, 1)) + if(5) + var/static/list/banned_reagents = list(/datum/reagent/colorful_reagent/powder/invisible, /datum/reagent/colorful_reagent/powder/white) + var/color = pick(subtypesof(/datum/reagent/colorful_reagent/powder) - banned_reagents) + if(mob.reagents.total_volume <= (mob.reagents.maximum_volume/10)) // no flooding humans with 1000 units of colorful reagent + mob.reagents.add_reagent(color, 5 * power * multiplier) + else + if (prob(50)) // spam + mob.visible_message(span_warning("[mob] looks rather vibrant..."), span_notice("The colors, man, the colors...")) diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/soreness.dm b/aquila/code/modules/virology/disease/symtoms/flavor/soreness.dm new file mode 100644 index 00000000000..2129604270a --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/soreness.dm @@ -0,0 +1,14 @@ +/datum/symptom/acute/soreness + name = "Myalgia Syndrome" + desc = "Makes the infected more perceptive of their aches and pains." + stage = 1 + chance = 5 + max_chance = 30 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/soreness/activate(mob/living/mob) + to_chat(mob, span_notice("You feel a little sore.")) + if(iscarbon(mob)) + var/mob/living/carbon/host = mob + host.adjustStaminaLoss(5 * power) diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/special_adaptation.dm b/aquila/code/modules/virology/disease/symtoms/flavor/special_adaptation.dm new file mode 100644 index 00000000000..51cad41f5a4 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/special_adaptation.dm @@ -0,0 +1,28 @@ +/datum/symptom/acute/adaptation + name = "Inorganic Biology" + desc = "The virus can survive and replicate even in an inorganic environment, increasing its resistance and infection rate." + max_count = 1 + stage = 4 + badness = EFFECT_DANGER_FLAVOR + severity = 0 + var/list/biotypes = list(MOB_ROBOTIC) + +/datum/symptom/acute/adaptation/OnAdd(datum/disease/acute/disease) + disease.infectable_biotypes |= biotypes + +/datum/symptom/acute/adaptation/OnRemove(datum/disease/acute/disease) + disease.infectable_biotypes &= ~(biotypes) + +/datum/symptom/acute/adaptation/undead + name = "Necrotic Metabolism" + desc = "The virus is able to thrive and act even within dead hosts." + biotypes = MOB_UNDEAD + severity = 1 + +/datum/symptom/acute/adaptation/undead/OnAdd(datum/disease/acute/disease) + . = ..() + disease.process_dead = TRUE + +/datum/symptom/acute/adaptation/undead/OnRemove(datum/disease/acute/disease) + disease.process_dead = FALSE + return ..() diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/telepathy.dm b/aquila/code/modules/virology/disease/symtoms/flavor/telepathy.dm new file mode 100644 index 00000000000..64830f132f1 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/telepathy.dm @@ -0,0 +1,32 @@ +GLOBAL_LIST_INIT(disease_hivemind_users, list()) +/datum/symptom/acute/telepathic + name = "Abductor Syndrome" + desc = "Repurposes a portion of the users brain, making them incapable of normal speech but allows you to talk into a hivemind." + stage = 3 + max_count = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/telepathic/first_activate(mob/living/carbon/mob) + GLOB.disease_hivemind_users |= mob + RegisterSignal(mob, COMSIG_MOB_SAY, PROC_REF(handle_speech)) + +/datum/symptom/acute/telepathic/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + GLOB.disease_hivemind_users -= mob + UnregisterSignal(mob, COMSIG_MOB_SAY) + +/datum/symptom/acute/telepathic/proc/handle_speech(datum/source, list/speech_args) + SIGNAL_HANDLER + var/message = speech_args[SPEECH_MESSAGE] + var/mob/living/carbon/human/mob = source + mob.log_talk(message, LOG_SAY, tag="HIVEMIND DISEASE") + for(var/mob/living/living as anything in GLOB.disease_hivemind_users) + if(!isliving(living)) + continue + to_chat(living, span_abductor("[mob.real_name]:[message]")) + + for(var/mob/dead_mob in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(dead_mob, mob) + to_chat(dead_mob, "[mob.real_name][link]:[message]") + + speech_args[SPEECH_MESSAGE] = "" //yep we dont speak anymore diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/twitch.dm b/aquila/code/modules/virology/disease/symtoms/flavor/twitch.dm new file mode 100644 index 00000000000..5a9ab638ed1 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/twitch.dm @@ -0,0 +1,10 @@ + +/datum/symptom/acute/twitch + name = "Twitcher" + desc = "Causes the infected to twitch." + stage = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/twitch/activate(mob/living/mob) + mob.emote("twitch") diff --git a/aquila/code/modules/virology/disease/symtoms/flavor/wheeze.dm b/aquila/code/modules/virology/disease/symtoms/flavor/wheeze.dm new file mode 100644 index 00000000000..c7bc7718444 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/flavor/wheeze.dm @@ -0,0 +1,9 @@ +/datum/symptom/acute/wheeze + name = "Wheezing" + desc = "Inhibits the infected's ability to breathe slightly, causing them to wheeze." + stage = 1 + badness = EFFECT_DANGER_FLAVOR + severity = 1 + +/datum/symptom/acute/wheeze/activate(mob/living/mob) + mob.emote("me",1,"wheezes.") diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/blindness.dm b/aquila/code/modules/virology/disease/symtoms/harmful/blindness.dm new file mode 100644 index 00000000000..3b866c58023 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/blindness.dm @@ -0,0 +1,42 @@ + + +/datum/symptom/acute/blindness + name = "Hyphema" + desc = "Sufferers exhibit dangerously low levels of frames per second in the eyes, leading to damage and eventually blindness." + max_multiplier = 4 + stage = 2 + badness = EFFECT_DANGER_HARMFUL + severity = 3 + +/datum/symptom/acute/blindness/activate(mob/living/carbon/mob) + if(!iscarbon(mob)) + return + + var/obj/item/organ/eyes/eyes = mob.get_organ_slot(ORGAN_SLOT_EYES) + if(!eyes) + return // can't do much + + switch(round(multiplier)) + if(1, 2) + if(prob(base_message_chance) && !suppress_warning) + to_chat(mob, span_warning("Your eyes itch.")) + + if(3, 4) + to_chat(mob, span_boldwarning("Your eyes burn!")) + mob.set_eye_blur_if_lower(10 SECONDS) + eyes.applyOrganDamage(1 * power) + + else + mob.set_eye_blur_if_lower(20 SECONDS) + eyes.applyOrganDamage(5 * power) + + // Applies nearsighted at minimum + if(!HAS_TRAIT(mob, TRAIT_NEARSIGHT) && eyes.damage <= eyes.low_threshold) + eyes.setOrganDamage(eyes.low_threshold) + + if(prob(eyes.damage - eyes.low_threshold + 1)) + if(!mob.eye_blind) + to_chat(mob, span_userdanger("You go blind!")) + eyes.applyOrganDamage(eyes.maxHealth) + else + to_chat(mob, span_userdanger("Your eyes burn horrifically!")) diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/butterfly_skin.dm b/aquila/code/modules/virology/disease/symtoms/harmful/butterfly_skin.dm new file mode 100644 index 00000000000..484754ece53 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/butterfly_skin.dm @@ -0,0 +1,41 @@ +/* +/datum/symptom/acute/butterfly_skin + name = "Epidermolysis Bullosa" + desc = "Inhibits the strength of the infected's skin, causing it to tear on contact." + stage = 3 + max_count = 1 + badness = EFFECT_DANGER_HARMFUL + severity = 3 + var/skip = FALSE + +/datum/symptom/acute/butterfly_skin/activate(mob/living/carbon/mob) + to_chat(mob, span_warning("Your skin feels a little fragile.")) + +/datum/symptom/acute/butterfly_skin/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + if(!skip) + to_chat(mob, span_notice("Your skin feels nice and durable again!")) + ..() + +/datum/symptom/acute/butterfly_skin/on_touch(mob/living/carbon/mob, toucher, touched, touch_type) + if(count && !skip) + var/obj/item/bodypart/part + if(ishuman(mob)) + var/mob/living/carbon/human/victim = mob + part = victim.get_bodypart(ran_zone()) + if(toucher == mob) + if(part) + to_chat(mob, span_warning("As you bump into \the [touched], some of the skin on your [part] shears off!")) + part.take_damage(10 * power) + else + to_chat(mob, span_warning("As you bump into \the [touched], some of your skin shears off!")) + mob.adjustBruteLoss(10 * power) + else + if(part) + to_chat(mob, span_warning("As \the [toucher] [touch_type == DISEASE_BUMP ? "bumps into" : "touches"] you, some of the skin on your [part] shears off!")) + to_chat(toucher, span_danger("As you [touch_type == DISEASE_BUMP ? "bump into" : "touch"] \the [mob], some of the skin on \his [part] shears off!")) + part.take_damage(10 * power) + else + to_chat(mob, span_warning("As \the [toucher] [touch_type == DISEASE_BUMP ? "bumps into" : "touches"] you, some of your skin shears off!")) + to_chat(toucher, span_danger("As you [touch_type == DISEASE_BUMP ? "bump into" : "touch"] \the [mob], some of \his skin shears off!")) + mob.adjustBruteLoss(10 * power) +*/ diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/dna_spread.dm b/aquila/code/modules/virology/disease/symtoms/harmful/dna_spread.dm new file mode 100644 index 00000000000..a5731eb2107 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/dna_spread.dm @@ -0,0 +1,44 @@ +/*/datum/symptom/acute/dnaspread //commented out due to causing enough problems to turn random people into monkies apon curing. + name = "Retrotransposis" + desc = "This symptom transplants the genetic code of the intial vector into new hosts." + badness = EFFECT_DANGER_HARMFUL + stage = 4 + var/datum/dna/saved_dna + var/original_name + var/activated = 0 + ///old info + var/datum/dna/old_dna + var/old_name + +/datum/symptom/acute/dnaspread/activate(mob/living/carbon/mob) + if(!activated) + to_chat(mob, span_warning("You don't feel like yourself..")) + old_dna = new + C.dna.copy_dna(old_dna) + old_name = C.real_name + + if(!iscarbon(mob)) + return + var/mob/living/carbon/C = mob + if(!saved_dna) + saved_dna = new + original_name = C.real_name + C.dna.copy_dna(saved_dna) + C.regenerate_icons() + saved_dna.copy_dna(C.dna) + C.real_name = original_name + activated = TRUE + +/datum/symptom/acute/dnaspread/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + activated = FALSE + if(!old_dna) + return + old_dna.copy_dna(C.dna) + C.real_name = old_name + +/datum/symptom/acute/dnaspread/Copy(datum/disease/acute/disease) + var/datum/symptom/acute/dnaspread/new_e = ..(disease) + new_e.original_name = original_name + new_e.saved_dna = saved_dna + return new_e +*/ diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/drunk.dm b/aquila/code/modules/virology/disease/symtoms/harmful/drunk.dm new file mode 100644 index 00000000000..48b05a9ad7d --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/drunk.dm @@ -0,0 +1,15 @@ +/datum/symptom/acute/drunk + name = "Vermouth Syndrome" + desc = "Causes the infected to synthesize pure ethanol." + stage = 2 + badness = EFFECT_DANGER_HARMFUL + severity = 2 + multiplier = 3 + max_multiplier = 7 + +/datum/symptom/acute/drunk/activate(mob/living/mob) + if(ismouse(mob) || !mob.reagents) + return + to_chat(mob, span_notice("You feel like you had one hell of a party!")) + if (mob.reagents.get_reagent_amount(/datum/reagent/consumable/ethanol/vermouth) < multiplier * 5) + mob.reagents.add_reagent(/datum/reagent/consumable/ethanol/vermouth, multiplier * power * 5) diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/lazy_mind.dm b/aquila/code/modules/virology/disease/symtoms/harmful/lazy_mind.dm new file mode 100644 index 00000000000..395eac11a4d --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/lazy_mind.dm @@ -0,0 +1,13 @@ +/datum/symptom/acute/mind + name = "Lazy Mind Syndrome" + desc = "Rots the infected's brain." + stage = 3 + badness = EFFECT_DANGER_HARMFUL + severity = 4 + +/datum/symptom/acute/mind/activate(mob/living/carbon/mob) + if(istype(mob, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = mob + H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5, 50) + else + mob.setToxLoss(50) diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/mint_toxin.dm b/aquila/code/modules/virology/disease/symtoms/harmful/mint_toxin.dm new file mode 100644 index 00000000000..4a64caa96ae --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/mint_toxin.dm @@ -0,0 +1,11 @@ +/datum/symptom/acute/minttoxin + name = "Creosote Syndrome" + desc = "Causes the infected to synthesize a wafer thin mint." + stage = 4 + badness = EFFECT_DANGER_HARMFUL + severity = 2 + +/datum/symptom/acute/minttoxin/activate(mob/living/carbon/mob) + if(istype(mob) && mob.reagents?.get_reagent_amount(/datum/reagent/toxin/mindbreaker) < 5) + to_chat(mob, span_notice("You feel a minty freshness.")) + mob.reagents.add_reagent(/datum/reagent/toxin/mindbreaker, 5 * power) diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/mob_spawn_symptoms.dm b/aquila/code/modules/virology/disease/symtoms/harmful/mob_spawn_symptoms.dm new file mode 100644 index 00000000000..b955e3b5b5c --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/mob_spawn_symptoms.dm @@ -0,0 +1,48 @@ +/datum/symptom/acute/spawn + name = "Arachnogenesis Effect" + desc = "Converts the infected's stomach to begin producing creatures of the arachnid variety." + stage = 4 + max_multiplier = 7 + badness = EFFECT_DANGER_HARMFUL + severity = 2 + var/list/spawn_types= list(/mob/living/simple_animal/hostile/spider/growing/spiderling = 10) + ///what gets added based on multiplier NOT INCLUSIVE OF PREVIOUS TIERS + var/list/multipler_unlocks = list() + +/datum/symptom/acute/spawn/activate(mob/living/mob) + check_unlocks() + playsound(mob.loc, 'sound/effects/splat.ogg', 50, 1) + var/atom/spawn_type = pick_weight(spawn_types) + if(!spawn_type) + return + var/spawn_name = initial(spawn_type.name) + var/mob/living/spawned_mob = new spawn_type(get_turf(mob)) + mob.emote("me", 1, "vomits up a live [spawn_name]!") + if(multiplier < 4) + addtimer(CALLBACK(src, PROC_REF(kill_mob), spawned_mob), 1 MINUTES) + +/datum/symptom/acute/spawn/proc/check_unlocks() + spawn_types = initial(spawn_types) + var/text_multi = num2text(round(multiplier)) + + if(!(text_multi in multipler_unlocks)) + return + spawn_types += multipler_unlocks[text_multi] + +/datum/symptom/acute/spawn/proc/kill_mob(mob/living/mob) + mob.visible_message(span_warning("The [mob] falls apart!"), span_warning("You fall apart!")) + mob.death() + +/datum/symptom/acute/spawn/roach + name = "Blattogenesis Effect" + desc = "Converts the infected's stomach to begin producing creatures of the blattid variety." + stage = 4 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + spawn_types = list(/mob/living/simple_animal/cockroach = 10) + multipler_unlocks = list( + "4" = list(/mob/living/simple_animal/cockroach = 3), + "5" = list(/mob/living/simple_animal/cockroach = 4), + "6" = list(/mob/living/simple_animal/cockroach = 5, /mob/living/simple_animal/cockroach = 3), + "7" = list(/mob/living/simple_animal/cockroach = 5, /mob/living/simple_animal/cockroach = 3, /mob/living/simple_animal/cockroach = 3), + ) diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/retro_virus.dm b/aquila/code/modules/virology/disease/symtoms/harmful/retro_virus.dm new file mode 100644 index 00000000000..76d0ce42b6d --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/retro_virus.dm @@ -0,0 +1,49 @@ +/datum/symptom/acute/retrovirus + name = "Retrovirus" + desc = "A DNA-altering retrovirus that constantly scrambles the structural and unique enzymes of its host." + max_multiplier = 4 + stage = 4 + badness = EFFECT_DANGER_HARMFUL + severity = 3 + +/datum/symptom/acute/retrovirus/activate(mob/living/carbon/affected_mob) + if(!iscarbon(affected_mob)) + return + switch(multiplier) + if(1) + if(prob(4)) + to_chat(affected_mob, span_danger("Your head hurts.")) + if(prob(4.5)) + to_chat(affected_mob, span_danger("You feel a tingling sensation in your chest.")) + if(prob(4.5)) + to_chat(affected_mob, span_danger("You feel angry.")) + if(2) + if(prob(4)) + to_chat(affected_mob, span_danger("Your skin feels loose.")) + if(prob(5)) + to_chat(affected_mob, span_danger("You feel very strange.")) + if(prob(2)) + to_chat(affected_mob, span_danger("You feel a stabbing pain in your head!")) + affected_mob.Unconscious(40) + if(prob(2)) + to_chat(affected_mob, span_danger("Your stomach churns.")) + if(3) + if(prob(5)) + to_chat(affected_mob, span_danger("Your entire body vibrates.")) + if(prob(19)) + switch(rand(1,3)) + if(1) + scramble_dna(affected_mob, 1, 0, 0, rand(15,45)) + if(2) + scramble_dna(affected_mob, 0, 1, 0, rand(15,45)) + if(3) + scramble_dna(affected_mob, 0, 0, 1, rand(15,45)) + if(4) + if(prob(37)) + switch(rand(1,3)) + if(1) + scramble_dna(affected_mob, 1, 0, 0, rand(50,75)) + if(2) + scramble_dna(affected_mob, 0, 1, 0, rand(50,75)) + if(3) + scramble_dna(affected_mob, 0, 0, 1, rand(50,75)) diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/species_modification.dm b/aquila/code/modules/virology/disease/symtoms/harmful/species_modification.dm new file mode 100644 index 00000000000..8c23f7ad603 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/species_modification.dm @@ -0,0 +1,33 @@ +/* +/datum/symptom/acute/species + name = "Lizarditis" + desc = "Turns you into a Lizard." + badness = EFFECT_DANGER_HARMFUL + stage = 4 + var/datum/species/old_species + var/datum/species/new_species = /datum/species/lizard + max_count = 1 + max_chance = 24 + +/datum/symptom/acute/species/activate(mob/living/carbon/mob) + var/mob/living/carbon/human/victim = mob + if(!ishuman(victim)) + return + old_species = mob.dna.species + if(!old_species) + return + victim.set_species(new_species) + +/datum/symptom/acute/species/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + var/mob/living/carbon/human/victim = mob + if(!ishuman(victim)) + return + if(!old_species) + return + victim.set_species(old_species) + +/datum/symptom/acute/species/moth + name = "Mothification" + desc = "Turns you into a Moth." + new_species = /datum/species/moth +*/ diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/teratoma.dm b/aquila/code/modules/virology/disease/symtoms/harmful/teratoma.dm new file mode 100644 index 00000000000..f13300179c3 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/teratoma.dm @@ -0,0 +1,65 @@ +/datum/symptom/acute/teratoma + name = "Teratoma Syndrome" + desc = "Causes the infected to oversynthesize stem cells engineered towards organ generation, causing damage to the host's organs in the process. Said generated organs are expelled from the body upon completion." + stage = 3 + badness = EFFECT_DANGER_HARMFUL + severity = 3 + COOLDOWN_DECLARE(organ_cooldown) + +/datum/symptom/acute/teratoma/activate(mob/living/carbon/mob) + if(ismouse(mob)) + var/mob/living/simple_animal/mouse/mouse = mob + mouse.splat() //tumors are bad for you, tumors equal to your body in size doubley so + return + if(!iscarbon(mob) || !COOLDOWN_FINISHED(src, organ_cooldown)) + return + COOLDOWN_START(src, organ_cooldown, 2 MINUTES) + + var/list/eligible_organs = get_eligible_organs(mob) + if(!length(eligible_organs)) + return + var/organ_type = pick(eligible_organs) + var/obj/item/organ/spawned_organ = new organ_type(mob.drop_location()) + + var/damage = 0 + if(ismonkey(mob)) //monkeys are smaller and thus have less space for human-organ sized tumors + damage += 15 * power + if(mob.getBruteLoss() <= 50) + damage += 5 * power + if(damage > 0) + mob.take_overall_damage(brute = damage) + mob.visible_message( + span_warning("\A [spawned_organ] is extruded from \the [mob]'s body and falls to the ground!"), + span_warning("\A [spawned_organ] is extruded from your body and falls to the ground!") + ) + +/datum/symptom/acute/teratoma/proc/get_eligible_organs(mob/living/carbon/target) + // blacklist, mostly for jank organs that shouldn't exist on their own + var/static/list/blacklisted_organs + if(isnull(blacklisted_organs)) + blacklisted_organs = typecacheof(list( + // AQ (nie mamy organow kosmetycznych z Monke): /obj/item/organ/external/anime_bottom, + // AQ (nie mamy organow kosmetycznych z Monke): /obj/item/organ/external/anime_head, + // AQ (nie mamy organow kosmetycznych z Monke): /obj/item/organ/external/anime_middle, + // AQ (nie mamy organow kosmetycznych z Monke): /obj/item/organ/external/anime_halo, + // AQ (nie mamy organow kosmetycznych z Monke): /obj/item/organ/external/floran_leaves, + /obj/item/organ/wings, + /obj/item/organ/wings, + /obj/item/organ/body_egg, + // AQ (nasze borery nie maja takiego organu): /obj/item/organ/borer_body, + // AQ (nasze borery nie maja takiego organu): /obj/item/organ/empowered_borer_egg, + )) + // we only want to blacklist the "parent" wing types, which should not exist on their own - the bioscrambler blacklist does the same thing. + blacklisted_organs -= subtypesof(/obj/item/organ/wings) + blacklisted_organs -= typesof(/obj/item/organ/wings/moth) + + . = list() + if(!iscarbon(target)) + return + for(var/obj/item/organ/organ in target.internal_organs) + if(is_type_in_typecache(organ, blacklisted_organs)) + continue + if(IS_ROBOTIC_ORGAN(organ)) + continue + . |= organ.type + diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/toxins.dm b/aquila/code/modules/virology/disease/symtoms/harmful/toxins.dm new file mode 100644 index 00000000000..d8ea35a75e2 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/toxins.dm @@ -0,0 +1,10 @@ +/datum/symptom/acute/toxins + name = "Hyperacidity" + desc = "Inhibits the infected's ability to process natural toxins, producing a buildup of said toxins." + stage = 3 + max_multiplier = 3 + badness = EFFECT_DANGER_HARMFUL + severity = 3 + +/datum/symptom/acute/toxins/activate(mob/living/carbon/mob) + mob.adjustToxLoss((2 * power * multiplier)) diff --git a/aquila/code/modules/virology/disease/symtoms/harmful/wizarditis.dm b/aquila/code/modules/virology/disease/symtoms/harmful/wizarditis.dm new file mode 100644 index 00000000000..d14a48e94b6 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/harmful/wizarditis.dm @@ -0,0 +1,93 @@ +/datum/symptom/acute/wizarditis + name = "Wizarditis" + max_multiplier = 4 + stage = 3 + desc = "Some speculate that this virus is the cause of the Space Wizard Federation's existence. Subjects affected show the signs of brain damage, yelling obscure sentences or total gibberish. On late stages subjects sometime express the feelings of inner power, and, cite, 'the ability to control the forces of cosmos themselves!' A gulp of strong, manly spirits usually reverts them to normal, humanlike, condition." + badness = EFFECT_DANGER_HARMFUL + severity = 3 + +/datum/symptom/acute/wizarditis/activate(mob/living/carbon/affected_mob) + switch(round(multiplier)) + if(2) + if(prob(10)) + affected_mob.say(pick("You shall not pass!", "Expeliarmus!", "By Merlins beard!", "Feel the power of the Dark Side!"), forced = "wizarditis") + if(prob(10)) + to_chat(affected_mob, span_danger("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(10)) + affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!", "STI KALY!", "TARCOL MINTI ZHERI!"), forced = "wizarditis") + if(prob(10)) + to_chat(affected_mob, span_danger("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(10)) + affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!","STI KALY!","EI NATH!"), forced = "wizarditis") + return + if(prob(5)) + to_chat(affected_mob, span_danger("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, affected_mob) + if(prob(1)) + teleport(affected_mob) + + +/datum/symptom/acute/wizarditis/proc/spawn_wizard_clothes(chance = 0, mob/living/carbon/affected_mob) + if(ishuman(affected_mob)) + var/mob/living/carbon/human/wizard = affected_mob + if(prob(chance)) + if(!istype(wizard.head, /obj/item/clothing/head/wizard)) + if(!wizard.dropItemToGround(wizard.head)) + qdel(wizard.head) + wizard.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(wizard), ITEM_SLOT_HEAD) + return + if(prob(chance)) + if(!istype(wizard.wear_suit, /obj/item/clothing/suit/wizrobe)) + if(!wizard.dropItemToGround(wizard.wear_suit)) + qdel(wizard.wear_suit) + wizard.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(wizard), ITEM_SLOT_OCLOTHING) + return + if(prob(chance)) + if(!istype(wizard.shoes, /obj/item/clothing/shoes/sandal/magic)) + if(!wizard.dropItemToGround(wizard.shoes)) + qdel(wizard.shoes) + wizard.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(wizard), ITEM_SLOT_FEET) + return + else + var/mob/living/carbon/wizard = affected_mob + if(prob(chance)) + var/obj/item/staff/staff = new(wizard) + if(!wizard.put_in_hands(staff)) + qdel(staff) + + +/datum/symptom/acute/wizarditis/proc/teleport(mob/living/carbon/affected_mob) + var/list/theareas = get_areas_in_range(80, affected_mob) + for(var/area/space/unsafe in theareas) + theareas -= unsafe + + if(!theareas || !theareas.len) + return + + var/area/thearea = pick(theareas) + + var/list/L = list() + var/turf/mob_turf = get_turf(affected_mob) + for(var/turf/T in get_area_turfs(thearea.type)) + if(!is_station_level((mob_turf).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/aquila/code/modules/virology/disease/symtoms/helpful/antitoxin.dm b/aquila/code/modules/virology/disease/symtoms/helpful/antitoxin.dm new file mode 100644 index 00000000000..53493ddda1b --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/antitoxin.dm @@ -0,0 +1,15 @@ +/datum/symptom/acute/antitox + name = "Antioxidantisation Syndrome" + desc = "A very real syndrome beloved by Super-Food Fans and Essential Oil Enthusiasts; encourages the production of anti-toxin within the body." + stage = 2 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + max_multiplier = 5 + max_chance = 12 + +/datum/symptom/acute/antitox/activate(mob/living/mob) + if(prob(2.5)) + to_chat(mob, span_notice("[pick("You feel your toxins being purged!", "Your liver feels as if can take the challenge.")]")) + mob?.adjustToxLoss(-2 * multiplier) + mob.adjustOrganLoss(ORGAN_SLOT_LIVER, -multiplier) + mob.adjustOrganLoss(ORGAN_SLOT_LIVER, -0.5 * multiplier) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/clockwork.dm b/aquila/code/modules/virology/disease/symtoms/helpful/clockwork.dm new file mode 100644 index 00000000000..3b171d0de3f --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/clockwork.dm @@ -0,0 +1,204 @@ +// This code techically works but I have a few edgecases to soften out - until then keep it restricted - ghosti + +/datum/symptom/acute/robotic_adaptation + restricted = TRUE // can remove once curing the disease is fully functional + name = "Biometallic Replication" + desc = "The disease can manipulate metal and silicate compounds, becoming able to infect robotic beings. The virus also provides a suitable substrate for nanites in otherwise inhospitable hosts" + illness = "Robotic evolution" + stealth = 0 + resistance = 1 + stage_speed = 4 //while the reference material has low speed, this virus will take a good while to completely convert someone + transmission = -1 + level = 8 + severity = 0 + symptom_delay_min = 10 + symptom_delay_max = 30 + //var/prefixes = list("Ratvarian ", "Keter ", "Clockwork ", "Robo") + bodies = list("Robot") + //var/suffixes = list("-217") + var/replaceorgans = FALSE + var/replacebody = FALSE + var/robustbits = FALSE + var/biotypes = MOB_ROBOTIC + threshold_desc = list( + "Stage Speed 4" = "The virus will replace the host's organic organs with mundane, biometallic versions.", + "Resistance 4" = "The virus will eventually convert the host's entire body to biometallic materials, and maintain its cellular integrity.", + "Stage Speed 10" = "Biometallic mass created by the virus will be superior to typical organic mass." + ) + max_multiplier = 12 + max_chance = 24 + +/datum/symptom/acute/robotic_adaptation/first_activate(mob/living/carbon/mob, datum/disease/acute/disease) + . = ..() + + if(multiplier >= 6) + replaceorgans = TRUE + if(chance <= 12) + replacebody = TRUE + if(disease.robustness == 100) //robust disease for robust parts + robustbits = TRUE //note that having this symptom means most healing symptoms won't work on you + +/datum/symptom/acute/robotic_adaptation/activate(mob/living/carbon/mob, datum/disease/acute/disease) + disease.infectable_biotypes |= biotypes + switch(disease.stage) + if(1, 2) + if(replaceorgans) + to_chat(mob, "[pick("You feel a grinding pain in your abdomen.", "You exhale a jet of steam.")]") + if(3, 4) + if(replaceorgans || replacebody) + if(Replace(mob)) + return + if(replacebody) + mob.adjustCloneLoss(-20) //repair mechanical integrity + return + + +/datum/symptom/acute/robotic_adaptation/proc/Replace(mob/living/carbon/human/Host) + if(replaceorgans) + for(var/obj/item/organ/Oldlimb in Host.internal_organs) + if(IS_ROBOTIC_ORGAN(Oldlimb)) //they are either part robotic or we already converted them! + continue + switch(Oldlimb.slot) //i hate doing it this way, but the cleaner way runtimes and does not work + if(ORGAN_SLOT_BRAIN) + Oldlimb.name = "enigmatic gearbox" + Oldlimb.desc ="An engineer would call this inconcievable wonder of gears and metal a 'black box'." + Oldlimb.icon_state = "brain-clock" + Oldlimb.organ_flags = ORGAN_ROBOTIC + return TRUE + if(ORGAN_SLOT_STOMACH) + if(HAS_TRAIT(Host, TRAIT_NOHUNGER))//for future, we could make this give people who requires no food to maintain its no food policy + var/obj/item/organ/stomach/battery/clockwork/organ = new() + //if(robustbits) + //organ.max_charge = 15000 //no longer exists(old bee code) + organ.Insert(Host, TRUE, FALSE) + else + var/obj/item/organ/stomach/clockwork/organ = new() + organ.Insert(Host, TRUE, FALSE) + if(prob(40)) + to_chat(Host, "You feel a stabbing pain in your abdomen!") + Host.emote("scream") + return TRUE + if(ORGAN_SLOT_EARS) + var/obj/item/organ/ears/robot/clockwork/organ = new() + if(robustbits) + organ.damage_multiplier = 0.5 + organ.Insert(Host, TRUE, FALSE) + to_chat(Host, "Your ears pop.") + return TRUE + if(ORGAN_SLOT_EYES) + var/obj/item/organ/eyes/robotic/clockwork/organ = new() + if(robustbits) + organ.flash_protect = 1 + organ.Insert(Host, TRUE, FALSE) + if(prob(40)) + to_chat(Host, "You feel a stabbing pain in your eyeballs!") + Host.emote("scream") + return TRUE + if(ORGAN_SLOT_LUNGS) + var/obj/item/organ/lungs/clockwork/organ = new() + if(robustbits) + // AQ (nasze pluca nie maja progu CO2): organ.safe_co2_max = 15 + // AQ (nasze pluca nie maja progu CO2): organ.safe_co2_max = 15 + // AQ (nasze pluca nie maja progow podtlenku azotu): organ.n2o_para_min = 15 + // AQ (nasze pluca nie maja progow podtlenku azotu): organ.n2o_sleep_min = 15 + organ.BZ_trip_balls_min = 15 + organ.gas_stimulation_min = 15 + organ.Insert(Host, TRUE, FALSE) + if(prob(40)) + to_chat(Host, "You feel a stabbing pain in your chest!") + Host.emote("scream") + return TRUE + if(ORGAN_SLOT_HEART) + var/obj/item/organ/heart/clockwork/organ = new() + organ.Insert(Host, TRUE, FALSE) + to_chat(Host, "You feel a stabbing pain in your chest!") + Host.emote("scream") + return TRUE + if(ORGAN_SLOT_LIVER) + var/obj/item/organ/liver/clockwork/organ = new() + if(robustbits) + organ.toxTolerance = 7 + organ.Insert(Host, TRUE, FALSE) + if(prob(40)) + to_chat(Host, "You feel a stabbing pain in your abdomen!") + Host.emote("scream") + return TRUE + if(ORGAN_SLOT_TONGUE) + if(robustbits) + var/obj/item/organ/tongue/robot/clockwork/better/organ = new() + organ.Insert(Host, TRUE, FALSE) + return TRUE + else + var/obj/item/organ/tongue/robot/clockwork/organ = new() + organ.Insert(Host, TRUE, FALSE) + return TRUE + //if(ORGAN_SLOT_EXTERNAL_TAIL) //disabled this part, because its not...QUITE working...it might be looping for moths/lizards somehow. + // var/obj/item/organ/external/tail/clockwork/organ = new() + // to_chat(Host, "imagine you have a tail or not.") + // organ.Insert(Host, TRUE, FALSE) + // return TRUE + //if(ORGAN_SLOT_EXTERNAL_WINGS) + // var/obj/item/organ/wings/clockwork/organ = new() + // to_chat(Host, "imagine you have wings or not.") + // //if(robustbits) + // //organ.flight_level = WINGS_FLYING //old bee code + // organ.Insert(Host, TRUE, FALSE) + // return TRUE + if(replacebody) + for(var/obj/item/bodypart/Oldlimb in Host.bodyparts) + if(!IS_ORGANIC_LIMB(Oldlimb)) + // AQ: nasze konczyny nie maja modyfikatorow obrazen - if(robustbits && Oldlimb.brute_modifier < 3 || Oldlimb.burn_modifier < 2) + // AQ: nasze konczyny nie maja modyfikatorow obrazen - Oldlimb.burn_modifier = max(4, Oldlimb.burn_modifier) + // AQ: nasze konczyny nie maja modyfikatorow obrazen - Oldlimb.brute_modifier = max(5, Oldlimb.brute_modifier) + continue + switch(Oldlimb.body_zone) + if(BODY_ZONE_HEAD)//i wish i knew how to transfer external organs from old limb to new limb, but i dont. + var/obj/item/bodypart/head/robot/clockwork/newlimb = new() + // AQ: nasze konczyny nie maja modyfikatorow obrazen - newlimb.burn_modifier = 4 + newlimb.replace_limb(Host, TRUE) + Host.visible_message("Your head feels numb, and cold.") + qdel(Oldlimb) + return TRUE + if(BODY_ZONE_CHEST)//i wish i knew how to transfer external organs from old limb to new limb, but i dont. + var/obj/item/bodypart/chest/robot/clockwork/newlimb = new() + // AQ: nasze konczyny nie maja modyfikatorow obrazen - newlimb.burn_modifier = 4 + newlimb.replace_limb(Host, TRUE) + Host.visible_message("Your [Oldlimb] feels numb, and cold.") + qdel(Oldlimb) + return TRUE + if(BODY_ZONE_L_ARM) + var/obj/item/bodypart/l_arm/robot/newlimb = new() + // AQ: nasze konczyny nie maja modyfikatorow obrazen - newlimb.burn_modifier = 4 + newlimb.replace_limb(Host, TRUE) + Host.visible_message("Your [Oldlimb] feels numb, and cold.") + qdel(Oldlimb) + return TRUE + if(BODY_ZONE_R_ARM) + var/obj/item/bodypart/r_arm/robot/newlimb = new() + // AQ: nasze konczyny nie maja modyfikatorow obrazen - newlimb.burn_modifier = 4 + newlimb.replace_limb(Host, TRUE) + Host.visible_message("Your [Oldlimb] feels numb, and cold.") + qdel(Oldlimb) + return TRUE + if(BODY_ZONE_L_LEG) + var/obj/item/bodypart/l_leg/robot/newlimb = new() + // AQ: nasze konczyny nie maja modyfikatorow obrazen - newlimb.burn_modifier = 4 + newlimb.replace_limb(Host, TRUE) + Host.visible_message("Your [Oldlimb] feels numb, and cold.") + qdel(Oldlimb) + return TRUE + if(BODY_ZONE_R_LEG) + var/obj/item/bodypart/r_leg/robot/newlimb = new() + // AQ: nasze konczyny nie maja modyfikatorow obrazen - newlimb.burn_modifier = 4 + newlimb.replace_limb(Host, TRUE) + Host.visible_message("Your [Oldlimb] feels numb, and cold.") + qdel(Oldlimb) + return TRUE + return FALSE + +/datum/symptom/acute/robotic_adaptation/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + disease.infectable_biotypes &= ~(biotypes) + if(disease.stage >= 3 && (replaceorgans || replacebody)) //sorry. no disease quartets allowed + to_chat(mob, "You feel lighter and springier as your innards lose their clockwork facade.") + mob.dna.species.regenerate_organs(mob, replace_current = TRUE) + // AQ: nasze konczyny nie maja modyfikatorow obrazen, wiec nie ma czego przywracac przy cofaniu objawu diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/coma.dm b/aquila/code/modules/virology/disease/symtoms/helpful/coma.dm new file mode 100644 index 00000000000..df71dedf552 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/coma.dm @@ -0,0 +1,92 @@ +#define COMA_COOLDOWN (40 SECONDS) +/datum/symptom/acute/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. Waking the host up some time later." + max_multiplier = 6 + max_chance = 100 + stage = 3 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + + var/passive_message = span_notice("The pain from your wounds makes you feel oddly sleepy...") + var/added_to_mob = FALSE + var/active_coma = FALSE //to prevent multiple coma procs + COOLDOWN_DECLARE(last_coma) + var/cooldown_alert = FALSE // To give some mechanical signal that it's back online + +/datum/symptom/acute/coma/activate(mob/living/carbon/mob, datum/disease/acute/disease) + . = ..() + if (!COOLDOWN_FINISHED(src, last_coma)) + cooldown_alert = FALSE + return + if(!added_to_mob && max_multiplier >= 4) + added_to_mob = TRUE + ADD_TRAIT(mob, TRAIT_NOCRITDAMAGE, type) + if(!cooldown_alert) + cooldown_alert = !cooldown_alert + INVOKE_ASYNC(mob, TYPE_PROC_REF(/mob, emote), "yawn") + to_chat(mob, span_warning("You can't help the urge to yawn.")) + var/effectiveness = CanHeal(mob) + if(!effectiveness) + return + if(passive_message_condition(mob)) + to_chat(mob, passive_message) + Heal(mob, effectiveness) + return + +/datum/symptom/acute/coma/side_effect(mob/living/mob) + if(active_coma) + uncoma() + if(!added_to_mob) + return + REMOVE_TRAIT(mob, TRAIT_NOCRITDAMAGE, type) + +/datum/symptom/acute/coma/proc/CanHeal(mob/living/victim) + if(!iscarbon(victim)) + return FALSE + if(HAS_TRAIT(victim, TRAIT_NOCRITDAMAGE)) + return FALSE + if(HAS_TRAIT(victim, TRAIT_DEATHCOMA)) + return multiplier + if(victim.IsSleeping()) + return multiplier * 0.3 //Voluntary unconsciousness yields lower healing. + switch(victim.stat) + if(UNCONSCIOUS, HARD_CRIT) + return multiplier * 0.9 + if(SOFT_CRIT) + return multiplier * 0.5 + if((victim.getBruteLoss() + victim.getFireLoss()) >= 80 && !active_coma) + to_chat(victim, span_warning("You feel yourself slip into a regenerative coma...")) + active_coma = TRUE + addtimer(CALLBACK(src, PROC_REF(coma), victim), 6 SECONDS) + return FALSE + +/datum/symptom/acute/coma/proc/coma(mob/living/victim) + if(QDELETED(victim) || victim.stat == DEAD) + return + victim.fakedeath("regenerative_coma", TRUE) + addtimer(CALLBACK(src, PROC_REF(uncoma), victim), 20 SECONDS) + +/datum/symptom/acute/coma/proc/uncoma(mob/living/victim) + if(QDELETED(victim) || !active_coma) + return + addtimer(CALLBACK(victim, TYPE_PROC_REF(/mob/living, cure_fakedeath), "regenerative_coma"), 15 SECONDS) + active_coma = FALSE + COOLDOWN_START(src, last_coma, COMA_COOLDOWN) + +/datum/symptom/acute/coma/proc/Heal(mob/living/carbon/victim, actual_power) + var/list/parts = victim.get_damaged_bodyparts(brute = TRUE, burn = TRUE) + if(!length(parts)) + return + var/heal_amt = (4 * actual_power) / length(parts) + victim.heal_overall_damage(brute = heal_amt, burn = heal_amt) + if(active_coma && (victim.getBruteLoss() + victim.getFireLoss()) == 0) + uncoma(victim) + return TRUE + +/datum/symptom/acute/coma/proc/passive_message_condition(mob/living/victim) + if((victim.getBruteLoss() + victim.getFireLoss()) > 30) + return TRUE + return FALSE + +#undef COMA_COOLDOWN diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/damge_to_toxin.dm b/aquila/code/modules/virology/disease/symtoms/helpful/damge_to_toxin.dm new file mode 100644 index 00000000000..8783822e3fd --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/damge_to_toxin.dm @@ -0,0 +1,20 @@ +/datum/symptom/acute/damage_converter + name = "Toxic Compensation" + desc = "Stimulates cellular growth within the body, causing it to regenerate tissue damage. Repair done by these cells causes toxins to build up in the body." + badness = EFFECT_DANGER_HELPFUL + stage = 3 + chance = 10 + max_chance = 50 + multiplier = 5 + max_multiplier = 10 + severity = 0 + +/datum/symptom/acute/damage_converter/activate(mob/living/carbon/mob) + if(HAS_TRAIT(mob, TRAIT_TOXINLOVER) || HAS_TRAIT(mob, TRAIT_TOXIMMUNE)) + return + + if(mob.getFireLoss() > 0 || mob.getBruteLoss() > 0) + var/get_damage = rand(1, 3) + mob.adjustFireLoss(-get_damage) + mob.adjustBruteLoss(-get_damage) + mob.adjustToxLoss(max(1,get_damage * multiplier / 5)) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/darkness.dm b/aquila/code/modules/virology/disease/symtoms/helpful/darkness.dm new file mode 100644 index 00000000000..776a75f84db --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/darkness.dm @@ -0,0 +1,41 @@ +/datum/symptom/acute/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." + max_multiplier = 8 + stage = 3 + max_chance = 33 + severity = 0 + badness = EFFECT_DANGER_HELPFUL + var/passive_message = span_notice("You feel tingling on your skin as light passes over it.") + COOLDOWN_DECLARE(heal_msg_cooldown) + +/datum/symptom/acute/darkness/activate(mob/living/mob, datum/disease/acute/disease) + . = ..() + switch(round(multiplier)) + if(4, 5, 6, 7, 8) + if(!CanHeal(mob)) + return + if(mob.getBruteLoss() || mob.getFireLoss()) + to_chat(mob, passive_message) + Heal(mob, multiplier) + else + multiplier = min(multiplier + 0.1, max_multiplier) + +/datum/symptom/acute/darkness/proc/CanHeal(mob/living/carbon/mob) + if(isturf(mob.loc)) //else, there's considered to be no light + var/turf/mob_turf = mob.loc + if(mob_turf.is_softly_lit()) + return TRUE + var/light_amount = min(1, mob_turf.get_lumcount()) - 0.5 + // AQ: nie mamy SHADOW_SPECIES_DIM_LIGHT, bierzemy nasz prog przycmionego swiatla + if(light_amount > 0.2) + return FALSE + return TRUE + +/datum/symptom/acute/darkness/proc/Heal(mob/living/carbon/victim, actual_power) + var/old_health = victim.health + victim.heal_overall_damage(brute = actual_power * 2, burn = actual_power) + if(victim.health > old_health && COOLDOWN_FINISHED(src, heal_msg_cooldown)) + to_chat(victim, span_notice("The darkness soothes and mends your wounds.")) + COOLDOWN_START(src, heal_msg_cooldown, 25 SECONDS) + return TRUE diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/delightful.dm b/aquila/code/modules/virology/disease/symtoms/helpful/delightful.dm new file mode 100644 index 00000000000..752ddcb54ae --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/delightful.dm @@ -0,0 +1,16 @@ +/datum/symptom/acute/delightful + name = "Delightful Effect" + desc = "A more powerful version of Full Glass. Makes the infected feel delightful." + stage = 4 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + +/datum/symptom/acute/delightful/activate(mob/living/carbon/mob) + // AQ: nasz komponent nastroju nie ma has_mood_of_category, wiec komunikat leci rzadziej + if(prob(10)) + to_chat(mob, span_hypnophrase("You feel delightful!")) + SEND_SIGNAL(mob, COMSIG_ADD_MOOD_EVENT, REF(src), /datum/mood_event/delightful_symptom) + +/datum/symptom/acute/delightful/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + SEND_SIGNAL(mob, COMSIG_CLEAR_MOOD_EVENT, REF(src)) + to_chat(mob, span_warning("You aren't quite sure what you were so happy about.")) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/invisible.dm b/aquila/code/modules/virology/disease/symtoms/helpful/invisible.dm new file mode 100644 index 00000000000..8061cc9f269 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/invisible.dm @@ -0,0 +1,9 @@ +/datum/symptom/acute/invisible + name = "Waiting Syndrome" + desc = "A self-defeating symptom that doesn't seem to do anything in particular." + stage = 1 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + +/datum/symptom/acute/invisible/activate(mob/living/mob) + return diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/lantern.dm b/aquila/code/modules/virology/disease/symtoms/helpful/lantern.dm new file mode 100644 index 00000000000..16eb4ea2f92 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/lantern.dm @@ -0,0 +1,40 @@ +/datum/symptom/acute/lantern + name = "Lantern Syndrome" + desc = "Causes the infected to glow." + stage = 2 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + multiplier = 4 + max_multiplier = 10 + chance = 10 + max_chance = 15 + var/uncolored = 0 + var/flavortext = 0 + var/color = rgb(255, 255, 255) + var/obj/effect/dummy/lighting_obj/moblight + +/datum/symptom/acute/lantern/activate(mob/living/mob) + if(!moblight) + moblight = new(mob) + if(ismouse(mob) || !mob.reagents) + moblight.set_light_range(multiplier) + moblight.set_light_power(multiplier / 3) + moblight.set_light_color(color) + return + if(mob.reagents.has_reagent(/datum/reagent/space_cleaner)) + uncolored = 1 //Having spacecleaner in your system when the effect activates will permanently make the color white. + if(mob.reagents.reagent_list.len == 0 || uncolored == TRUE) + color = rgb(255, 255, 255) + else + color = mix_color_from_reagents(mob.reagents.reagent_list) + if(!flavortext) + to_chat(mob, span_notice("You are glowing!")) + flavortext = 1 + moblight.set_light_range(multiplier) + moblight.set_light_power(multiplier / 3) + moblight.set_light_color(color) + +/datum/symptom/acute/lantern/deactivate(mob/living/mob, datum/disease/acute/disease, safe = FALSE) + QDEL_NULL(moblight) + to_chat(mob, span_notice("You don't feel as bright.")) + flavortext = 0 diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/longevity.dm b/aquila/code/modules/virology/disease/symtoms/helpful/longevity.dm new file mode 100644 index 00000000000..9636386e933 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/longevity.dm @@ -0,0 +1,34 @@ +/datum/symptom/acute/immortal + name = "Longevity Syndrome" + desc = "Grants functional immortality to the infected so long as the symptom is active. Heals broken bones and healing external damage. Creates a backlash if cured." + stage = 4 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + var/total_healed = 0 + +/datum/symptom/acute/immortal/activate(mob/living/carbon/mob) + if(ishuman(mob)) + for(var/datum/wound/wound as anything in mob.get_all_wounds()) + to_chat(mob, span_notice("You feel the [wound] heal itself.")) + wound.remove_wound() + break + + var/heal_amt = 4 * multiplier + var/current_health = mob.getBruteLoss() + if(current_health >= heal_amt) + total_healed += heal_amt * 0.5 + else + total_healed += (heal_amt - current_health) * 0.5 + mob.heal_overall_damage(brute = heal_amt, burn = heal_amt) + if(!(HAS_TRAIT(mob, TRAIT_TOXINLOVER) || HAS_TRAIT(mob, TRAIT_TOXIMMUNE))) + mob.adjustToxLoss(-heal_amt) + +/datum/symptom/acute/immortal/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + if(safe) // don't kill people if they get ahealed!! + return + if(ishuman(mob)) + var/mob/living/carbon/human/person = mob + to_chat(person, span_warning("You suddenly feel hurt and old...")) + person.age += 4 * multiplier * total_healed + if(total_healed > 0) + mob.take_overall_damage(brute = (total_healed / 2), burn = (total_healed / 2)) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/metabolism_boost.dm b/aquila/code/modules/virology/disease/symtoms/helpful/metabolism_boost.dm new file mode 100644 index 00000000000..dce77598f00 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/metabolism_boost.dm @@ -0,0 +1,18 @@ +/datum/symptom/acute/metabolism + name = "Metabolic Boost" + desc = "The disease causes the host's metabolism to accelerate rapidly, making them process chemicals twice as fast,\ + but also causing increased hunger." + max_multiplier = 10 + stage = 3 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + +/datum/symptom/acute/metabolism/activate(mob/living/carbon/mob) + if(!iscarbon(mob) || !mob.reagents) + return + + mob.reagents.metabolize(mob, (multiplier * 0.5) * SSMOBS_DT, 0, can_overdose=TRUE) //this works even without a liver; it's intentional since the virus is metabolizing by itself + mob.overeatduration = max(mob.overeatduration - 4 SECONDS, 0) + mob.adjust_nutrition(-(4 + multiplier) * HUNGER_FACTOR) //Hunger depletes at 10x the normal speed + if(prob(2 * multiplier)) + to_chat(mob, span_notice("You feel an odd gurgle in your stomach, as if it was working much faster than normal.")) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/mind_restoration.dm b/aquila/code/modules/virology/disease/symtoms/helpful/mind_restoration.dm new file mode 100644 index 00000000000..b9bb29da288 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/mind_restoration.dm @@ -0,0 +1,20 @@ +/datum/symptom/acute/mind_restoration + name = "Mind Restoration" + desc = "The virus repairs the bonds between neurons, reversing some damage to the mind." + stage = 3 + max_multiplier = 3 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + max_chance = 12 + +/datum/symptom/acute/mind_restoration/activate(mob/living/carbon/mob) + if(!iscarbon(mob)) + return + + mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -multiplier) + if(prob(10)) + switch(round(multiplier, 1)) + if(2) + mob.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC, ignore_flags = TRAUMA_SPECIAL_CURE_PROOF) + if(3) + mob.cure_trauma_type(resilience = TRAUMA_RESILIENCE_SURGERY, ignore_flags = TRAUMA_SPECIAL_CURE_PROOF) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/nanites.dm b/aquila/code/modules/virology/disease/symtoms/helpful/nanites.dm new file mode 100644 index 00000000000..ff8cbfd4c2e --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/nanites.dm @@ -0,0 +1,73 @@ +/* codeblocked until functional. Not converted to datum/disease/acute yet +/datum/symptom/acute/nano_boost + name = "Nano-symbiosis" + desc = "The virus reacts to nanites in the host's bloodstream by enhancing their replication cycle." + stealth = 0 + resistance = 2 + stage_speed = 2 + transmission = -1 + level = 7 + severity = 0 + symptom_delay_min = 1 + symptom_delay_max = 1 + var/reverse_boost = FALSE + threshold_desc = list( + "Transmission 5" = "Increases the virus' growth rate while nanites are present.", + "Stage Speed 7" = "Increases the replication boost." + ) + +/datum/symptom/acute/nano_boost/Start(datum/disease/advance/A) + . = ..() + if(!.) + return + if(A.totalTransmittable() >= 5) //reverse boost + reverse_boost = TRUE + if(A.totalStageSpeed() >= 7) //more nanites + power = 2 + +/datum/symptom/acute/nano_boost/Activate(datum/disease/advance/A) + . = ..() + if(!.) + return + var/mob/living/carbon/M = A.affected_mob + SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, 0.5 * power) + if(reverse_boost && SEND_SIGNAL(M, COMSIG_HAS_NANITES)) + if(prob(A.stage_prob)) + A.stage = min(A.stage + 1,A.max_stages) + +/datum/symptom/acute/nano_destroy + name = "Silicolysis" + desc = "The virus reacts to nanites in the host's bloodstream by attacking and consuming them." + stealth = 0 + resistance = 4 + stage_speed = -1 + transmission = 1 + level = 7 + severity = 0 + symptom_delay_min = 1 + symptom_delay_max = 1 + var/reverse_boost = FALSE + threshold_desc = list( + "Stage Speed 5" = "Increases the virus' growth rate while nanites are present.", + "Resistance 7" = "Severely increases the rate at which the nanites are destroyed." + ) + +/datum/symptom/acute/nano_destroy/Start(datum/disease/advance/A) + . = ..() + if(!.) + return + if(A.totalStageSpeed() >= 5) //reverse boost + reverse_boost = TRUE + if(A.totalResistance() >= 7) //more nanites + power = 3 + +/datum/symptom/acute/nano_destroy/Activate(datum/disease/advance/A) + . = ..() + if(!.) + return + var/mob/living/carbon/M = A.affected_mob + SEND_SIGNAL(M, COMSIG_NANITE_ADJUST_VOLUME, -0.4 * power) + if(reverse_boost && SEND_SIGNAL(M, COMSIG_HAS_NANITES)) + if(prob(A.stage_prob)) + A.stage = min(A.stage + 1,A.max_stages) +*/ diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/plasma_heal.dm b/aquila/code/modules/virology/disease/symtoms/helpful/plasma_heal.dm new file mode 100644 index 00000000000..eadf739bc56 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/plasma_heal.dm @@ -0,0 +1,113 @@ +/// Determines the rate at which Plasma Fixation heals based on the amount of plasma in the air +#define HEALING_PER_MOL 1.1 +/// Determines the rate at which Plasma Fixation heals based on the amount of plasma being breathed through internals +#define HEALING_PER_BREATH_PRESSURE 0.05 +/// Determines the highest amount you can be healed for when breathing plasma from internals +#define MAX_HEAL_COEFFICIENT_INTERNALS 0.75 +/// Determines the highest amount you can be healed for from pulling plasma from the environment +#define MAX_HEAL_COEFFICIENT_ENVIRONMENT 0.5 +/// Determines the highest amount you can be healed for when there is plasma in the bloodstream +#define MAX_HEAL_COEFFICIENT_BLOODSTREAM 0.75 +/// This is the base heal amount before being multiplied by the healing coefficients +#define BASE_HEAL_PLASMA_FIXATION 4 + +/datum/symptom/acute/plasma_heal + name = "Plasma Fixation" + desc = "The disease draws plasma from the atmosphere and from inside the body to heal and stabilize body temperature." + + stage = 1 + max_multiplier = 5 + max_chance = 45 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + + var/passive_message = span_notice("You feel an odd attraction to plasma.") + var/temp_rate = 1 + +/datum/symptom/acute/plasma_heal/first_activate(mob/living/carbon/mob, datum/disease/acute/disease) + . = ..() + if(!HAS_TRAIT(mob, TRAIT_PLASMA_LOVER_METABOLISM)) + to_chat(mob, span_notice("You suddenly love plasma.")) + ADD_TRAIT(mob, TRAIT_PLASMA_LOVER_METABOLISM, type) + +/datum/symptom/acute/plasma_heal/side_effect(mob/living/mob) + . = ..() + REMOVE_TRAIT(mob, TRAIT_PLASMA_LOVER_METABOLISM, type) + +/datum/symptom/acute/plasma_heal/activate(mob/living/carbon/mob, datum/disease/acute/disease) + . = ..() + var/effectiveness = CanHeal(mob) + if(!effectiveness) + return + if(passive_message_condition(mob)) + to_chat(mob, passive_message) + Heal(mob, effectiveness) + +/datum/symptom/acute/plasma_heal/proc/CanHeal(mob/living/diseased_mob) + var/datum/gas_mixture/environment + + var/base = 0 + + // Check internals + /// the amount of mols in a breath is significantly lower than in the environment so we are just going to use the tank's + /// distribution pressure as an abstraction rather than calculate it using the ideal gas equation. + /// balanced around a tank set to 4kpa = about 0.2 healing power. maxes out at 0.75 healing power, or 15kpa. + if(iscarbon(diseased_mob)) + var/mob/living/carbon/breather = diseased_mob + var/obj/item/tank/internals/internals_tank = breather.internal + if(internals_tank) + var/datum/gas_mixture/tank_contents = internals_tank.return_air() + if(tank_contents && round(tank_contents.return_pressure())) // make sure the tank is not empty or 0 pressure + if(tank_contents.get_moles(/datum/gas/plasma)) + // higher tank distribution pressure leads to more healing, but once you get to about 15kpa you reach the max + base += power * min(MAX_HEAL_COEFFICIENT_INTERNALS, internals_tank.distribute_pressure * HEALING_PER_BREATH_PRESSURE) + // Check environment + if(diseased_mob.loc) + environment = diseased_mob.loc.return_air() + if(environment && environment.get_moles(/datum/gas/plasma)) + . += power * min(MAX_HEAL_COEFFICIENT_INTERNALS, environment.get_moles(/datum/gas/plasma) * HEALING_PER_MOL) + // Check for reagents in bloodstream + if(diseased_mob.reagents?.has_reagent(/datum/reagent/toxin/plasma, needs_metabolizing = TRUE)) + base += power * MAX_HEAL_COEFFICIENT_BLOODSTREAM //Determines how much the symptom heals if injected or ingested + return base + +/datum/symptom/acute/plasma_heal/proc/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss()) + return TRUE + return FALSE + +/datum/symptom/acute/plasma_heal/proc/Heal(mob/living/M, actual_power) + var/heal_amt = BASE_HEAL_PLASMA_FIXATION * actual_power + + if(prob(5)) + to_chat(M, span_notice("You feel yourself absorbing plasma inside and around you...")) + + var/target_temp = BODYTEMP_NORMAL + if(M.bodytemperature > target_temp) + M.adjust_bodytemperature(-2 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT, target_temp) + if(prob(5)) + to_chat(M, span_notice("You feel less hot.")) + else if(M.bodytemperature < (BODYTEMP_NORMAL + 1)) + M.adjust_bodytemperature(2 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT, 0, target_temp) + if(prob(5)) + to_chat(M, span_notice("You feel warmer.")) + + var/should_update = M.adjustToxLoss(-heal_amt, forced = TRUE, updating_health = FALSE) + + if(M.getBruteLoss() > 0 || M.getFireLoss() > 0) + M.heal_overall_damage(brute = heal_amt, burn = heal_amt, required_bodytype = BODYTYPE_ORGANIC, updating_health = FALSE) + should_update = TRUE + if(prob(5)) + to_chat(M, span_notice("The pain from your wounds fades rapidly.")) + + if(should_update) + M.updatehealth() + return TRUE + +///Plasma End +#undef HEALING_PER_MOL +#undef HEALING_PER_BREATH_PRESSURE +#undef MAX_HEAL_COEFFICIENT_INTERNALS +#undef MAX_HEAL_COEFFICIENT_ENVIRONMENT +#undef MAX_HEAL_COEFFICIENT_BLOODSTREAM +#undef BASE_HEAL_PLASMA_FIXATION diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/self_respiration.dm b/aquila/code/modules/virology/disease/symtoms/helpful/self_respiration.dm new file mode 100644 index 00000000000..cc2ddac9a38 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/self_respiration.dm @@ -0,0 +1,25 @@ +/datum/symptom/acute/oxygen + name = "Self-Respiration" + desc = "The disease synthesizes oxygen, which can remove the need for breathing at high symptom strength." + stage = 4 + max_multiplier = 5 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + var/breathing = TRUE + +/datum/symptom/acute/oxygen/activate(mob/living/carbon/mob, datum/disease/acute/disease) + mob.losebreath = max(0, mob.losebreath - multiplier) + mob.adjustOxyLoss(-2 * multiplier) + if(multiplier >= 4) + if(prob(base_message_chance)) + to_chat(mob, span_notice("[pick("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.")]")) + if(breathing) + breathing = FALSE + ADD_TRAIT(mob, TRAIT_NOBREATH, DISEASE_TRAIT) + +/datum/symptom/acute/oxygen/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + if(!breathing) + breathing = TRUE + REMOVE_TRAIT(mob, TRAIT_NOBREATH, DISEASE_TRAIT) + mob.emote("gasp") + to_chat(mob, span_notice("You feel the need to breathe again.")) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/sensory_restoration.dm b/aquila/code/modules/virology/disease/symtoms/helpful/sensory_restoration.dm new file mode 100644 index 00000000000..9a9f4a2180c --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/sensory_restoration.dm @@ -0,0 +1,50 @@ +/datum/symptom/acute/sensory_restoration + name = "Sensory Restoration" + desc = "The disease adjusts how light is absorbed in the eyes, leading to enhanced vision at high strength." + stage = 3 + max_multiplier = 3 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + max_chance = 12 + var/squinting = TRUE + var/darkness = FALSE + +/datum/symptom/acute/sensory_restoration/activate(mob/living/carbon/mob) + if(!iscarbon(mob)) + return + + mob.adjustOrganLoss(ORGAN_SLOT_EYES, -2 * multiplier) + + if(prob(10)) + switch(round(multiplier, 1)) + if(2) + if(squinting) + ADD_TRAIT(mob, TRAIT_NEARSIGHTED_CORRECTED, DISEASE_TRAIT) + darkness = TRUE + mob.update_sight() + if(prob(2.5)) + to_chat(mob, span_notice("[pick("Your eyes feel great.", "You are able to make out finer details at distance.", "You don't feel the need to squint.")]")) + + if(3) + if(!darkness) + ADD_TRAIT(mob, TRAIT_NEARSIGHTED_CORRECTED, DISEASE_TRAIT) + squinting = FALSE + ADD_TRAIT(mob, TRAIT_TRUE_NIGHT_VISION, DISEASE_TRAIT) + darkness = TRUE + mob.update_sight() + if(prob(2.5)) + to_chat(mob, span_notice("[pick("Your eyes feel amazing.", "You are able to make out the details in darkness.", "You don't feel the need to squint.")]")) + +/datum/symptom/acute/sensory_restoration/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + if(!squinting) + squinting = TRUE + REMOVE_TRAIT(mob, TRAIT_NEARSIGHTED_CORRECTED, DISEASE_TRAIT) + mob.emote("blinks") + to_chat(mob, span_notice("You feel the need to squint again.")) + if(darkness) + darkness = FALSE + REMOVE_TRAIT(mob, TRAIT_TRUE_NIGHT_VISION, DISEASE_TRAIT) + to_chat(mob, span_notice("Your eyes are no longer are able to pierce through the darkness.")) + mob.update_sight() + + diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/space_adapt.dm b/aquila/code/modules/virology/disease/symtoms/helpful/space_adapt.dm new file mode 100644 index 00000000000..13f96cdeb2b --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/space_adapt.dm @@ -0,0 +1,23 @@ +/datum/symptom/acute/spaceadapt + name = "Space Adaptation Effect" + desc = "Causes the infected to secrete a thin thermally insulating and spaceproof barrier from their skin." + stage = 4 + max_count = 1 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + + chance = 12 + max_chance = 25 + var/spaceproof = FALSE +/datum/symptom/acute/spaceadapt/activate(mob/living/mob) + mob.add_traits(list(TRAIT_RESISTCOLD, TRAIT_RESISTLOWPRESSURE), type) + if(!spaceproof) + spaceproof = TRUE + to_chat(mob, span_notice("You feel a warm glow cover your skin.")) + +/datum/symptom/acute/spaceadapt/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + mob.remove_traits(list(TRAIT_RESISTCOLD, TRAIT_RESISTLOWPRESSURE), type) + if(spaceproof) + spaceproof = FALSE + to_chat(mob, span_notice("You feel the chill of the air once more.")) + mob.emote("shiver") diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/stimulant.dm b/aquila/code/modules/virology/disease/symtoms/helpful/stimulant.dm new file mode 100644 index 00000000000..5d1abd6a92a --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/stimulant.dm @@ -0,0 +1,20 @@ +/datum/symptom/acute/stimulant + name = "Adrenaline Extra" + desc = "Causes the infected to synthesize artificial adrenaline." + stage = 2 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + max_multiplier = 20 + +/datum/symptom/acute/stimulant/activate(mob/living/mob) + to_chat(mob, span_notice("You feel a rush of energy inside you!")) + if(ismouse(mob)) + mob.Shake(3,3, 10 SECONDS) + return + if (mob.reagents.get_reagent_amount(/datum/reagent/adrenaline) < 10) + if(prob(5 * multiplier) && multiplier >= 8) + mob.reagents.add_reagent(/datum/reagent/adrenaline, 11) //you are gonna probably die + else + mob.reagents.add_reagent(/datum/reagent/adrenaline, 4) + if (prob(30)) + mob.Jitter(1 SECONDS) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/thick_blood.dm b/aquila/code/modules/virology/disease/symtoms/helpful/thick_blood.dm new file mode 100644 index 00000000000..d9db8d7efa1 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/thick_blood.dm @@ -0,0 +1,13 @@ +/datum/symptom/acute/thick_blood + name = "Hyper-Fibrinogenesis" + desc = "Causes the infected to oversynthesize coagulant, as well as rapidly restore lost blood." + stage = 3 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + +/datum/symptom/acute/thick_blood/activate(mob/living/carbon/mob) + var/mob/living/carbon/human/victim = mob + if (ishuman(victim)) + if(victim.is_bleeding()) + victim.restore_blood() + to_chat(victim, span_notice("You feel your blood regenerate as your bleeding stops!")) diff --git a/aquila/code/modules/virology/disease/symtoms/helpful/water_heal.dm b/aquila/code/modules/virology/disease/symtoms/helpful/water_heal.dm new file mode 100644 index 00000000000..114700ff5b9 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/helpful/water_heal.dm @@ -0,0 +1,50 @@ +/datum/symptom/acute/water_heal + 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." + + stage = 1 + max_multiplier = 5 + max_chance = 45 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + + var/passive_message = span_notice("Your skin feels oddly dry...") + var/absorption_coeff = 1 + + +/datum/symptom/acute/water_heal/activate(mob/living/carbon/mob, datum/disease/acute/disease) + . = ..() + var/effectiveness = CanHeal(mob) + if(!effectiveness) + return + if(passive_message_condition(mob)) + to_chat(mob, passive_message) + Heal(mob, effectiveness) + +/datum/symptom/acute/water_heal/proc/CanHeal(mob/living/M) + if(!iscarbon(M)) + return 1 + var/base = 0 + if(M.fire_stacks < 0) + M.adjust_fire_stacks(min(absorption_coeff, -M.fire_stacks)) + base += multiplier + if(M.reagents?.has_reagent(/datum/reagent/water/holywater, needs_metabolizing = FALSE)) + M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) + base += multiplier * 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) + base += multiplier * 0.5 + return base + +/datum/symptom/acute/water_heal/proc/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss()) + return TRUE + return FALSE + +/datum/symptom/acute/water_heal/proc/Heal(mob/living/M, actual_power) + if(prob(5)) + to_chat(M, span_notice("You feel yourself absorbing the water around you to soothe your damaged skin.")) + + M.heal_overall_damage(brute = actual_power, burn = actual_power * 2) + + return TRUE diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/choking.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/choking.dm new file mode 100644 index 00000000000..59d5a92c219 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/choking.dm @@ -0,0 +1,15 @@ +/datum/symptom/acute/choking + name = "Choking" + desc = "The virus causes inflammation of the host's air conduits, leading to intermittent choking." + max_multiplier = 10 + multiplier = 1 + badness = EFFECT_DANGER_HINDRANCE + max_chance = 20 + stage = 2 + severity = 2 + +/datum/symptom/acute/choking/activate(mob/living/carbon/mob) + mob.emote("gasp") + if(prob(25)) + to_chat(mob, span_warning("[pick("You're having difficulty breathing.", "Your breathing becomes heavy.")]")) + mob.adjustOxyLoss(rand(2, 3) * power * multiplier) diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/confusion.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/confusion.dm new file mode 100644 index 00000000000..576bd314532 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/confusion.dm @@ -0,0 +1,13 @@ +/datum/symptom/acute/confusion + name = "Topographical Cretinism" + desc = "Attacks the infected's ability to differentiate left and right." + stage = 3 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + max_multiplier = 5 + symptom_delay_min = 1 + symptom_delay_max = 5 + +/datum/symptom/acute/confusion/activate(mob/living/carbon/mob) + to_chat(mob, span_warning("You have trouble telling right and left apart all of a sudden!")) + mob.confused = min(mob.confused + (1 SECONDS * power * multiplier), 20 SECONDS) diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/cult_vomit.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/cult_vomit.dm new file mode 100644 index 00000000000..93391778a8a --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/cult_vomit.dm @@ -0,0 +1,24 @@ +/datum/symptom/acute/cult_vomit + name = "Hemoptysis" + desc = "Causes the infected to cough up blood." + stage = 2 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + var/active = 0 + +/datum/symptom/acute/cult_vomit/activate(mob/living/carbon/mob) + if(!ishuman(mob) || active) + return + if(istype(get_area(mob), /area/chapel/main)) + return + if(IS_CULTIST(mob)) + return + + var/mob/living/carbon/human/victim = mob + active = 1 + to_chat(victim, span_warning("You feel a burning sensation in your throat.")) + sleep(10 SECONDS) + to_chat(victim, span_danger("You feel an agonizing pain in your throat!")) + sleep(10 SECONDS) + victim.vomit(10 * power, TRUE) + active = 0 diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/deafness.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/deafness.dm new file mode 100644 index 00000000000..da8b4a6fae4 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/deafness.dm @@ -0,0 +1,26 @@ + +/datum/symptom/acute/deaf + name = "Dead Ear Syndrome" + desc = "Kills the infected's aural senses." + stage = 4 + max_multiplier = 5 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + +/datum/symptom/acute/deaf/activate(mob/living/carbon/mob) + var/obj/item/organ/ears/ears = mob.get_organ_slot(ORGAN_SLOT_EARS) + if(!ears) + return //cutting off your ears to cure the deafness: the ultimate own + to_chat(mob, span_userdanger("Your ears pop and begin ringing loudly!")) + ears.deaf = min(20, ears.deaf + 15) + + if(prob(multiplier * power * 5)) + if(ears.damage < ears.maxHealth) + to_chat(mob, span_userdanger("Your ears pop painfully and start bleeding!")) + // Just absolutely murder me man + ears.applyOrganDamage(ears.maxHealth) + mob.emote("scream") + ADD_TRAIT(mob, TRAIT_DEAF, type) + +/datum/symptom/acute/deaf/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + REMOVE_TRAIT(mob, TRAIT_DEAF, type) diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/drowsness.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/drowsness.dm new file mode 100644 index 00000000000..0f3dd3c50bb --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/drowsness.dm @@ -0,0 +1,12 @@ + +/datum/symptom/acute/drowsness + name = "Automated Sleeping Syndrome" + desc = "Makes the infected feel more drowsy." + stage = 2 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + multiplier = 5 + max_multiplier = 10 + +/datum/symptom/acute/drowsness/activate(mob/living/mob) + mob.drowsyness = min(mob.drowsyness + (multiplier * power), 40 SECONDS) diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/elvis.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/elvis.dm new file mode 100644 index 00000000000..053b74f4dbf --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/elvis.dm @@ -0,0 +1,62 @@ +/datum/symptom/acute/elvis + name = "Elvisism" + desc = "Makes the infected the king of rock and roll." + stage = 3 + badness = EFFECT_DANGER_HINDRANCE + severity = 2 + +/datum/symptom/acute/elvis/first_activate(mob/living/carbon/mob) + if(ismouse(mob)) + var/mob/living/simple_animal/mouse/mouse = mob + mouse.icon_state = "mouse_elvis" + mouse.base_icon_state = "mouse_elvis" + mouse.icon_living = "mouse_elvis" + mouse.icon_dead = "mouse_brown_dead" + return + mob.dna.add_mutation(/datum/mutation/elvis, MUTATION_SOURCE_GENE_SYMPTOM) + +/datum/symptom/acute/elvis/activate(mob/living/carbon/mob) + if(!ishuman(mob)) + return + + var/mob/living/carbon/human/victim = mob + + /* + var/obj/item/clothing/glasses/H_glasses = H.get_item_by_slot(slot_glasses) + if(!istype(H_glasses, /obj/item/clothing/glasses/sunglasses/virus)) + var/obj/item/clothing/glasses/sunglasses/virus/virussunglasses = new + mob.u_equip(H_glasses,1) + mob.equip_to_slot(virussunglasses, slot_glasses) + */ + + mob.confused += (1 SECONDS) + + if(prob(50)) + mob.say(pick("Uh HUH!", "Thank you, Thank you very much...", "I ain't nothin' but a hound dog!", "Swing low, sweet chariot!")) + else + mob.emote("me",1,pick("curls his lip!", "gyrates his hips!", "thrusts his hips!")) + + if(istype(victim)) + addtimer(CALLBACK(src, PROC_REF(set_hairstyle), victim), 5 SECONDS, TIMER_UNIQUE) + +/datum/symptom/acute/elvis/proc/set_hairstyle(mob/living/carbon/human/victim) + if(QDELETED(src)) + return + if(victim.hair_style != "Pompadour (Big)") + victim.hair_style = "Pompadour (Big)" + victim.hair_color = "#242424" + if(victim.facial_hair_style != "Sideburns (Elvis)") + victim.facial_hair_style = "Sideburns (Elvis)" + victim.facial_hair_color = "#242424" + +/datum/symptom/acute/elvis/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + if(ismouse(mob)) + return + /* + if(ishuman(mob)) + var/mob/living/carbon/human/dude = mob + if(istype(dude.glasses, /obj/item/clothing/glasses/sunglasses/virus)) + dude.glasses.canremove = 1 + dude.u_equip(dude.glasses,1) + */ + mob.dna.remove_mutation(/datum/mutation/elvis, MUTATION_SOURCE_GENE_SYMPTOM) diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/famine.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/famine.dm new file mode 100644 index 00000000000..9dc8b9de9e7 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/famine.dm @@ -0,0 +1,20 @@ +/datum/symptom/acute/famine + name = "Faminous Potation" + desc = "The infected emanates a field that kills off plantlife. Lethal to species descended from plants." + stage = 2 + max_multiplier = 3 + badness = EFFECT_DANGER_HINDRANCE + severity = 2 + +/datum/symptom/acute/famine/activate(mob/living/mob) + if(ishuman(mob)) + var/mob/living/carbon/human/victim = mob + if(ispodperson(victim)) //Plantmen take a LOT of damage + victim.adjustToxLoss(5 * multiplier) + + for(var/obj/item/food/grown/crop in range(2 * multiplier,mob)) + crop.visible_message(span_warning("\The [crop] rots at an alarming rate!")) + new /obj/item/food/badrecipe(get_turf(crop)) + qdel(crop) + if(prob(30 * power / multiplier)) + break diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/fragile_bones.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/fragile_bones.dm new file mode 100644 index 00000000000..8e752aee6d1 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/fragile_bones.dm @@ -0,0 +1,19 @@ +/datum/symptom/acute/bones + name = "Fragile Person Syndrome" + desc = "Attacks the infected's body structure, making it more fragile." + stage = 4 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + +/datum/symptom/acute/bones/activate(mob/living/carbon/human/victim) + if(!ishuman(victim)) + return + // AQ: nasze konczyny nie maja wound_resistance, wiec objaw tylko zwieksza szanse na rany przez max_damage + for(var/obj/item/bodypart/part in victim.bodyparts) + part.max_damage = max(1, part.max_damage - 10) + +/datum/symptom/acute/bones/deactivate(mob/living/carbon/human/victim, safe = FALSE) + if(!ishuman(victim)) + return + for(var/obj/item/bodypart/part in victim.bodyparts) + part.max_damage = part.max_damage + 10 diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/gregarious.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/gregarious.dm new file mode 100644 index 00000000000..26ddb155fe3 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/gregarious.dm @@ -0,0 +1,28 @@ +/datum/symptom/acute/gregarious + name = "Gregarious Impetus" + desc = "Infests the social structures of the infected's brain, causing them to feel better in crowds of other potential victims, and punishing them for being alone." + stage = 4 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + max_chance = 25 + max_multiplier = 4 + +/datum/symptom/acute/gregarious/activate(mob/living/carbon/mob) + var/others_count = 0 + for(var/mob/living/carbon/m in oview(5, mob)) + others_count += 1 + + if (others_count >= multiplier) + to_chat(mob, span_notice("A friendly sensation is satisfied with how many are near you - for now.")) + mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -multiplier) + mob.reagents?.add_reagent(/datum/reagent/drug/happiness, multiplier) // ADDICTED TO HAVING FRIENDS + if (multiplier < max_multiplier) + multiplier += 0.15 // The virus gets greedier + else + to_chat(mob, span_warning("A hostile sensation in your brain stings you... it wants more of the living near you.")) + mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, multiplier * power / 2) + mob.AdjustParalyzed(multiplier * power) // This practically permaparalyzes you at higher multipliers but + mob.AdjustKnockdown(multiplier * power) // that's your fucking fault for not being near enough people + mob.AdjustStun(multiplier * power) // You'll have to wait until the multiplier gets low enough + if (multiplier > 1) + multiplier -= 0.3 // The virus tempers expectations diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/hallucination.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/hallucination.dm new file mode 100644 index 00000000000..5793df817ef --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/hallucination.dm @@ -0,0 +1,9 @@ +/datum/symptom/acute/hallucinations + name = "Hallucinational Syndrome" + desc = "Induces hallucination in the infected." + stage = 3 + badness = EFFECT_DANGER_HINDRANCE + severity = 2 + +/datum/symptom/acute/hallucinations/activate(mob/living/carbon/mob) + mob.adjust_hallucinations(5 SECONDS * power) diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/horse_throat.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/horse_throat.dm new file mode 100644 index 00000000000..363553d182d --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/horse_throat.dm @@ -0,0 +1,27 @@ +/datum/symptom/acute/horsethroat + name = "Horse Throat" + desc = "Inhibits communication from the infected through spontaneous generation of a horse mask." + stage = 3 + badness = EFFECT_DANGER_HINDRANCE + severity = 2 + +/datum/symptom/acute/horsethroat/activate(mob/living/carbon/mob) + if(ismouse(mob)) + var/mob/living/simple_animal/mouse/mouse = mob + mouse.icon_state = "mouse_horse" + mouse.icon_living = "mouse_horse" + mouse.icon_dead = "mouse_horse_dead" + mouse.held_state = "mouse_horse" + + mob.say(pick("NEIGH!", "Neigh!", "Neigh.", "Neigh?", "Neigh!!", "Neigh?!", "Neigh...")) + if(!ishuman(mob)) + return + + var/mob/living/carbon/human/human = mob + var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead + if(human.wear_mask && !istype(human.wear_mask,/obj/item/clothing/mask/horsehead)) + human.dropItemToGround(human.wear_mask, TRUE) + human.equip_to_slot(magichead, ITEM_SLOT_MASK) + if(!human.wear_mask) + human.equip_to_slot(magichead, ITEM_SLOT_MASK) + to_chat(human, span_warning("You feel a little horse!")) diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/pierotts_throat.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/pierotts_throat.dm new file mode 100644 index 00000000000..b87c1d6c41f --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/pierotts_throat.dm @@ -0,0 +1,66 @@ + + +/datum/symptom/acute/pthroat + name = "Pierrot's Throat" + desc = "Overinduces a sense of humor in the infected, causing them to be overcome by the spirit of a clown." + stage = 3 + max_multiplier = 4 + badness = EFFECT_DANGER_HINDRANCE + severity = 2 + +/datum/symptom/acute/pthroat/activate(mob/living/carbon/mob) + if(ismouse(mob)) + var/mob/living/simple_animal/mouse/mouse = mob + mouse.icon_state = "mouse_clown" + mouse.icon_living = "mouse_clown" + mouse.icon_dead = "mouse_clown_dead" + mouse.held_state = "mouse_clown" + + mob.say(pick("HONK!", "Honk!", "Honk.", "Honk?", "Honk!!", "Honk?!", "Honk...")) + if(ishuman(mob)) + var/mob/living/carbon/human/affected = mob + if(multiplier >=2) //clown mask added + var/obj/item/clothing/mask/gas/clown_hat/virus/virusclown_hat = new /obj/item/clothing/mask/gas/clown_hat/virus + if(affected.wear_mask && !istype(affected.wear_mask, /obj/item/clothing/mask/gas/clown_hat/virus)) + affected.dropItemToGround(mob.wear_mask, TRUE) + affected.equip_to_slot(virusclown_hat, ITEM_SLOT_MASK) + if(!affected.wear_mask) + affected.equip_to_slot(virusclown_hat, ITEM_SLOT_MASK) + if(multiplier >=3) //clown shoes added + var/obj/item/clothing/shoes/clown_shoes/virusshoes = new /obj/item/clothing/shoes/clown_shoes + if(affected.shoes && !istype(affected.shoes, /obj/item/clothing/shoes/clown_shoes)) + affected.dropItemToGround(affected.shoes, TRUE) + affected.equip_to_slot(virusshoes, ITEM_SLOT_FEET) + if(!affected.shoes) + affected.equip_to_slot(virusshoes, ITEM_SLOT_FEET) + if(multiplier >=4) //clown suit added + var/obj/item/clothing/under/rank/civilian/clown/virussuit = new /obj/item/clothing/under/rank/civilian/clown + if(affected.w_uniform && !istype(affected.w_uniform, /obj/item/clothing/under/rank/civilian/clown)) + affected.dropItemToGround(affected.w_uniform, TRUE) + affected.equip_to_slot(virussuit, ITEM_SLOT_ICLOTHING) + if(!affected.w_uniform) + affected.equip_to_slot(virussuit, ITEM_SLOT_ICLOTHING) + +/datum/symptom/acute/pthroat/first_activate(mob/living/carbon/mob) + RegisterSignal(mob, COMSIG_MOB_SAY, PROC_REF(handle_speech)) + +/datum/symptom/acute/pthroat/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + UnregisterSignal(mob, COMSIG_MOB_SAY) + +/datum/symptom/acute/pthroat/proc/handle_speech(datum/source, list/speech_args) + SIGNAL_HANDLER + + 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 * multiplier * power)) //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 diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/shaking.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/shaking.dm new file mode 100644 index 00000000000..e7223db6743 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/shaking.dm @@ -0,0 +1,10 @@ +/datum/symptom/acute/shakey + name = "World Shaking Syndrome" + desc = "Attacks the infected's motor output, giving them a sense of vertigo." + stage = 3 + max_multiplier = 3 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + +/datum/symptom/acute/shakey/activate(mob/living/carbon/mob) + shake_camera(mob, 5*multiplier) diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/spiky_skin.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/spiky_skin.dm new file mode 100644 index 00000000000..615dbac2489 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/spiky_skin.dm @@ -0,0 +1,58 @@ +/* +/datum/symptom/acute/spiky_skin + name = "Porokeratosis Acanthus" + desc = "Causes the infected to generate keratin spines along their skin." + stage = 2 + max_count = 1 + badness = EFFECT_DANGER_HINDRANCE + var/skip = FALSE + multiplier = 4 + max_multiplier = 8 + +/datum/symptom/acute/spiky_skin/activate(mob/living/mob, multiplier) + to_chat(mob, span_warning("Your skin feels a little prickly.")) + +/datum/symptom/acute/spiky_skin/deactivate(mob/living/mob) + if(!skip) + to_chat(mob, span_notice("Your skin feels nice and smooth again!")) + ..() + +/datum/symptom/acute/spiky_skin/on_touch(mob/living/mob, mob/living/toucher, mob/living/touched, touch_type) + if(!count || skip) + return + if(!istype(toucher) || !istype(touched)) + return + var/obj/item/bodypart/bodypartTarget + var/mob/living/carbon/human/target + if(toucher == mob) //we bumped into someone else + if(ishuman(touched)) + target = touched + bodypartTarget = target.get_bodypart(ran_zone()) + else //someone else bumped into us + if(ishuman(toucher)) + target = toucher + bodypartTarget = target.get_bodypart(ran_zone()) + + if(toucher == mob) + if(bodypartTarget) + to_chat(mob, span_warning("As you bump into \the [touched], your spines dig into \his [bodypartTarget]!")) + bodypartTarget.take_damage(multiplier, BRUTE) + else + to_chat(mob, span_warning("As you bump into \the [touched], your spines dig into \him!")) + var/mob/living/impaled = touched + if(istype(impaled) && !istype(impaled, /mob/living/silicon)) + impaled.apply_damage(multiplier, BRUTE, bodypartTarget) + else + if(bodypartTarget) + to_chat(mob, span_warning("As \the [toucher] [touch_type == DISEASE_BUMP ? "bumps into" : "touches"] you, your spines dig into \his [bodypartTarget]!")) + to_chat(toucher, span_danger("As you [touch_type == DISEASE_BUMP ? "bump into" : "touch"] \the [mob], \his spines dig into your [bodypartTarget]!")) + bodypartTarget.take_damage(multiplier) + else + to_chat(mob, span_warning("As \the [toucher] [touch_type == DISEASE_BUMP ? "bumps into" : "touches"] you, your spines dig into \him!")) + to_chat(toucher, span_danger("As you [touch_type == DISEASE_BUMP ? "bump into" : "touch"] \the [mob], \his spines dig into you!")) + var/mob/living/victim = toucher + if(istype(victim) && !istype(victim, /mob/living/silicon)) + victim.apply_damage(multiplier) + var/mob/attacker = touched + log_attack("[attacker] damaged [target] with keratin spikes") +*/ diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/sweat.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/sweat.dm new file mode 100644 index 00000000000..eb1d02d6ce3 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/sweat.dm @@ -0,0 +1,12 @@ +/datum/symptom/acute/sweat + name = "Hyper-perspiration Effect" + desc = "Causes the infected's sweat glands to go into overdrive." + stage = 3 + badness = EFFECT_DANGER_HINDRANCE + severity = 2 + +/datum/symptom/acute/sweat/activate(mob/living/carbon/mob) + if(prob(30 * power)) + mob.emote("me",1,"is sweating profusely!") + + // AQ: nie mamy plynow rozlewanych po podlodze (liquids ze Skyrata), wiec zostaje sam pot diff --git a/aquila/code/modules/virology/disease/symtoms/hinderance/wendigo_vomit.dm b/aquila/code/modules/virology/disease/symtoms/hinderance/wendigo_vomit.dm new file mode 100644 index 00000000000..73b81460155 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/hinderance/wendigo_vomit.dm @@ -0,0 +1,15 @@ +/datum/symptom/acute/wendigo_vomit + name = "Gastrointestinal Inflammation" + desc = "Inflames the GI tract of the infected, causing relentless vomitting." + stage = 2 + badness = EFFECT_DANGER_HINDRANCE + severity = 2 + chance = 6 + max_chance = 12 + +/datum/symptom/acute/wendigo_vomit/activate(mob/living/mob) + if(!ishuman(mob)) + return + + var/mob/living/carbon/human/victim = mob + victim.vomit(stun = FALSE) diff --git a/aquila/code/modules/virology/disease/symtoms/restricted/stage1.dm b/aquila/code/modules/virology/disease/symtoms/restricted/stage1.dm new file mode 100644 index 00000000000..c88e48aa333 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/restricted/stage1.dm @@ -0,0 +1,204 @@ +/datum/symptom/acute/transformation + name = "Human Transformation" + desc = "Turns the target into a human." + restricted = 1 + max_count = 10 + stage = 1 + badness = EFFECT_DANGER_HARMFUL + severity = 3 + max_chance = 5 + var/new_form = /mob/living/carbon/human + var/bantype + var/transformed_antag_datum //Do we add a specific antag datum once the transformation is complete? + var/old_form + +/datum/symptom/acute/transformation/activate(mob/living/carbon/mob) + old_form = mob.type + do_disease_transformation(mob, new_form) + +/* +/datum/symptom/acute/transformation/deactivate(mob/living/carbon/mob, datum/disease/acute/disease, safe = FALSE) + do_disease_transformation(mob, old_form) + to_chat(mob, span_notice("You feel like yourself again!")) +*/ + +/datum/symptom/acute/transformation/proc/do_disease_transformation(mob/living/affected_mob, form) + if(!form) + return + if(affected_mob.stat != DEAD) + if(QDELETED(affected_mob)) + return + if(HAS_TRAIT_FROM(affected_mob, TRAIT_NO_TRANSFORM, REF(src))) + return + ADD_TRAIT(affected_mob, TRAIT_NO_TRANSFORM, REF(src)) + if(iscarbon(affected_mob)) + // AQ: nasze get_equipped_items nie ma flagi INCLUDE_POCKETS + 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 form(affected_mob.loc) + if(istype(new_mob)) + if(bantype && is_banned_from(affected_mob.ckey, bantype)) + replace_banned_player(new_mob, affected_mob) + new_mob.set_combat_mode(TRUE) + if(affected_mob.mind) + affected_mob.mind.transfer_to(new_mob) + else + new_mob.key = affected_mob.key + if(transformed_antag_datum && !QDELETED(new_mob.mind)) + // AQ: nie mamy limitu antagów ze storytellera (FLAG_ANTAG_CAP_IGNORE), więc tylko nadajemy datum + new_mob.mind.has_antag_datum(transformed_antag_datum) || new_mob.mind.add_antag_datum(transformed_antag_datum) + new_mob.name = affected_mob.real_name + new_mob.real_name = new_mob.name + qdel(affected_mob) + +/datum/symptom/acute/transformation/proc/replace_banned_player(mob/living/new_mob, mob/living/affected_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/mob/dead/observer/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [affected_mob.real_name]?", check_jobban = bantype, role = bantype, poll_time = 5 SECONDS, checked_target = affected_mob, alert_pic = affected_mob, role_name_text = "transformation victim") + if(isnull(chosen_one)) + to_chat(new_mob, span_userdanger("Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!")) + new_mob.investigate_log("has been killed because there was no one to replace them as a job-banned player.", INVESTIGATE_DEATHS) + new_mob.death() + if (!QDELETED(new_mob)) + new_mob.ghostize(can_reenter_corpse = FALSE) + return + to_chat(affected_mob, span_userdanger("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(chosen_one)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") + affected_mob.ghostize(FALSE) + affected_mob.key = chosen_one.key + + +/datum/symptom/acute/transformation/robot + name = "Robotic Transformation" + new_form = /mob/living/silicon/robot + bantype = JOB_NAME_CYBORG + desc = "Restructures the subject cells into a Cyborg. Cure: Synthetic Cleaner" + +/datum/symptom/acute/transformation/jungle_fever + name = "Jungle Fever" + desc = "Restructures the subject cells into a Monkey. Cure: Pulped Banana" + +/datum/symptom/acute/transformation/jungle_fever/activate(mob/living/carbon/mob) + mob.monkeyize() // so that they keep the virus + +/datum/symptom/acute/transformation/xeno + name = "Xenomorph Transformation" + new_form = /mob/living/carbon/alien/humanoid/hunter + bantype = ROLE_ALIEN + transformed_antag_datum = /datum/antagonist/xeno + desc = "Restructures the subject cells into a premative xeno hunter. Cure: Phlogisto" + +/datum/symptom/acute/transformation/slime + name = "Advanced Mutation Transformation" + new_form = /mob/living/simple_animal/slime + desc = "Restructures the subject cells into a slime. Cure: Water" + +/datum/symptom/acute/transformation/corgi + name = "The Barkening" + new_form = /mob/living/simple_animal/pet/dog/corgi + desc = "Restructures the subject cells into a corgi. Cure: Coco Powder" + +/datum/symptom/acute/transformation/morph + name = "Gluttony's Blessing" + new_form = /mob/living/simple_animal/hostile/morph + transformed_antag_datum = /datum/antagonist/morph + desc = "Restructures the subject cells into a morph. Cure: Lipolicide" + +/datum/symptom/acute/transformation/gondola + name = "Gondola Transformation" + max_chance = 10 + new_form = /mob/living/simple_animal/pet/gondola + desc = "Restructures the subject cells into a gondola. Cure: Condensed Capsaicin" + +/datum/symptom/acute/transformation/gondola/digital + max_chance = 50 + new_form = /mob/living/simple_animal/pet/gondola/virtual_domain + +/datum/symptom/acute/anxiety + name = "Severe Anxiety" + desc = "Causes the host to suffer from severe anxiety" + stage = 1 + badness = EFFECT_DANGER_ANNOYING + severity = 3 + restricted = TRUE + max_multiplier = 4 + +/datum/symptom/acute/anxiety/activate(mob/living/carbon/mob, datum/disease/acute/disease) + + switch(round(multiplier, 1)) + if(2) //also changes say, see say.dm + if(prob(2.5)) + to_chat(mob, span_notice("You feel anxious.")) + if(3) + if(prob(5)) + to_chat(mob, span_notice("Your stomach flutters.")) + if(prob(2.5)) + to_chat(mob, span_notice("You feel panicky.")) + if(prob(1)) + to_chat(mob, span_danger("You're overtaken with panic!")) + mob.confused += (rand(2 SECONDS, 3 SECONDS)) + if(4) + if(prob(5)) + to_chat(mob, span_danger("You feel butterflies in your stomach.")) + if(prob(2.5)) + mob.visible_message(span_danger("[mob] stumbles around in a panic."), \ + span_userdanger("You have a panic attack!")) + mob.confused += (rand(6 SECONDS, 8 SECONDS)) + mob.Jitter(rand(12 SECONDS, 16 SECONDS)) + if(prob(1)) + mob.visible_message(span_danger("[mob] coughs up butterflies!"), \ + span_userdanger("You cough up butterflies!")) + new /mob/living/simple_animal/butterfly(mob.loc) + new /mob/living/simple_animal/butterfly(mob.loc) + +/datum/symptom/acute/death_sandwich + name = "Death Sandwich" + desc = "You ate it wrong, and now you will die. Cure: Anacea" + stage = 1 + badness = EFFECT_DANGER_DEADLY + severity = 5 + restricted = TRUE + max_multiplier = 3 + chance = 25 + max_chance = 25 + +/datum/symptom/acute/death_sandwich/activate(mob/living/carbon/mob, datum/disease/acute/disease) + switch(round(multiplier)) + if(1) + if(prob(12)) + mob.emote("cough") + if(prob(4)) + mob.emote("gag") + if(prob(4)) + mob.adjustToxLoss(5) + if(2) + if(prob(40)) + mob.emote("cough") + if(prob(20)) + mob.emote("gag") + if(prob(8)) + to_chat(mob, span_danger("Your body feels hot!")) + if(prob(20)) + mob.take_bodypart_damage(burn = 1) + if(prob(24)) + mob.adjustToxLoss(10) + if(3) + if(prob(40)) + mob.emote("gag") + if(prob(80)) + mob.emote("gasp") + if(prob(20)) + mob.vomit(20, TRUE) + if(prob(20)) + to_chat(mob, span_danger("Your body feels hot!")) + if(prob(60)) + mob.take_bodypart_damage(burn = 2) + if(prob(48)) + mob.adjustToxLoss(15) + if(prob(12)) + to_chat(mob, span_danger("You try to scream, but nothing comes out!")) + mob.set_silence_if_lower(5 SECONDS) + // AQ (nie mamy wyciszania emotek): mob.set_emote_mute_if_lower(5 SECONDS) + multiplier_tweak(0.1) diff --git a/aquila/code/modules/virology/disease/symtoms/restricted/stage2.dm b/aquila/code/modules/virology/disease/symtoms/restricted/stage2.dm new file mode 100644 index 00000000000..95307babeeb --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/restricted/stage2.dm @@ -0,0 +1,222 @@ +/datum/symptom/acute/narcolepsy + name = "Aurora Snorealis" + desc = "The virus causes a hormone imbalance, making the host sleepy and narcoleptic." + stage = 2 + restricted = TRUE + badness = EFFECT_DANGER_ANNOYING + severity = 3 + max_multiplier = 5 + var/yawning = FALSE + + +/datum/symptom/acute/narcolepsy/activate(mob/living/carbon/M) + switch(round(multiplier, 1)) + if(1) + if(prob(50)) + to_chat(M, span_warning("You feel tired.")) + if(2) + if(prob(50)) + to_chat(M, span_warning("You feel very tired.")) + if(3) + if(prob(50)) + to_chat(M, span_warning("You try to focus on staying awake.")) + + M.drowsyness = min(M.drowsyness + (2.5 SECONDS), 20 SECONDS) + + if(4) + if(prob(50)) + if(yawning) + to_chat(M, span_warning("You try and fail to suppress a yawn.")) + else + to_chat(M, span_warning("You nod off for a moment.")) //you can't really yawn while nodding off, can you? + + M.drowsyness = min(M.drowsyness + (5 SECONDS), 20 SECONDS) + + if(yawning) + M.emote("yawn") + if(M.check_airborne_sterility()) + return + var/strength = 0 + for (var/datum/disease/acute/V as anything in M.diseases) + strength += V.infectionchance + strength = round(strength/M.diseases.len) + + var/i = 1 + while (strength > 0 && i < 10) //stronger viruses create more clouds at once, max limit of 10 clouds + new /obj/effect/pathogen_cloud/core(get_turf(src), M, virus_copylist(M.diseases)) + strength -= 30 + i++ + + if(5) + if(prob(50)) + to_chat(M, span_warning("[pick("So tired...","You feel very sleepy.","You have a hard time keeping your eyes open.","You try to stay awake.")]")) + + M.drowsyness = min(M.drowsyness + (10 SECONDS), 20 SECONDS) + + if(yawning) + M.emote("yawn") + if(M.check_airborne_sterility()) + return + var/strength = 0 + for (var/datum/disease/acute/V as anything in M.diseases) + strength += V.infectionchance + strength = round(strength/M.diseases.len) + + var/i = 1 + while (strength > 0 && i < 10) //stronger viruses create more clouds at once, max limit of 10 clouds + new /obj/effect/pathogen_cloud/core(get_turf(src), M, virus_copylist(M.diseases)) + strength -= 30 + i++ + + +#define STARLIGHT_CAN_HEAL 2 +#define STARLIGHT_CAN_HEAL_WITH_PENALTY 1 +#define STARLIGHT_CANNOT_HEAL 0 +#define STARLIGHT_MAX_RANGE 2 + +/datum/symptom/acute/starlight + name = "Starlight Condensation" + desc = "The virus reacts to direct starlight, producing regenerative chemicals. Works best against toxin-based damage." + max_multiplier = 5 + stage = 2 + restricted = TRUE + badness = EFFECT_DANGER_HELPFUL + severity = 0 + + var/list/passive_message = span_notice("You miss the feeling of starlight on your skin.") + var/nearspace_penalty = 0.3 + +/datum/symptom/acute/starlight/activate(mob/living/carbon/mob) + + var/mob/living/M = mob + switch(round(multiplier)) + if(4, 5) + var/effectiveness = CanHeal(mob) + if(!effectiveness) + if(passive_message && prob(2) && passive_message_condition(M)) + to_chat(M, passive_message) + return + else + Heal(M, effectiveness) + return + +/datum/symptom/acute/starlight/proc/CanTileHealDirectional(turf/turf_to_check, direction) + if(direction == UP) + turf_to_check = GET_TURF_ABOVE(turf_to_check) + if(!turf_to_check) + return STARLIGHT_CANNOT_HEAL + var/area/area_to_check = get_area(turf_to_check) + var/levels_of_glass = 0 // Since starlight condensation only works 2 tiles to the side anyways, it shouldn't work with like 100 z-levels of glass + while(levels_of_glass <= STARLIGHT_MAX_RANGE) + // Outdoors covers lavaland and unroofed areas but with tiles under, + // while space covers normal space and those caused by explosions, + // if there is a floor tile when checking above, that means + // a roof exists so the outdoors should only work downwards + if(isspaceturf(turf_to_check) || (area_to_check.outdoors && direction == DOWN)) + if (levels_of_glass) + return STARLIGHT_CAN_HEAL_WITH_PENALTY // Glass gives a penalty. + return STARLIGHT_CAN_HEAL // No glass = can heal fully. + + // Our turf is transparent, but it's NOT openspace - it's something like glass which reduces power + if(istransparentturf(turf_to_check) && !(istype(turf_to_check, /turf/open/openspace))) + levels_of_glass += 1 + + // Our turf is transparent OR openspace - we can check higher or lower z-levels + if(istransparentturf(turf_to_check) || istype(turf_to_check, /turf/open/openspace)) + // Check above or below us + if(direction == UP) + turf_to_check = GET_TURF_ABOVE(turf_to_check) + else + turf_to_check = locate(turf_to_check.x, turf_to_check.y, turf_to_check.z - 1) + + // If we found a turf above or below us, + // then we can rerun the loop on the newly found turf / area + // (Probably, with +1 to levels_of_glass) + if(turf_to_check) + area_to_check = get_area(turf_to_check) + continue + + // If we didn't find a turf above or below us - + // Checking below, we assume that space is below us (as we're standing on station) + // Checking above, we check that the area is "outdoors" before assuming if it is space or not. + else + if(direction == DOWN || (direction == UP && area_to_check.outdoors)) + if (levels_of_glass) + return STARLIGHT_CAN_HEAL_WITH_PENALTY + return STARLIGHT_CAN_HEAL + + return STARLIGHT_CANNOT_HEAL // Hit a non-space, Non-transparent turf - no healsies + +/datum/symptom/acute/starlight/proc/CanTileHeal(turf/original_turf, satisfied_with_penalty) + var/current_heal_level = CanTileHealDirectional(original_turf, DOWN) + if(current_heal_level == STARLIGHT_CAN_HEAL) + return current_heal_level + if(current_heal_level && satisfied_with_penalty) // do not care if there is a healing penalty or no + return current_heal_level + var/heal_level_from_above = CanTileHealDirectional(original_turf, UP) + if(heal_level_from_above > current_heal_level) + return heal_level_from_above + else + return current_heal_level + +/datum/symptom/acute/starlight/proc/CanHeal(mob/living/carbon/mob) + var/mob/living/affected_mob = mob + var/turf/turf_of_mob = get_turf(affected_mob) + switch(CanTileHeal(turf_of_mob, FALSE)) + if(STARLIGHT_CAN_HEAL_WITH_PENALTY) + return power * nearspace_penalty + if(STARLIGHT_CAN_HEAL) + return power + for(var/turf/turf_to_check in view(affected_mob, STARLIGHT_MAX_RANGE)) + if(CanTileHeal(turf_to_check, TRUE)) + return power * nearspace_penalty + +#undef STARLIGHT_CAN_HEAL +#undef STARLIGHT_CAN_HEAL_WITH_PENALTY +#undef STARLIGHT_CANNOT_HEAL +#undef STARLIGHT_MAX_RANGE + +/datum/symptom/acute/starlight/proc/Heal(mob/living/M, actual_power) + var/heal_amt = actual_power + if(M.getToxLoss() && prob(5)) + to_chat(M, span_notice("Your skin tingles as the starlight seems to heal you.")) + + M.adjustToxLoss(-4 * heal_amt, updating_health = FALSE) //most effective on toxins + M.heal_overall_damage(heal_amt, heal_amt, required_bodytype = BODYTYPE_ORGANIC) + return TRUE + +/datum/symptom/acute/starlight/proc/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss() || M.getToxLoss()) + return TRUE + return FALSE + +/datum/symptom/acute/toxolysis + name = "Toxolysis" + desc = "The virus rapidly breaks down any foreign chemicals in the bloodstream." + max_multiplier = 10 + stage = 2 + badness = EFFECT_DANGER_HELPFUL + severity = 0 + var/food_conversion = FALSE + +/datum/symptom/acute/toxolysis/activate(mob/living/carbon/mob, datum/disease/acute/disease) + . = ..() + var/mob/living/M = mob + switch(round(multiplier)) + if(9, 10) + food_conversion = TRUE + Heal(M, multiplier) + if(4, 5, 6, 7, 8) + Heal(M, multiplier) + else + multiplier = min(multiplier + 0.1, max_multiplier) + return + +/datum/symptom/acute/toxolysis/proc/Heal(mob/living/M, 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, span_notice("You feel a mild warmth as your blood purifies itself.")) + return TRUE diff --git a/aquila/code/modules/virology/disease/symtoms/restricted/stage3.dm b/aquila/code/modules/virology/disease/symtoms/restricted/stage3.dm new file mode 100644 index 00000000000..debaef19d82 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/restricted/stage3.dm @@ -0,0 +1,73 @@ +/datum/symptom/acute/bad_adrenaline + name = "Bad Adrenaline" + desc = "If left untreated the subject will suffer from lethargy, dizziness and periodic loss of conciousness." + stage = 3 + restricted = TRUE + max_multiplier = 2 + badness = EFFECT_DANGER_HINDRANCE + severity = 3 + +/datum/symptom/acute/bad_adrenaline/activate(mob/living/carbon/affected_mob) + switch(round(multiplier)) + if(1) + if(prob(2.5)) + to_chat(affected_mob, span_warning(pick("You feel lightheaded.", "You feel lethargic."))) + if(2) + if(prob(5)) + affected_mob.Unconscious(40) + + if(prob(10)) + affected_mob.adjust_slurring(14 SECONDS) + + if(prob(7)) + affected_mob.set_dizzy_if_lower(20 SECONDS) + + if(prob(2.5)) + to_chat(affected_mob, span_warning(pick("You feel pain shoot down your legs!", "You feel like you are going to pass out at any moment.", "You feel really dizzy."))) + +/datum/symptom/acute/mutation + name = "DNA Degradation" + desc = "Attacks the infected's DNA, causing it to break down." + stage = 3 + badness = EFFECT_DANGER_DEADLY + severity = 5 + max_multiplier = 5 + restricted = TRUE + +/datum/symptom/acute/mutation/activate(mob/living/carbon/mob) + switch(round(multiplier, 1)) + if(2) + if(prob(1)) + mob.emote("itch") + if(prob(1)) + mob.emote("yawn") + if(3) + if(prob(1)) + mob.emote("itch") + if(prob(1)) + mob.emote("drool") + if(prob(1.5)) + mob.adjustToxLoss(1, FALSE) + if(prob(1)) + to_chat(mob, span_danger("Your skin feels strange.")) + + if(4) + if(prob(1)) + mob.emote("itch") + if(prob(1)) + mob.emote("drool") + if(prob(2.5)) + mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 170) + mob.adjustToxLoss(2, FALSE) + if(prob(7.5)) + mob.adjust_stutter(6 SECONDS) + if(5) + if(prob(1)) + mob.emote("itch") + if(prob(1)) + mob.emote("drool") + if(prob(2.5)) + to_chat(mob, span_danger("Your skin starts degrading!")) + if(prob(5)) + mob.adjustToxLoss(5, FALSE) + mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170) diff --git a/aquila/code/modules/virology/disease/symtoms/restricted/stage4.dm b/aquila/code/modules/virology/disease/symtoms/restricted/stage4.dm new file mode 100644 index 00000000000..fb0f6772f97 --- /dev/null +++ b/aquila/code/modules/virology/disease/symtoms/restricted/stage4.dm @@ -0,0 +1,170 @@ +/datum/symptom/acute/heart_failure + name = "Myocardial Infarction" + desc = "If left untreated the subject will die!" + restricted = TRUE + max_multiplier = 5 + chance = 6 + badness = EFFECT_DANGER_DEADLY + severity = 5 + var/sound = FALSE + var/final_timer + +/datum/symptom/acute/heart_failure/Destroy(force) + if(final_timer) + deltimer(final_timer) + final_timer = null + return ..() + +/datum/symptom/acute/heart_failure/activate(mob/living/carbon/affected_mob, datum/disease/acute/disease) + . = ..() + if(ismouse(affected_mob)) + affected_mob.death() + return FALSE + + if(!affected_mob.can_heartattack()) + disease?.cure(target = affected_mob) + return + else if(final_timer || affected_mob.undergoing_cardiac_arrest()) // don't bother ticking if their heart has already stopped or is getting ready to + return + + switch(round(multiplier)) + if(1 to 2) + if(prob(5)) + to_chat(affected_mob, span_warning("You feel [pick("discomfort", "pressure", "a burning sensation", "pain")] in your chest.")) + if(prob(5)) + to_chat(affected_mob, span_warning("You feel dizzy.")) + affected_mob.confused += (6 SECONDS) + if(prob(7.5)) + to_chat(affected_mob, span_warning("You feel [pick("full", "nauseated", "sweaty", "weak", "tired", "short of breath", "uneasy")].")) + if(3 to 4) + if(!sound) + affected_mob.playsound_local(affected_mob, 'sound/health/slowbeat.ogg', vol = 40, vary = FALSE, channel = CHANNEL_HEARTBEAT, pressure_affected = FALSE, use_reverb = FALSE) + sound = TRUE + if(prob(7.5)) + to_chat(affected_mob, span_danger("You feel a sharp pain in your chest!")) + if(prob(30)) + affected_mob.vomit(95) + affected_mob.emote("cough") + affected_mob.Paralyze(4 SECONDS) + affected_mob.losebreath += 4 + if(prob(7.5)) + to_chat(affected_mob, span_danger("You feel very weak and dizzy...")) + affected_mob.confused += (8 SECONDS) + affected_mob.adjustStaminaLoss(20, FALSE) + affected_mob.emote("cough") + if(5) + affected_mob.stop_sound_channel(CHANNEL_HEARTBEAT) + affected_mob.playsound_local(affected_mob, 'sound/effects/singlebeat.ogg', vol = 100, vary = FALSE, channel = CHANNEL_HEARTBEAT, pressure_affected = FALSE, use_reverb = FALSE) + affected_mob.adjustStaminaLoss(30, FALSE) + // To give the victim a final chance to shock their heart before losing consciousness + final_timer = addtimer(CALLBACK(src, PROC_REF(finally_stop_heart), affected_mob), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_STOPPABLE) + return FALSE + multiplier_tweak(0.1) + +/datum/symptom/acute/heart_failure/deactivate(mob/living/carbon/affected_mob, datum/disease/acute/disease, safe = FALSE) + . = ..() + if(final_timer) + deltimer(final_timer) + final_timer = null + if(iscarbon(affected_mob)) + affected_mob.set_heartattack(FALSE) + +/datum/symptom/acute/heart_failure/proc/finally_stop_heart(mob/living/carbon/target) + final_timer = null + if(!QDELETED(src) && !QDELETED(target)) + target.set_heartattack(TRUE) + +/datum/symptom/acute/catapult_sneeze + name = "Sneezing?" + desc = "The virus causes irritation of the nasal cavity, making the host sneeze occasionally. Sneezes from this symptom will spread the virus in a 4 meter cone in front of the host." + restricted = TRUE + stage = 4 + max_multiplier = 10 + badness = EFFECT_DANGER_HARMFUL + severity = 3 + COOLDOWN_DECLARE(launch_cooldown) + +/datum/symptom/acute/catapult_sneeze/activate(mob/living/mob) + mob.emote("sneeze") + + if(prob(5 * multiplier) && COOLDOWN_FINISHED(src, launch_cooldown)) + to_chat(mob, span_userdanger("You are launched violently backwards by an all-mighty sneeze!")) + var/launch_distance = round(multiplier) + var/turf/target = get_ranged_target_turf(mob, turn(mob.dir, 180), launch_distance) + mob.throw_at(target, launch_distance, rand(3,9)) //with the wounds update, sneezing at 7 speed was causing peoples bones to spontaneously explode, turning cartoonish sneezing into a nightmarishly lethal GBS 2.0 outbreak + COOLDOWN_START(src, launch_cooldown, 10 SECONDS) + + if(ishuman(mob)) + var/mob/living/carbon/human/host = mob + if (prob(50) && isturf(mob.loc)) + if(istype(host.wear_mask, /obj/item/clothing/mask/cigarette)) + var/obj/item/clothing/mask/cigarette/I = host.get_item_by_slot(ITEM_SLOT_MASK) + if(prob(20)) + var/turf/Q = get_turf(mob) + var/turf/endLocation + var/spitForce = pick(0,1,2,3) + endLocation = get_ranged_target_turf(Q, mob.dir, spitForce) + to_chat(mob, "You sneezed \the [host.wear_mask] out of your mouth!") + host.dropItemToGround(I) + I.throw_at(endLocation,spitForce,1) + +/datum/symptom/acute/fungal_tb + name = "Fungal tuberculosis" + desc = "Rumoured to be carefully grown and cultured by clandestine bio-weapon specialists. Causes fever, blood vomiting, lung damage, weight loss, and fatigue. Cure: Convermol and Spaceallin" + restricted = TRUE + max_multiplier = 5 + stage = 4 + badness = EFFECT_DANGER_DEADLY + severity = 6 + max_chance = 75 + chance = 50 + +/datum/symptom/acute/fungal_tb/activate(mob/living/affected_mob) + if(HAS_TRAIT(affected_mob, TRAIT_NOBREATH)) + return + if(prob(10)) + multiplier_tweak(0.1) + switch(round(multiplier)) + if(1, 2) + if(prob(2.5)) + affected_mob.emote("cough") + to_chat(affected_mob, span_danger("Your chest hurts.")) + if(prob(2.5)) + to_chat(affected_mob, span_danger("Your stomach violently rumbles!")) + if(prob(7.5)) + to_chat(affected_mob, span_danger("You feel a cold sweat form.")) + if(3, 4) + if(prob(2.5)) + to_chat(affected_mob, span_userdanger("You see four of everything!")) + affected_mob.set_dizzy_if_lower(10 SECONDS) + if(prob(2.5)) + to_chat(affected_mob, span_danger("You feel a sharp pain from your lower chest!")) + affected_mob.adjustOxyLoss(5, FALSE) + affected_mob.emote("gasp") + if(prob(12.5)) + to_chat(affected_mob, span_danger("You feel air escape from your lungs painfully.")) + affected_mob.adjustOxyLoss(25, FALSE) + affected_mob.emote("gasp") + if(5) + if(prob(2.5)) + to_chat(affected_mob, span_userdanger("[pick("You feel your heart slowing...", "You relax and slow your heartbeat.")]")) + affected_mob.adjustStaminaLoss(35, FALSE) + if(prob(12.5)) + affected_mob.adjustStaminaLoss(50, FALSE) + affected_mob.visible_message(span_warning("[affected_mob] faints!"), span_userdanger("You surrender yourself and feel at peace...")) + affected_mob.AdjustSleeping(100) + if(prob(2.5)) + to_chat(affected_mob, span_userdanger("You feel your mind relax and your thoughts drift!")) + affected_mob.confused = min(affected_mob.confused + (8 SECONDS), 100 SECONDS) + if(prob(12.5)) + if(ishuman(affected_mob)) + var/mob/living/carbon/human/human = affected_mob + human.vomit(20) + if(prob(4)) + to_chat(affected_mob, span_warning("[pick("Your stomach silently rumbles...", "Your stomach seizes up and falls limp, muscles dead and lifeless.", "You could eat a crayon")]")) + affected_mob.overeatduration = max(affected_mob.overeatduration - (200 SECONDS), 0) + affected_mob.adjust_nutrition(-100) + if(prob(15)) + to_chat(affected_mob, span_danger("[pick("You feel uncomfortably hot...", "You feel like unzipping your jumpsuit...", "You feel like taking off some clothes...")]")) + affected_mob.adjust_bodytemperature(40) + diff --git a/aquila/code/modules/virology/effects/cleanables.dm b/aquila/code/modules/virology/effects/cleanables.dm new file mode 100644 index 00000000000..cb1647d8fb6 --- /dev/null +++ b/aquila/code/modules/virology/effects/cleanables.dm @@ -0,0 +1,51 @@ +GLOBAL_LIST_EMPTY(infected_cleanables) + +/obj/effect/decal/cleanable/Initialize(mapload, list/datum/disease/diseases) + ..() + // AQ: nasz rdzeń dostaje listę chorób, ale jej nie trzyma (robi to tylko komponent infective), + // a patogeny muszą w plamie zostać, żeby dalej zarażały przez kontakt i powietrze. + for(var/datum/disease/acute/infection as anything in diseases) + if(istype(infection)) + LAZYOR(src.diseases, infection) + return INITIALIZE_HINT_LATELOAD + +/obj/effect/decal/cleanable/LateInitialize(mapload_arg) + //cleanables can get infected in many different ways when they spawn so it's much easier to handle the pathogen overlay here after a delay + addtimer(CALLBACK(src, PROC_REF(setup_diseases)), 0.1 SECONDS) + +/obj/effect/decal/cleanable/proc/setup_diseases() + if(!length(src.diseases) || QDELETED(src)) + return + GLOB.infected_cleanables += src + if (!pathogen) + pathogen = image('aquila/icons/virology/effects.dmi', src, "pathogen_blood") + pathogen.plane = HUD_PLANE + pathogen.appearance_flags = RESET_COLOR|RESET_ALPHA + for (var/mob/L in GLOB.virus_viewers) + if (L.client) + L.client.images |= pathogen + +/obj/effect/decal/cleanable/Destroy() + GLOB.infected_cleanables -= src + return ..() + +/obj/effect/decal/cleanable/Entered(mob/living/perp) + ..() + infection_attempt(perp) + +/obj/effect/decal/cleanable/infection_attempt(mob/living/perp) + //Now if your feet aren't well protected, or are bleeding, you might get infected. + var/block = 0 + var/bleeding = 0 + if (perp.lying & LYING_DOWN) + block = perp.check_contact_sterility(BODY_ZONE_EVERYTHING) + bleeding = perp.check_bodypart_bleeding(BODY_ZONE_EVERYTHING) + else + block = perp.check_contact_sterility(BODY_ZONE_LEGS) + bleeding = perp.check_bodypart_bleeding(BODY_ZONE_LEGS) + + for(var/datum/disease/acute/contained_virus as anything in diseases) + if (!block && (contained_virus.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)) + perp.infect_disease(contained_virus, notes="(Contact, from [(perp.lying & LYING_DOWN)?"lying":"standing"] over [src]])") + else if (bleeding && (contained_virus.spread_flags & DISEASE_SPREAD_BLOOD)) + perp.infect_disease(contained_virus, notes="(Blood, from [(perp.lying & LYING_DOWN)?"lying":"standing"] over [src]])") diff --git a/aquila/code/modules/virology/effects/pathogen_cloud.dm b/aquila/code/modules/virology/effects/pathogen_cloud.dm new file mode 100644 index 00000000000..0ebb31b8174 --- /dev/null +++ b/aquila/code/modules/virology/effects/pathogen_cloud.dm @@ -0,0 +1,133 @@ +GLOBAL_LIST_INIT(pathogen_clouds, list()) +GLOBAL_LIST_INIT(virus_viewers, list()) + +/obj/effect/pathogen_cloud + name = "" + icon = 'aquila/icons/virology/96x96.dmi' + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + icon_state = "" + color = COLOR_GREEN + pixel_x = -32 + pixel_y = -32 + opacity = 0 + anchored = 0 + density = 0 + var/mob/source = null + var/sourceIsCarrier = TRUE + var/list/viruses = list() + var/lifetime = 10 SECONDS //how long until we naturally disappear, humans breath about every 8 seconds, so it has to survive at least this long to have a chance to infect + var/turf/target = null //when created, we'll slowly move toward this turf + var/core = FALSE + var/modified = FALSE + var/moving = TRUE + var/list/id_list = list() + +/obj/effect/pathogen_cloud/Initialize(mapload, mob/sourcemob, list/virus, isCarrier = TRUE, isCore = TRUE) + . = ..() + if (!loc || length(virus) <= 0) + return INITIALIZE_HINT_QDEL + if(!SSpathogen_clouds.can_fire) // presumably an admin has manually disabled this bc it's being laggy as shit + return INITIALIZE_HINT_QDEL + core = isCore + sourceIsCarrier = isCarrier + GLOB.pathogen_clouds += src + + viruses = virus + + for(var/datum/disease/acute/D as anything in viruses) + id_list += "[D.uniqueID]-[D.subID]" + + if(!core) + var/obj/effect/pathogen_cloud/core/core = locate(/obj/effect/pathogen_cloud/core) in src.loc + if(get_turf(core) == get_turf(src)) + core.add_virus(viruses) + return INITIALIZE_HINT_QDEL + + if(istype(src, /obj/effect/pathogen_cloud/core)) + SSpathogen_clouds.cores += src + else + SSpathogen_clouds.clouds += src + + pathogen = image('aquila/icons/virology/96x96.dmi',src,"pathogen_airborne") + pathogen.plane = HUD_PLANE + pathogen.appearance_flags = RESET_COLOR|RESET_ALPHA + for (var/mob/living/wearer as anything in GLOB.virus_viewers) + if(QDELETED(wearer) || QDELETED(wearer.client)) + continue + wearer.client.images |= pathogen + + source = sourcemob + + QDEL_IN(src, lifetime) + +/obj/effect/pathogen_cloud/proc/add_virus(list/viruses) + . = FALSE + if(!islist(viruses)) + viruses = list(viruses) + for(var/datum/disease/acute/virus as anything in viruses) + var/id = "[virus.uniqueID]-[virus.subID]" + if(id in id_list) + continue + viruses += virus.Copy() + id_list += id + . = TRUE + if(.) + modified = TRUE + +/obj/effect/pathogen_cloud/core + core = TRUE + +/obj/effect/pathogen_cloud/Destroy() + if(istype(src, /obj/effect/pathogen_cloud/core)) + SSpathogen_clouds.cores -= src + SSpathogen_clouds.current_run_cores -= src + else + SSpathogen_clouds.clouds -= src + SSpathogen_clouds.current_run_clouds -= src + + if (pathogen) + for (var/mob/living/wearer as anything in GLOB.virus_viewers) + if(QDELETED(wearer) || QDELETED(wearer.client)) + continue + wearer.client.images -= pathogen + pathogen = null + GLOB.pathogen_clouds -= src + source = null + viruses = list() + lifetime = 3 + target = null + return ..() + +/obj/effect/pathogen_cloud/core/Initialize(mapload, mob/sourcemob, list/virus) + . = ..() + if(.) + return + + var/strength = 0 + for (var/datum/disease/acute/V as anything in viruses) + strength += V.infectionchance + strength = round(strength / length(viruses)) + var/list/possible_turfs = list() + var/max_range = clamp((strength / 20) - 1, 0, 7) + for (var/turf/open/T in range(max_range, loc))//stronger viruses can reach turfs further away. + possible_turfs += T + if(!length(possible_turfs)) + return INITIALIZE_HINT_QDEL + target = pick(possible_turfs) + START_PROCESSING(SSfastprocess, src) + +/obj/effect/pathogen_cloud/core/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/effect/pathogen_cloud/core/process(seconds_per_tick) + var/turf/open/turf = get_turf(src) + if ((turf != target) && moving) + if (prob(75)) + if(!step_towards(src,target)) // we hit a wall and our momentum is shattered + moving = FALSE + else + step_rand(src) + var/obj/effect/pathogen_cloud/C = new /obj/effect/pathogen_cloud(turf, source, viruses, sourceIsCarrier, FALSE) + C.modified = modified + C.moving = FALSE diff --git a/aquila/code/modules/virology/fullscreen.dm b/aquila/code/modules/virology/fullscreen.dm new file mode 100644 index 00000000000..bc0897e90db --- /dev/null +++ b/aquila/code/modules/virology/fullscreen.dm @@ -0,0 +1,4 @@ +/atom/movable/screen/fullscreen/science + //icon = 'aquila/icons/virology/screens.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "science" diff --git a/aquila/code/modules/virology/immune_systems/_immune_system.dm b/aquila/code/modules/virology/immune_systems/_immune_system.dm new file mode 100644 index 00000000000..d7d6c855896 --- /dev/null +++ b/aquila/code/modules/virology/immune_systems/_immune_system.dm @@ -0,0 +1,220 @@ +/datum/immune_system + var/mob/living/carbon/host = null + var/strength = 1 + var/boost = 1 + var/overloaded = FALSE + var/list/antibodies = list( + ANTIGEN_O = 0, + ANTIGEN_A = 0, + ANTIGEN_B = 0, + ANTIGEN_RH = 0, + ANTIGEN_Q = 0, + ANTIGEN_U = 0, + ANTIGEN_V = 0, + ANTIGEN_M = 0, + ANTIGEN_N = 0, + ANTIGEN_P = 0, + ANTIGEN_X = 0, + ANTIGEN_Y = 0, + ANTIGEN_Z = 0, + ANTIGEN_IG = 0, + ) + +/datum/immune_system/Destroy(force) + host = null + antibodies = null + return ..() + +/datum/immune_system/New(mob/living/carbon/host, boost = 1) + ..() + if(QDELETED(host)) + stack_trace("Attempted to initialize immune system on invalid entity!") + qdel(src) + return + src.host = host + src.boost = boost + for(var/antibody in antibodies) + if(antibody in GLOB.rare_antigens) + antibodies[antibody] = rand(1, 15) * boost + if (prob(5)) + antibodies[antibody] += 10 * boost + if(antibody in GLOB.common_antigens) + antibodies[antibody] = rand(10, 30) * boost + if(antibody in GLOB.blood_antigens) + antibodies[antibody] = rand(10, 20) * boost + // AQ: dna jest tylko na /mob/living/carbon, a uklad odpornosciowy maja tez myszy + var/blood_type = iscarbon(host) ? host.dna?.blood_type : null + if(blood_type) + switch(antibody) + if(ANTIGEN_O) + antibodies[antibody] += rand(12, 15) * boost + if(ANTIGEN_A) + if(findtext(blood_type, "A")) + antibodies[antibody] += rand(12, 15) * boost + if(ANTIGEN_B) + if(findtext(blood_type, "B")) + antibodies[antibody] += rand(12, 15) * boost + if(ANTIGEN_RH) + if(findtext(blood_type, "+")) + antibodies[antibody] += rand(12, 15) * boost + if(boost > 1) + antibodies[antibody] = min(antibodies[antibody], boost) + +/datum/immune_system/proc/transfer_to(mob/living/carbon/source) + if(QDELETED(source)) + CRASH("Attempted to transfer immune system to non-existant mob") + if(QDELETED(source.immune_system)) + source.immune_system = new(source) + + source.immune_system.strength = strength + source.immune_system.boost = boost + source.immune_system.overloaded = overloaded + source.immune_system.antibodies = antibodies.Copy() + +/datum/immune_system/proc/change_boost(new_boost = 1) + var/old_boost = boost + if(new_boost == old_boost) + return + for(var/antibody in antibodies) + antibodies[antibody] /= old_boost + antibodies[antibody] *= new_boost + boost = new_boost + +/datum/immune_system/proc/change_strength(new_strength = 1) + var/old_strength = strength + if(new_strength == old_strength) + return + strength = new_strength + +/datum/immune_system/proc/GetImmunity() + return list(strength, antibodies.Copy()) + +/datum/immune_system/proc/Overload() + host.adjustToxLoss(100) + host.AddComponent(/datum/component/radioactive) + host.bodytemperature = max(host.bodytemperature, BODYTEMP_HEAT_DAMAGE_LIMIT) + to_chat(host, span_danger("A terrible fever assails your host, you feel ill as your immune system kicks into overdrive to drive away your infections.")) + if (ishuman(host)) + var/mob/living/carbon/human/H = host + H.vomit(0,1)//hope you're wearing a biosuit or you'll get reinfected from your vomit, lol + for(var/ID in host.diseases) + var/datum/disease/acute/D = host.diseases[ID] + D.cure(target = host) + strength = 0 + overloaded = TRUE + + +//If even one antibody has sufficient concentration, the disease won't be able to infect +/datum/immune_system/proc/CanInfect(datum/disease/acute/disease) + if (overloaded || (HAS_TRAIT(host, TRAIT_IMMUNODEFICIENCY) && !HAS_TRAIT(host, TRAIT_VIRUS_RESISTANCE))) + return TRUE + + if(HAS_TRAIT(host, TRAIT_VIRUSIMMUNE)) + return FALSE + + for (var/antigen in disease.antigen) + if ((antibodies[antigen]) >= disease.strength) + return FALSE + return TRUE + +/datum/immune_system/proc/ApplyAntipathogenics(threshold) + if(overloaded) + return + + for(var/datum/disease/acute/disease as anything in host.diseases) + for(var/A in disease.antigen) + var/tally = 0.5 + if (isturf(host.loc) && (host.lying)) + tally += 0.5 + var/obj/structure/bed/B = locate() in host.loc + if (host.buckled == B)//fucking chairs n stuff + tally += 1 + if (host.IsUnconscious()) + if (tally < 2) + tally += 1 + else + tally += 2//if we're sleeping in a bed, we get up to 4 + else if(istype(host.loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) + tally += 2.5 + if(HAS_TRAIT(host, TRAIT_IMMUNODEFICIENCY) && !HAS_TRAIT(host, TRAIT_VIRUS_RESISTANCE)) + tally -= 1 + + tally *= boost + if (antibodies[A] < threshold) + antibodies[A] = min(antibodies[A] + tally, threshold)//no overshooting here + else + if (prob(threshold) && prob(tally * 10) && prob((100 - antibodies[A])*100/(100-threshold)))//smaller and smaller chance for further increase + antibodies[A] = min(antibodies[A] + 1, 100) + +/datum/immune_system/proc/NaturalImmune() //called with a 8% chance every time a virus activates + for (var/datum/disease/acute/disease as anything in host.diseases) + for (var/A in disease.antigen) + var/tally = 1.5 + if (isturf(host.loc) && (host.lying)) + tally += 0.5 + var/obj/structure/bed/B = locate() in host.loc + if (host.buckled == B)//fucking chairs n stuff + tally += 0.5 + if (host.IsUnconscious() || host.IsSleeping()) + if (tally < 2.5) + tally += 1 + else + tally += 2//if we're sleeping in a bed, we get up to 5.5 + else if(istype(host.loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) + tally += 3.5 + + if(!HAS_TRAIT(host, TRAIT_NOHUNGER)) + switch(host.nutrition) + if(NUTRITION_LEVEL_FAT to INFINITY) + tally -= 0.5 + if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) + tally += 1.5 + if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) + tally += 1 + if(0 to NUTRITION_LEVEL_STARVING) + tally = max(tally - 1.5, 0.5) + if(HAS_TRAIT(host, TRAIT_IMMUNODEFICIENCY) && !HAS_TRAIT(host, TRAIT_VIRUS_RESISTANCE)) + tally -= 1 + + tally *= boost + if (antibodies[A] < 69) + antibodies[A] = min(antibodies[A] + tally * strength, 70) + else + if(strength < 0.7) //stop trying at all once below 70% strength and above 70% antibodies + return + if(prob(80)) //immune system begins gaining attrition over 70% antigen. + strength = max(strength - 0.05, 0) + antibodies[A] = min(antibodies[A] + tally * strength, 100) + +// fix immune system damage +/datum/immune_system/proc/ImmuneRepair(level, threshold) + if(HAS_TRAIT(host, TRAIT_IMMUNODEFICIENCY)) + level *= 0.5 + if(level < 0) + strength = max(strength + level, 0) + return + if(strength > threshold) //do not turn 500% into 100% because you drank a lower level immune healer + return + strength = min(strength + level, threshold) + +//instantly maxxes the antibodies for any disease in your body +/datum/immune_system/proc/AntibodyCure() + if (overloaded) + return + + for (var/datum/disease/acute/disease as anything in host.diseases) + for (var/A in disease.antigen) + antibodies[A] = 100 + +/datum/immune_system/proc/ApplyVaccine(list/antigen, amount = 1, decay = 0) + if (overloaded) + return + + for (var/A in antigen) + antibodies[A] = min(antibodies[A] + 5 * amount, 100) + if(decay) + addtimer(CALLBACK(src, PROC_REF(decay_vaccine), antigen, amount), decay) + +/datum/immune_system/proc/decay_vaccine(list/antigens, amount = 1) + for (var/A in antigens) + antibodies[A] = max(antibodies[A] - 2.5 * amount, 10) diff --git a/aquila/code/modules/virology/immune_systems/job_boosting.dm b/aquila/code/modules/virology/immune_systems/job_boosting.dm new file mode 100644 index 00000000000..c2984f78a98 --- /dev/null +++ b/aquila/code/modules/virology/immune_systems/job_boosting.dm @@ -0,0 +1,26 @@ +/datum/job + /// If set, players spawning with this job will start with a boosted immune system. + var/boost_immune_system + +/datum/job/after_spawn(mob/living/spawned, client/player_client) + . = ..() + if(QDELETED(spawned.immune_system) || isnull(spawned.immune_system)) + spawned.immune_system = new(spawned, boost_immune_system) + if(isnull(boost_immune_system)) + return + spawned.immune_system.change_boost(boost_immune_system) + +/datum/job/chief_medical_officer + boost_immune_system = 1.7 + +/datum/job/doctor + boost_immune_system = 1.4 + +/datum/job/paramedic + boost_immune_system = 1.4 + +/datum/job/virologist + boost_immune_system = 1.8 + +/datum/job/janitor + boost_immune_system = 2 diff --git a/aquila/code/modules/virology/items/_base_item_additions.dm b/aquila/code/modules/virology/items/_base_item_additions.dm new file mode 100644 index 00000000000..1594b2c8f31 --- /dev/null +++ b/aquila/code/modules/virology/items/_base_item_additions.dm @@ -0,0 +1,95 @@ + +GLOBAL_LIST_INIT(infected_items, list()) + +/obj/item + //how sterile an item is, not used for much atm + var/sterility = 0 +/obj/item + var/list/viruses + +/obj/item/attack_hand(mob/user, list/modifiers) + . = ..() + if(!QDELETED(user)) + disease_contact(user) + +//Called by attack_hand(), transfers diseases between the mob and the item +/obj/item/proc/disease_contact(mob/living/carbon/M, bodypart = null) + //first let's try to infect them with our viruses + for(var/datum/disease/acute/V as anything in viruses) + infection_attempt(M, V, bodypart) + + if (!bodypart)//no bodypart specified? that should mean we're being held. + bodypart = BODY_ZONE_ARMS + + //secondly, do they happen to carry contact-spreading viruses themselves? + var/list/contact_diseases = filter_disease_by_spread(M.diseases, required = DISEASE_SPREAD_CONTACT_SKIN) + if (length(contact_diseases)) + //if so are their hands protected? + var/block = M.check_contact_sterility(bodypart) + for (var/datum/disease/acute/D in contact_diseases) + if(!block) + infect_disease(D, notes="(Contact, from being touched by [M])") + +//Called by disease_contact(), trying to infect people who pick us up +/obj/item/infection_attempt(mob/living/perp, datum/disease/acute/D, bodypart = null) + if (!istype(D)) + return + + if (src in perp.held_items) + bodypart = BODY_ZONE_ARMS + + if (bodypart) + var/block = perp.check_contact_sterility(bodypart) + var/bleeding = perp.check_bodypart_bleeding(bodypart) + if (!block && (D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)) + perp.infect_disease(D, notes="(Contact, from picking up \a [src])") + else if (bleeding && (D.spread_flags & DISEASE_SPREAD_BLOOD))//if we're covered with a blood-spreading disease, we may infect people with bleeding hands. + perp.infect_disease(D, notes="(Blood, from picking up \a [src])") + +/obj/item/infect_disease(datum/disease/acute/disease, forced = FALSE, notes = "", decay = TRUE) + if(!istype(disease)) + return FALSE + if(!disease.spread_flags) + return FALSE + if(prob(disease.infectionchance) || forced) + var/datum/disease/acute/D = disease.Copy() + D.log += "
[ROUND_TIME()] Infected \a [src] [notes]" + + GLOB.infected_items |= src + + LAZYADD(viruses, D) + //SSdisease.active_diseases += D + D.after_add() + + log_virus("[src.name] was infected by virus [D.real_name()] at [loc_name(loc)]", list("disease_data" = D.admin_details(), "location" = loc_name(loc))) + + if (!pathogen) + pathogen = image('aquila/icons/virology/effects.dmi', src, "pathogen_contact") + pathogen.plane = HUD_PLANE + pathogen.appearance_flags = RESET_COLOR|RESET_ALPHA + for (var/mob/L in GLOB.virus_viewers) + if (L.client) + L.client.images |= pathogen + if (decay) + addtimer(CALLBACK(src, PROC_REF(remove_disease), D), (disease.infectionchance/10) MINUTES) + +/obj/item/proc/remove_disease(datum/disease/disease) + viruses -= disease + SSdisease.active_diseases -= disease + if(!length(viruses)) + GLOB.infected_items -= src + if (pathogen) + for (var/mob/L in GLOB.virus_viewers) + if(L.client) + L.client.images -= pathogen + + +/obj/item/try_infect_with_mobs_diseases(mob/living/carbon/infectee) + if(!infectee) + return + if(!length(infectee.diseases)) + return + var/list/blood_diseases = filter_disease_by_spread(infectee.diseases, required = DISEASE_SPREAD_BLOOD) + if(length(blood_diseases)) + for(var/datum/disease/acute/V as anything in blood_diseases) + infect_disease(V, TRUE, "(Blood, coming from [infectee])") diff --git a/aquila/code/modules/virology/items/antibodyscanner.dm b/aquila/code/modules/virology/items/antibodyscanner.dm new file mode 100644 index 00000000000..ed7e1e14ed8 --- /dev/null +++ b/aquila/code/modules/virology/items/antibodyscanner.dm @@ -0,0 +1,163 @@ + +/obj/item/device/antibody_scanner + name = "immunity scanner" + desc = "A hand-held body scanner able to evaluate the immune system of the subject." + icon = 'aquila/icons/virology/items.dmi' + icon_state = "antibody" + item_state = "healthanalyzer" + worn_icon_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + siemens_coefficient = 1 + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 5 + custom_materials = list(/datum/material/iron=200) + + +/obj/item/device/antibody_scanner/attack(mob/living/carbon/L, mob/living/carbon/human/user) + if(!istype(L)) + //to_chat(user, span_notice("Incompatible object, scan aborted.")) + return + + if (issilicon(L)) + to_chat(user, span_warning("Incompatible with silicon lifeforms, scan aborted.")) + return + + playsound(user, 'sound/items/weeoo1.ogg', 50, 1) + L.visual_masked_scan() + var/info = "" + var/icon/scan = icon('aquila/icons/virology/virology_bg.dmi',"immunitybg") + var/display_width = scan.Width() + + var/list/antigens_that_matter = list() + if (L.immune_system) + finding_antigens: + for (var/antibody in L.immune_system.antibodies) + if (L.immune_system.antibodies[antibody] > 0) + antigens_that_matter += antibody + continue + if (length(L.diseases)) + for (var/datum/disease/acute/D as anything in L.diseases) + D.Refresh_Acute() + var/ID = "[D.uniqueID]-[D.subID]" + if(ID in GLOB.virusDB) + if (antibody in D.antigen) + antigens_that_matter += antibody + continue finding_antigens + + var/bar_spacing = round(display_width/antigens_that_matter.len) + var/bar_width = round(bar_spacing/2) + var/bar_offset = round(bar_width/4) + var/x_adjustment = 5//Sometimes you have to adjust things manually so they look good. This var moves all the gauges and graduations on the x axis. + + if (L.immune_system) + var/immune_system = L.immune_system.GetImmunity() + var/immune_str = immune_system[1] + var/list/antibodies = immune_system[2] + + info += "Immune System Status: [round(immune_str*100)]%" + info += "
Antibody Concentrations:" + + var/i = 0 + for (var/antibody in antigens_that_matter) + var/rgb = "#FFFFFF" + switch (antibody) + if ("O","A","B","Rh") + rgb = "#80DEFF" + if ("Q","U","V") + rgb = "#81FF9F" + if ("M","N","P") + rgb = "#E6FF81" + if ("X","Y","Z") + rgb = "#FF9681" + if ("C", "Ig") + rgb = "#F54B4B" + //add colors for new special antigens here + scan.DrawBox(rgb,i*bar_spacing+bar_offset+x_adjustment,6,i*bar_spacing+bar_width+bar_offset+x_adjustment,6+antibodies[antibody]*3) + i++ + + if (length(L.diseases)) + for (var/datum/disease/acute/D as anything in L.diseases) + var/ID = "[D.uniqueID]-[D.subID]" + scan.DrawBox("#FF0000",6,6+D.strength*3,display_width-5,6+D.strength*3) + if(ID in GLOB.virusDB) + var/subdivision = (D.strength - ((D.robustness * D.strength) / 100)) / D.max_stages + var/i = 0 + for (var/antigen in antigens_that_matter) + if (antigen in D.antigen) + var/box_size = 3 + scan.DrawBox("#FF0000",bar_width-box_size+bar_spacing*i,6+D.strength*3-3,bar_width+box_size+bar_spacing*i,6+D.strength*3+3) + scan.DrawBox("#FF0000",bar_width+bar_spacing*i,6+D.strength*3,bar_width+bar_spacing*i,6+round(D.strength - D.max_stages * subdivision)*3) + var/stick_out = 6//how far the graduations go left and right of the gauge + for (var/j = 1 to D.max_stages) + var/alt = round(D.strength - j * subdivision) + scan.DrawBox("#FF0000",i*bar_spacing+bar_offset-stick_out+x_adjustment,6+alt*3,i*bar_spacing+bar_offset+bar_width+stick_out+x_adjustment,6+alt*3) + i++ + + info += "
" + info += "
" + info += "" + info += "" + if (L.immune_system) + for (var/antibody in antigens_that_matter) + info += "" + info += "" + info += "" + if (L.immune_system) + for (var/antibody in antigens_that_matter) + info += "" + info += "
[antibody]
[round(L.immune_system.antibodies[antibody])]%" + info += "
" + + if (length(L.diseases)) + for (var/datum/disease/acute/D as anything in L.diseases) + var/ID = "[D.uniqueID]-[D.subID]" + if(ID in GLOB.virusDB) + var/datum/data/record/V = GLOB.virusDB[ID] + info += "
[V.fields["name"]][V.fields["nickname"] ? " \"[V.fields["nickname"]]\"" : ""] detected. Strength: [D.strength]. Robustness: [D.robustness]. Antigen: [D.get_antigen_string()]" + for(var/datum/symptom/acute/e in D.symptoms) + info += "
Stage [e.stage] - [e.name] (Danger: [e.badness]): [e.desc]" + else + info += "
Unknown [D.form] detected. Strength: [D.strength]" + + var/datum/browser/popup = new(user, "\ref[src]", name, 600, 600, src) + popup.set_content(info) + popup.open() + +/obj/item/device/antibody_scanner/pre_attack(atom/A, mob/living/user, params) + if(!Adjacent(A)) + return + if (isitem(A)) + var/obj/item/I = A + playsound(user, 'sound/items/weeoo1.ogg', 50, 1) + var/span = "warning" + if(I.sterility <= 0) + span = "danger" + else if (I.sterility >= 100) + span = "notice" + to_chat(user,"Scanning \the [I]...sterility level = [I.sterility]%") + if (isvirusdish(I)) + var/obj/item/weapon/virusdish/dish = I + if (dish.open && dish.contained_virus) + to_chat(user,span_danger("However, since its lid has been opened, unprotected contact with the dish can result in infection.")) + + . = ..() + + +/obj/effect/abstract/blank/scan + alpha = 255 + icon = 'aquila/icons/virology/items.dmi' + icon_state = "scan" + +/atom/movable/proc/visual_masked_scan() + var/obj/effect/abstract/blank/scan/scanline = new + + vis_contents += scanline + addtimer(CALLBACK(src, PROC_REF(remove_scanline), scanline), 2 SECONDS) + +/atom/movable/proc/remove_scanline(atom/scanline) + vis_contents -= scanline + qdel(scanline) + diff --git a/aquila/code/modules/virology/items/bio_hazard_deployable.dm b/aquila/code/modules/virology/items/bio_hazard_deployable.dm new file mode 100644 index 00000000000..298eb337cc9 --- /dev/null +++ b/aquila/code/modules/virology/items/bio_hazard_deployable.dm @@ -0,0 +1,23 @@ +///obj/structure/holosign/barrier/medical + //icon = 'aquila/icons/virology/biohazard.dmi' + //icon_state = "biohazard" + //alpha = 125 + +/obj/item/holosign_creator/medical/proc/try_alert(atom/movable/AM, area/host_area) + if(!isliving(AM)) + return + + var/mob/living/living = AM + var/say_text = "VIRUS DETECTED AT: [host_area]. SOURCE: [living]. " + + if(length(living.diseases) > 1) + say_text += "SUBJECT HAS MULTIPLE VIRUSES." + else + var/line = " DISEASE IS IN DATABASE:" + for(var/datum/disease/acute/disease as anything in living.diseases) + if("[disease.uniqueID]-[disease.subID]" in GLOB.virusDB) + var/datum/data/record/target = GLOB.virusDB["[disease.uniqueID]-[disease.subID]"] + line += " [target.name]" + say_text += line + say(say_text) + playsound(src, 'sound/machines/defib_success.ogg', 50, FALSE) diff --git a/aquila/code/modules/virology/items/clothing/mask.dm b/aquila/code/modules/virology/items/clothing/mask.dm new file mode 100644 index 00000000000..faf9e5a4a36 --- /dev/null +++ b/aquila/code/modules/virology/items/clothing/mask.dm @@ -0,0 +1,2 @@ +/obj/item/clothing/mask/gas/clown_hat/virus + desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask. On second look, it looks like it's coming out of the wearers skin!" diff --git a/aquila/code/modules/virology/items/clothing/shoes.dm b/aquila/code/modules/virology/items/clothing/shoes.dm new file mode 100644 index 00000000000..21b8ec77085 --- /dev/null +++ b/aquila/code/modules/virology/items/clothing/shoes.dm @@ -0,0 +1,7 @@ +/obj/item/clothing/shoes/kneesocks + name = "kneesocks" + desc = "A pair of girly knee-high socks." + icon_state = "kneesock" + worn_icon_state = "kneesock" + icon = 'aquila/icons/virology/shoes.dmi' + worn_icon = 'aquila/icons/virology/shoes_worn.dmi' diff --git a/aquila/code/modules/virology/items/disease_disk.dm b/aquila/code/modules/virology/items/disease_disk.dm new file mode 100644 index 00000000000..c79612ba9b4 --- /dev/null +++ b/aquila/code/modules/virology/items/disease_disk.dm @@ -0,0 +1,19 @@ +/obj/item/disk/disease + name = "blank GNA disk" + desc = "A disk for storing the structure of a pathogen's Glycol Nucleic Acid pertaining to a specific symptom." + var/datum/symptom/acute/effect = null + var/stage = 1 + var/analyzed = FALSE + +/obj/item/disk/disease/premade + +/obj/item/disk/disease/premade/Initialize(mapload) + . = ..() + name = "blank GNA disk (stage: [stage])" + effect = new /datum/symptom/acute + +/obj/item/disk/disease/update_desc(updates) + . = ..() + desc = "[initial(desc)]\n" + desc += "Strength: [effect.multiplier]\n" + desc += "Occurrence: [effect.chance]" diff --git a/aquila/code/modules/virology/items/extrapolator.dm b/aquila/code/modules/virology/items/extrapolator.dm new file mode 100644 index 00000000000..a78752b0c01 --- /dev/null +++ b/aquila/code/modules/virology/items/extrapolator.dm @@ -0,0 +1,114 @@ +// Ekstrapolator — dodatki z Monkestation2.0 (items/extrapolator.dm). +// Nasz ekstrapolator (code/game/objects/items/devices/scanners.dm) zostaje: skanuje nasze +// choroby advance i izoluje z nich objawy. Tutaj dokładamy tryb sondy z disease2: +// na człowieku odpala minigrę symptom crackera, a na szalce/chorym pozwala wszczepić +// warient objawu przez okno Extrapolator. + +/mob/living/carbon/human/extrapolator_act(mob/user, obj/item/extrapolator/E, scan = TRUE) + if(!created_symptoms) + create_symptoms() + created_symptoms = TRUE + return ..() + +/obj/item/extrapolator + /// Cel ostatniego kliknięcia sondą — czyta go okno TGUI. + var/atom/last_attacked_target + /// Ostatnio zeskanowany żywy cel (do ponownego użycia bez klikania). + var/datum/weakref/user_data + +/obj/item/extrapolator/Destroy() + user_data = null + last_attacked_target = null + return ..() + +/obj/item/extrapolator/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + if(proximity_flag && isliving(target) && target != user) + user_data = WEAKREF(target) + if(proximity_flag && !scan) + try_disease_modification(user, target) + return + return ..() + +/obj/item/extrapolator/proc/try_disease_modification(mob/user, atom/target) + if(!isliving(target) && !isvirusdish(target)) + return + + if(isvirusdish(target)) + var/obj/item/weapon/virusdish/dish = target + if(!dish.contained_virus) + to_chat(user, "Szalka jest pusta.") + return + + last_attacked_target = target + ui_interact(user) + +/obj/item/extrapolator/proc/try_symptom_change(mob/user, datum/weakref/choice_ref, datum/symptom_varient/new_varient, datum/weakref/symptom_ref) + var/datum/symptom/acute/symptom = symptom_ref?.resolve() + if(!symptom || !new_varient) + return + var/datum/disease/choice = choice_ref?.resolve() + + if(symptom.attached_varient) + to_chat(user, "[src] brzęczy: ten objaw już jest szczepem wariantowym.") + return + + symptom.attached_varient = new new_varient(symptom, choice) + symptom.update_name() + +/obj/item/extrapolator/proc/generate_varient() + var/list/weighted_list = list() + for(var/datum/symptom_varient/varient as anything in subtypesof(/datum/symptom_varient)) + var/weight = initial(varient.weight) + if(weight > 0) + weighted_list[varient] = weight + if(!length(weighted_list)) + return null + return pickweight(weighted_list) + +/obj/item/extrapolator/ui_interact(mob/user, datum/tgui/ui = null) + . = ..() + if(!last_attacked_target) + return + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Extrapolator") + ui.open() + +/obj/item/extrapolator/ui_state(mob/user) + return GLOB.inventory_state + +/obj/item/extrapolator/ui_data(mob/user) + var/list/data = list() + var/list/diseases = list() + + if(isvirusdish(last_attacked_target)) + var/obj/item/weapon/virusdish/dish = last_attacked_target + if(dish.contained_virus) + diseases += list(disease_entry(dish.contained_virus)) + else if(isliving(last_attacked_target)) + var/mob/living/target = last_attacked_target + for(var/datum/disease/acute/disease as anything in target.diseases) + if(istype(disease)) + diseases += list(disease_entry(disease)) + + data["diseases"] = diseases + data["varients"] = list() + return data + +/obj/item/extrapolator/proc/disease_entry(datum/disease/acute/disease) + var/list/symptom_data = list() + for(var/datum/symptom/acute/symptom as anything in disease.symptoms) + symptom_data += list(list("name" = symptom.name, "ref" = REF(symptom))) + return list("name" = disease.name(), "ref" = REF(disease), "symptoms" = symptom_data) + +/obj/item/extrapolator/ui_act(action, list/params) + . = ..() + if(.) + return + switch(action) + if("add_varient") + var/datum/symptom_varient/new_varient = generate_varient() + var/datum/weakref/disease_ref = WEAKREF(locate(params["disease_ref"])) + var/datum/weakref/symptom_ref = WEAKREF(locate(params["symptom_ref"])) + try_symptom_change(usr, disease_ref, new_varient, symptom_ref) + return TRUE diff --git a/aquila/code/modules/virology/items/mousecubes.dm b/aquila/code/modules/virology/items/mousecubes.dm new file mode 100644 index 00000000000..d6d7ce374ef --- /dev/null +++ b/aquila/code/modules/virology/items/mousecubes.dm @@ -0,0 +1,10 @@ +/obj/item/food/monkeycube/mouse + name = "mouse cube" + tastes = list("the sewers" = 1, "cheese" = 1) + food_reagents = list(/datum/reagent/blood = 30) + spawned_mob = /mob/living/simple_animal/mouse + +/obj/item/storage/box/monkeycubes/mousecubes + name = "mouse cube box" + desc = "Drymate brand mouse cubes. Just add water!" + cube_type = /obj/item/food/monkeycube/mouse diff --git a/aquila/code/modules/virology/items/pathology_glasses.dm b/aquila/code/modules/virology/items/pathology_glasses.dm new file mode 100644 index 00000000000..0142dc5876d --- /dev/null +++ b/aquila/code/modules/virology/items/pathology_glasses.dm @@ -0,0 +1,80 @@ +// Okulary patologa — port z Monkestation2.0 (items/pathology_glasses.dm + datums/components/pathology_glasses.dm). +// U nas okulary nie mają komponentu z tg (brak add_item_action/color_cutoffs), więc przełącznik +// siedzi wprost na przedmiocie i działa przez nasz /datum/action/item_action/toggle. + +/obj/item/clothing/glasses/pathology + name = "gogle analityczne patologa" + desc = "Gogle z analizatorem cząstek wirusowych i odczynników. Mają przełącznik, żeby nie zalewało wzroku." + icon = 'icons/obj/clothing/glasses.dmi' + icon_state = "pathology_on" + item_state = "glasses" + actions_types = list(/datum/action/item_action/toggle) + /// Czy podgląd patogenów jest włączony. + var/pathology_enabled = TRUE + var/icon_state_on = "pathology_on" + var/icon_state_off = "pathology_off" + +/obj/item/clothing/glasses/pathology/Initialize(mapload) + . = ..() + update_icon_state() + +/obj/item/clothing/glasses/pathology/update_icon_state() + icon_state = pathology_enabled ? icon_state_on : icon_state_off + return ..() + +/obj/item/clothing/glasses/pathology/examine(mob/user) + . = ..() + . += "Podgląd patogenów jest [pathology_enabled ? "włączony" : "wyłączony"]. Użyj przycisku akcji, żeby przełączyć." + +/obj/item/clothing/glasses/pathology/equipped(mob/living/user, slot) + . = ..() + if(slot == ITEM_SLOT_EYES && pathology_enabled) + add_virus_view(user) + +/obj/item/clothing/glasses/pathology/dropped(mob/living/user) + . = ..() + remove_virus_view(user) + +/obj/item/clothing/glasses/pathology/proc/add_virus_view(mob/living/user) + if(!istype(user)) + return + ADD_TRAIT(user, TRAIT_VIRUS_SCANNER, "[REF(src)]") + user.AddElement(/datum/element/virus_viewer) + +/obj/item/clothing/glasses/pathology/proc/remove_virus_view(mob/living/user) + if(!istype(user)) + return + REMOVE_TRAIT(user, TRAIT_VIRUS_SCANNER, "[REF(src)]") + if(!HAS_TRAIT(user, TRAIT_VIRUS_SCANNER)) + user.RemoveElement(/datum/element/virus_viewer) + +/obj/item/clothing/glasses/pathology/ui_action_click(mob/user, datum/actiontype) + pathology_enabled = !pathology_enabled + if(pathology_enabled) + add_virus_view(user) + else + remove_virus_view(user) + update_icon_state() + if(ishuman(user)) + var/mob/living/carbon/human/human_user = user + human_user.update_inv_glasses() + to_chat(user, "[pathology_enabled ? "Włączasz" : "Wyłączasz"] podgląd patogenów.") + +/obj/item/clothing/glasses/pathology/sunglasses + name = "okulary analityczne patologa" + desc = "Przeciwsłoneczne okulary z analizatorem cząstek wirusowych i odczynników." + icon_state = "sunhudpatho_on" + icon_state_on = "sunhudpatho_on" + icon_state_off = "sunhudpatho_off" + flash_protect = 1 + tint = 1 + +/obj/item/clothing/glasses/pathology/night + name = "noktowizyjne gogle patologa" + desc = "Noktowizor z analizatorem cząstek wirusowych i odczynników." + icon_state = "pathohudnight_on" + icon_state_on = "pathohudnight_on" + icon_state_off = "pathohudnight_off" + darkness_view = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + flash_protect = -1 diff --git a/aquila/code/modules/virology/items/virusdish.dm b/aquila/code/modules/virology/items/virusdish.dm new file mode 100644 index 00000000000..bfd92eb2a05 --- /dev/null +++ b/aquila/code/modules/virology/items/virusdish.dm @@ -0,0 +1,333 @@ +///////////////VIRUS DISH/////////////// +GLOBAL_LIST_INIT(virusdishes, list()) +/obj/item/weapon/virusdish + name = "growth dish" + desc = "A petri dish fit to contain viral, bacteriologic, parasitic, or any other kind of pathogenic culture." + icon = 'aquila/icons/virology/virology.dmi' + icon_state = "virusdish" + w_class = WEIGHT_CLASS_SMALL + //sterility = 100//the outside of the dish is sterile. + var/growth = 0 + var/info = "" + var/analysed = FALSE + var/datum/disease/acute/contained_virus + /// Boolean, TRUE if the lid is opened + var/open = FALSE + var/cloud_delay = 8 SECONDS//similar to a mob's breathing + var/mob/last_openner + COOLDOWN_DECLARE(cloud_cooldown) + +/obj/item/weapon/virusdish/Initialize(mapload) + . = ..() + reagents = new(10) + reagents.my_atom = src + GLOB.virusdishes += src + + var/list/reagent_change_signals = list( + ADD_REAGENT, + ADD_REAGENT, + ADD_REAGENT, + ADD_REAGENT, + ADD_REAGENT, + ADD_REAGENT, + ) + RegisterSignals(src.reagents, reagent_change_signals, PROC_REF(handle_reagent_change)) + +/obj/item/weapon/virusdish/Destroy() + GLOB.virusdishes -= src + STOP_PROCESSING(SSobj, src) + contained_virus = null + last_openner = null + return ..() + +/* +/obj/item/weapon/virusdish/clean_blood() + ..() + if(open) + contained_virus = null + growth = 0 + update_icon() +*/ + +/obj/item/weapon/virusdish/update_icon() + . = ..() + overlays.len = 0 + if(!contained_virus) + if(open) + icon_state = "virusdish1" + else + icon_state = "virusdish" + return + icon_state = "virusdish-outline" + var/image/I1 = image(icon,src,"virusdish-bottom") + I1.color = contained_virus.color + var/image/I2 = image(icon,src,"pattern-[contained_virus.pattern]") + I2.color = contained_virus.pattern_color + var/image/I3 = image(icon,src,"virusdish-[open?"open":"closed"]") + I3.color = contained_virus.color + overlays += I1 + if(open) + overlays += I3 + I2.alpha = growth*255/200+127 + overlays += I2 + else + overlays += I2 + overlays += I3 + I2.alpha = (growth*255/200+127)*60/100 + overlays += I2 + var/image/I4 = image(icon,src,"virusdish-reflection") + overlays += I4 + if(analysed) + overlays += "virusdish-label" + else if(info != "" && copytext(info, 1, 9) == "OUTDATED") + overlays += "virusdish-outdated" + +/obj/item/weapon/virusdish/attack_hand(mob/living/user, list/modifiers) + ..() + infection_attempt(user) + +/obj/item/weapon/virusdish/attack_self(mob/living/user, list/modifiers) + open = !open + update_appearance() + to_chat(user,span_notice("You [open?"open":"close"] dish's lid.")) + if(open) + last_openner = user + if(contained_virus) + contained_virus.log += "
[ROUND_TIME()] Containment Dish opened by [key_name(user)]." + START_PROCESSING(SSobj, src) + else + if(contained_virus) + contained_virus.log += "
[ROUND_TIME()] Containment Dish closed by [key_name(user)]." + STOP_PROCESSING(SSobj, src) + infection_attempt(user) + +/obj/item/weapon/virusdish/is_open_container() + return open + +/obj/item/weapon/virusdish/attackby(obj/item/tool, mob/user, params) + if(user.combat_mode && tool.force) + visible_message(span_danger("The virus dish is smashed to bits!")) + shatter(user) + return TRUE + + if(istype(tool, /obj/item/reagent_containers/syringe)) + if(growth < 50) + to_chat(user, span_warning("There isn't enough growth in the [src].")) + return TRUE + growth = growth - 50 + var/obj/item/reagent_containers/syringe/syringe_tool = tool + var/list/data = list( + "viruses" = null, + "blood_DNA" = null, + "blood_type" = get_blood_type(BLOOD_TYPE_O_MINUS), + "resistances" = null, + "trace_chem" = null, + "viruses" = list(), + "immunity" = list() + ) + data["viruses"] |= list(contained_virus) + syringe_tool.reagents.add_reagent(/datum/reagent/blood, syringe_tool.volume, data) + to_chat(user, span_notice("You take some blood from the [src].")) + return TRUE + + if(istype(tool, /obj/item/reagent_containers)) + if(!open) + user.balloon_alert(user, "lid closed") + return TRUE + + if(!tool.is_open_container()) + to_chat(user, span_notice("[tool] is not an open container")) + return TRUE + + var/transfered_amount = 0 + transfered_amount = tool.reagents.trans_to(src, 10, transferred_by = user) + if(transfered_amount > 0) + to_chat(user, span_notice("You transfer [transfered_amount] units of the solution to \the [src].")) + return TRUE + +/obj/item/weapon/virusdish/afterattack(atom/interacting_with, mob/user, proximity_flag, click_parameters) + if(istype(interacting_with, /obj/machinery/smartfridge/chemistry)) + return FALSE + + if(!open) + user.balloon_alert(user, "lid closed") + return TRUE + + if(is_reagent_container(interacting_with)) + var/amount_transfered = reagents.trans_to(interacting_with, 10, transferred_by = user) + if(amount_transfered > 0) + to_chat(user, span_notice("You transfer [amount_transfered] units of the solution to \the [interacting_with].")) + return TRUE + return TRUE + + if(istype(interacting_with, /obj/structure/reagent_dispensers)) + var/obj/structure/reagent_dispensers/dispenser = interacting_with + var/amount_transfered = dispenser.reagents.trans_to(src, 10, transferred_by = user) + if(amount_transfered > 0) + to_chat(user, span_notice("You transfer [amount_transfered] units of the solution to \the [src].")) + return TRUE + return TRUE + + if(istype(interacting_with, /obj/structure/urinal) || istype(interacting_with, /obj/structure/sink)) + empty(user, interacting_with) + return TRUE + + if(istype(interacting_with, /obj/structure/toilet)) + var/obj/structure/toilet/convenient_disposal = interacting_with + if(!convenient_disposal.open) + return NONE + empty(user, interacting_with) + return TRUE + +/// Empties out the virus dish +/obj/item/weapon/virusdish/proc/empty(mob/user, atom/target) + if(user && target) + to_chat(user,span_notice("You empty \the [src]'s reagents into \the [target].")) + reagents.clear_reagents() + +// AQ: Monke podpina tu /datum/element/blood_reagent; u nas krew w szalce nie potrzebuje hooka. + +/obj/item/weapon/virusdish/process() + if(!contained_virus || !open) + return PROCESS_KILL + if(isliving(loc)) + var/mob/living/holder = loc + if(holder.is_holding(src)) + infection_attempt(holder, contained_virus) + else if(isopenturf(loc)) + for(var/mob/living/potential_victim in loc.contents) + infection_attempt(potential_victim, contained_virus) + if(contained_virus.spread_flags & DISEASE_SPREAD_AIRBORNE) + if(COOLDOWN_FINISHED(src, cloud_cooldown)) + COOLDOWN_START(src, cloud_cooldown, cloud_delay) + var/list/L = list(contained_virus) + new /obj/effect/pathogen_cloud/core(get_turf(src), last_openner, virus_copylist(L), FALSE) + +/obj/item/weapon/virusdish/random + name = "growth dish" + +/obj/item/weapon/virusdish/random/Initialize(mapload) + . = ..() + if(!loc) //because fuck you /datum/subsystem/supply_shuttle/Initialize() + GLOB.virusdishes -= src + return + var/virus_choice = pick(WILD_ACUTE_DISEASES) + contained_virus = new virus_choice + var/list/anti = list( + ANTIGEN_BLOOD = 2, + ANTIGEN_COMMON = 2, + ANTIGEN_RARE = 1, + ANTIGEN_ALIEN = 0, + ) + var/list/bad = list( + EFFECT_DANGER_HELPFUL = 1, + EFFECT_DANGER_FLAVOR = 2, + EFFECT_DANGER_ANNOYING = 2, + EFFECT_DANGER_HINDRANCE = 2, + EFFECT_DANGER_HARMFUL = 2, + EFFECT_DANGER_DEADLY = 0, + ) + contained_virus.makerandom(list(50,90),list(10,100),anti,bad,src) + contained_virus.Refresh_Acute() + growth = rand(5, 50) + name = "growth dish (Unknown [contained_virus.form])" + update_appearance() + contained_virus.origin = "Random Dish" + +/obj/item/weapon/virusdish/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + ..() + if(isturf(hit_atom)) + visible_message(span_danger("The virus dish shatters on impact!")) + shatter(throwingdatum.thrower) + +/obj/item/weapon/virusdish/proc/incubate(mutatechance=5, growthrate=3, effect_focus = 0) + if(contained_virus) + if(reagents.remove_reagent(/datum/reagent/consumable/virus_food, 0.2)) + growth = min(growth + growthrate, 100) + if(reagents.remove_reagent(/datum/reagent/water, 0.2)) + growth = max(growth - growthrate, 0) + contained_virus.incubate(src,mutatechance,effect_focus) + +/obj/item/weapon/virusdish/proc/handle_reagent_change(datum/reagents/reagents) + SIGNAL_HANDLER + + if(contained_virus) + var/datum/reagent/blood/blood = locate() in reagents.reagent_list + if(blood) + var/list/L = list() + L |= contained_virus + LAZYOR(blood.data["diseases"], filter_disease_by_spread(L, required = DISEASE_SPREAD_BLOOD)) + +/obj/item/weapon/virusdish/proc/shatter(mob/user) + var/obj/effect/decal/cleanable/virusdish/dish = new(get_turf(src)) + dish.pixel_x = pixel_x + dish.pixel_y = pixel_y + if(contained_virus) + dish.contained_virus = contained_virus.Copy() + dish.last_openner = key_name(user) + src.transfer_fingerprints_to(dish) + playsound(get_turf(src), "shatter", 70, 1) + var/image/I1 + var/image/I2 + if(contained_virus) + I1 = image(icon,src,"brokendish-color") + I1.color = contained_virus.color + I2 = image(icon,src,"pattern-[contained_virus.pattern]b") + I2.color = contained_virus.pattern_color + else + I1 = image(icon,src,"brokendish") + dish.overlays += I1 + if(contained_virus) + dish.overlays += I2 + contained_virus.log += "
[ROUND_TIME()] Containment Dish shattered by [key_name(user)]." + if(contained_virus.spread_flags & DISEASE_SPREAD_AIRBORNE) + var/strength = contained_virus.infectionchance + var/list/L = list() + L += contained_virus + while (strength > 0) + new /obj/effect/pathogen_cloud/core(get_turf(src), last_openner, virus_copylist(L), FALSE) + strength -= 40 + qdel(src) + +/obj/item/weapon/virusdish/update_desc(updates) + . = ..() + desc = initial(desc) + if(open) + desc += "\nIts lid is open!" + else + desc += "\nIts lid is closed!" + if(info) + desc += "\nThere is a sticker with some printed information on it. (Read it)" + + +/obj/item/weapon/virusdish/Topic(href, href_list) + if(..()) + return TRUE + if(href_list["examine"]) + var/datum/browser/popup = new(usr, "\ref[src]", name, 600, 300, src) + popup.set_content(info) + popup.open() + +/obj/item/weapon/virusdish/infection_attempt(mob/living/perp, datum/disease/D) + if(open)//If the dish is open, we may get infected by the disease inside on top of those that might be stuck on it. + var/block = 0 + var/bleeding = 0 + if(src in perp.held_items) + block = perp.check_contact_sterility(BODY_ZONE_ARMS) + bleeding = perp.check_bodypart_bleeding(BODY_ZONE_ARMS) + if(!block && (contained_virus.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)) + perp.infect_disease(contained_virus, notes="(Contact, from picking up \a [src])") + else if(bleeding && (contained_virus.spread_flags & DISEASE_SPREAD_BLOOD)) + perp.infect_disease(contained_virus, notes="(Blood, from picking up \a [src])") + else if(isturf(loc) && loc == perp.loc)//is our perp standing over the open dish? + if(perp.lying & LYING_DOWN) + block = perp.check_contact_sterility(BODY_ZONE_EVERYTHING) + bleeding = perp.check_bodypart_bleeding(BODY_ZONE_EVERYTHING) + else + block = perp.check_contact_sterility(BODY_ZONE_LEGS) + bleeding = perp.check_bodypart_bleeding(BODY_ZONE_LEGS) + if(!block && (contained_virus.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)) + perp.infect_disease(contained_virus, notes="(Contact, from [(perp.lying & LYING_DOWN)?"lying":"standing"] over a virus dish[last_openner ? " opened by [key_name(last_openner)]" : ""])") + else if(bleeding && (contained_virus.spread_flags & DISEASE_SPREAD_BLOOD)) + perp.infect_disease(contained_virus, notes="(Blood, from [(perp.lying & LYING_DOWN)?"lying":"standing"] over a virus dish[last_openner ? " opened by [key_name(last_openner)]" : ""])") + ..(perp,D) diff --git a/aquila/code/modules/virology/living/infection_procs.dm b/aquila/code/modules/virology/living/infection_procs.dm new file mode 100644 index 00000000000..75310acd247 --- /dev/null +++ b/aquila/code/modules/virology/living/infection_procs.dm @@ -0,0 +1,115 @@ +/mob/living/proc/find_nearby_disease()//only tries to find Contact and Blood spread diseases. Airborne ones are handled by breath_airborne_diseases() + if(buckled)//Riding a vehicle? + return + if(HAS_TRAIT(src, TRAIT_MOVE_FLYING))//Flying? + return + + //Now to check for stuff that's on the floor + + var/checked = FALSE + var/block + var/bleeding + + for(var/obj/effect/decal/cleanable/yucky in loc) + if(!length(yucky.diseases)) + continue + if(!checked) + if(lying) + block = check_contact_sterility(BODY_ZONE_EVERYTHING) + bleeding = check_bodypart_bleeding(BODY_ZONE_EVERYTHING) + else + block = check_contact_sterility(BODY_ZONE_LEGS) + bleeding = check_bodypart_bleeding(BODY_ZONE_LEGS) + checked = TRUE + assume_contact_diseases(yucky.diseases, yucky, block, bleeding) + + return FALSE + +//This one is used for one-way infections, such as getting splashed with someone's blood due to clobbering them to death +/mob/living/proc/oneway_contact_diseases(mob/living/carbon/L, block=0, bleeding=0) + assume_contact_diseases(L.diseases,L,block,bleeding) + +//This one is used for two-ways infections, such as hand-shakes, hugs, punches, people bumping into each others, etc +/mob/living/proc/share_contact_diseases(mob/living/carbon/L, block=0, bleeding=0) + L.assume_contact_diseases(diseases ,src,block,bleeding) + assume_contact_diseases(L.diseases, L, block, bleeding) + +///////////////////////DISEASE STUFF/////////////////////////////////////////////////////////////////// + +//Blocked is whether clothing prevented the spread of contact/blood +/mob/living/proc/assume_contact_diseases(list/disease_list, atom/source, blocked=0, bleeding=0) + if(!length(disease_list)) + return + for(var/datum/disease/acute/V as anything in disease_list) + if (!V) + message_admins("[key_name(src)] is trying to assume contact diseases from touching \a [source], but the disease_list contains an ID ([V.uniqueID]-[V.subID]) that isn't associated to an actual disease datum! Ping Dwasint about it please.") + return + if(!blocked && V.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + infect_disease(V, notes="(Contact, from [source])") + /* + else if(suitable_colony() && V.spread & SPREAD_COLONY) + infect_disease(V, notes="(Colonized, from [source])") + */ + else if(!blocked && bleeding && (V.spread_flags & DISEASE_SPREAD_BLOOD)) + infect_disease(V, notes="(Blood, from [source])") + +//Called in Life() by humans (in handle_breath.dm), monkeys and mice +/mob/living/proc/breath_airborne_diseases()//only tries to find Airborne spread diseases. Blood and Contact ones are handled by find_nearby_disease() + if (!check_airborne_sterility() && isturf(loc))//checking for sterile mouth protections + breath_airborne_diseases_from_clouds() + + for(var/obj/effect/decal/cleanable/C in loc) + if (!length(C.diseases)) + continue + for(var/datum/disease/acute/V as anything in C.diseases) + if(V.spread_flags & DISEASE_SPREAD_AIRBORNE) + infect_disease(V, notes="(Airborne, from [C])") + +/mob/living/proc/breath_airborne_diseases_from_clouds() + var/sanity = 0 + for(var/obj/effect/pathogen_cloud/cloud in range(1, src)) + if(sanity > 10) + break + sanity++ //anything more than 10 and you aint getting air really + if (!cloud.sourceIsCarrier || cloud.source != src || cloud.modified) + for (var/datum/disease/acute/V in cloud.viruses) + //if (V.spread & SPREAD_AIRBORNE) //Anima Syndrome allows for clouds of non-airborne viruses + infect_disease(V, notes="(Airborne, from a pathogenic cloud[cloud.source ? " created by [key_name(cloud.source)]" : ""])") + +/mob/living/proc/handle_virus_updates(seconds_per_tick, times_fired) + if(HAS_TRAIT(src, TRAIT_GODMODE) || HAS_TRAIT(src, TRAIT_VIRUSIMMUNE)) + return FALSE + + if(stat != DEAD && isopenturf(loc)) // if we're dead or inside a locker/body bag/etc, don't bother + find_nearby_disease()//getting diseases from blood/mucus/vomit splatters and open dishes + + activate_diseases(seconds_per_tick, times_fired) + +/mob/living/proc/activate_diseases(seconds_per_tick, times_fired) + if (length(diseases)) + var/active_disease = pick(diseases)//only one disease will activate its effects at a time. + for (var/datum/disease/disease as anything in diseases) + var/datum/disease/acute/acute_disease = disease + if(istype(acute_disease)) + acute_disease.activate(src, active_disease != acute_disease, seconds_per_tick) + + if((src.radiation > 0)) + if (prob(50))//radiation turns your body into an inefficient pathogenic incubator. + acute_disease.incubate(src, 1) + //effect mutations won't occur unless the mob also has ingested mutagen + //and even if they occur, the new effect will have a badness similar to the old one, so helpful pathogen won't instantly become deadly ones. + else if(istype(disease)) + if(stat != DEAD || disease.process_dead) + disease.stage_act(seconds_per_tick, times_fired) + else + //Slowly decay back to regular strength immune system while you are sick + if(immune_system?.strength > 1) + immune_system.strength = max(immune_system.strength - 0.01, 1) + +/mob/living/proc/try_contact_infect(datum/disease/acute/D, zone = BODY_ZONE_EVERYTHING, note = "Try Contact Infect") + if(!(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)) + return + var/block = check_contact_sterility(zone) + if(!block) + infect_disease(D, notes = note) + diff --git a/aquila/code/modules/virology/living/mouse.dm b/aquila/code/modules/virology/living/mouse.dm new file mode 100644 index 00000000000..5558e202d1e --- /dev/null +++ b/aquila/code/modules/virology/living/mouse.dm @@ -0,0 +1,70 @@ +/mob/living/simple_animal/mouse + var/disease_chance = 25 + var/diseased = TRUE + +/mob/living/simple_animal/mouse/ratking + diseased = FALSE + +/mob/living/simple_animal/mouse/Initialize(mapload, tame, new_body_color) + . = ..() + immune_system = new(src) + + if(prob(disease_chance) && diseased) + var/virus_choice = pick(WILD_ACUTE_DISEASES) + var/list/anti = list( + ANTIGEN_BLOOD = 2, + ANTIGEN_COMMON = 2, + ANTIGEN_RARE = 1, + ANTIGEN_ALIEN = 0, + ) + var/list/bad = list( + EFFECT_DANGER_HELPFUL = 1, + EFFECT_DANGER_FLAVOR = 2, + EFFECT_DANGER_ANNOYING = 2, + EFFECT_DANGER_HINDRANCE = 2, + EFFECT_DANGER_HARMFUL = 2, + EFFECT_DANGER_DEADLY = 2, + ) + var/datum/disease/acute/disease = new virus_choice + disease.makerandom(list(50,90),list(10,100),anti,bad,src) + disease.spread_flags &= ~DISEASE_SPREAD_AIRBORNE + diseases = list() + diseases += disease + disease.after_add() + src.med_hud_set_status() + disease.log += "
[ROUND_TIME()] Infected [src]" + + log_virus("[key_name(src)] was infected by virus: [disease.admin_details()] at [loc_name(loc)]") + disease.origin = "[capitalize(name)]" + disease.AddToGoggleView(src) + +/mob/living/simple_animal/mouse/Destroy() + QDEL_NULL(immune_system) + return ..() + +/mob/living/simple_animal/mouse/attackby(obj/item/attacking_item, mob/living/user, params) + . = ..() + if(!istype(attacking_item, /obj/item/reagent_containers/syringe)) + return + if(!do_after(user, 1.5 SECONDS, src)) + return + var/obj/item/reagent_containers/syringe/I = attacking_item + var/list/data = list("blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"viruses"=list(),"immunity"=list()) + if(diseases) + data["viruses"] |= diseases + data["immunity"] = immune_system.GetImmunity() + I.reagents.add_reagent(/datum/reagent/blood, I.volume, data) + +/mob/living/simple_animal/mouse/Life(seconds_per_tick, times_fired) + . = ..() + // mice diseases are not very important, so let's just. ensure this yields. + if(TICK_CHECK) + return + handle_virus_updates(seconds_per_tick, times_fired) + breath_airborne_diseases() + for(var/mob/living/simple_animal/mouse/mouse in range(1, src)) + if(TICK_CHECK) + break + share_contact_diseases(mouse)//Mice automatically share contact diseases among themselves + if(!(stat == DEAD)) + spread_airborne_diseases() diff --git a/aquila/code/modules/virology/living/spread_disease.dm b/aquila/code/modules/virology/living/spread_disease.dm new file mode 100644 index 00000000000..878533109a6 --- /dev/null +++ b/aquila/code/modules/virology/living/spread_disease.dm @@ -0,0 +1,100 @@ +/mob/living + ///our immune system + var/datum/immune_system/immune_system + +/atom + ///image + var/image/pathogen + +/mob/living/proc/spread_airborne_diseases() + //spreading our own airborne viruses + if (diseases && diseases.len > 0) + var/list/airborne_viruses = filter_disease_by_spread(diseases, required = DISEASE_SPREAD_AIRBORNE) + if (airborne_viruses && airborne_viruses.len > 0) + var/strength = 0 + for (var/datum/disease/acute/V as anything in airborne_viruses) + strength += V.infectionchance + strength = round(strength/airborne_viruses.len) + while (strength > 0)//stronger viruses create more clouds at once + new /obj/effect/pathogen_cloud/core(get_turf(src), src, airborne_viruses) + strength -= 40 + +/mob/living/infect_disease(datum/disease/acute/disease, forced = FALSE, notes = "", decay = TRUE, patient_zero = FALSE) + if(!istype(disease)) + return FALSE + + if(!(disease.infectable_biotypes & mob_biotypes)) + return + + if(!disease.spread_flags && !(disease.disease_flags & DISEASE_DORMANT)) + return FALSE + + for(var/datum/disease/acute/old_disease as anything in diseases) + if(disease.uniqueID == old_disease.uniqueID && disease.subID == old_disease.subID) // child ids are for pathogenic mutations and aren't accounted for as thats fucked. + return FALSE + + if(immune_system && !immune_system.CanInfect(disease)) + return FALSE + + if(prob(disease.infectionchance) || forced) + var/datum/disease/acute/D = disease.Copy() + if (D.infectionchance > 5) + D.infectionchance = max(5, D.infectionchance - 5)//The virus gets weaker as it jumps from people to people + + D.stage = clamp(disease.stage + D.stage_variance, 1, D.max_stages) + D.log += "
[ROUND_TIME()] Infected [key_name(src)] [notes]. Infection chance now [D.infectionchance]%" + + LAZYADD(diseases, D) + if(patient_zero) + D.carrier = TRUE + D.affected_mob = src + //SSdisease.active_diseases += D + D.after_add() + src.med_hud_set_status() + + log_virus("[key_name(src)] was infected by virus: [D.admin_details()] at [loc_name(loc)]") + + D.AddToGoggleView(src) + + if(GLOB.static_plague_team) + var/datum/team/plague_rat/plague = GLOB.static_plague_team + if (plague && ("[D.uniqueID]-[D.subID]" == plague.disease_id)) + var/datum/objective/plague/O = locate() in plague.objectives + if (O && !istype(src, /mob/living/simple_animal/mouse/plague)) + O.total_infections++ + plague.update_hud_icons() + + return TRUE + +/mob/dead/new_player/proc/DiseaseCarrierCheck(mob/living/carbon/human/H) + // 10% of players are joining the station with some minor disease if latejoined + //don't let the compromised die right out of the gates. + if(("Immunodeficiency" in H?.client?.prefs?.active_character?.all_quirks) || HAS_TRAIT(H, TRAIT_IMMUNODEFICIENCY)) + return + if(prob(10)) + var/virus_choice = pick(WILD_ACUTE_DISEASES) + var/datum/disease/acute/D = new virus_choice + + var/list/anti = list( + ANTIGEN_BLOOD = 1, + ANTIGEN_COMMON = 1, + ANTIGEN_RARE = 0, + ANTIGEN_ALIEN = 0, + ) + var/list/bad = list( + EFFECT_DANGER_HELPFUL = 1, + EFFECT_DANGER_FLAVOR = 4, + EFFECT_DANGER_ANNOYING = 4, + EFFECT_DANGER_HINDRANCE = 0, + EFFECT_DANGER_HARMFUL = 0, + EFFECT_DANGER_DEADLY = 0, + ) + + D.makerandom(list(30,55),list(0,50),anti,bad,null) + + D.log += "
[ROUND_TIME()] Infected [key_name(H)]" + if(!length(H.diseases)) + H.diseases = list() + H.diseases += D + + D.AddToGoggleView(H) diff --git a/aquila/code/modules/virology/living/sterile_procs.dm b/aquila/code/modules/virology/living/sterile_procs.dm new file mode 100644 index 00000000000..17cb879f15f --- /dev/null +++ b/aquila/code/modules/virology/living/sterile_procs.dm @@ -0,0 +1,66 @@ +/mob/living/proc/check_contact_sterility(body_part) + return FALSE + +/mob/living/carbon/human/check_contact_sterility(body_part) + var/check_flags + if(body_part == BODY_ZONE_EVERYTHING) + check_flags = FULL_BODY + else if(body_part == BODY_ZONE_LEGS) + check_flags = LEGS | FEET + else if(body_part == BODY_ZONE_ARMS) + check_flags = ARMS | HANDS + else + check_flags = body_zone2cover_flags(body_part) + + for(var/obj/item/cloth as anything in get_equipped_items()) + if(QDELETED(cloth)) + continue + if((cloth.body_parts_covered & check_flags) && prob(cloth.armor?["bio"])) + return TRUE + return FALSE + +/mob/living/proc/check_bodypart_bleeding(zone) + return FALSE + +/mob/living/carbon/human/check_bodypart_bleeding(zone) + if(HAS_TRAIT(src, TRAIT_NO_BLOOD) || blood_volume <= 0) + return FALSE + + var/list/checks + if(zone == BODY_ZONE_EVERYTHING) + checks = list(BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG, BODY_ZONE_R_ARM, BODY_ZONE_HEAD) + else if(zone == BODY_ZONE_LEGS) + checks = list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + else if(zone == BODY_ZONE_ARMS) + checks = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) + else + checks = islist(zone) ? zone : list(zone) + + // AQ: u nas krwawienie jest efektem statusowym calego moba (/datum/status_effect/bleeding), + // a nie polem konczyny, wiec krwawiace sa wszystkie sprawdzane strefy. + var/bleeding_flags = NONE + if(is_bleeding()) + for(var/obj/item/bodypart/limb as anything in bodyparts) + if(limb.body_zone in checks) + bleeding_flags |= limb.body_part + + if(!bleeding_flags) + return FALSE + + for(var/obj/item/cloth as anything in get_equipped_items()) + if(QDELETED(cloth)) + continue + if(prob(cloth.armor?["bio"])) + bleeding_flags &= ~cloth.body_parts_covered + + return !!bleeding_flags + +/mob/living/proc/check_airborne_sterility() + return FALSE + +/mob/living/carbon/human/check_airborne_sterility() + if (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && prob(wear_mask.armor?["bio"])) + return TRUE + if (head && (head.flags_cover & HEADCOVERSMOUTH) && prob(head.armor?["bio"])) + return TRUE + return FALSE diff --git a/aquila/code/modules/virology/machines/analyzer.dm b/aquila/code/modules/virology/machines/analyzer.dm new file mode 100644 index 00000000000..64576784bdb --- /dev/null +++ b/aquila/code/modules/virology/machines/analyzer.dm @@ -0,0 +1,270 @@ +/obj/machinery/disease2/diseaseanalyser + name = "disease analyzer" + desc = "For analysing pathogenic dishes of sufficient growth." + icon = 'aquila/icons/virology/virology.dmi' + icon_state = "analyser" + anchored = TRUE + density = TRUE + light_color = "#6496FA" + light_range = 2 + light_power = 1 + + circuit = /obj/item/circuitboard/machine/diseaseanalyser + + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.1 //1000 extra power once per analysis + + var/process_time = 5 + var/minimum_growth = 100 + var/obj/item/weapon/virusdish/dish = null + var/last_scan_name = "" + var/last_scan_info = "" + + var/processing = FALSE + /// Reference to the mob that is currently scanning whatever virus is inserted + var/mob/scanner = null + +/obj/machinery/disease2/diseaseanalyser/Destroy() + if(!QDELETED(dish)) + dish.forceMove(drop_location()) + dish = null + scanner = null + return ..() + +/obj/machinery/disease2/diseaseanalyser/RefreshParts() + . = ..() + var/scancount = 0 + for(var/obj/item/stock_parts/scanning_module/M in component_parts) + scancount += M.rating + minimum_growth = round((initial(minimum_growth) - (scancount * 6))) + +/obj/machinery/disease2/diseaseanalyser/attackby(obj/item/tool, mob/user, params) + if(machine_stat & (BROKEN)) + to_chat(user, span_warning("\The [src] is broken. Some components will have to be replaced before it can work again.")) + return NONE + + if(scanner) + to_chat(user, span_warning("\The [scanner] is currently busy using this analyzer.")) + return TRUE + + if(dish) + if(isvirusdish(tool)) + to_chat(user, span_warning("There is already a dish in there. Alt+Click or perform the analysis to retrieve it first.")) + return TRUE + if(is_reagent_container(tool)) + // If you click on the machine with a reagent container, you can interact directly with the dish + return dish.attackby(tool, user, params) + + // There's no dish so lets insert one + if(!isvirusdish(tool)) + return NONE + + var/obj/item/weapon/virusdish/inserting_dish = tool + if(!inserting_dish.open) + to_chat(user, span_warning("You must open the dish's lid before it can be analysed. Be sure to wear proper protection first (at least a sterile mask and latex gloves).")) + return TRUE + if(!user.transferItemToLoc(tool, src)) + return TRUE + visible_message(span_notice("\The [user] inserts \the [tool] in \the [src]."), + span_notice("You insert \the [tool] in \the [src].")) + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + dish = tool + update_appearance() + return TRUE + +/obj/machinery/disease2/diseaseanalyser/attack_hand(mob/user) + . = ..() + if(machine_stat & (BROKEN)) + to_chat(user, span_notice("\The [src] is broken. Some components will have to be replaced before it can work again.")) + return + if(machine_stat & (NOPOWER)) + to_chat(user, span_notice("Deprived of power, \the [src] is unresponsive.")) + if(dish) + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + dish.forceMove(drop_location()) + dish = null + update_appearance() + return + + if(.) + return + + if(scanner) + to_chat(user, span_warning("\The [scanner] is currently busy using this analyzer.")) + return + + if(!dish) + to_chat(user, span_notice("Place an open growth dish first to analyse its pathogen.")) + return + + if(dish.growth < minimum_growth) + say("Pathogen growth insufficient. Minimal required growth: [minimum_growth]%.") + to_chat(user,span_notice("Add some virus food to the dish and incubate.")) + if(minimum_growth == 100) + to_chat(user,span_notice("Replacing the machine's scanning modules with better parts will lower the growth requirement.")) + dish.forceMove(drop_location()) + dish = null + update_appearance() + return + + scanner = user + icon_state = "analyzer_processing" + processing = TRUE + update_appearance() + + var/mutable_appearance/light_overlay = mutable_appearance(icon, "analyser_light", offset_spokesman = src, plane = ABOVE_LIGHTING_PLANE) + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, add_overlay), light_overlay), 0.1 SECONDS) + use_power(1000) + set_light(2,2) + playsound(src, 'sound/machines/chime.ogg', vol = 50) + + if(do_after(user, 5 SECONDS, src)) + if(!is_operational) + processing = FALSE // Make sure to return the machine to normal operation if power outage + update_appearance() + scanner = null + return + if(!istype(dish.contained_virus, /datum/disease/acute)) + QDEL_NULL(dish) + say("ERROR:Bad Pathogen detected PURGING") + processing = FALSE // Make sure to return the machine to normal operation after purge + update_appearance() + scanner = null + return + if(dish.contained_virus.addToDB()) + say("Added new pathogen to database.") + var/datum/data/record/v = GLOB.virusDB["[dish.contained_virus.uniqueID]-[dish.contained_virus.subID]"] + dish.info = dish.contained_virus.get_info() + dish.update_desc() + last_scan_name = dish.contained_virus.name(TRUE) + if(v) + last_scan_name += v.fields["nickname"] ? " \"[v.fields["nickname"]]\"" : "" + + dish.name = "growth dish ([last_scan_name])" + last_scan_info = dish.info + dish.contained_virus.Refresh_Acute() + var/datum/browser/popup = new(user, "\ref[dish]", dish.name, 600, 500, src) + popup.set_content(dish.info) + popup.open() + dish.analysed = TRUE + dish.contained_virus.disease_flags |= DISEASE_ANALYZED + dish.update_appearance() + dish.forceMove(drop_location()) + dish = null + else + playsound(src, 'sound/machines/buzz-sigh.ogg', vol = 25) + + processing = FALSE + update_appearance() + scanner = null + +/obj/machinery/disease2/diseaseanalyser/update_icon_state() + if(processing) + icon_state = "analyzer-processing" + else if(machine_stat & (NOPOWER)) + icon_state = "analyser0" + else if(machine_stat & (BROKEN)) + icon_state = "analyserb" + else + icon_state = "analyser" + return ..() + +/obj/machinery/disease2/diseaseanalyser/update_appearance(updates) + . = ..() + if(!is_operational) + set_light(0) + else + set_light(2, 1) + +/obj/machinery/disease2/diseaseanalyser/update_overlays() + . = ..() + if(processing) + . += emissive_appearance(icon, "analyzer-processing-emissive", src) + + . += mutable_appearance(icon,"analyser_light") + . += emissive_appearance(icon, "analyser_light_e", src) + + . += emissive_appearance(icon, "analyzer-emissive", src) + if(dish) + . += mutable_appearance(icon, "smalldish-outline") + if(dish.contained_virus) + var/mutable_appearance/dish_color_overlay = mutable_appearance(icon, "smalldish-color") + dish_color_overlay.color = dish.contained_virus.color + . += dish_color_overlay + else + . += mutable_appearance(icon, "smalldish-empty") + +/obj/machinery/disease2/diseaseanalyser/verb/PrintPaper() + set name = "Print last analysis" + set category = "Object" + set src in oview(1) + + if(!isturf(usr?.loc)) + return + + if(machine_stat & (BROKEN)) + to_chat(usr, span_notice("\The [src] is broken. Some components will have to be replaced before it can work again.")) + return + if(machine_stat & (NOPOWER)) + to_chat(usr, span_notice("Deprived of power, \the [src] is unresponsive.")) + return + + playsound(src, "sound/effects/fax.ogg", vol = 50, vary = TRUE) + var/image/paper = image(icon, src, "analyser-paper") + flick_overlay(paper, GLOB.clients, 3 SECONDS) + visible_message("\The [src] prints a sheet of paper.") + addtimer(CALLBACK(src, PROC_REF(print_paper))) + +/obj/machinery/disease2/diseaseanalyser/proc/print_paper() + var/obj/item/paper/paper = new(drop_location()) + paper.name = last_scan_name + paper.add_raw_text(last_scan_info) + paper.pixel_x = 8 + paper.pixel_y = -8 + paper.update_appearance() + +/obj/machinery/disease2/diseaseanalyser/process() + if(!is_operational) + scanner = null + return + + if(scanner && !(scanner in range(src,1))) + update_appearance() + processing = FALSE + scanner = null + +/obj/machinery/disease2/diseaseanalyser/AltClick(mob/user) + if(!dish && scanner) + return TRUE + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + dish.forceMove(drop_location()) + dish = null + update_appearance() + return TRUE + +/obj/machinery/disease2/diseaseanalyser/fullupgrade + circuit = /obj/item/circuitboard/machine/diseaseanalyser/fullupgrade + + +/obj/machinery/disease2/diseaseanalyser/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + if(processing) + to_chat(user, span_warning("\The [src] is currently processing! Please wait for process to finish")) + return FALSE + return default_deconstruction_screwdriver(user, "analyseru", "analyser", I) + +/obj/machinery/disease2/diseaseanalyser/crowbar_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + if(processing) + to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion.")) + return FALSE + return default_deconstruction_crowbar(I) + + +/obj/machinery/disease2/diseaseanalyser/attack_ai(mob/user) + if(!panel_open) + return attack_hand(user) + +/obj/machinery/disease2/diseaseanalyser/attack_robot(mob/user) + return attack_ai(user) diff --git a/aquila/code/modules/virology/machines/centrifuge.dm b/aquila/code/modules/virology/machines/centrifuge.dm new file mode 100644 index 00000000000..190eccef0ac --- /dev/null +++ b/aquila/code/modules/virology/machines/centrifuge.dm @@ -0,0 +1,542 @@ +#define CENTRIFUGE_LIGHTSPECIAL_OFF 0 +#define CENTRIFUGE_LIGHTSPECIAL_BLINKING 1 +#define CENTRIFUGE_LIGHTSPECIAL_ON 2 + + +/obj/machinery/disease2/centrifuge + name = "isolation centrifuge" + desc = "Used to isolate pathogen and antibodies in blood. Make sure to keep the tubes balanced when spinning for optimal efficiency." + icon = 'aquila/icons/virology/virology.dmi' + + base_icon_state = "centrifuge" + icon_state = "centrifuge" + density = TRUE + anchored = TRUE + + processing_flags = START_PROCESSING_MANUALLY + + var/datum/browser/popup = null + + var/on = FALSE + + var/list/tubes = list(null,null,null,null) + var/list/tube_valid = list(0,0,0,0) + var/list/tube_task = list( + list(0,0,0,0,0,), + list(0,0,0,0,0,), + list(0,0,0,0,0,), + list(0,0,0,0,0,), + ) + + light_color = "#8DC6E9" + light_range = 2 + light_power = 1 + + circuit = /obj/item/circuitboard/machine/centrifuge + + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.3 + + var/base_efficiency = 1 + var/upgrade_efficiency = 0.3 // the higher, the better will upgrade affect efficiency + + var/efficiency = 1 + + var/special = CENTRIFUGE_LIGHTSPECIAL_OFF + + var/ejecting = FALSE + +/obj/machinery/disease2/centrifuge/Initialize(mapload) + . = ..() + RefreshParts() + +/obj/machinery/disease2/centrifuge/Destroy() + for (var/i = 1 to length(tubes)) + var/obj/item/reagent_containers/glass/tube/tube = tubes[i] + tube?.forceMove(drop_location()) + tubes = list(null,null,null,null) + tube_valid = list(0,0,0,0) + tube_task = list( + list(0,0,0,0,0,), + list(0,0,0,0,0,), + list(0,0,0,0,0,), + list(0,0,0,0,0,), + ) + special = CENTRIFUGE_LIGHTSPECIAL_OFF + return ..() + +/obj/machinery/disease2/centrifuge/RefreshParts() + . = ..() + var/manipcount = 0 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + manipcount += M.rating + base_efficiency = 1 + upgrade_efficiency * (manipcount-2) + +/obj/machinery/disease2/centrifuge/attackby(obj/item/tool, mob/user, params) + if(!istype(tool, /obj/item/reagent_containers/glass/tube)) + return NONE + + if(machine_stat & (BROKEN)) + to_chat(user, span_warning("\The [src] is broken. Some components will have to be replaced before it can work again.") ) + return NONE + + special = CENTRIFUGE_LIGHTSPECIAL_OFF + if(on) + to_chat(user,span_warning("You cannot add or remove tubes while the centrifuge is active. Turn it off first.")) + return TRUE + var/obj/item/reagent_containers/glass/tube/inserting_tube = tool + for(var/to_insert in 1 to length(tubes)) + if(!isnull(tubes[to_insert])) + continue + if(!user.transferItemToLoc(inserting_tube, src)) + return + tubes[to_insert] = inserting_tube + tube_valid[to_insert] = tube_has_antibodies(inserting_tube) + visible_message(span_notice("[user] adds \the [inserting_tube] to \the [src]."), span_notice("You add \the [inserting_tube] to \the [src].")) + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + update_appearance(UPDATE_OVERLAYS) + updateUsrDialog() + return TRUE + + to_chat(user, span_warning("There is no room for more tubes.")) + return TRUE + +/obj/machinery/disease2/centrifuge/proc/tube_has_antibodies(obj/item/reagent_containers/glass/tube/tube) + if (!tube) + return FALSE + + for(var/datum/reagent/blood in tube.reagents.reagent_list) + if (length(blood?.data) && ("immunity" in blood.data)) + var/list/immune_system = blood.data["immunity"] + if (islist(immune_system) && length(immune_system) > 0) + var/list/antibodies = immune_system[2] + for (var/antibody in antibodies) + if (antibodies[antibody] >= 30) + return TRUE + +//Also handles luminosity +/obj/machinery/disease2/centrifuge/update_icon_state() + if (machine_stat & NOPOWER) + icon_state = "[base_icon_state]0" + else if (machine_stat & BROKEN) + icon_state = "[base_icon_state]b" + else if(on) + icon_state = "[base_icon_state]_moving" + else + icon_state = base_icon_state + return ..() + +/obj/machinery/disease2/centrifuge/update_appearance(updates) + . = ..() + if(!is_operational) + set_light(0) + else if(on) + set_light(2, 2) + else + set_light(2, 1) + +/obj/machinery/disease2/centrifuge/update_overlays() + . = ..() + if(is_operational) + if(on) + . += emissive_appearance(icon, "centrifuge-emisisve", src) + var/mutable_appearance/centrifuge_glow = emissive_appearance(icon, "centrifuge_glow_e", src) + centrifuge_glow.blend_mode = BLEND_ADD + . += centrifuge_glow + + var/mutable_appearance/centrifuge_glow_n = mutable_appearance(icon, "centrifuge_glow") + . += centrifuge_glow_n + + switch (special) + if (CENTRIFUGE_LIGHTSPECIAL_BLINKING) + . += emissive_appearance(icon, "centrifuge_special_update_e", src) + . += mutable_appearance(icon, "centrifuge_special_update") + special = CENTRIFUGE_LIGHTSPECIAL_ON + if (CENTRIFUGE_LIGHTSPECIAL_ON) + . += emissive_appearance(icon, "centrifuge_special_e", src) + . += mutable_appearance(icon, "centrifuge_special") + + for (var/i = 1 to length(tubes)) + var/obj/item/reagent_containers/glass/tube/tube = tubes[i] + if(!tube) + continue + . += mutable_appearance(icon, "centrifuge_vial[i][on ? "_moving" : ""]") + if(tube.reagents.total_volume) + var/mutable_appearance/filling = mutable_appearance(icon, "centrifuge_vial[i]_filling[on ? "_moving" : ""]") + filling.color = mix_color_from_reagents(tube.reagents.reagent_list) + . += filling + +/obj/machinery/disease2/centrifuge/on_set_machine_stat(old_value) + . = ..() + update_appearance(UPDATE_ICON_STATE | UPDATE_OVERLAYS) + +/obj/machinery/disease2/centrifuge/on_set_is_operational(old_value) + . = ..() + if(!is_operational) + set_on(FALSE) + +/obj/machinery/disease2/centrifuge/proc/add_tube_dat(obj/item/reagent_containers/glass/tube/tube, list/tube_task = list(0,0,0,0,0), slot = 1) + var/dat = "" + var/valid = tube_valid[slot] + + var/passes = FALSE + for(var/datum/reagent/blood in tube.reagents.reagent_list) + if (!length(blood.data) || !("blood_DNA" in blood.data)) + continue + passes = TRUE + + if(!passes) + var/datum/reagent/vaccine/vaccine = locate() in tube.reagents.reagent_list + if (!vaccine) + dat += "[tube.name] (no blood detected)" + else + var/vaccines = "" + for (var/A in vaccine.data["antigen"]) + vaccines += "[A]" + if (vaccines == "") + vaccines = "blank" + dat += "[tube.name] (Vaccine ([vaccines]))" + else + if (tube_task[1]) + switch (tube_task[1]) + if ("dish") + var/target = tube_task[2] + var/progress = tube_task[3] + dat += "[tube.name] (isolating [target]: [round(progress)]%) X" + if ("vaccine") + var/target = tube_task[2] + var/progress = tube_task[3] + dat += "[tube.name] (synthesizing vaccine ([target]): [round(progress)]%) X" + + else + for(var/datum/reagent/blood in tube.reagents.reagent_list) + if(length(blood.data) && blood.data["viruses"]) + var/list/blood_diseases = blood.data["viruses"] + if (length(blood_diseases) > 0) + dat += "[tube.name] (pathogen detected) ISOLATE TO DISH [valid ? "SYNTHESIZE VACCINE" : "(not enough antibodies for a vaccine)"]" + else + dat += "[tube.name] (no pathogen detected) [valid ? "SYNTHESIZE VACCINE" : "(not enough antibodies for a vaccine)"]" + return dat + +/obj/machinery/disease2/centrifuge/proc/eject_when_no_power() + ejecting = TRUE + for(var/i = 1 to length(tubes)) + var/obj/item/reagent_containers/glass/tube/tube = tubes[i] + if(!tube) + continue + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + tube.forceMove(drop_location()) + tubes[i] = null + tube_valid[i] = 0 + tube_task[i] = list(0,0,0,0,0) + update_appearance(UPDATE_OVERLAYS) + sleep(0.1 SECONDS) + ejecting = FALSE + +/obj/machinery/disease2/centrifuge/attack_hand(mob/user, list/modifiers) + . = ..() + if(machine_stat & (BROKEN)) + to_chat(user, span_notice("\The [src] is broken. Some components will have to be replaced before it can work again.") ) + return + + if(machine_stat & (NOPOWER)) + if(!ejecting) + to_chat(user, span_notice("Deprived of power, \the [src] is unresponsive.")) + INVOKE_ASYNC(src, PROC_REF(eject_when_no_power)) + return + + if(.) + return + + user.set_machine(src) + + special = CENTRIFUGE_LIGHTSPECIAL_OFF + + var/dat = "" + dat += "Power status: [on?"On":"Off"]" + dat += "
" + for (var/i = 1 to length(tubes)) + if(tubes[i]) + dat += add_tube_dat(tubes[i],tube_task[i],i) + else + dat += "Insert a tube" + if(i < length(tubes)) + dat += "
" + dat += "
" + + popup = new(user, "centrifuge", "Isolation Centrifuge", 666, 189) + popup.set_window_options("can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;") + popup.set_content(dat) + popup.open() + +/obj/machinery/disease2/centrifuge/process() + if(!on || !is_operational) + return PROCESS_KILL + + //first of all, let's see how (un)balanced are those tubes. + //we're not taking reagent density into account because even my autism has its limits + var/obj/item/reagent_containers/glass/tube/tube1 = tubes[1]//left + var/obj/item/reagent_containers/glass/tube/tube2 = tubes[2]//up + var/obj/item/reagent_containers/glass/tube/tube3 = tubes[3]//right + var/obj/item/reagent_containers/glass/tube/tube4 = tubes[4]//down + var/tube_unbalance_X = 0 + if (tube1) + tube_unbalance_X += 5 + tube1.reagents.total_volume + if (tube3) + tube_unbalance_X -= 5 + tube3.reagents.total_volume + var/tube_unbalance_Y = 0 + if (tube2) + tube_unbalance_Y += 5 + tube2.reagents.total_volume + if (tube4) + tube_unbalance_Y -= 5 + tube4.reagents.total_volume + + var/tube_unbalance = abs(tube_unbalance_X) + abs(tube_unbalance_Y) // tubes can contain up to 30 units, so maximal unbalance is 70. + + efficiency = base_efficiency / (1 + tube_unbalance / 70) // which will at most double the time taken. + + var/should_update = FALSE + for (var/i = 1 to length(tubes)) + if(!tubes[i]) + continue + should_update = TRUE + var/list/v_task = tube_task[i] + if(v_task[1]) + tube_task[i] = centrifuge_act(tubes[i],tube_task[i]) + + if(should_update) + updateUsrDialog() + update_appearance(UPDATE_OVERLAYS) + +/obj/machinery/disease2/centrifuge/proc/centrifuge_act(obj/item/reagent_containers/glass/tube/tube, list/tube_task = list(0,0,0,0,0)) + var/list/result = list(0,0,0,0,0) + if (!tube) + return result + result = tube_task + switch (result[1]) + if ("dish") + result[3] += (efficiency * 2) / (1 + 0.3 * result[5])//additional pathogen in the sample will lengthen the process + if (result[3] >= 100) + print_dish(result[4]) + result = list(0,0,0,0,0) + if ("vaccine") + if (result[4] > 50) + result[3] += (efficiency * 2) * max(1,result[4]-50) + else if (result[4] < 50) + result[3] += (efficiency * 2) / max(1,50-result[4]) + else + result[3] += (efficiency * 2) + if (result[3] >= 100) + special = CENTRIFUGE_LIGHTSPECIAL_BLINKING + var/amt = tube.reagents.total_volume + tube.reagents.clear_reagents() + var/data = list("antigen" = list(result[2])) + tube.reagents.add_reagent(/datum/reagent/vaccine, amt, data) + result = list(0,0,0,0,0) + return result + +/obj/machinery/disease2/centrifuge/Topic(href, href_list) + + if(..()) + return TRUE + + special = CENTRIFUGE_LIGHTSPECIAL_OFF + + if (href_list["power"]) + set_on(!on) + + else if (href_list["insertvial"]) + var/mob/living/user + if (isliving(usr)) + user = usr + if (!user) + return + var/obj/item/reagent_containers/glass/tube/tube = user.get_active_hand() + if (istype(tube)) + if (on) + to_chat(user, span_warning("You cannot add or remove tubes while the centrifuge is active. Turn it off first.")) + return + else + var/i = text2num(href_list["insertvial"]) + if (!tubes[i]) + if(!user.transferItemToLoc(tube, src)) + return + tubes[i] = tube + tube_valid[i] = tube_has_antibodies(tube) + visible_message(span_notice("[user] adds \the [tube] to \the [src]."), span_notice("You add \the [tube] to \the [src].")) + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + else + to_chat(user,span_warning("There is already a tube in that slot.")) + return + + else if (href_list["ejectvial"]) + if (on) + to_chat(usr,span_warning("You cannot add or remove tubes while the centrifuge is active. Turn it Off first.")) + return + else + var/i = text2num(href_list["ejectvial"]) + var/obj/item/reagent_containers/glass/tube/tube = tubes[i] + if (tube) + tube.forceMove(drop_location()) + if (Adjacent(usr)) + usr.put_in_hands(tube) + tubes[i] = null + tube_valid[i] = 0 + tube_task[i] = list(0,0,0,0,0) + + else if (href_list["interrupt"]) + var/i = text2num(href_list["interrupt"]) + tube_task[i] = list(0,0,0,0,0) + + else if (href_list["isolate"]) + var/i = text2num(href_list["isolate"]) + tube_task[i] = isolate(tubes[i],usr) + + else if (href_list["synthvaccine"]) + var/i = text2num(href_list["synthvaccine"]) + tube_task[i] = cure(tubes[i],usr) + + update_appearance() + add_fingerprint(usr) + updateUsrDialog() + attack_hand(usr) + +/obj/machinery/disease2/centrifuge/proc/isolate(obj/item/reagent_containers/glass/tube/tube, mob/user) + var/list/result = list(0,0,0,0,0) + if (!tube) + return result + + for(var/datum/reagent/blood in tube.reagents.reagent_list) + if (blood && length(blood.data) && ("viruses" in blood.data)) + var/list/blood_viruses = blood.data["viruses"] + if (islist(blood_viruses) && length(blood_viruses) > 0) + var/list/pathogen_list = list() + for (var/datum/disease/acute/D as anything in blood_viruses) + if(!istype(D)) + continue + var/pathogen_name = "Unknown [D.form]" + pathogen_list[pathogen_name] = D + + popup.close() + user.unset_machine() + var/choice = input(user, "Choose a pathogen to isolate on a growth dish.", "Isolate to dish") as null|anything in pathogen_list + user.set_machine() + if (!choice) + return result + var/datum/disease/acute/target = pathogen_list[choice] + + result[1] = "dish" + result[2] = "Unknown [target.form]" + result[3] = 0 + result[4] = target + result[5] = length(pathogen_list) + + return result + +/obj/machinery/disease2/centrifuge/proc/cure(obj/item/reagent_containers/glass/tube/tube, mob/user) + var/list/result = list(0,0,0,0,0) + if (!tube) + return result + + for(var/datum/reagent/blood in tube.reagents.reagent_list) + if (length(blood.data) && ("immunity" in blood.data)) + var/list/immune_system = blood.data["immunity"] + if (islist(immune_system) && length(immune_system) > 0) + if (immune_system[1] < 1) + to_chat(user,span_warning("Impossible to acquire antibodies from this blood sample. It seems that it came from a donor with a poor immune system, either due to recent cloning or a radium overload.") ) + return result + + var/list/antibodies = immune_system[2] + var/list/antibody_choices = list() + for (var/antibody in antibodies) + if (antibodies[antibody] >= 30) + if (antibodies[antibody] > 50) + var/delay = max(1,60 / max(1,(antibodies[antibody] - 50))) + antibody_choices["[antibody] (Expected Duration: [round(delay)] seconds)"] = antibody + else if (antibodies[antibody] < 50) + var/delay = max(1,50 - min(49,antibodies[antibody] - 25)) + antibody_choices["[antibody] (Expected Duration: [round(delay)] minutes)"] = antibody + else + antibody_choices["[antibody] (Expected Duration: one minute)"] = antibody + + if (length(antibody_choices) <= 0) + to_chat(user,span_warning("Impossible to create a vaccine from this blood sample. Antibody levels too low. Minimal level = 30%. The higher the concentration, the faster the vaccine is synthesized.") ) + return result + + popup.close() + user.unset_machine() + var/choice = input(user, "Choose an antibody to develop into a vaccine. This will destroy the blood sample. The higher the concentration, the faster the vaccine is synthesized.", "Synthesize Vaccine") as null|anything in antibody_choices + user.set_machine() + if (!choice) + return result + + var/antibody = antibody_choices[choice] + + result[1] = "vaccine" + result[2] = antibody + result[3] = 0 + result[4] = antibodies[antibody] + + return result + +/obj/machinery/disease2/centrifuge/proc/set_on(status, mob/user) + if(status == on) + return + on = status + if(on) + use_power = ACTIVE_POWER_USE + begin_processing() + else + use_power = IDLE_POWER_USE + end_processing() + update_appearance(UPDATE_ICON_STATE | UPDATE_OVERLAYS) + +/obj/machinery/disease2/centrifuge/proc/print_dish(datum/disease/acute/disease) + special = CENTRIFUGE_LIGHTSPECIAL_BLINKING + update_appearance(UPDATE_OVERLAYS) + /* + anim(target = src, a_icon = icon, flick_anim = "centrifuge_print", sleeptime = 10) + anim(target = src, a_icon = icon, flick_anim = "centrifuge_print_color", sleeptime = 10, col = D.color) + */ + visible_message(span_notice("\The [src] prints a growth dish.")) + balloon_alert_to_viewers("growth dish printed") + addtimer(CALLBACK(src, PROC_REF(finish_print_dish), disease.Copy()), 1 SECONDS) + +/obj/machinery/disease2/centrifuge/proc/finish_print_dish(datum/disease/acute/disease) + var/obj/item/weapon/virusdish/dish = new(drop_location()) + if(disease.disease_flags & DISEASE_DORMANT) + balloon_alert_to_viewers("activating virus sample") + disease.disease_flags &= ~DISEASE_DORMANT + disease.randomize_spread() + dish.contained_virus = disease + dish.contained_virus.infectionchance = dish.contained_virus.infectionchance_base + dish.update_appearance() + dish.name = "growth dish (Unknown [dish.contained_virus.form])" + +/obj/machinery/disease2/centrifuge/fullupgrade + circuit = /obj/item/circuitboard/machine/centrifuge/fullupgrade + +/obj/machinery/disease2/centrifuge/screwdriver_act(mob/living/user, obj/item/tool) + if(..()) + return TRUE + if(on) + to_chat(user, span_warning("\The [src] is currently on! Please turn the machine off.")) + return FALSE + return default_deconstruction_screwdriver(user, "[base_icon_state]u", base_icon_state, tool) + +/obj/machinery/disease2/centrifuge/crowbar_act(mob/living/user, obj/item/tool) + if(..()) + return TRUE + if(on) + to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion.")) + return FALSE + return default_deconstruction_crowbar(tool) + +/obj/machinery/disease2/centrifuge/attack_ai(mob/user) + if(!panel_open) + return attack_hand(user) + +/obj/machinery/disease2/centrifuge/attack_robot(mob/user) + return attack_ai(user) + +#undef CENTRIFUGE_LIGHTSPECIAL_OFF +#undef CENTRIFUGE_LIGHTSPECIAL_BLINKING +#undef CENTRIFUGE_LIGHTSPECIAL_ON diff --git a/aquila/code/modules/virology/machines/curer.dm b/aquila/code/modules/virology/machines/curer.dm new file mode 100644 index 00000000000..c321e32de51 --- /dev/null +++ b/aquila/code/modules/virology/machines/curer.dm @@ -0,0 +1,105 @@ +/obj/machinery/computer/curer + name = "Cure Research Machine" + icon_state = "dna" + var/curing + var/virusing + + var/obj/item/reagent_containers/glass/tube/container = null + +/obj/machinery/computer/curer/Destroy() + if(!QDELETED(container)) + container.forceMove(drop_location()) + container = null + return ..() + +/obj/machinery/computer/curer/attackby(obj/item/tool, mob/user, params) + if(istype(tool, /obj/item/reagent_containers/glass/tube)) + if(!container && tool.forceMove(src)) + container = tool + return TRUE + return TRUE + + if(isvirusdish(tool)) + if(virusing) + to_chat(user, "The pathogen materializer is still recharging..") + return TRUE + var/obj/item/reagent_containers/glass/tube/product = new(loc) + var/list/data = list("viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"viruses"=list(),"immunity"=0) + data["viruses"] |= tool:viruses + product.reagents.add_reagent(/datum/reagent/blood, 30,data) + virusing = 1 + spawn(1200) virusing = 0 + return TRUE + + src.attack_hand(user) + return TRUE + +/obj/machinery/computer/curer/attack_hand(mob/user) + if(..()) + return + var/dat + if(curing) + dat = "Antibody production in progress" + else if(virusing) + dat = "Virus production in progress" + else if(container) + // see if there's any blood in the container + var/datum/reagent/blood/B = locate(/datum/reagent/blood) in container.reagents.reagent_list + + if(B) + dat = "Blood sample inserted." + var/code = "" + for(var/V in GLOB.all_antigens) if(text2num(V) & B.data["antibodies"]) code += GLOB.all_antigens[V] + dat += "
Antibodies: [code]" + dat += "
Begin antibody production" + else + dat += "
Please check container contents." + dat += "
Eject container" + else + dat = "Please insert a container." + + user << browse(HTML_SKELETON(dat), "window=computer;size=400x500") + onclose(user, "computer") + return + +/obj/machinery/computer/curer/process() + ..() + + if(machine_stat & (NOPOWER|BROKEN)) + return + use_power(500) + + if(curing) + curing -= 1 + if(curing == 0) + if(container) + createcure(container) + return + +/obj/machinery/computer/curer/Topic(href, href_list) + if(..()) + return + usr.machine = src + + if (href_list["antibody"]) + curing = 10 + else if(href_list["eject"]) + container.forceMove(src.loc) + container = null + + src.add_fingerprint(usr) + src.updateUsrDialog() + return + + +/obj/machinery/computer/curer/proc/createcure(obj/item/reagent_containers/glass/beaker/container) + var/obj/item/reagent_containers/glass/tube/product = new(src.loc) + + var/datum/reagent/blood/B = locate() in container.reagents.reagent_list + + var/list/data = list() + var/list/immunity = B.data["immunity"] + if(length(immunity)) + data["antigen"] = immunity[2] + + product.reagents.add_reagent(/datum/reagent/vaccine, 30, data) diff --git a/aquila/code/modules/virology/machines/disease_records.dm b/aquila/code/modules/virology/machines/disease_records.dm new file mode 100644 index 00000000000..9806ca147df --- /dev/null +++ b/aquila/code/modules/virology/machines/disease_records.dm @@ -0,0 +1,71 @@ +/obj/machinery/computer/records/pathology + name = "disease records console" + desc = "This can be used to check disease records." + icon_screen = "medcomp" + icon_keyboard = "med_key" + req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS, ACCESS_GENETICS) + circuit = /obj/item/circuitboard/computer/pathology_data + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/records/pathology/ui_interact(mob/user, datum/tgui/ui) + . = ..() + if(.) + return + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "PathologyRecords") + ui.set_autoupdate(FALSE) + ui.open() + +/obj/machinery/computer/records/pathology/ui_data(mob/user) + var/list/data = ..() + + var/list/records = list() + for(var/ID in GLOB.virusDB) + var/datum/data/record/target = GLOB.virusDB[ID] + records += list(list( + crew_ref = "[target.fields["id"]]-[target.fields["sub"]]", + id = target.fields["id"], + sub = target.fields["sub"], + child = target.fields["child"], + form = target.fields["form"], + name = target.fields["name"], + nickname = target.fields["nickname"], + description = target.fields["description"], + custom_desc = target.fields["custom_desc"], + antigen = target.fields["antigen"], + spread_flags = target.fields["spread_flags_type"], + danger = target.fields["danger"], + )) + + data["records"] = records + + return data + +/obj/machinery/computer/records/pathology/ui_act(action, list/params, datum/tgui/ui) + . = ..() + var/datum/data/record/target + if(params["crew_ref"] && (params["crew_ref"] in GLOB.virusDB)) + target = GLOB.virusDB[params["crew_ref"]] + if(!target && params["crew_ref"]) + return FALSE + + switch(action) + if("edit_field") + var/field = params["field"] + if(!field || !(field in target?.fields)) + return FALSE + + var/value = reject_bad_name(params["value"], allow_numbers = TRUE, max_length = MAX_BROADCAST_LEN, strict = TRUE, cap_after_symbols = FALSE) || "Unknown" + target.fields[field] = value + + return TRUE + if("expunge_record") + GLOB.virusDB[params["crew_ref"]] = null + qdel(target) + GLOB.virusDB -= params["crew_ref"] + return TRUE + if(.) + return + + return FALSE diff --git a/aquila/code/modules/virology/machines/floor_health_scanner.dm b/aquila/code/modules/virology/machines/floor_health_scanner.dm new file mode 100644 index 00000000000..e4ed052f3de --- /dev/null +++ b/aquila/code/modules/virology/machines/floor_health_scanner.dm @@ -0,0 +1,83 @@ +/obj/machinery/health_scanner_floor + name = "Vitals Scanning Pad" + desc = "Gives patients a brief medical overview by stepping on it." + + icon_state = "floor_scanner" + icon = 'aquila/icons/virology/virology.dmi' + circuit = /obj/item/circuitboard/machine/vital_floor_scanner + + density = FALSE + anchored = TRUE + + maptext_x = -16 + maptext_y = 32 + maptext_width = 64 + + pixel_y = -8 + var/obj/effect/abstract/maptext_holder/floor_scanner/maptext_obj + + COOLDOWN_DECLARE(scan_cd) + +/obj/machinery/health_scanner_floor/Initialize(mapload) + . = ..() + + // AQ: nie mamy flatpackow ani /datum/element/repackable z Monke + + maptext_obj = new(src) + vis_contents += maptext_obj + + var/static/list/connections = list( + COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_entered), + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + COMSIG_ATOM_EXITED = PROC_REF(on_exited), + ) + AddElement(/datum/element/connect_loc, connections) + // AQ (nie mamy elementu podwyzszenia): AddElement(/datum/element/elevation, 4) + +/obj/machinery/health_scanner_floor/Destroy() + if(!QDELETED(maptext_obj)) + vis_contents -= maptext_obj + qdel(maptext_obj) + maptext_obj = null + return ..() + +/obj/machinery/health_scanner_floor/proc/generate_maptext(mob/living/carbon/stepper) + var/health_precent = stepper.health + + var/oxygen = stepper.getOxyLoss() + var/toxin = stepper.getToxLoss() + var/brute = stepper.getBruteLoss() + var/burn = stepper.getFireLoss() + //screw cloning I hate clone damage + + return "[MAPTEXT("[health_precent]%")]\n [oxygen] - [toxin] - [burn] - [brute]" + +/obj/machinery/health_scanner_floor/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) + if(!iscarbon(arrived)) + return + if(!COOLDOWN_FINISHED(src, scan_cd)) + return + COOLDOWN_START(src, scan_cd, 3 SECONDS) + + arrived.visual_masked_scan() + maptext_obj.maptext = generate_maptext(arrived) + occupant = arrived + animate(maptext_obj, 0.25 SECONDS, maptext_y = 32, easing = BOUNCE_EASING) + + addtimer(CALLBACK(src, PROC_REF(clear_maptext)), 3 SECONDS) + +/obj/machinery/health_scanner_floor/proc/on_exited(datum/source, atom/movable/departed) + SIGNAL_HANDLER + if(occupant != departed) + return + occupant = null + +/obj/machinery/health_scanner_floor/proc/clear_maptext() + maptext_obj.maptext = null + maptext_obj.maptext_y = 0 + +/obj/effect/abstract/maptext_holder/floor_scanner + plane = GAME_PLANE + + maptext_x = -16 + maptext_width = 64 diff --git a/aquila/code/modules/virology/machines/incubator.dm b/aquila/code/modules/virology/machines/incubator.dm new file mode 100644 index 00000000000..4a45d3201db --- /dev/null +++ b/aquila/code/modules/virology/machines/incubator.dm @@ -0,0 +1,492 @@ +#define INCUBATOR_DISH_GROWTH (1 << 0) +#define INCUBATOR_DISH_REAGENT (1 << 1) +#define INCUBATOR_DISH_MAJOR (1 << 2) +#define INCUBATOR_DISH_MINOR (1 << 3) + +/obj/machinery/disease2/incubator + name = "pathogenic incubator" + desc = "Uses radiation to accelerate the incubation of pathogen. The dishes must be filled with reagents for the incubation to have any effects." + density = TRUE + anchored = TRUE + icon = 'aquila/icons/virology/virology.dmi' + base_icon_state = "incubator" + icon_state = "incubator" + + circuit = /obj/item/circuitboard/machine/incubator + + light_color = "#6496FA" + light_range = 2 + light_power = 1 + + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.2 + + processing_flags = START_PROCESSING_MANUALLY + + // Contains instances of /datum/dish_incubator_dish. + var/list/dish_data = list(null, null, null) + + var/on = FALSE + + var/mutatechance = 10 + var/growthrate = 8 + var/can_focus = FALSE //Whether the machine can focus on an effect to mutate it or not + var/effect_focus = 0 //What effect of the disease are we focusing on? + + +/obj/machinery/disease2/incubator/Destroy() + for(var/i in 1 to length(dish_data)) + var/datum/dish_incubator_dish/dish_datum = dish_data[i] + if(isnull(dish_datum)) + continue + + if(!QDELETED(dish_datum.dish)) + dish_datum.dish.forceMove(drop_location()) + dish_data[i] = null + return ..() + +/obj/machinery/disease2/incubator/RefreshParts() + . = ..() + var/scancount = 0 + var/lasercount = 0 + for(var/obj/item/stock_parts/scanning_module/SP in component_parts) + scancount += SP.rating * 0.5 + for(var/obj/item/stock_parts/micro_laser/SP in component_parts) + lasercount += SP.rating * 0.5 + if(lasercount >= 4) + can_focus = TRUE + else + can_focus = FALSE + mutatechance = initial(mutatechance) * max(1, scancount) + growthrate = initial(growthrate) + lasercount + + +/obj/machinery/disease2/incubator/attackby(obj/item/tool, mob/user, params) + if(!isvirusdish(tool)) + return NONE + + if(machine_stat & BROKEN) + to_chat(user, span_warning("\The [src] is broken. Some components will have to be replaced before it can work again.")) + return TRUE + + for(var/i in 1 to length(dish_data)) + if(isnull(dish_data[i])) // Empty slot + add_dish(tool, user, i) + return TRUE + + to_chat(user, span_warning("There is no more room inside \the [src]. Remove a dish first.")) + return TRUE + +/obj/machinery/disease2/incubator/screwdriver_act(mob/living/user, obj/item/tool) + if(..()) + return TRUE + if(on) + to_chat(user, span_warning("\The [src] is currently on! Please turn the machine off.")) + return FALSE + return default_deconstruction_screwdriver(user, "[base_icon_state]u", base_icon_state, tool) + +/obj/machinery/disease2/incubator/crowbar_act(mob/living/user, obj/item/tool) + if(..()) + return TRUE + if(on) + to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion.")) + return FALSE + return default_deconstruction_crowbar(tool) + +/obj/machinery/disease2/incubator/proc/add_dish(obj/item/weapon/virusdish/dish, mob/user, slot) + if(!dish.open) + to_chat(user, span_warning("You must open the dish's lid before it can be put inside the incubator. Be sure to wear proper protection first (at least a sterile mask and latex gloves).")) + return + + if(!isnull(dish_data[slot])) + to_chat(user, span_warning("This slot is already occupied. Remove the dish first.")) + return + + if(!user.transferItemToLoc(dish, src)) + return + + dish_data[slot] = new /datum/dish_incubator_dish(dish) + + visible_message(span_notice("[user] adds \the [dish] to \the [src]."), span_notice("You add \the [dish] to \the [src].")) + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + update_appearance(UPDATE_OVERLAYS) + +/obj/machinery/disease2/incubator/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + var/mob/user = ui.user + switch(action) + if("power") + set_on(!on, user) + return TRUE + + if("ejectdish") + var/slot = text2num(params["slot"]) + if(!isnum(slot) || !ISINRANGE(slot, 1, length(dish_data))) + return TRUE + + var/datum/dish_incubator_dish/dish_datum = dish_data[slot] + if(isnull(dish_datum)) + return TRUE + + dish_datum.dish.forceMove(drop_location()) + if(Adjacent(user)) + user.put_in_hands(dish_datum.dish) + + dish_datum.dish.update_appearance() + dish_data[slot] = null + update_appearance(UPDATE_OVERLAYS) + return TRUE + + if("insertdish") + var/slot = text2num(params["slot"]) + if(!isnum(slot) || !ISINRANGE(slot, 1, length(dish_data))) + return TRUE + + if(!isliving(user)) + return TRUE + + var/obj/item/weapon/virusdish/VD = user.get_active_hand() + if(istype(VD)) + add_dish(VD, user, slot) + + return TRUE + + if("examinedish") + var/slot = text2num(params["slot"]) + if(!isnum(slot) || !ISINRANGE(slot, 1, length(dish_data))) + return TRUE + + var/datum/dish_incubator_dish/dish_datum = dish_data[slot] + if(isnull(dish_datum)) + return TRUE + + user.examinate(dish_datum.dish) + return TRUE + + if("flushdish") + var/slot = text2num(params["slot"]) + if(!isnum(slot) || !ISINRANGE(slot, 1, length(dish_data))) + return TRUE + + var/datum/dish_incubator_dish/dish_datum = dish_data[slot] + if(isnull(dish_datum)) + return TRUE + + dish_datum.dish.reagents.clear_reagents() + return TRUE + if("changefocus") + var/slot = text2num(params["slot"]) + if(!isnum(slot) || !ISINRANGE(slot, 1, length(dish_data))) + return TRUE + var/datum/dish_incubator_dish/dish_datum = dish_data[slot] + if(isnull(dish_datum)) + return TRUE + var/stage_to_focus = tgui_input_number(user, "Choose a stage to focus on. This will block symptoms from other stages from being mutated. Input 0 to disable effect focusing.", "Choose a stage.") + if(!stage_to_focus) + to_chat(user, span_notice("The effect focusing is now turned off.")) + else + to_chat(user, span_notice("\The [src] will now focus on stage [stage_to_focus].")) + effect_focus = stage_to_focus + return TRUE + +/obj/machinery/disease2/incubator/attack_hand(mob/user) + . = ..() + if(machine_stat & BROKEN) + to_chat(user, span_notice("\The [src] is broken. Some components will have to be replaced before it can work again.")) + return + if(machine_stat & NOPOWER) + to_chat(user, span_notice("Deprived of power, \the [src] is unresponsive.")) + for(var/i in 1 to length(dish_data)) + var/datum/dish_incubator_dish/dish_datum = dish_data[i] + if(isnull(dish_datum)) + continue + + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + dish_datum.dish.forceMove(drop_location()) + dish_data[i] = null + update_appearance(UPDATE_OVERLAYS) + sleep(0.1 SECONDS) + + if(.) + return + + ui_interact(user) + +/obj/machinery/disease2/incubator/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DiseaseIncubator", "Incubator") + ui.open() + +/obj/machinery/disease2/incubator/ui_data(mob/user) + // this is the data which will be sent to the ui + var/list/data = list() + + var/dish_amt = length(dish_data) + data["on"] = on + data["can_focus"] = can_focus + data["focus_stage"] = effect_focus + var/list/dish_ui_data = new /list(dish_amt) + data["dishes"] = dish_ui_data + + for(var/i = 1 to dish_amt) + var/datum/dish_incubator_dish/dish_datum = dish_data[i] + var/list/dish_ui_datum = list() + dish_ui_data[i] = dish_ui_datum + + var/inserted = !isnull(dish_datum) + dish_ui_datum["inserted"] = inserted + if(!inserted) + dish_ui_datum["name"] = "Empty Slot" + continue + + dish_ui_datum["name"] = dish_datum.dish.name + dish_ui_datum["growth"] = dish_datum.dish.growth + dish_ui_datum["reagents_volume"] = round(dish_datum.dish.reagents.total_volume, CHEMICAL_VOLUME_ROUNDING) + dish_ui_datum["major_mutations"] = dish_datum.major_mutations_count + dish_ui_datum["minor_mutations_strength"] = mutatechance //add support for other reagents + dish_ui_datum["minor_mutations_robustness"] = mutatechance //add support for other reagents + dish_ui_datum["minor_mutations_effects"] = mutatechance //add support for other reagents + dish_ui_datum["dish_slot"] = i + + var/list/symptom_data = list() + var/obj/item/weapon/virusdish/dish = dish_datum.dish + dish_ui_datum["contains_disease"] = istype(dish.contained_virus) ? TRUE : FALSE + for(var/datum/symptom/acute/symptom in dish.contained_virus?.symptoms) + if(!(dish.contained_virus.disease_flags & DISEASE_ANALYZED)) + symptom_data += list(list("name" = "Unknown", "desc" = "Unknown", "strength" = symptom.multiplier, "max_strength" = symptom.max_multiplier, "chance" = symptom.chance, "max_chance" = symptom.max_chance, "stage" = symptom.stage)) + continue + symptom_data += list(list("name" = symptom.name, "desc" = symptom.desc, "strength" = symptom.multiplier, "max_strength" = symptom.max_multiplier, "chance" = symptom.chance, "max_chance" = symptom.max_chance, "stage" = symptom.stage)) + dish_ui_datum["symptom_data"] = symptom_data + + return data + +/obj/machinery/disease2/incubator/on_set_machine_stat(old_value) + . = ..() + update_appearance(UPDATE_ICON_STATE | UPDATE_OVERLAYS) + +/obj/machinery/disease2/incubator/on_set_is_operational(old_value) + . = ..() + if(!is_operational) + set_on(FALSE) + +/obj/machinery/disease2/incubator/process() + if(!on) + return PROCESS_KILL + + for(var/datum/dish_incubator_dish/dish_datum in dish_data) + dish_datum.dish.incubate(mutatechance, growthrate, effect_focus) + +/obj/machinery/disease2/incubator/proc/set_on(status, mob/user) + if(status == on) + return + on = status + if(on) + use_power = ACTIVE_POWER_USE + begin_processing() + if(user) + for(var/datum/dish_incubator_dish/dish_datum in dish_data) + if(dish_datum.dish?.contained_virus) + dish_datum.dish.contained_virus.log += "
[ROUND_TIME()] Incubation started by [key_name(user)]" + else + use_power = IDLE_POWER_USE + end_processing() + update_appearance(UPDATE_ICON_STATE | UPDATE_OVERLAYS) + +/obj/machinery/disease2/incubator/proc/find_dish_datum(obj/item/weapon/virusdish/dish) + for(var/datum/dish_incubator_dish/dish_datum in dish_data) + if(dish_datum.dish == dish) + return dish_datum + + return null + +/obj/machinery/disease2/incubator/proc/update_major(obj/item/weapon/virusdish/dish) + var/datum/dish_incubator_dish/dish_datum = find_dish_datum(dish) + if(isnull(dish_datum)) + return + + dish_datum.updates_new |= INCUBATOR_DISH_MAJOR + dish_datum.updates &= ~INCUBATOR_DISH_MAJOR + dish_datum.major_mutations_count++ + +/obj/machinery/disease2/incubator/proc/update_minor(obj/item/weapon/virusdish/dish, str=0, rob=0, eff=0) + var/datum/dish_incubator_dish/dish_datum = find_dish_datum(dish) + if(isnull(dish_datum)) + return + + dish_datum.updates_new |= INCUBATOR_DISH_MINOR + dish_datum.updates &= ~INCUBATOR_DISH_MINOR + dish_datum.minor_mutation_strength += str; + dish_datum.minor_mutation_robustness += rob; + dish_datum.minor_mutation_effects += eff; + +/obj/machinery/disease2/incubator/update_icon_state() + if(machine_stat & BROKEN) + icon_state = "[base_icon_state]b" + else if(machine_stat & NOPOWER) + icon_state = "[base_icon_state]0" + else + icon_state = base_icon_state + return ..() + +/obj/machinery/disease2/incubator/update_appearance(updates) + . = ..() + if(!is_operational) + set_light(0) + else if(on) + set_light(2, 2) + else + set_light(2, 1) + +/obj/machinery/disease2/incubator/update_overlays() + . = ..() + if(on && is_operational) + . += mutable_appearance(icon, "incubator_light") + . += mutable_appearance(icon, "incubator_glass") + . += emissive_appearance(icon, "incubator_light_e", src) + . += emissive_appearance(icon, "incubator_glass_e", src) + + for(var/i = 1 to length(dish_data)) + if(!isnull(dish_data[i])) + . += add_dish_sprite(dish_data[i], i) + +/obj/machinery/disease2/incubator/proc/add_dish_sprite(datum/dish_incubator_dish/dish_datum, slot) + var/obj/item/weapon/virusdish/dish = dish_datum.dish + var/list/overlays = list() + + slot-- + var/mutable_appearance/dish_outline = mutable_appearance(icon, "smalldish2-outline") + dish_outline.alpha = 128 + dish_outline.pixel_z = -5 * slot + overlays += dish_outline + var/mutable_appearance/dish_content = mutable_appearance(icon, "smalldish2-empty") + dish_content.alpha = 128 + dish_content.pixel_z = -5 * slot + if(dish.contained_virus) + dish_content.icon_state = "smalldish2-color" + dish_content.color = dish.contained_virus.color + overlays += dish_content + + //updating the light indicators + if(dish.contained_virus && is_operational) + var/mutable_appearance/grown_gauge = mutable_appearance(icon, "incubator_growth7", offset_spokesman = src, plane = ABOVE_LIGHTING_PLANE) + grown_gauge.pixel_z = -5 * slot + if(dish.growth < 100) + grown_gauge.icon_state = "incubator_growth[clamp(round(dish.growth * 70 / 1000), 1, 6)]" + else + var/update = FALSE + if(!(dish_datum.updates & INCUBATOR_DISH_GROWTH)) + dish_datum.updates += INCUBATOR_DISH_GROWTH + update = TRUE + + if(update) + var/mutable_appearance/grown_light = emissive_appearance(icon, "incubator_grown_update_e", src) + grown_light.pixel_z = -5 * slot + var/mutable_appearance/grown_light_n = mutable_appearance(icon, "incubator_grown_update") + grown_light_n.pixel_z = -5 * slot + + overlays += grown_light + overlays += grown_light_n + else + var/mutable_appearance/grown_light = emissive_appearance(icon, "incubator_grown_e", src) + grown_light.pixel_z = -5 * slot + var/mutable_appearance/grown_light_n = mutable_appearance(icon, "incubator_grown") + grown_light_n.pixel_z = -5 * slot + + overlays += grown_light_n + overlays += grown_light + + overlays += grown_gauge + if(round(dish.reagents.total_volume, CHEMICAL_QUANTISATION_LEVEL) < 0.02) + var/update = FALSE + if(!(dish_datum.updates & INCUBATOR_DISH_REAGENT)) + dish_datum.updates += INCUBATOR_DISH_REAGENT + update = TRUE + + if(update) + var/mutable_appearance/reagents_light = emissive_appearance(icon, "incubator_reagents_update_e", src) + reagents_light.pixel_z = -5 * slot + var/mutable_appearance/reagents_light_n = mutable_appearance(icon, "incubator_reagents_update") + reagents_light_n.pixel_z = -5 * slot + + overlays += reagents_light_n + overlays += reagents_light + else + var/mutable_appearance/reagents_light = emissive_appearance(icon, "incubator_reagents_e", src) + reagents_light.pixel_z = -5 * slot + var/mutable_appearance/reagents_light_n = mutable_appearance(icon, "incubator_reagents") + reagents_light_n.pixel_z = -5 * slot + + overlays += reagents_light_n + overlays += reagents_light + + /* + if(dish_datum.updates_new & INCUBATOR_DISH_MAJOR) + if(!(dish_datum.updates & INCUBATOR_DISH_MAJOR)) + dish_datum.updates += INCUBATOR_DISH_MAJOR + var/mutable_appearance/effect_light = emissive_appearance(icon, "incubator_major_update", src) + effect_light.pixel_z = -5 * slot + var/mutable_appearance/effect_light_n = mutable_appearance(icon, "incubator_major_update") + effect_light_n.pixel_z = -5 * slot + + overlays += effect_light_n + overlays += effect_light + else + var/mutable_appearance/effect_light = emissive_appearance(icon, "incubator_major", src) + effect_light.pixel_z = -5 * slot + var/mutable_appearance/effect_light_n = mutable_appearance(icon, "incubator_major") + effect_light_n.pixel_z = -5 * slot + + overlays += effect_light_n + overlays += effect_light + + if(dish_datum.updates_new & INCUBATOR_DISH_MINOR) + if(!(dish_datum.updates & INCUBATOR_DISH_MINOR)) + dish_datum.updates += INCUBATOR_DISH_MINOR + var/mutable_appearance/effect_light = emissive_appearance(icon, "incubator_minor_update", src) + effect_light.pixel_z = -5 * slot + var/mutable_appearance/effect_light_n = mutable_appearance(icon, "incubator_minor_update") + effect_light_n.pixel_z = -5 * slot + + overlays += effect_light_n + + overlays += effect_light + else + var/mutable_appearance/effect_light = mutable_appearance(icon, "incubator_minor") + effect_light.pixel_z = -5 * slot + var/mutable_appearance/effect_light_n = mutable_appearance(icon, "incubator_minor", src) + effect_light_n.pixel_z = -5 * slot + + overlays += effect_light_n + overlays += effect_light + */ + + return overlays + +/datum/dish_incubator_dish + // The inserted virus dish. + var/obj/item/weapon/virusdish/dish + + var/major_mutations_count = 0 + + var/minor_mutation_strength = 0 + var/minor_mutation_robustness = 0 + var/minor_mutation_effects = 0 + + var/updates_new = NONE + var/updates = NONE + +/datum/dish_incubator_dish/New(obj/item/weapon/virusdish/dish) + . = ..() + src.dish = dish + +/datum/dish_incubator_dish/Destroy(force) + dish = null + return ..() + +#undef INCUBATOR_DISH_GROWTH +#undef INCUBATOR_DISH_REAGENT +#undef INCUBATOR_DISH_MAJOR +#undef INCUBATOR_DISH_MINOR diff --git a/aquila/code/modules/virology/machines/isolator.dm b/aquila/code/modules/virology/machines/isolator.dm new file mode 100644 index 00000000000..115410101ad --- /dev/null +++ b/aquila/code/modules/virology/machines/isolator.dm @@ -0,0 +1,114 @@ +/obj/machinery/disease2/isolator + name = "Pathogenic Isolator" + desc = "Takes a syringe of blood, and isolates the pathogens inside into a dish." + density = TRUE + anchored = TRUE + icon = 'aquila/icons/virology/virology.dmi' + icon_state = "isolator" + var/datum/disease/acute/isolated_disease = null + var/isolating = 0 + var/beaker = null + +/obj/machinery/disease2/isolator/Destroy() + isolated_disease = null + return ..() + +/obj/machinery/disease2/isolator/attackby(obj/item/tool, mob/user, params) + if(!istype(tool, /obj/item/reagent_containers/syringe)) + return NONE + + var/obj/item/reagent_containers/syringe/B = tool + if(beaker) + to_chat(user, "A syringe is already loaded into the machine.") + return TRUE + if(!user.dropItemToGround(B)) + return TRUE + if(!B.forceMove(src)) + return TRUE + beaker = B + to_chat(user, "You add the syringe to the machine!") + src.updateUsrDialog() + icon_state = "isolator_in" + return TRUE + +/obj/machinery/disease2/isolator/Topic(href, href_list) + if(..()) + return + + usr.machine = src + if(!beaker) + return + var/datum/reagents/R = beaker:reagents + + if (href_list["isolate"]) + var/datum/reagent/Blood + for(var/datum/reagent/B in R.reagent_list) + if(length(B.data) && ("viruses" in B.data)) + Blood = B + break + // /vg/: Try to fix isolators + if(!Blood) + to_chat(usr, span_warning("ERROR: Unable to locate blood within the beaker. Bug?")) +// testing("Unable to locate blood in [beaker]!") + return + var/list/virus = virus_copylist(Blood.data["viruses"]) + var/choice = text2num(href_list["isolate"]) + for (var/datum/disease/acute/V as anything in virus) + if (V.uniqueID == choice) + isolated_disease = V + isolating = 40 + icon_state = "isolator_processing" + src.updateUsrDialog() + return + + else if (href_list["main"]) + attack_hand(usr) + return + else if (href_list["eject"]) + beaker:forceMove(src.loc) + beaker = null + icon_state = "isolator" + src.updateUsrDialog() + return + +/obj/machinery/disease2/isolator/attack_hand(mob/user, list/modifiers) + if(machine_stat & BROKEN) + return + user.machine = src + var/dat = "" + if(!beaker) + + dat = {"Please insert sample into the isolator.
+Close"} + else if(isolating) + dat = "Isolating" + else + var/datum/reagents/R = beaker:reagents + dat += "Eject

" + if(!R.total_volume) + dat += "[beaker] is empty." + else + dat += "Contained reagents:" user << browse(HTML_SKELETON_TITLE("Pathogenic Isolator", dat), "window=isolator;size=575x400") onclose(user, "isolator") return From 8ab6f6c580236a3a0a62c1ae64cd4b3b14b69fd3 Mon Sep 17 00:00:00 2001 From: joker Date: Tue, 27 Oct 2026 23:47:24 +0100 Subject: [PATCH 09/11] =?UTF-8?q?Wirusologia:=20patogeny=20disease2=20nie?= =?UTF-8?q?=20mieszaj=C4=85=20si=C4=99=20ze=20starymi=20chorobami?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Na mobach w liście diseases siedzą też stare choroby (wyrostek, katar, advance). Pętle as anything brały je za /datum/disease/acute - skaner przeciwciał wywalał się na Refresh_Acute(), a filtry rozsiewu przepuszczały stary katar do chmur patogenów. Pętle są teraz typowane. Rdzeń przy dotyku/wpadaniu wołał ContactContractDisease() także na patogenach disease2, a stare try_infect() dodawało je bez układu odpornościowego. Kontakt dla disease2 idzie tylko przez share_contact_diseases, a try_infect kieruje do infect_disease(). --- aquila/code/modules/virology/_core/hooks.dm | 18 ++++++++++++++++++ .../disease/base_disease_folder/_base.dm | 2 +- .../disease/base_disease_folder/admin.dm | 6 +++--- .../base_disease_folder/general_procs.dm | 4 ++-- .../disease/base_disease_folder/logging.dm | 4 ++-- .../virology/disease/plague_rat/objective.dm | 2 +- .../disease/symtoms/restricted/stage2.dm | 4 ++-- .../modules/virology/effects/cleanables.dm | 4 ++-- .../modules/virology/effects/pathogen_cloud.dm | 6 +++--- .../virology/immune_systems/_immune_system.dm | 6 +++--- .../virology/items/_base_item_additions.dm | 4 ++-- .../modules/virology/items/antibodyscanner.dm | 6 +++--- .../virology/items/bio_hazard_deployable.dm | 2 +- .../modules/virology/items/extrapolator.dm | 2 +- .../modules/virology/living/infection_procs.dm | 4 ++-- .../modules/virology/living/spread_disease.dm | 4 ++-- .../modules/virology/machines/centrifuge.dm | 2 +- 17 files changed, 49 insertions(+), 31 deletions(-) diff --git a/aquila/code/modules/virology/_core/hooks.dm b/aquila/code/modules/virology/_core/hooks.dm index 74a3766d7d9..0a1d8d16397 100644 --- a/aquila/code/modules/virology/_core/hooks.dm +++ b/aquila/code/modules/virology/_core/hooks.dm @@ -45,6 +45,24 @@ var/bleeding = check_bodypart_bleeding(BODY_ZONE_EVERYTHING) && other.check_bodypart_bleeding(BODY_ZONE_EVERYTHING) share_contact_diseases(other, block, bleeding) +// Rdzeń przy dotyku, wpadaniu i ciągnięciu woła ContactContractDisease() na wszystkich chorobach. +// Patogeny disease2 mają kontakt wyżej (share_contact_diseases), więc tamtą drogę pomijamy, +// inaczej zarażały podwójnie i z pominięciem układu odpornościowego. +/mob/living/ContactContractDisease(datum/disease/D) + if(istype(D, /datum/disease/acute)) + return FALSE + return ..() + +/mob/living/carbon/ContactContractDisease(datum/disease/D, target_zone) + if(istype(D, /datum/disease/acute)) + return FALSE + return ..() + +// Pozostałe stare wejścia (ForceContractDisease: ugryzienia, admin, eventy) kończą w try_infect(). +// Stare infect() wrzucało patogen bez sprawdzeń - kierujemy go do naszego infect_disease(). +/datum/disease/acute/try_infect(mob/living/infectee, make_copy = TRUE) + return infectee.infect_disease(src, forced = TRUE, notes = "(ForceContractDisease)") + // --- latejoin: część dołączających ma łagodny patogen ------------------------- /mob/dead/new_player/AttemptLateSpawn(rank) . = ..() diff --git a/aquila/code/modules/virology/disease/base_disease_folder/_base.dm b/aquila/code/modules/virology/disease/base_disease_folder/_base.dm index cf608de1be4..2e6113212be 100644 --- a/aquila/code/modules/virology/disease/base_disease_folder/_base.dm +++ b/aquila/code/modules/virology/disease/base_disease_folder/_base.dm @@ -193,7 +193,7 @@ GLOBAL_LIST_INIT(virusDB, list()) ticks = 0 //Pathogen killing each others - for (var/datum/disease/acute/enemy_pathogen as anything in mob.diseases) + for (var/datum/disease/acute/enemy_pathogen in mob.diseases) if(enemy_pathogen == src) continue diff --git a/aquila/code/modules/virology/disease/base_disease_folder/admin.dm b/aquila/code/modules/virology/disease/base_disease_folder/admin.dm index 67a758dc7f6..0a0658164d9 100644 --- a/aquila/code/modules/virology/disease/base_disease_folder/admin.dm +++ b/aquila/code/modules/virology/disease/base_disease_folder/admin.dm @@ -24,7 +24,7 @@ if (chosen_form == "infect with an already existing pathogen") var/list/existing_pathogen = list() - for(var/datum/disease/acute/dis as anything in GLOB.inspectable_diseases) + for(var/datum/disease/acute/dis in GLOB.inspectable_diseases) existing_pathogen += dis var/chosen_pathogen = input(C, "Choose a pathogen", "Choose a pathogen") as null | anything in existing_pathogen if (!chosen_pathogen) @@ -204,7 +204,7 @@ var/infctd_items = 0 var/dishes = 0 for (var/mob/living/L in GLOB.mob_list) - for(var/datum/disease/acute/D as anything in L.diseases) + for(var/datum/disease/acute/D in L.diseases) if (ID == "[D.uniqueID]-[D.subID]") infctd_mobs++ if (L.stat == DEAD) @@ -215,7 +215,7 @@ logs["[ID]"]["[L]"] = D.log for (var/obj/item/I in GLOB.infected_items) - for(var/datum/disease/acute/D as anything in I.viruses) + for(var/datum/disease/acute/D in I.viruses) if (ID == "[D.uniqueID]-[D.subID]") infctd_items++ if(!length(logs["[ID]"])) diff --git a/aquila/code/modules/virology/disease/base_disease_folder/general_procs.dm b/aquila/code/modules/virology/disease/base_disease_folder/general_procs.dm index f16f0375b5d..d93d586cdbe 100644 --- a/aquila/code/modules/virology/disease/base_disease_folder/general_procs.dm +++ b/aquila/code/modules/virology/disease/base_disease_folder/general_procs.dm @@ -3,7 +3,7 @@ return list() var/list/viable = list() - for(var/datum/disease/acute/disease as anything in diseases) + for(var/datum/disease/acute/disease in diseases) if(!(disease.spread_flags & required)) continue viable += disease @@ -13,7 +13,7 @@ if(!length(list)) return list() var/list/L = list() - for(var/datum/disease/acute/D as anything in list) + for(var/datum/disease/acute/D in list) L += D.Copy() return L diff --git a/aquila/code/modules/virology/disease/base_disease_folder/logging.dm b/aquila/code/modules/virology/disease/base_disease_folder/logging.dm index 1d5a128c3c0..66fd89fd641 100644 --- a/aquila/code/modules/virology/disease/base_disease_folder/logging.dm +++ b/aquila/code/modules/virology/disease/base_disease_folder/logging.dm @@ -11,12 +11,12 @@ for (var/mob/living/L in GLOB.mob_list) if(!length(L.diseases)) continue - for(var/datum/disease/acute/D as anything in L.diseases) + for(var/datum/disease/acute/D in L.diseases) if (ID == "[D.uniqueID]-[D.subID]") return for (var/obj/item/I in GLOB.infected_items) - for(var/datum/disease/acute/D as anything in I.viruses) + for(var/datum/disease/acute/D in I.viruses) if (ID == "[D.uniqueID]-[D.subID]") return diff --git a/aquila/code/modules/virology/disease/plague_rat/objective.dm b/aquila/code/modules/virology/disease/plague_rat/objective.dm index 936d9129e7f..632d91543c5 100644 --- a/aquila/code/modules/virology/disease/plague_rat/objective.dm +++ b/aquila/code/modules/virology/disease/plague_rat/objective.dm @@ -23,7 +23,7 @@ for (var/mob/living/L in GLOB.mob_living_list) if (L.stat == DEAD) // z mob_living_list wypada dopiero w Destroy(), wiec trupy trzeba odfiltrowac continue - for (var/datum/disease/acute/carried as anything in L.diseases) + for (var/datum/disease/acute/carried in L.diseases) if (istype(carried) && "[carried.uniqueID]-[carried.subID]" == disease_id) return TRUE//if we infected at least one individual, and there is still an infected individual alive, that's good enough. diff --git a/aquila/code/modules/virology/disease/symtoms/restricted/stage2.dm b/aquila/code/modules/virology/disease/symtoms/restricted/stage2.dm index 50688655c37..0ccb4dad210 100644 --- a/aquila/code/modules/virology/disease/symtoms/restricted/stage2.dm +++ b/aquila/code/modules/virology/disease/symtoms/restricted/stage2.dm @@ -37,7 +37,7 @@ if(M.check_airborne_sterility()) return var/strength = 0 - for (var/datum/disease/acute/V as anything in M.diseases) + for (var/datum/disease/acute/V in M.diseases) strength += V.infectionchance strength = round(strength/M.diseases.len) @@ -58,7 +58,7 @@ if(M.check_airborne_sterility()) return var/strength = 0 - for (var/datum/disease/acute/V as anything in M.diseases) + for (var/datum/disease/acute/V in M.diseases) strength += V.infectionchance strength = round(strength/M.diseases.len) diff --git a/aquila/code/modules/virology/effects/cleanables.dm b/aquila/code/modules/virology/effects/cleanables.dm index cb1647d8fb6..4e24100e2d5 100644 --- a/aquila/code/modules/virology/effects/cleanables.dm +++ b/aquila/code/modules/virology/effects/cleanables.dm @@ -4,7 +4,7 @@ GLOBAL_LIST_EMPTY(infected_cleanables) ..() // AQ: nasz rdzeń dostaje listę chorób, ale jej nie trzyma (robi to tylko komponent infective), // a patogeny muszą w plamie zostać, żeby dalej zarażały przez kontakt i powietrze. - for(var/datum/disease/acute/infection as anything in diseases) + for(var/datum/disease/acute/infection in diseases) if(istype(infection)) LAZYOR(src.diseases, infection) return INITIALIZE_HINT_LATELOAD @@ -44,7 +44,7 @@ GLOBAL_LIST_EMPTY(infected_cleanables) block = perp.check_contact_sterility(BODY_ZONE_LEGS) bleeding = perp.check_bodypart_bleeding(BODY_ZONE_LEGS) - for(var/datum/disease/acute/contained_virus as anything in diseases) + for(var/datum/disease/acute/contained_virus in diseases) if (!block && (contained_virus.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)) perp.infect_disease(contained_virus, notes="(Contact, from [(perp.lying & LYING_DOWN)?"lying":"standing"] over [src]])") else if (bleeding && (contained_virus.spread_flags & DISEASE_SPREAD_BLOOD)) diff --git a/aquila/code/modules/virology/effects/pathogen_cloud.dm b/aquila/code/modules/virology/effects/pathogen_cloud.dm index 8bd0cbb4774..7c63ff8ec9f 100644 --- a/aquila/code/modules/virology/effects/pathogen_cloud.dm +++ b/aquila/code/modules/virology/effects/pathogen_cloud.dm @@ -34,7 +34,7 @@ GLOBAL_LIST_INIT(virus_viewers, list()) viruses = virus - for(var/datum/disease/acute/D as anything in viruses) + for(var/datum/disease/acute/D in viruses) id_list += "[D.uniqueID]-[D.subID]" if(!core) @@ -66,7 +66,7 @@ GLOBAL_LIST_INIT(virus_viewers, list()) . = FALSE if(!islist(new_viruses)) new_viruses = list(new_viruses) - for(var/datum/disease/acute/virus as anything in new_viruses) + for(var/datum/disease/acute/virus in new_viruses) var/id = "[virus.uniqueID]-[virus.subID]" if(id in id_list) continue @@ -106,7 +106,7 @@ GLOBAL_LIST_INIT(virus_viewers, list()) return var/strength = 0 - for (var/datum/disease/acute/V as anything in viruses) + for (var/datum/disease/acute/V in viruses) strength += V.infectionchance strength = round(strength / length(viruses)) var/list/possible_turfs = list() diff --git a/aquila/code/modules/virology/immune_systems/_immune_system.dm b/aquila/code/modules/virology/immune_systems/_immune_system.dm index d7d6c855896..07a61a58392 100644 --- a/aquila/code/modules/virology/immune_systems/_immune_system.dm +++ b/aquila/code/modules/virology/immune_systems/_immune_system.dm @@ -121,7 +121,7 @@ if(overloaded) return - for(var/datum/disease/acute/disease as anything in host.diseases) + for(var/datum/disease/acute/disease in host.diseases) for(var/A in disease.antigen) var/tally = 0.5 if (isturf(host.loc) && (host.lying)) @@ -147,7 +147,7 @@ antibodies[A] = min(antibodies[A] + 1, 100) /datum/immune_system/proc/NaturalImmune() //called with a 8% chance every time a virus activates - for (var/datum/disease/acute/disease as anything in host.diseases) + for (var/datum/disease/acute/disease in host.diseases) for (var/A in disease.antigen) var/tally = 1.5 if (isturf(host.loc) && (host.lying)) @@ -202,7 +202,7 @@ if (overloaded) return - for (var/datum/disease/acute/disease as anything in host.diseases) + for (var/datum/disease/acute/disease in host.diseases) for (var/A in disease.antigen) antibodies[A] = 100 diff --git a/aquila/code/modules/virology/items/_base_item_additions.dm b/aquila/code/modules/virology/items/_base_item_additions.dm index 1594b2c8f31..ad4307e09f0 100644 --- a/aquila/code/modules/virology/items/_base_item_additions.dm +++ b/aquila/code/modules/virology/items/_base_item_additions.dm @@ -15,7 +15,7 @@ GLOBAL_LIST_INIT(infected_items, list()) //Called by attack_hand(), transfers diseases between the mob and the item /obj/item/proc/disease_contact(mob/living/carbon/M, bodypart = null) //first let's try to infect them with our viruses - for(var/datum/disease/acute/V as anything in viruses) + for(var/datum/disease/acute/V in viruses) infection_attempt(M, V, bodypart) if (!bodypart)//no bodypart specified? that should mean we're being held. @@ -91,5 +91,5 @@ GLOBAL_LIST_INIT(infected_items, list()) return var/list/blood_diseases = filter_disease_by_spread(infectee.diseases, required = DISEASE_SPREAD_BLOOD) if(length(blood_diseases)) - for(var/datum/disease/acute/V as anything in blood_diseases) + for(var/datum/disease/acute/V in blood_diseases) infect_disease(V, TRUE, "(Blood, coming from [infectee])") diff --git a/aquila/code/modules/virology/items/antibodyscanner.dm b/aquila/code/modules/virology/items/antibodyscanner.dm index ed7e1e14ed8..1a2b586cbc6 100644 --- a/aquila/code/modules/virology/items/antibodyscanner.dm +++ b/aquila/code/modules/virology/items/antibodyscanner.dm @@ -39,7 +39,7 @@ antigens_that_matter += antibody continue if (length(L.diseases)) - for (var/datum/disease/acute/D as anything in L.diseases) + for (var/datum/disease/acute/D in L.diseases) D.Refresh_Acute() var/ID = "[D.uniqueID]-[D.subID]" if(ID in GLOB.virusDB) @@ -79,7 +79,7 @@ i++ if (length(L.diseases)) - for (var/datum/disease/acute/D as anything in L.diseases) + for (var/datum/disease/acute/D in L.diseases) var/ID = "[D.uniqueID]-[D.subID]" scan.DrawBox("#FF0000",6,6+D.strength*3,display_width-5,6+D.strength*3) if(ID in GLOB.virusDB) @@ -112,7 +112,7 @@ info += "" if (length(L.diseases)) - for (var/datum/disease/acute/D as anything in L.diseases) + for (var/datum/disease/acute/D in L.diseases) var/ID = "[D.uniqueID]-[D.subID]" if(ID in GLOB.virusDB) var/datum/data/record/V = GLOB.virusDB[ID] diff --git a/aquila/code/modules/virology/items/bio_hazard_deployable.dm b/aquila/code/modules/virology/items/bio_hazard_deployable.dm index 298eb337cc9..7f9420a454e 100644 --- a/aquila/code/modules/virology/items/bio_hazard_deployable.dm +++ b/aquila/code/modules/virology/items/bio_hazard_deployable.dm @@ -14,7 +14,7 @@ say_text += "SUBJECT HAS MULTIPLE VIRUSES." else var/line = " DISEASE IS IN DATABASE:" - for(var/datum/disease/acute/disease as anything in living.diseases) + for(var/datum/disease/acute/disease in living.diseases) if("[disease.uniqueID]-[disease.subID]" in GLOB.virusDB) var/datum/data/record/target = GLOB.virusDB["[disease.uniqueID]-[disease.subID]"] line += " [target.name]" diff --git a/aquila/code/modules/virology/items/extrapolator.dm b/aquila/code/modules/virology/items/extrapolator.dm index a78752b0c01..1671500f25c 100644 --- a/aquila/code/modules/virology/items/extrapolator.dm +++ b/aquila/code/modules/virology/items/extrapolator.dm @@ -87,7 +87,7 @@ diseases += list(disease_entry(dish.contained_virus)) else if(isliving(last_attacked_target)) var/mob/living/target = last_attacked_target - for(var/datum/disease/acute/disease as anything in target.diseases) + for(var/datum/disease/acute/disease in target.diseases) if(istype(disease)) diseases += list(disease_entry(disease)) diff --git a/aquila/code/modules/virology/living/infection_procs.dm b/aquila/code/modules/virology/living/infection_procs.dm index 75310acd247..80f8222e82f 100644 --- a/aquila/code/modules/virology/living/infection_procs.dm +++ b/aquila/code/modules/virology/living/infection_procs.dm @@ -40,7 +40,7 @@ /mob/living/proc/assume_contact_diseases(list/disease_list, atom/source, blocked=0, bleeding=0) if(!length(disease_list)) return - for(var/datum/disease/acute/V as anything in disease_list) + for(var/datum/disease/acute/V in disease_list) if (!V) message_admins("[key_name(src)] is trying to assume contact diseases from touching \a [source], but the disease_list contains an ID ([V.uniqueID]-[V.subID]) that isn't associated to an actual disease datum! Ping Dwasint about it please.") return @@ -61,7 +61,7 @@ for(var/obj/effect/decal/cleanable/C in loc) if (!length(C.diseases)) continue - for(var/datum/disease/acute/V as anything in C.diseases) + for(var/datum/disease/acute/V in C.diseases) if(V.spread_flags & DISEASE_SPREAD_AIRBORNE) infect_disease(V, notes="(Airborne, from [C])") diff --git a/aquila/code/modules/virology/living/spread_disease.dm b/aquila/code/modules/virology/living/spread_disease.dm index 862c7ecc7f7..9a2077e76ac 100644 --- a/aquila/code/modules/virology/living/spread_disease.dm +++ b/aquila/code/modules/virology/living/spread_disease.dm @@ -12,7 +12,7 @@ var/list/airborne_viruses = filter_disease_by_spread(diseases, required = DISEASE_SPREAD_AIRBORNE) if (airborne_viruses && airborne_viruses.len > 0) var/strength = 0 - for (var/datum/disease/acute/V as anything in airborne_viruses) + for (var/datum/disease/acute/V in airborne_viruses) strength += V.infectionchance strength = round(strength/airborne_viruses.len) while (strength > 0)//stronger viruses create more clouds at once @@ -30,7 +30,7 @@ if(!disease.spread_flags && !(disease.disease_flags & DISEASE_DORMANT)) return FALSE - for(var/datum/disease/acute/old_disease as anything in diseases) + for(var/datum/disease/acute/old_disease in diseases) if(disease.uniqueID == old_disease.uniqueID && disease.subID == old_disease.subID) // child ids are for pathogenic mutations and aren't accounted for as thats fucked. return FALSE diff --git a/aquila/code/modules/virology/machines/centrifuge.dm b/aquila/code/modules/virology/machines/centrifuge.dm index 190eccef0ac..76e89e12fab 100644 --- a/aquila/code/modules/virology/machines/centrifuge.dm +++ b/aquila/code/modules/virology/machines/centrifuge.dm @@ -409,7 +409,7 @@ var/list/blood_viruses = blood.data["viruses"] if (islist(blood_viruses) && length(blood_viruses) > 0) var/list/pathogen_list = list() - for (var/datum/disease/acute/D as anything in blood_viruses) + for (var/datum/disease/acute/D in blood_viruses) if(!istype(D)) continue var/pathogen_name = "Unknown [D.form]" From f2b64b89e77da2b9c045d888b3f36b2452268cca Mon Sep 17 00:00:00 2001 From: joker Date: Wed, 28 Oct 2026 17:31:41 +0100 Subject: [PATCH 10/11] =?UTF-8?q?Wirusologia:=20test=20mieszanych=20chor?= =?UTF-8?q?=C3=B3b,=20izolator=20bez=20klienta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test pathology_mixed_diseases: człowiek z wyrostkiem i katarem plus patogen disease2 - filtr rozsiewu, skaner przeciwciał, izolator (strzykawka, menu, szalka), analizator ze strzykawką, dotyk, ForceContractDisease przy przeciwciałach i Life bez runtime'ów. Test złapał onclose() bez klienta w izolatorze (runtime dla NPC). --- .../modules/unit_tests/aquila_pathology.dm | 84 +++++++++++++++++++ .../modules/virology/machines/isolator.dm | 3 + 2 files changed, 87 insertions(+) diff --git a/aquila/code/modules/unit_tests/aquila_pathology.dm b/aquila/code/modules/unit_tests/aquila_pathology.dm index 9ad69a9645a..e4f8234344a 100644 --- a/aquila/code/modules/unit_tests/aquila_pathology.dm +++ b/aquila/code/modules/unit_tests/aquila_pathology.dm @@ -89,5 +89,89 @@ TEST_ASSERT(dish.growth > 0, "Szalka nie rosła na pożywce") TEST_ASSERT_EQUAL(dish.contained_virus, pathogen, "Szalka zgubiła patogen") +/// Stare choroby (wyrostek, katar) i patogen disease2 w jednej liście diseases: +/// skaner przeciwciał, izolator, dotyk i Life bez runtime'ów, a stare ścieżki zarażania szanują odporność. +/datum/unit_test/pathology_mixed_diseases + +/datum/unit_test/pathology_mixed_diseases/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/doctor = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/immune = allocate(/mob/living/carbon/human) + + patient.ForceContractDisease(new /datum/disease/appendicitis, FALSE, TRUE) + patient.ForceContractDisease(new /datum/disease/cold, FALSE, TRUE) + TEST_ASSERT_EQUAL(length(patient.diseases), 2, "Nie udało się zarazić wyrostkiem i katarem") + + var/datum/disease/acute/virus/pathogen = new + pathogen.makerandom(list(60, 60), list(50, 50), list(ANTIGEN_COMMON = 1), list(EFFECT_DANGER_FLAVOR = 1), null) + pathogen.spread_flags = DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_BLOOD + for(var/antigen in pathogen.antigen) + patient.immune_system.antibodies[antigen] = 0 + patient.infect_disease(pathogen, forced = TRUE, notes = "(test jednostkowy)") + var/datum/disease/acute/carried = locate(/datum/disease/acute) in patient.diseases + TEST_ASSERT(carried, "Patogen disease2 nie zaraził nosiciela starych chorób") + var/carried_id = "[carried.uniqueID]-[carried.subID]" + carried.addToDB() + + var/runtimes_before = GLOB.total_runtimes + + // filtry rozsiewu biorą tylko patogeny disease2 (stary katar jest airborne) + for(var/datum/disease/D as anything in filter_disease_by_spread(patient.diseases, required = DISEASE_SPREAD_AIRBORNE)) + TEST_ASSERT(istype(D, /datum/disease/acute), "Filtr rozsiewu przepuścił starą chorobę [D.type]") + + // skaner przeciwciał przechodzi po wszystkich chorobach pacjenta + var/obj/item/device/antibody_scanner/scanner = allocate(/obj/item/device/antibody_scanner) + scanner.attack(patient, doctor) + TEST_ASSERT_EQUAL(GLOB.total_runtimes, runtimes_before, "Runtime w skanerze przeciwciał") + + // izolator: strzykawka z krwią, menu, wyizolowanie na szalkę + var/obj/item/reagent_containers/syringe/syringe = allocate(/obj/item/reagent_containers/syringe) + syringe.reagents.add_reagent(/datum/reagent/blood, 10, patient.get_blood_data(/datum/reagent/blood)) + var/datum/reagent/blood/sample = locate() in syringe.reagents.reagent_list + TEST_ASSERT(sample, "Strzykawka nie ma krwi") + var/list/isolated = virus_copylist(sample.data["viruses"]) + TEST_ASSERT_EQUAL(length(isolated), 1, "Z krwi powinien dać się wyizolować tylko patogen disease2") + doctor.put_in_hands(syringe) + var/obj/machinery/disease2/isolator/isolator = allocate(/obj/machinery/disease2/isolator) + isolator.attackby(syringe, doctor) + TEST_ASSERT_EQUAL(isolator.beaker, syringe, "Izolator nie przyjął strzykawki") + isolator.attack_hand(doctor) + TEST_ASSERT_EQUAL(GLOB.total_runtimes, runtimes_before, "Runtime w menu izolatora") + isolator.isolated_disease = isolated[1] + isolator.isolating = 1 + isolator.process() + var/obj/item/weapon/virusdish/dish = locate() in isolator.loc + TEST_ASSERT(dish?.contained_virus, "Izolator nie wydał szalki z patogenem") + allocated += dish + + // analizator ze strzykawką tylko podpowiada izolator + var/obj/item/reagent_containers/syringe/second = allocate(/obj/item/reagent_containers/syringe) + doctor.put_in_hands(second) + var/obj/machinery/disease2/diseaseanalyser/analyser = allocate(/obj/machinery/disease2/diseaseanalyser) + analyser.attackby(second, doctor) + TEST_ASSERT(!analyser.dish && second.loc == doctor, "Analizator wziął strzykawkę") + + // dotyk w obie strony i stara ścieżka kontaktu z rdzenia + patient.attack_hand(doctor) + doctor.attack_hand(patient) + var/acute_on_doctor = 0 + for(var/datum/disease/acute/D in doctor.diseases) + acute_on_doctor++ + TEST_ASSERT(acute_on_doctor <= 1, "Lekarz dostał ten sam patogen więcej niż raz") + TEST_ASSERT_EQUAL(GLOB.total_runtimes, runtimes_before, "Runtime przy dotyku") + + // ForceContractDisease (ugryzienia, admin) przechodzi przez układ odpornościowy + for(var/antigen in carried.antigen) + immune.immune_system.antibodies[antigen] = carried.strength + 10 + immune.ForceContractDisease(carried, TRUE) + TEST_ASSERT(!(locate(/datum/disease/acute) in immune.diseases), "ForceContractDisease ominął przeciwciała") + + // kilka ticków życia z trzema chorobami naraz + for(var/i in 1 to 5) + patient.Life(2, i) + TEST_ASSERT_EQUAL(GLOB.total_runtimes, runtimes_before, "Runtime w Life przy mieszanych chorobach") + + GLOB.virusDB -= carried_id + #undef TEST_ASSERT #undef TEST_ASSERT_EQUAL diff --git a/aquila/code/modules/virology/machines/isolator.dm b/aquila/code/modules/virology/machines/isolator.dm index bc9fbf62cd2..6a25c963675 100644 --- a/aquila/code/modules/virology/machines/isolator.dm +++ b/aquila/code/modules/virology/machines/isolator.dm @@ -100,6 +100,9 @@ if(!passes) dat += "
  • No pathogen
  • " dat += "" + // AQ: onclose() bez klienta (NPC) daje runtime "bad client" + if(!user.client) + return user << browse(HTML_SKELETON_TITLE("Pathogenic Isolator", dat), "window=isolator;size=575x400") onclose(user, "isolator") return From 3c53fb7eeec381606732b10f146710c3b7ebebc8 Mon Sep 17 00:00:00 2001 From: joker Date: Fri, 30 Oct 2026 01:22:54 +0100 Subject: [PATCH 11/11] =?UTF-8?q?Wirusologia:=20wir=C3=B3wka,=20izolator?= =?UTF-8?q?=20i=20maszyna=20lecz=C4=85ca=20na=20TGUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wirówka, izolator i maszyna lecząca stały na starych oknach browse(). U nas interact() prowadzi już tylko do TGUI, więc updateUsrDialog() (które woła ui_interact) nie odświeżało takiego okna - kliknięcia w nim nic nie dawały. Te trzy maszyny mają teraz okna TGUI. Druga przyczyna, przez którą wirówka wyglądała na zupełnie martwą: u nas get_active_hand() zwraca KOŃCZYNĘ, nie trzymany przedmiot (w Monke odwrotnie), więc "Insert a tube" nigdy nie wkładało probówki i robiło to bez żadnego komunikatu. Ten sam błąd siedział w oknie inkubatora przy wkładaniu szalki - oba poprawione na get_active_held_item() z komunikatem, gdy ręka jest pusta. Przy okazji w maszynie leczącej: szalka dawała krew z patogenami z POWIERZCHNI szalki zamiast jej patogenu, a lista przeciwciał czytała starą bitflagę data["antibodies"] zamiast data["immunity"]. Izolator ma typowane pole na strzykawkę (bez operatora :) i oddaje ją przy rozbiórce. Test pathology_machine_ui przechodzi wszystkie cztery maszyny tak, jak robi to gracz przez okno. --- .../modules/unit_tests/aquila_pathology.dm | 131 +++++++++ .../modules/virology/machines/centrifuge.dm | 272 +++++++++--------- .../code/modules/virology/machines/curer.dm | 177 +++++++----- .../modules/virology/machines/incubator.dm | 9 +- .../modules/virology/machines/isolator.dm | 188 ++++++------ .../tgui/interfaces/DiseaseCentrifuge.tsx | 180 ++++++++++++ .../packages/tgui/interfaces/DiseaseCurer.tsx | 102 +++++++ .../tgui/interfaces/DiseaseIsolator.tsx | 81 ++++++ 8 files changed, 846 insertions(+), 294 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/DiseaseCentrifuge.tsx create mode 100644 tgui/packages/tgui/interfaces/DiseaseCurer.tsx create mode 100644 tgui/packages/tgui/interfaces/DiseaseIsolator.tsx diff --git a/aquila/code/modules/unit_tests/aquila_pathology.dm b/aquila/code/modules/unit_tests/aquila_pathology.dm index e4f8234344a..8bf0855b5ab 100644 --- a/aquila/code/modules/unit_tests/aquila_pathology.dm +++ b/aquila/code/modules/unit_tests/aquila_pathology.dm @@ -173,5 +173,136 @@ GLOB.virusDB -= carried_id +/// Maszyny wirusologii dają się obsłużyć z okna: wirówka, izolator i maszyna lecząca. +/// Stare okna browse() nie odświeżały się przez updateUsrDialog(), więc klikanie w nich nic nie dawało. +/datum/unit_test/pathology_machine_ui + +/datum/unit_test/pathology_machine_ui/Run() + var/mob/living/carbon/human/doctor = allocate(/mob/living/carbon/human) + // tgui woła ui_act z ustawionym usr, więc w teście robimy to samo + usr = doctor + + var/runtimes_before = GLOB.total_runtimes + + // --- wirówka --- + var/obj/machinery/disease2/centrifuge/centrifuge = allocate(/obj/machinery/disease2/centrifuge) + var/obj/item/reagent_containers/glass/tube/tube = allocate(/obj/item/reagent_containers/glass/tube) + tube.reagents.add_reagent(/datum/reagent/blood, 20, list( + "blood_DNA" = "test", + "blood_type" = get_blood_type(BLOOD_TYPE_O_MINUS), + "viruses" = list(), + "immunity" = list(100, list(ANTIGEN_COMMON = 70)), + )) + + var/datum/tgui/centrifuge_ui = new(doctor, centrifuge, "DiseaseCentrifuge") + TEST_ASSERT(islist(centrifuge.ui_data(doctor)), "Wirówka nie oddała danych do okna") + + centrifuge.ui_act("power", list(), centrifuge_ui) + TEST_ASSERT(centrifuge.on, "Wirówka nie włączyła się z okna") + centrifuge.ui_act("power", list(), centrifuge_ui) + TEST_ASSERT(!centrifuge.on, "Wirówki nie dało się wyłączyć z okna") + + doctor.drop_all_held_items() + doctor.put_in_active_hand(tube, forced = TRUE) + centrifuge.ui_act("insert_tube", list("slot" = "1"), centrifuge_ui) + TEST_ASSERT_EQUAL(centrifuge.tubes[1], tube, "Probówka nie trafiła do wirówki z okna") + TEST_ASSERT(centrifuge.tube_valid[1], "Wirówka nie wykryła przeciwciał w probówce") + + var/list/tube_data = centrifuge.tube_ui_data(1) + TEST_ASSERT(tube_data["has_blood"], "Okno wirówki nie pokazuje krwi w probówce") + TEST_ASSERT(tube_data["has_antibodies"], "Okno wirówki nie pokazuje przeciwciał") + + centrifuge.ui_act("eject_tube", list("slot" = "1"), centrifuge_ui) + TEST_ASSERT(isnull(centrifuge.tubes[1]), "Probówki nie dało się wyjąć z okna") + qdel(centrifuge_ui) + + // --- inkubator --- + var/obj/machinery/disease2/incubator/incubator = allocate(/obj/machinery/disease2/incubator) + var/obj/item/weapon/virusdish/incubator_dish = allocate(/obj/item/weapon/virusdish) + incubator_dish.open = TRUE // inkubator nie przyjmuje zamkniętej szalki + doctor.drop_all_held_items() + doctor.put_in_active_hand(incubator_dish, forced = TRUE) + var/datum/tgui/incubator_ui = new(doctor, incubator, "DiseaseIncubator") + incubator.ui_act("insertdish", list("slot" = "1"), incubator_ui) + var/datum/dish_incubator_dish/inserted = incubator.dish_data[1] + TEST_ASSERT_EQUAL(inserted?.dish, incubator_dish, "Szalka nie trafiła do inkubatora z okna") + incubator.ui_act("ejectdish", list("slot" = "1"), incubator_ui) + TEST_ASSERT(isnull(incubator.dish_data[1]), "Szalki nie dało się wyjąć z okna inkubatora") + qdel(incubator_ui) + + // --- izolator --- + var/obj/machinery/disease2/isolator/isolator = allocate(/obj/machinery/disease2/isolator) + var/obj/item/reagent_containers/syringe/syringe = allocate(/obj/item/reagent_containers/syringe) + var/datum/disease/acute/virus/pathogen = new + pathogen.makerandom(list(50, 50), list(50, 50), list(ANTIGEN_COMMON = 1), list(EFFECT_DANGER_FLAVOR = 1), null) + syringe.reagents.add_reagent(/datum/reagent/blood, 10, list( + "blood_DNA" = "test", + "blood_type" = get_blood_type(BLOOD_TYPE_O_MINUS), + "viruses" = list(pathogen), + "immunity" = list(), + )) + doctor.drop_all_held_items() + doctor.put_in_active_hand(syringe, forced = TRUE) + isolator.attackby(syringe, doctor) + TEST_ASSERT_EQUAL(isolator.beaker, syringe, "Izolator nie przyjął strzykawki") + + var/datum/tgui/isolator_ui = new(doctor, isolator, "DiseaseIsolator") + var/list/isolator_data = isolator.ui_data(doctor) + TEST_ASSERT_EQUAL(length(isolator_data["pathogens"]), 1, "Okno izolatora nie pokazuje patogenu z krwi") + + isolator.ui_act("isolate", list("id" = "[pathogen.uniqueID]"), isolator_ui) + TEST_ASSERT(isolator.isolating > 0, "Izolator nie zaczął izolowania z okna") + for(var/i in 1 to 50) + isolator.process() + // na tej kratce lezy juz szalka wyjeta z inkubatora, wiec szukamy tej z patogenem + var/obj/item/weapon/virusdish/printed + for(var/obj/item/weapon/virusdish/candidate in isolator.loc) + if(candidate.contained_virus) + printed = candidate + break + TEST_ASSERT(printed, "Izolator nie wydał szalki z patogenem") + allocated += printed + + isolator.ui_act("eject", list(), isolator_ui) + TEST_ASSERT(isnull(isolator.beaker), "Strzykawki nie dało się wyjąć z okna izolatora") + qdel(isolator_ui) + + // --- maszyna lecząca --- + var/obj/machinery/computer/curer/curer = allocate(/obj/machinery/computer/curer) + var/obj/item/reagent_containers/glass/tube/sample = allocate(/obj/item/reagent_containers/glass/tube) + sample.reagents.add_reagent(/datum/reagent/blood, 20, list( + "blood_DNA" = "test", + "blood_type" = get_blood_type(BLOOD_TYPE_O_MINUS), + "viruses" = list(), + "immunity" = list(100, list(ANTIGEN_COMMON = 70)), + )) + doctor.drop_all_held_items() + doctor.put_in_active_hand(sample, forced = TRUE) + curer.attackby(sample, doctor) + TEST_ASSERT_EQUAL(curer.container, sample, "Maszyna lecząca nie przyjęła probówki") + + var/datum/tgui/curer_ui = new(doctor, curer, "DiseaseCurer") + var/list/curer_data = curer.ui_data(doctor) + TEST_ASSERT(curer_data["has_blood"], "Okno maszyny leczącej nie widzi krwi") + TEST_ASSERT_EQUAL(length(curer_data["antibodies"]), 1, "Okno maszyny leczącej nie pokazuje przeciwciał") + + curer.ui_act("antibody", list(), curer_ui) + TEST_ASSERT(curer.curing > 0, "Maszyna lecząca nie ruszyła z produkcją z okna") + curer.curing = 1 + curer.process() + // tak samo tutaj: obok lezy probowka z wirowki, wiec szukamy tej ze szczepionka + var/found_vaccine = FALSE + for(var/obj/item/reagent_containers/glass/tube/candidate in curer.loc) + if(locate(/datum/reagent/vaccine) in candidate.reagents.reagent_list) + found_vaccine = TRUE + break + TEST_ASSERT(found_vaccine, "Maszyna lecząca nie wydała szczepionki") + + curer.ui_act("eject", list(), curer_ui) + TEST_ASSERT(isnull(curer.container), "Probówki nie dało się wyjąć z okna maszyny leczącej") + qdel(curer_ui) + + TEST_ASSERT_EQUAL(GLOB.total_runtimes, runtimes_before, "Runtime przy obsłudze maszyn z okien") + #undef TEST_ASSERT #undef TEST_ASSERT_EQUAL diff --git a/aquila/code/modules/virology/machines/centrifuge.dm b/aquila/code/modules/virology/machines/centrifuge.dm index 76e89e12fab..b50ac1bda64 100644 --- a/aquila/code/modules/virology/machines/centrifuge.dm +++ b/aquila/code/modules/virology/machines/centrifuge.dm @@ -15,8 +15,6 @@ processing_flags = START_PROCESSING_MANUALLY - var/datum/browser/popup = null - var/on = FALSE var/list/tubes = list(null,null,null,null) @@ -94,7 +92,7 @@ visible_message(span_notice("[user] adds \the [inserting_tube] to \the [src]."), span_notice("You add \the [inserting_tube] to \the [src].")) playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) update_appearance(UPDATE_OVERLAYS) - updateUsrDialog() + SStgui.update_uis(src) return TRUE to_chat(user, span_warning("There is no room for more tubes.")) @@ -174,48 +172,54 @@ if(!is_operational) set_on(FALSE) -/obj/machinery/disease2/centrifuge/proc/add_tube_dat(obj/item/reagent_containers/glass/tube/tube, list/tube_task = list(0,0,0,0,0), slot = 1) - var/dat = "" - var/valid = tube_valid[slot] - - var/passes = FALSE - for(var/datum/reagent/blood in tube.reagents.reagent_list) - if (!length(blood.data) || !("blood_DNA" in blood.data)) +/obj/machinery/disease2/centrifuge/proc/tube_ui_data(slot) + var/list/data = list( + "slot" = slot, + "inserted" = FALSE, + "name" = "Empty slot", + "volume" = 0, + "has_blood" = FALSE, + "has_pathogen" = FALSE, + "has_antibodies" = FALSE, + "vaccine" = null, + "task" = null, + "target" = null, + "progress" = 0, + ) + + var/obj/item/reagent_containers/glass/tube/tube = tubes[slot] + if(!tube) + return data + + data["inserted"] = TRUE + data["name"] = tube.name + data["volume"] = round(tube.reagents.total_volume, 0.01) + data["has_antibodies"] = tube_valid[slot] ? TRUE : FALSE + + for(var/datum/reagent/reagent in tube.reagents.reagent_list) + if(!length(reagent.data)) continue - passes = TRUE - - if(!passes) - var/datum/reagent/vaccine/vaccine = locate() in tube.reagents.reagent_list - if (!vaccine) - dat += "[tube.name] (no blood detected)" - else - var/vaccines = "" - for (var/A in vaccine.data["antigen"]) - vaccines += "[A]" - if (vaccines == "") - vaccines = "blank" - dat += "[tube.name] (Vaccine ([vaccines]))" - else - if (tube_task[1]) - switch (tube_task[1]) - if ("dish") - var/target = tube_task[2] - var/progress = tube_task[3] - dat += "[tube.name] (isolating [target]: [round(progress)]%) X" - if ("vaccine") - var/target = tube_task[2] - var/progress = tube_task[3] - dat += "[tube.name] (synthesizing vaccine ([target]): [round(progress)]%) X" - - else - for(var/datum/reagent/blood in tube.reagents.reagent_list) - if(length(blood.data) && blood.data["viruses"]) - var/list/blood_diseases = blood.data["viruses"] - if (length(blood_diseases) > 0) - dat += "[tube.name] (pathogen detected) ISOLATE TO DISH [valid ? "SYNTHESIZE VACCINE" : "(not enough antibodies for a vaccine)"]" - else - dat += "[tube.name] (no pathogen detected) [valid ? "SYNTHESIZE VACCINE" : "(not enough antibodies for a vaccine)"]" - return dat + if("blood_DNA" in reagent.data) + data["has_blood"] = TRUE + if(length(reagent.data["viruses"])) + data["has_pathogen"] = TRUE + + var/datum/reagent/vaccine/vaccine = locate() in tube.reagents.reagent_list + if(vaccine) + var/antigens = "" + var/list/vaccine_data = vaccine.data + if(length(vaccine_data)) + for(var/antigen in vaccine_data["antigen"]) + antigens += "[antigen]" + data["vaccine"] = antigens || "blank" + + var/list/task = tube_task[slot] + if(task[1]) + data["task"] = task[1] + data["target"] = "[task[2]]" + data["progress"] = round(task[3], 0.1) + + return data /obj/machinery/disease2/centrifuge/proc/eject_when_no_power() ejecting = TRUE @@ -232,41 +236,40 @@ sleep(0.1 SECONDS) ejecting = FALSE +// AQ: sprawdzamy stan PRZED ..(), bo ..() otwiera juz okno TGUI przez interact() /obj/machinery/disease2/centrifuge/attack_hand(mob/user, list/modifiers) - . = ..() if(machine_stat & (BROKEN)) to_chat(user, span_notice("\The [src] is broken. Some components will have to be replaced before it can work again.") ) - return + return TRUE if(machine_stat & (NOPOWER)) if(!ejecting) to_chat(user, span_notice("Deprived of power, \the [src] is unresponsive.")) INVOKE_ASYNC(src, PROC_REF(eject_when_no_power)) - return + return TRUE - if(.) - return + special = CENTRIFUGE_LIGHTSPECIAL_OFF + return ..() - user.set_machine(src) +/obj/machinery/disease2/centrifuge/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DiseaseCentrifuge", "Isolation Centrifuge") + // AQ: u nas okna TGUI nie odswieżaja sie same, a tu leci postep zadan + ui.set_autoupdate(TRUE) + ui.open() - special = CENTRIFUGE_LIGHTSPECIAL_OFF +/obj/machinery/disease2/centrifuge/ui_data(mob/user) + var/list/data = list() + data["on"] = on + data["efficiency"] = round(efficiency, 0.01) - var/dat = "" - dat += "Power status: [on?"On":"Off"]" - dat += "
    " - for (var/i = 1 to length(tubes)) - if(tubes[i]) - dat += add_tube_dat(tubes[i],tube_task[i],i) - else - dat += "Insert a tube" - if(i < length(tubes)) - dat += "
    " - dat += "
    " - - popup = new(user, "centrifuge", "Isolation Centrifuge", 666, 189) - popup.set_window_options("can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;") - popup.set_content(dat) - popup.open() + var/list/tube_data = list() + for(var/i = 1 to length(tubes)) + tube_data += list(tube_ui_data(i)) + data["tubes"] = tube_data + + return data /obj/machinery/disease2/centrifuge/process() if(!on || !is_operational) @@ -303,7 +306,7 @@ tube_task[i] = centrifuge_act(tubes[i],tube_task[i]) if(should_update) - updateUsrDialog() + SStgui.update_uis(src) update_appearance(UPDATE_OVERLAYS) /obj/machinery/disease2/centrifuge/proc/centrifuge_act(obj/item/reagent_containers/glass/tube/tube, list/tube_task = list(0,0,0,0,0)) @@ -333,71 +336,68 @@ result = list(0,0,0,0,0) return result -/obj/machinery/disease2/centrifuge/Topic(href, href_list) - - if(..()) - return TRUE +/obj/machinery/disease2/centrifuge/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + var/mob/user = ui.user special = CENTRIFUGE_LIGHTSPECIAL_OFF - if (href_list["power"]) - set_on(!on) - - else if (href_list["insertvial"]) - var/mob/living/user - if (isliving(usr)) - user = usr - if (!user) - return - var/obj/item/reagent_containers/glass/tube/tube = user.get_active_hand() - if (istype(tube)) - if (on) - to_chat(user, span_warning("You cannot add or remove tubes while the centrifuge is active. Turn it off first.")) - return - else - var/i = text2num(href_list["insertvial"]) - if (!tubes[i]) - if(!user.transferItemToLoc(tube, src)) - return - tubes[i] = tube - tube_valid[i] = tube_has_antibodies(tube) - visible_message(span_notice("[user] adds \the [tube] to \the [src]."), span_notice("You add \the [tube] to \the [src].")) - playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) - else - to_chat(user,span_warning("There is already a tube in that slot.")) - return - - else if (href_list["ejectvial"]) - if (on) - to_chat(usr,span_warning("You cannot add or remove tubes while the centrifuge is active. Turn it Off first.")) - return - else - var/i = text2num(href_list["ejectvial"]) - var/obj/item/reagent_containers/glass/tube/tube = tubes[i] - if (tube) - tube.forceMove(drop_location()) - if (Adjacent(usr)) - usr.put_in_hands(tube) - tubes[i] = null - tube_valid[i] = 0 - tube_task[i] = list(0,0,0,0,0) - - else if (href_list["interrupt"]) - var/i = text2num(href_list["interrupt"]) - tube_task[i] = list(0,0,0,0,0) + if(action == "power") + set_on(!on, user) + update_appearance() + return TRUE - else if (href_list["isolate"]) - var/i = text2num(href_list["isolate"]) - tube_task[i] = isolate(tubes[i],usr) + var/slot = text2num(params["slot"]) + if(!isnum(slot) || !ISINRANGE(slot, 1, length(tubes))) + return TRUE - else if (href_list["synthvaccine"]) - var/i = text2num(href_list["synthvaccine"]) - tube_task[i] = cure(tubes[i],usr) + switch(action) + if("insert_tube") + if(on) + to_chat(user, span_warning("You cannot add or remove tubes while the centrifuge is active. Turn it off first.")) + return TRUE + if(tubes[slot]) + to_chat(user, span_warning("There is already a tube in that slot.")) + return TRUE + // AQ: u nas get_active_hand() zwraca KONCZYNE, nie trzymany przedmiot (w Monke odwrotnie) + var/obj/item/reagent_containers/glass/tube/tube = user.get_active_held_item() + if(!istype(tube)) + to_chat(user, span_warning("You need to hold a tube to put it in \the [src].")) + return TRUE + if(!user.transferItemToLoc(tube, src)) + return TRUE + tubes[slot] = tube + tube_valid[slot] = tube_has_antibodies(tube) + visible_message(span_notice("[user] adds \the [tube] to \the [src]."), span_notice("You add \the [tube] to \the [src].")) + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + + if("eject_tube") + if(on) + to_chat(user, span_warning("You cannot add or remove tubes while the centrifuge is active. Turn it off first.")) + return TRUE + var/obj/item/reagent_containers/glass/tube/tube = tubes[slot] + if(!tube) + return TRUE + tube.forceMove(drop_location()) + if(Adjacent(user)) + user.put_in_hands(tube) + tubes[slot] = null + tube_valid[slot] = 0 + tube_task[slot] = list(0,0,0,0,0) + + if("interrupt") + tube_task[slot] = list(0,0,0,0,0) + + if("isolate") + tube_task[slot] = isolate(tubes[slot], user) + + if("synthvaccine") + tube_task[slot] = cure(tubes[slot], user) update_appearance() - add_fingerprint(usr) - updateUsrDialog() - attack_hand(usr) + return TRUE /obj/machinery/disease2/centrifuge/proc/isolate(obj/item/reagent_containers/glass/tube/tube, mob/user) var/list/result = list(0,0,0,0,0) @@ -412,13 +412,16 @@ for (var/datum/disease/acute/D in blood_viruses) if(!istype(D)) continue + // AQ: dwa patogeny tej samej formy nadpisywaly sie w liscie wyboru var/pathogen_name = "Unknown [D.form]" - pathogen_list[pathogen_name] = D - - popup.close() - user.unset_machine() - var/choice = input(user, "Choose a pathogen to isolate on a growth dish.", "Isolate to dish") as null|anything in pathogen_list - user.set_machine() + var/unique_name = pathogen_name + var/suffix = 2 + while(unique_name in pathogen_list) + unique_name = "[pathogen_name] ([suffix])" + suffix++ + pathogen_list[unique_name] = D + + var/choice = tgui_input_list(user, "Choose a pathogen to isolate on a growth dish.", "Isolate to dish", pathogen_list) if (!choice) return result var/datum/disease/acute/target = pathogen_list[choice] @@ -461,10 +464,7 @@ to_chat(user,span_warning("Impossible to create a vaccine from this blood sample. Antibody levels too low. Minimal level = 30%. The higher the concentration, the faster the vaccine is synthesized.") ) return result - popup.close() - user.unset_machine() - var/choice = input(user, "Choose an antibody to develop into a vaccine. This will destroy the blood sample. The higher the concentration, the faster the vaccine is synthesized.", "Synthesize Vaccine") as null|anything in antibody_choices - user.set_machine() + var/choice = tgui_input_list(user, "Choose an antibody to develop into a vaccine. This will destroy the blood sample. The higher the concentration, the faster the vaccine is synthesized.", "Synthesize Vaccine", antibody_choices) if (!choice) return result diff --git a/aquila/code/modules/virology/machines/curer.dm b/aquila/code/modules/virology/machines/curer.dm index c321e32de51..b51dbf5e339 100644 --- a/aquila/code/modules/virology/machines/curer.dm +++ b/aquila/code/modules/virology/machines/curer.dm @@ -1,8 +1,11 @@ /obj/machinery/computer/curer name = "Cure Research Machine" + desc = "Grows antibodies from a blood sample into a vaccine, and materializes a blood sample out of a growth dish." icon_state = "dna" - var/curing - var/virusing + /// Ile ticków zostało do końca produkcji szczepionki. + var/curing = 0 + /// Czy materializator patogenu się jeszcze ładuje. + var/virusing = FALSE var/obj/item/reagent_containers/glass/tube/container = null @@ -14,92 +17,134 @@ /obj/machinery/computer/curer/attackby(obj/item/tool, mob/user, params) if(istype(tool, /obj/item/reagent_containers/glass/tube)) - if(!container && tool.forceMove(src)) - container = tool + if(container) + to_chat(user, span_warning("There is already a container inside \the [src].")) return TRUE + if(!user.transferItemToLoc(tool, src)) + return TRUE + container = tool + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) + SStgui.update_uis(src) return TRUE if(isvirusdish(tool)) if(virusing) - to_chat(user, "The pathogen materializer is still recharging..") + to_chat(user, span_warning("The pathogen materializer is still recharging.")) + return TRUE + + var/obj/item/weapon/virusdish/dish = tool + // AQ: Monke bralo tu viruses z POWIERZCHNI szalki (tool:viruses), nie jej patogen + if(!istype(dish.contained_virus)) + to_chat(user, span_warning("\The [dish] holds no pathogen.")) return TRUE - var/obj/item/reagent_containers/glass/tube/product = new(loc) - var/list/data = list("viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"viruses"=list(),"immunity"=0) - data["viruses"] |= tool:viruses - product.reagents.add_reagent(/datum/reagent/blood, 30,data) - virusing = 1 - spawn(1200) virusing = 0 + + var/obj/item/reagent_containers/glass/tube/product = new(drop_location()) + var/list/data = list( + "blood_DNA" = null, + "blood_type" = get_blood_type(BLOOD_TYPE_O_MINUS), + "resistances" = null, + "trace_chem" = null, + "viruses" = list(dish.contained_virus.Copy()), + "immunity" = list(), + ) + product.reagents.add_reagent(/datum/reagent/blood, 30, data) + virusing = TRUE + addtimer(VARSET_CALLBACK(src, virusing, FALSE), 2 MINUTES) + visible_message(span_notice("\The [src] materializes a blood sample.")) + SStgui.update_uis(src) return TRUE - src.attack_hand(user) - return TRUE + return ..() + +/obj/machinery/computer/curer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DiseaseCurer", "Cure Research Machine") + // AQ: u nas okna TGUI nie odswieżaja sie same, a tu leci produkcja + ui.set_autoupdate(TRUE) + ui.open() -/obj/machinery/computer/curer/attack_hand(mob/user) - if(..()) +/obj/machinery/computer/curer/ui_data(mob/user) + var/list/data = list() + data["curing"] = curing > 0 + data["virusing"] = virusing + data["container"] = container?.name + data["has_blood"] = FALSE + + var/list/antibodies = list() + if(container) + var/datum/reagent/blood/blood = locate() in container.reagents.reagent_list + if(blood) + data["has_blood"] = TRUE + // AQ: Monke czytalo tu stara bitflage data["antibodies"]; u nas przeciwciala + // siedza w data["immunity"] = list(sila, lista antygen -> poziom) + var/list/blood_data = blood.data + var/list/immune_system = length(blood_data) ? blood_data["immunity"] : null + if(length(immune_system) >= 2) + var/list/levels = immune_system[2] + for(var/antigen in levels) + if(levels[antigen] <= 0) + continue + antibodies += list(list( + "name" = "[antigen]", + "level" = round(levels[antigen], 1), + )) + + data["antibodies"] = antibodies + return data + +/obj/machinery/computer/curer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) return - var/dat - if(curing) - dat = "Antibody production in progress" - else if(virusing) - dat = "Virus production in progress" - else if(container) - // see if there's any blood in the container - var/datum/reagent/blood/B = locate(/datum/reagent/blood) in container.reagents.reagent_list - - if(B) - dat = "Blood sample inserted." - var/code = "" - for(var/V in GLOB.all_antigens) if(text2num(V) & B.data["antibodies"]) code += GLOB.all_antigens[V] - dat += "
    Antibodies: [code]" - dat += "
    Begin antibody production" - else - dat += "
    Please check container contents." - dat += "
    Eject container" - else - dat = "Please insert a container." - - user << browse(HTML_SKELETON(dat), "window=computer;size=400x500") - onclose(user, "computer") - return + + var/mob/user = ui.user + switch(action) + if("antibody") + if(curing > 0 || !container) + return TRUE + var/datum/reagent/blood/blood = locate() in container.reagents.reagent_list + if(!blood) + to_chat(user, span_warning("No blood sample detected.")) + return TRUE + curing = 10 + return TRUE + + if("eject") + if(!container) + return TRUE + container.forceMove(drop_location()) + if(Adjacent(user)) + user.put_in_hands(container) + container = null + return TRUE /obj/machinery/computer/curer/process() - ..() + . = ..() if(machine_stat & (NOPOWER|BROKEN)) return use_power(500) - if(curing) - curing -= 1 - if(curing == 0) - if(container) - createcure(container) - return - -/obj/machinery/computer/curer/Topic(href, href_list) - if(..()) + if(curing <= 0) return - usr.machine = src - if (href_list["antibody"]) - curing = 10 - else if(href_list["eject"]) - container.forceMove(src.loc) - container = null + curing -= 1 + if(curing <= 0 && container) + createcure(container) + SStgui.update_uis(src) - src.add_fingerprint(usr) - src.updateUsrDialog() - return - - -/obj/machinery/computer/curer/proc/createcure(obj/item/reagent_containers/glass/beaker/container) - var/obj/item/reagent_containers/glass/tube/product = new(src.loc) - - var/datum/reagent/blood/B = locate() in container.reagents.reagent_list +/obj/machinery/computer/curer/proc/createcure(obj/item/reagent_containers/glass/tube/source) + var/datum/reagent/blood/blood = locate() in source.reagents.reagent_list + if(!blood) + return + var/obj/item/reagent_containers/glass/tube/product = new(drop_location()) var/list/data = list() - var/list/immunity = B.data["immunity"] - if(length(immunity)) + var/list/blood_data = blood.data + var/list/immunity = length(blood_data) ? blood_data["immunity"] : null + if(length(immunity) >= 2) data["antigen"] = immunity[2] product.reagents.add_reagent(/datum/reagent/vaccine, 30, data) + visible_message(span_notice("\The [src] dispenses a vaccine.")) diff --git a/aquila/code/modules/virology/machines/incubator.dm b/aquila/code/modules/virology/machines/incubator.dm index f15d79ba7c6..b2c061a25cf 100644 --- a/aquila/code/modules/virology/machines/incubator.dm +++ b/aquila/code/modules/virology/machines/incubator.dm @@ -147,9 +147,12 @@ if(!isliving(user)) return TRUE - var/obj/item/weapon/virusdish/VD = user.get_active_hand() - if(istype(VD)) - add_dish(VD, user, slot) + // AQ: u nas get_active_hand() zwraca KONCZYNE, nie trzymany przedmiot (w Monke odwrotnie) + var/obj/item/weapon/virusdish/VD = user.get_active_held_item() + if(!istype(VD)) + to_chat(user, span_warning("You need to hold a growth dish to put it in \the [src].")) + return TRUE + add_dish(VD, user, slot) return TRUE diff --git a/aquila/code/modules/virology/machines/isolator.dm b/aquila/code/modules/virology/machines/isolator.dm index 6a25c963675..810d44c3c2e 100644 --- a/aquila/code/modules/virology/machines/isolator.dm +++ b/aquila/code/modules/virology/machines/isolator.dm @@ -1,3 +1,6 @@ +/// Ile ticków SSmachines zajmuje jedno izolowanie. +#define ISOLATOR_ISOLATION_TICKS 40 + /obj/machinery/disease2/isolator name = "Pathogenic Isolator" desc = "Takes a syringe of blood, and isolates the pathogens inside into a dish." @@ -7,113 +10,120 @@ icon_state = "isolator" var/datum/disease/acute/isolated_disease = null var/isolating = 0 - var/beaker = null + // AQ: w Monke to bylo nietypowane i czytane operatorem : (beaker:reagents) + var/obj/item/reagent_containers/syringe/beaker = null /obj/machinery/disease2/isolator/Destroy() isolated_disease = null + if(!QDELETED(beaker)) + beaker.forceMove(drop_location()) + beaker = null return ..() /obj/machinery/disease2/isolator/attackby(obj/item/tool, mob/user, params) if(!istype(tool, /obj/item/reagent_containers/syringe)) return NONE - var/obj/item/reagent_containers/syringe/B = tool if(beaker) - to_chat(user, "A syringe is already loaded into the machine.") - return TRUE - if(!user.dropItemToGround(B)) + to_chat(user, span_warning("A syringe is already loaded into the machine.")) return TRUE - if(!B.forceMove(src)) + if(!user.transferItemToLoc(tool, src)) return TRUE - beaker = B - to_chat(user, "You add the syringe to the machine!") - src.updateUsrDialog() + + beaker = tool + to_chat(user, span_notice("You add the syringe to the machine.")) + playsound(src, 'sound/machines/click.ogg', vol = 50, vary = TRUE) icon_state = "isolator_in" + SStgui.update_uis(src) return TRUE -/obj/machinery/disease2/isolator/Topic(href, href_list) - if(..()) - return +// AQ: stan sprawdzamy PRZED ..(), bo ..() otwiera juz okno TGUI przez interact() +/obj/machinery/disease2/isolator/attack_hand(mob/user, list/modifiers) + if(machine_stat & BROKEN) + to_chat(user, span_warning("\The [src] is broken. Some components will have to be replaced before it can work again.")) + return TRUE + return ..() - usr.machine = src - if(!beaker) - return - var/datum/reagents/R = beaker:reagents - - if (href_list["isolate"]) - var/datum/reagent/Blood - for(var/datum/reagent/B in R.reagent_list) - if(length(B.data) && ("viruses" in B.data)) - Blood = B - break - // /vg/: Try to fix isolators - if(!Blood) - to_chat(usr, span_warning("ERROR: Unable to locate blood within the beaker. Bug?")) -// testing("Unable to locate blood in [beaker]!") - return - var/list/virus = virus_copylist(Blood.data["viruses"]) - var/choice = text2num(href_list["isolate"]) - for (var/datum/disease/acute/V in virus) - if (V.uniqueID == choice) - isolated_disease = V - isolating = 40 - icon_state = "isolator_processing" - src.updateUsrDialog() - return +/obj/machinery/disease2/isolator/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DiseaseIsolator", "Pathogenic Isolator") + // AQ: u nas okna TGUI nie odswieżaja sie same, a tu leci postep izolowania + ui.set_autoupdate(TRUE) + ui.open() - else if (href_list["main"]) - attack_hand(usr) - return - else if (href_list["eject"]) - beaker:forceMove(src.loc) - beaker = null - icon_state = "isolator" - src.updateUsrDialog() - return +/obj/machinery/disease2/isolator/ui_data(mob/user) + var/list/data = list() + data["isolating"] = isolating > 0 + data["progress"] = isolating > 0 ? round(100 * (ISOLATOR_ISOLATION_TICKS - isolating) / ISOLATOR_ISOLATION_TICKS, 1) : 0 + data["beaker"] = beaker?.name + data["empty"] = beaker ? !beaker.reagents.total_volume : TRUE -/obj/machinery/disease2/isolator/attack_hand(mob/user, list/modifiers) - if(machine_stat & BROKEN) - return - user.machine = src - var/dat = "" - if(!beaker) - - dat = {"Please insert sample into the isolator.
    -Close"} - else if(isolating) - dat = "Isolating" - else - var/datum/reagents/R = beaker:reagents - dat += "Eject

    " - if(!R.total_volume) - dat += "[beaker] is empty." - else - dat += "Contained reagents:
      " - var/passes = FALSE - for(var/datum/reagent/G in R.reagent_list) - if(length(G.data) && ("viruses" in G.data)) - var/list/virus = G.data["viruses"] - // AQ: we krwi siedza tez nasze stare /datum/disease bez uniqueID - pomijamy je; |= na tekscie wywalalo runtime - for (var/datum/disease/acute/V in virus) - passes = TRUE - dat += "
    • [G.name]: Isolate pathogen #[V.uniqueID]
    • " - if(!passes) - dat += "
    • No pathogen
    • " - dat += "
    " - // AQ: onclose() bez klienta (NPC) daje runtime "bad client" - if(!user.client) + var/list/pathogens = list() + if(beaker) + for(var/datum/reagent/reagent in beaker.reagents.reagent_list) + if(!length(reagent.data) || !("viruses" in reagent.data)) + continue + // AQ: we krwi siedza tez stare /datum/disease bez uniqueID - pomijamy je + for(var/datum/disease/acute/pathogen in reagent.data["viruses"]) + pathogens += list(list( + "id" = pathogen.uniqueID, + "name" = "[reagent.name]: pathogen #[pathogen.uniqueID]", + )) + data["pathogens"] = pathogens + + return data + +/obj/machinery/disease2/isolator/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) return - user << browse(HTML_SKELETON_TITLE("Pathogenic Isolator", dat), "window=isolator;size=575x400") - onclose(user, "isolator") - return + + var/mob/user = ui.user + switch(action) + if("eject") + if(!beaker) + return TRUE + beaker.forceMove(drop_location()) + if(Adjacent(user)) + user.put_in_hands(beaker) + beaker = null + icon_state = "isolator" + return TRUE + + if("isolate") + if(isolating > 0 || !beaker) + return TRUE + var/chosen_id = text2num(params["id"]) + if(!isnum(chosen_id)) + return TRUE + for(var/datum/reagent/reagent in beaker.reagents.reagent_list) + if(!length(reagent.data) || !("viruses" in reagent.data)) + continue + for(var/datum/disease/acute/pathogen in virus_copylist(reagent.data["viruses"])) + if(pathogen.uniqueID != chosen_id) + continue + isolated_disease = pathogen + isolating = ISOLATOR_ISOLATION_TICKS + icon_state = "isolator_processing" + return TRUE + return TRUE /obj/machinery/disease2/isolator/process() - if(isolating > 0) - isolating -= 1 - if(isolating == 0) - var/obj/item/weapon/virusdish/d = new /obj/item/weapon/virusdish(src.loc) - d.contained_virus = isolated_disease.Copy() - d.contained_virus.log += "[ROUND_TIME()]
    Transferred to Virus dish" - d.update_icon() - isolated_disease = null - icon_state = "isolator_in" + if(isolating <= 0) + return + + isolating -= 1 + if(isolating <= 0) + if(isolated_disease) + var/obj/item/weapon/virusdish/dish = new(drop_location()) + dish.contained_virus = isolated_disease.Copy() + dish.contained_virus.log += "[ROUND_TIME()]
    Transferred to Virus dish" + dish.update_icon() + visible_message(span_notice("\The [src] prints a growth dish.")) + isolated_disease = null + icon_state = beaker ? "isolator_in" : "isolator" + + SStgui.update_uis(src) + +#undef ISOLATOR_ISOLATION_TICKS diff --git a/tgui/packages/tgui/interfaces/DiseaseCentrifuge.tsx b/tgui/packages/tgui/interfaces/DiseaseCentrifuge.tsx new file mode 100644 index 00000000000..f31c6c58498 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DiseaseCentrifuge.tsx @@ -0,0 +1,180 @@ +import type { BooleanLike } from 'tgui-core/react'; + +import { useBackend } from '../backend'; +import { + Button, + LabeledList, + NoticeBox, + ProgressBar, + Section, +} from '../components'; +import { Window } from '../layouts'; + +type Tube = { + slot: number; + inserted: BooleanLike; + name: string; + volume: number; + has_blood: BooleanLike; + has_pathogen: BooleanLike; + has_antibodies: BooleanLike; + vaccine: string | null; + task: string | null; + target: string | null; + progress: number; +}; + +type Data = { + on: BooleanLike; + efficiency: number; + tubes: Tube[]; +}; + +/** What the centrifuge sees inside a tube. */ +const contentsLabel = (tube: Tube) => { + if (tube.vaccine) { + return `Vaccine (${tube.vaccine})`; + } + if (!tube.has_blood) { + return 'No blood detected'; + } + return tube.has_pathogen + ? 'Blood, pathogen detected' + : 'Blood, no pathogen detected'; +}; + +const taskLabel = (tube: Tube) => { + if (tube.task === 'dish') { + return `Isolating ${tube.target}`; + } + if (tube.task === 'vaccine') { + return `Synthesizing vaccine (${tube.target})`; + } + return 'Working'; +}; + +const TubeSlot = (props: { tube: Tube }) => { + const { act, data } = useBackend(); + const { on } = data; + const { tube } = props; + + if (!tube.inserted) { + return ( +
    +
    + ); + } + + return ( +
    act('eject_tube', { slot: tube.slot })} + /> + } + > + + + {contentsLabel(tube)} + + {tube.volume}u + {!!tube.task && ( + act('interrupt', { slot: tube.slot })} + /> + } + > + + {Math.round(tube.progress)}% + + + )} + + {!tube.task && ( + <> +
    + ); +}; + +export function DiseaseCentrifuge(props) { + const { act, data } = useBackend(); + const { on, efficiency, tubes = [] } = data; + + const queued = tubes.some((tube) => !!tube.task); + + return ( + + +
    act('power')} + /> + } + > + + + {efficiency}x + + + {!on && queued && ( + + Turn the centrifuge on to work through the queued samples. + + )} + {!!on && ( + + Keep opposite slots evenly filled for the best efficiency. + + )} +
    + {tubes.map((tube) => ( + + ))} +
    +
    + ); +} diff --git a/tgui/packages/tgui/interfaces/DiseaseCurer.tsx b/tgui/packages/tgui/interfaces/DiseaseCurer.tsx new file mode 100644 index 00000000000..2eceb05ce9c --- /dev/null +++ b/tgui/packages/tgui/interfaces/DiseaseCurer.tsx @@ -0,0 +1,102 @@ +import type { BooleanLike } from 'tgui-core/react'; + +import { useBackend } from '../backend'; +import { + Button, + LabeledList, + NoticeBox, + ProgressBar, + Section, +} from '../components'; +import { Window } from '../layouts'; + +type Antibody = { + name: string; + level: number; +}; + +type Data = { + curing: BooleanLike; + virusing: BooleanLike; + container: string | null; + has_blood: BooleanLike; + antibodies: Antibody[]; +}; + +export function DiseaseCurer(props) { + const { act, data } = useBackend(); + const { curing, virusing, container, has_blood, antibodies = [] } = data; + + return ( + + +
    act('eject')} + /> + } + > + {!!curing && Antibody production in progress.} + {!!virusing && ( + The pathogen materializer is recharging. + )} + {!container && ( + Please insert a container. + )} + {!!container && ( + + {container} + + {has_blood ? 'Blood sample inserted' : 'No blood detected'} + + + )} +
    + {!!container && !!has_blood && ( +
    act('antibody')} + /> + } + > + {!antibodies.length && ( + + This donor carries no antibodies. A vaccine needs a sample from + someone who fought the pathogen off. + + )} + + {antibodies.map((antibody) => ( + + + {antibody.level}% + + + ))} + +
    + )} +
    +
    + ); +} diff --git a/tgui/packages/tgui/interfaces/DiseaseIsolator.tsx b/tgui/packages/tgui/interfaces/DiseaseIsolator.tsx new file mode 100644 index 00000000000..e18f559f21f --- /dev/null +++ b/tgui/packages/tgui/interfaces/DiseaseIsolator.tsx @@ -0,0 +1,81 @@ +import type { BooleanLike } from 'tgui-core/react'; + +import { useBackend } from '../backend'; +import { + Button, + LabeledList, + NoticeBox, + ProgressBar, + Section, +} from '../components'; +import { Window } from '../layouts'; + +type Pathogen = { + id: number; + name: string; +}; + +type Data = { + isolating: BooleanLike; + progress: number; + beaker: string | null; + empty: BooleanLike; + pathogens: Pathogen[]; +}; + +export function DiseaseIsolator(props) { + const { act, data } = useBackend(); + const { isolating, progress, beaker, empty, pathogens = [] } = data; + + return ( + + +
    act('eject')} + /> + } + > + {!beaker && ( + Please insert a syringe into the isolator. + )} + {!!beaker && ( + + {beaker} + {!!isolating && ( + + + {Math.round(progress)}% + + + )} + + )} +
    + {!!beaker && !isolating && ( +
    + {!!empty && The syringe is empty.} + {!empty && !pathogens.length && ( + No pathogen found in this sample. + )} + {pathogens.map((pathogen) => ( +
    + )} +
    +
    + ); +}