From 2df5e9fe0ec8ee54c554917e1ffbd29645dd457d Mon Sep 17 00:00:00 2001 From: Atermonera Date: Fri, 30 Nov 2018 21:05:42 -0800 Subject: [PATCH] Merges AI Branch into Master --- code/ZAS/Airflow.dm | 3 - code/__defines/_planes+layers.dm | 5 +- code/__defines/misc.dm | 2 +- code/__defines/mobs.dm | 40 + code/__defines/subsystems.dm | 2 + code/_global_vars/misc.dm | 2 +- code/_global_vars/mobs.dm | 2 +- code/_helpers/time.dm | 2 +- code/_helpers/unsorted.dm | 2 +- code/_macros.dm | 10 +- code/_onclick/hud/fullscreen.dm | 2 +- code/_onclick/hud/hud.dm | 2 - code/_onclick/hud/other_mobs.dm | 7 +- code/_onclick/item_attack.dm | 2 +- code/_onclick/other_mobs.dm | 55 - code/controllers/subsystems/ai.dm | 36 + code/controllers/subsystems/vote.dm | 746 ++--- code/datums/ghost_query.dm | 7 + code/datums/mind.dm | 17 +- code/game/antagonist/alien/borer.dm | 4 +- code/game/antagonist/station/cultist.dm | 4 +- code/game/gamemodes/cult/construct_spells.dm | 34 +- code/game/gamemodes/cult/cult_items.dm | 8 +- code/game/gamemodes/cult/cult_structures.dm | 13 +- code/game/gamemodes/cult/cultify/mob.dm | 2 +- .../gamemodes/cult}/soulstone.dm | 488 ++-- code/game/gamemodes/events.dm | 2 +- .../gamemodes/events/holidays/Christmas.dm | 2 +- code/game/gamemodes/objective.dm | 4 +- .../technomancer/assistance/golem.dm | 258 -- code/game/gamemodes/technomancer/core_obj.dm | 2 +- .../technomancer/spell_objs_helpers.dm | 23 +- .../technomancer/spells/abjuration.dm | 12 +- .../gamemodes/technomancer/spells/control.dm | 83 +- .../gamemodes/technomancer/spells/gambit.dm | 6 +- .../gamemodes/technomancer/spells/illusion.dm | 123 +- .../technomancer/spells/modifier/modifier.dm | 7 +- .../technomancer/spells/resurrect.dm | 6 +- .../technomancer/spells/spawner/pulsar.dm | 45 +- .../spells/summon/summon_creature.dm | 36 +- .../technomancer/spells/summon/summon_ward.dm | 121 +- code/game/machinery/adv_med.dm | 14 +- code/game/machinery/camera/camera.dm | 2 +- code/game/machinery/computer/prisonshuttle.dm | 2 +- .../machinery/computer/specops_shuttle.dm | 2 +- .../computer/syndicate_specops_shuttle.dm | 2 +- .../computer3/computers/HolodeckControl.dm | 4 +- code/game/machinery/cryo.dm | 23 +- code/game/machinery/cryopod.dm | 7 +- code/game/machinery/doors/airlock.dm | 9 +- code/game/machinery/doors/door.dm | 4 +- code/game/machinery/doors/firedoor.dm | 8 +- code/game/machinery/portable_turret.dm | 10 +- code/game/machinery/teleporter.dm | 2 +- .../mecha/equipment/tools/medical_tools.dm | 7 +- code/game/mecha/equipment/tools/tools.dm | 9 +- code/game/mecha/mecha.dm | 8 +- code/game/mecha/mecha_wreckage.dm | 11 + code/game/mecha/medical/odysseus.dm | 2 +- code/game/objects/effects/overlays.dm | 10 + code/game/objects/effects/spiders.dm | 24 +- code/game/objects/items/devices/aicard.dm | 2 +- .../objects/items/devices/laserpointer.dm | 2 +- code/game/objects/items/devices/scanners.dm | 20 +- code/game/objects/items/glassjar.dm | 2 +- code/game/objects/items/poi_items.dm | 11 - code/game/objects/items/robot/robot_parts.dm | 7 - code/game/objects/items/weapons/AI_modules.dm | 2 +- .../items/weapons/grenades/spawnergrenade.dm | 12 +- code/game/objects/items/weapons/handcuffs.dm | 2 +- .../items/weapons/implants/implantchair.dm | 7 +- .../objects/items/weapons/melee/energy.dm | 9 +- code/game/objects/items/weapons/policetape.dm | 2 +- code/game/objects/items/weapons/stunbaton.dm | 5 +- .../items/weapons/tools/screwdriver.dm | 2 +- .../items/weapons/tools/weldingtool.dm | 7 + .../objects/items/weapons/tools/wrench.dm | 2 +- code/game/objects/random/mob.dm | 168 +- code/game/objects/structures.dm | 4 +- .../structures/crates_lockers/closets.dm | 6 +- .../structures/crates_lockers/largecrate.dm | 12 +- .../structures/ghost_pods/mysterious.dm | 4 +- code/game/objects/structures/girders.dm | 4 +- code/game/objects/structures/gravemarker.dm | 2 +- code/game/objects/structures/loot_piles.dm | 45 +- code/game/objects/structures/musician.dm | 2 +- code/game/objects/structures/plasticflaps.dm | 4 +- code/game/objects/structures/props/nest.dm | 2 +- code/game/objects/structures/simple_doors.dm | 2 +- .../structures/stool_bed_chair_nest/chairs.dm | 2 +- code/game/objects/structures/window.dm | 2 +- code/game/objects/weapons.dm | 14 +- .../game/turfs/simulated/outdoors/outdoors.dm | 2 +- code/game/turfs/simulated/outdoors/snow.dm | 3 + code/game/turfs/simulated/wall_attacks.dm | 12 +- code/game/turfs/simulated/water.dm | 24 +- code/game/turfs/turf.dm | 4 + code/game/turfs/turf_changing.dm | 2 +- code/modules/admin/topic.dm | 26 +- code/modules/admin/verbs/buildmode.dm | 108 + code/modules/admin/verbs/lightning_strike.dm | 214 +- code/modules/ai/__readme.dm | 201 ++ code/modules/ai/_defines.dm | 29 + .../ai/aI_holder_subtypes/simple_mob_ai.dm | 136 + .../ai/aI_holder_subtypes/slime_xenobio_ai.dm | 262 ++ code/modules/ai/ai_holder.dm | 290 ++ code/modules/ai/ai_holder_combat.dm | 308 ++ code/modules/ai/ai_holder_combat_unseen.dm | 43 + code/modules/ai/ai_holder_communication.dm | 134 + code/modules/ai/ai_holder_cooperation.dm | 115 + code/modules/ai/ai_holder_debug.dm | 89 + code/modules/ai/ai_holder_disabled.dm | 95 + code/modules/ai/ai_holder_fleeing.dm | 45 + code/modules/ai/ai_holder_follow.dm | 68 + code/modules/ai/ai_holder_movement.dm | 154 + code/modules/ai/ai_holder_pathfinding.dm | 58 + code/modules/ai/ai_holder_targeting.dm | 237 ++ code/modules/ai/interfaces.dm | 92 + code/modules/ai/say_list.dm | 119 + code/modules/artifice/deadringer.dm | 9 +- code/modules/assembly/mousetrap.dm | 2 +- code/modules/blob2/blobs/factory.dm | 6 +- code/modules/blob2/overmind/overmind.dm | 2 +- code/modules/blob2/overmind/types.dm | 18 +- code/modules/client/client defines.dm | 2 +- .../preference_setup/loadout/loadout_head.dm | 2 +- code/modules/events/carp_migration.dm | 10 +- code/modules/events/infestation.dm | 4 +- code/modules/events/rogue_drones.dm | 8 +- code/modules/food/kitchen/gibber.dm | 472 ++-- code/modules/food/kitchen/microwave.dm | 784 +++--- code/modules/food/kitchen/smartfridge.dm | 4 + .../gamemaster/actions/carp_migration.dm | 4 +- .../actions/surprise_carp_attack.dm | 4 +- code/modules/games/cards.dm | 4 +- code/modules/holodeck/HolodeckControl.dm | 10 +- code/modules/holodeck/HolodeckObjects.dm | 16 +- code/modules/hydroponics/seed.dm | 4 +- code/modules/hydroponics/seed_datums.dm | 2 +- .../core/assemblies/device.dm | 168 +- code/modules/mob/_modifiers/auras.dm | 18 + code/modules/mob/_modifiers/modifiers.dm | 7 + code/modules/mob/_modifiers/modifiers_misc.dm | 69 +- code/modules/mob/_modifiers/traits_phobias.dm | 18 +- code/modules/mob/_modifiers/unholy.dm | 4 +- code/modules/mob/animations.dm | 27 +- code/modules/mob/dead/observer/observer.dm | 4 +- code/modules/mob/emote.dm | 7 + code/modules/mob/freelook/mask/chunk.dm | 4 +- code/modules/mob/hear_say.dm | 19 +- code/modules/mob/language/outsider.dm | 6 +- code/modules/mob/living/bot/secbot.dm | 10 +- .../living/carbon/alien/diona/diona_powers.dm | 2 +- code/modules/mob/living/carbon/brain/brain.dm | 2 +- code/modules/mob/living/carbon/brain/life.dm | 1 - .../mob/living/carbon/carbon_powers.dm | 8 +- code/modules/mob/living/carbon/human/death.dm | 4 +- .../mob/living/carbon/human/human_defense.dm | 35 + .../mob/living/carbon/human/human_helpers.dm | 10 + code/modules/mob/living/carbon/human/life.dm | 4 +- code/modules/mob/living/carbon/human/say.dm | 2 +- .../human/species/station/prometheans.dm | 2 +- code/modules/mob/living/death.dm | 6 + code/modules/mob/living/life.dm | 6 + code/modules/mob/living/living.dm | 37 +- code/modules/mob/living/living_defense.dm | 35 +- code/modules/mob/living/living_defines.dm | 6 +- code/modules/mob/living/login.dm | 5 + code/modules/mob/living/logout.dm | 9 +- .../living/silicon/robot/drone/drone_items.dm | 2 +- .../living/simple_animal/aliens/faithless.dm | 1 - .../living/simple_animal/aliens/hivebot.dm | 4 +- .../mob/living/simple_animal/animals/cat.dm | 201 ++ .../mob/living/simple_animal/animals/corgi.dm | 1 + .../mob/living/simple_animal/animals/mouse.dm | 6 +- .../living/simple_animal/animals/spiderbot.dm | 2 +- .../mob/living/simple_animal/borer/borer.dm | 24 +- .../simple_animal/borer/borer_captive.dm | 8 +- .../simple_animal/borer/borer_powers.dm | 31 +- .../mob/living/simple_animal/borer/say.dm | 2 +- .../simple_animal/constructs/constructs.dm | 488 ---- .../living/simple_animal/humanoids/head.dm | 61 - .../simple_animal/humanoids/mechamobs.dm | 2 +- .../mob/living/simple_animal/simple_animal.dm | 28 +- .../mob/living/simple_animal/slime/slime.dm | 4 +- .../mob/living/simple_mob/appearance.dm | 76 + code/modules/mob/living/simple_mob/combat.dm | 236 ++ code/modules/mob/living/simple_mob/defense.dm | 224 ++ code/modules/mob/living/simple_mob/hands.dm | 143 + code/modules/mob/living/simple_mob/life.dm | 160 ++ .../modules/mob/living/simple_mob/on_click.dm | 48 + .../mob/living/simple_mob/simple_hud.dm | 311 ++ .../mob/living/simple_mob/simple_mob.dm | 290 ++ .../simple_mob/subtypes/animal/animal.dm | 9 + .../simple_mob/subtypes/animal/borer/borer.dm | 237 ++ .../subtypes/animal/borer/borer_captive.dm | 59 + .../subtypes/animal/borer/borer_powers.dm | 354 +++ .../subtypes/animal/farm animals/chicken.dm | 156 + .../subtypes/animal/farm animals/cow.dm | 67 + .../subtypes/animal/farm animals/goat.dm | 81 + .../animal/giant_spider/_giant_spider.dm | 64 + .../subtypes/animal/giant_spider/carrier.dm | 67 + .../subtypes/animal/giant_spider/electric.dm | 45 + .../subtypes/animal/giant_spider/frost.dm | 20 + .../subtypes/animal/giant_spider/hunter.dm | 165 ++ .../subtypes/animal/giant_spider/lurker.dm | 103 + .../subtypes/animal/giant_spider/nurse.dm | 230 ++ .../subtypes/animal/giant_spider/pepper.dm | 21 + .../animal/giant_spider/phorogenic.dm | 56 + .../subtypes/animal/giant_spider/thermic.dm | 20 + .../subtypes/animal/giant_spider/tunneler.dm | 185 ++ .../animal/giant_spider/webslinger.dm | 37 + .../subtypes/animal/passive/crab.dm | 25 + .../subtypes/animal/passive/fish.dm | 77 + .../subtypes/animal/passive/lizard.dm | 24 + .../subtypes/animal/passive/misc.dm | 29 + .../subtypes/animal/passive/mouse.dm | 115 + .../subtypes/animal/passive/passive.dm | 5 + .../subtypes/animal/passive/penguin.dm | 27 + .../simple_mob/subtypes/animal/pets/bird.dm | 93 + .../simple_mob/subtypes/animal/pets/cat.dm | 136 + .../simple_mob/subtypes/animal/pets/dog.dm | 234 ++ .../simple_mob/subtypes/animal/pets/parrot.dm | 252 ++ .../simple_mob/subtypes/animal/sif/diyaab.dm | 35 + .../subtypes/animal/sif/hooligan_crab.dm | 102 + .../simple_mob/subtypes/animal/sif/savik.dm | 48 + .../simple_mob/subtypes/animal/sif/shantak.dm | 70 + .../simple_mob/subtypes/animal/sif/sif.dm | 5 + .../simple_mob/subtypes/animal/space/alien.dm | 118 + .../simple_mob/subtypes/animal/space/bats.dm | 52 + .../simple_mob/subtypes/animal/space/bear.dm | 46 + .../simple_mob/subtypes/animal/space/carp.dm | 128 + .../simple_mob/subtypes/animal/space/goose.dm | 44 + .../simple_mob/subtypes/animal/space/space.dm | 15 + .../living/simple_mob/subtypes/blob/blob.dm | 62 + .../living/simple_mob/subtypes/blob/spore.dm | 148 + .../simple_mob/subtypes/humanoid/clown.dm | 29 + .../simple_mob/subtypes/humanoid/humanoid.dm | 26 + .../subtypes/humanoid/mercs/mercs.dm | 278 ++ .../simple_mob/subtypes/humanoid/pirates.dm | 40 + .../simple_mob/subtypes/humanoid/russian.dm | 35 + .../simple_mob/subtypes/illusion/illusion.dm | 101 + .../subtypes/mechanical/combat_drone.dm | 77 + .../simple_mob/subtypes/mechanical/golem.dm | 152 + .../subtypes/mechanical/hivebot/hivebot.dm | 54 + .../mechanical/hivebot/ranged_damage.dm | 147 + .../subtypes/mechanical/hivebot/support.dm | 87 + .../subtypes/mechanical/hivebot/tank.dm | 166 ++ .../mechanical/mecha/adv_dark_gygax.dm | 261 ++ .../subtypes/mechanical/mecha/combat_mecha.dm | 38 + .../subtypes/mechanical/mecha/durand.dm | 53 + .../subtypes/mechanical/mecha/gygax.dm | 57 + .../subtypes/mechanical/mecha/hoverpod.dm | 27 + .../subtypes/mechanical/mecha/marauder.dm | 44 + .../subtypes/mechanical/mecha/mecha.dm | 141 + .../subtypes/mechanical/mecha/odysseus.dm | 71 + .../subtypes/mechanical/mecha/phazon.dm | 22 + .../subtypes/mechanical/mecha/ripley.dm | 64 + .../subtypes/mechanical/mechanical.dm | 25 + .../subtypes/mechanical/viscerator.dm | 51 + .../subtypes/mechanical/ward/monitor_ward.dm | 106 + .../subtypes/mechanical/ward/ward.dm | 42 + .../subtypes/occult/constructs/_construct.dm | 163 ++ .../subtypes/occult/constructs/artificer.dm | 30 + .../subtypes/occult/constructs/harvester.dm | 40 + .../subtypes/occult/constructs/juggernaut.dm | 143 + .../subtypes/occult/constructs/shade.dm | 48 + .../subtypes/occult/constructs/wraith.dm | 33 + .../simple_mob/subtypes/occult/creature.dm | 68 + .../simple_mob/subtypes/occult/faithless.dm | 77 + .../simple_mob/subtypes/plant/tomato.dm | 27 + .../living/simple_mob/subtypes/plant/tree.dm | 43 + .../simple_mob/subtypes/slime/feral/feral.dm | 94 + .../living/simple_mob/subtypes/slime/slime.dm | 216 ++ .../subtypes/slime/xenobio/combat.dm | 76 + .../subtypes/slime/xenobio/consumption.dm | 169 ++ .../subtypes/slime/xenobio/defense.dm | 54 + .../subtypes/slime/xenobio/discipline.dm | 23 + .../subtypes/slime/xenobio/subtypes.dm | 784 ++++++ .../subtypes/slime/xenobio/xenobio.dm | 284 ++ code/modules/mob/mob.dm | 7 +- code/modules/mob/mob_defines.dm | 3 +- code/modules/mob/mob_helpers.dm | 19 +- code/modules/mob/mob_movement.dm | 29 +- code/modules/mob/transform_procs.dm | 38 +- code/modules/multiz/movement.dm | 50 +- code/modules/multiz/turf.dm | 9 + code/modules/organs/internal/brain.dm | 2 +- code/modules/organs/misc.dm | 2 +- code/modules/paperwork/paperbin.dm | 2 +- code/modules/paperwork/photography.dm | 28 - code/modules/planet/sif.dm | 2 +- code/modules/power/singularity/act.dm | 9 + code/modules/power/tesla/energy_ball.dm | 3 +- code/modules/power/tesla/tesla_act.dm | 3 + code/modules/projectiles/gun.dm | 5 +- .../projectiles/guns/energy/special.dm | 4 +- code/modules/projectiles/projectile.dm | 15 +- code/modules/projectiles/projectile/change.dm | 2 +- code/modules/random_map/drop/droppod.dm | 2 +- code/modules/random_map/noise/desert.dm | 2 +- code/modules/random_map/noise/tundra.dm | 4 +- .../Chemistry-Reagents-Core.dm | 4 +- .../Chemistry-Reagents-Food-Drinks.dm | 6 - .../Chemistry-Reagents-Medicine.dm | 4 + .../Chemistry-Reagents-Other.dm | 2 +- .../reagents/reagent_containers/glass.dm | 4 +- code/modules/shieldgen/directional_shield.dm | 4 + code/modules/shieldgen/energy_field.dm | 6 + code/modules/shuttles/shuttle.dm | 11 + code/modules/spells/aoe_turf/summons.dm | 6 +- code/modules/spells/spell_code.dm | 8 +- code/modules/spells/spellbook.dm | 5 - code/modules/surgery/implant.dm | 4 +- code/modules/tension/tension.dm | 48 +- code/modules/ventcrawl/ventcrawl.dm | 11 +- .../xenoarcheaology/artifacts/autocloner.dm | 28 +- .../xenoarcheaology/artifacts/replicator.dm | 5 + code/modules/xenoarcheaology/finds/special.dm | 4 + code/modules/xenobio/items/extracts.dm | 30 +- code/modules/xenobio/items/slimepotions.dm | 87 +- code/modules/xenobio/items/weapons.dm | 64 +- code/modules/xenobio/machinery/processor.dm | 8 +- code/stylesheet.dm | 7 + html/changelog.html | 8 +- html/changelogs/Neerti-AI.yml | 37 + icons/effects/beam.dmi | Bin 112631 -> 120236 bytes icons/effects/effects.dmi | Bin 401565 -> 416434 bytes icons/mecha/mecha.dmi | Bin 167769 -> 213024 bytes icons/misc/buildmode.dmi | Bin 1452 -> 1520 bytes icons/mob/animal.dmi | Bin 279887 -> 266875 bytes icons/mob/critter.dmi | Bin 34610 -> 34362 bytes icons/mob/hivebot.dmi | Bin 15565 -> 15677 bytes icons/mob/mob.dmi | Bin 194750 -> 211298 bytes icons/mob/modifier_effects.dmi | Bin 16104 -> 20018 bytes icons/mob/screen1.dmi | Bin 167147 -> 167577 bytes icons/mob/slime2.dmi | Bin 32723 -> 28566 bytes maps/RandomZLevels/wildwest.dm | 176 ++ maps/northern_star/polaris-1.dmm | 31 +- maps/northern_star/polaris-2.dmm | 454 +-- maps/northern_star/polaris-5.dmm | 4 +- maps/southern_cross/southern_cross-1.dmm | 13 +- maps/southern_cross/southern_cross-3.dmm | 1824 ++++++++++++ maps/southern_cross/southern_cross-4.dmm | 1398 ++++----- maps/southern_cross/southern_cross-6.dmm | 2505 +++++++++++++++++ maps/southern_cross/southern_cross_areas.dm | 6 +- .../surface_submaps/mountains/BlastMine1.dmm | 4 +- .../surface_submaps/mountains/CaveTrench.dmm | 39 + .../surface_submaps/mountains/Cavelake.dmm | 18 + .../mountains/CrashedMedShuttle1.dmm | 14 +- .../surface_submaps/mountains/Scave1.dmm | 20 + .../surface_submaps/mountains/SwordCave.dmm | 110 +- .../mountains/crashedcontainmentshuttle.dmm | 240 +- .../surface_submaps/mountains/digsite.dmm | 125 +- .../mountains/quarantineshuttle.dmm | 291 +- .../surface_submaps/mountains/vault3.dmm | 2 +- .../surface_submaps/mountains/vault4.dmm | 4 +- .../surface_submaps/mountains/vault5.dmm | 4 +- .../surface_submaps/plains/Oldhouse.dmm | 55 + .../surface_submaps/plains/PooledR.dmm | 6 +- .../surface_submaps/plains/RationCache.dmm | 4 +- .../surface_submaps/plains/Shakden.dmm | 4 +- .../wilderness/Blackshuttledown.dmm | 126 + .../surface_submaps/wilderness/Boombase.dmm | 4 +- .../surface_submaps/wilderness/CaveS.dmm | 43 + .../surface_submaps/wilderness/DJOutpost1.dmm | 2 +- .../surface_submaps/wilderness/DoomP.dmm | 93 + .../surface_submaps/wilderness/Drugden.dmm | 50 + .../surface_submaps/wilderness/MCamp1.dmm | 40 + .../surface_submaps/wilderness/MHR.dmm | 22 + .../surface_submaps/wilderness/Manor1.dmm | 148 + .../surface_submaps/wilderness/Mudpit.dmm | 18 + .../surface_submaps/wilderness/Rockybase.dmm | 230 ++ .../surface_submaps/wilderness/spider1.dmm | 12 + sound/effects/break_stone.ogg | Bin 0 -> 35871 bytes sound/effects/servostep.ogg | Bin 0 -> 10714 bytes sound/effects/suitstep1.ogg | Bin 0 -> 10777 bytes sound/effects/suitstep2.ogg | Bin 0 -> 12220 bytes vorestation.dme | 120 +- 379 files changed, 22460 insertions(+), 4751 deletions(-) create mode 100644 code/controllers/subsystems/ai.dm rename code/{modules/mob/living/simple_animal/constructs => game/gamemodes/cult}/soulstone.dm (88%) delete mode 100644 code/game/gamemodes/technomancer/assistance/golem.dm mode change 100755 => 100644 code/game/objects/items/weapons/AI_modules.dm create mode 100644 code/modules/ai/__readme.dm create mode 100644 code/modules/ai/_defines.dm create mode 100644 code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm create mode 100644 code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm create mode 100644 code/modules/ai/ai_holder.dm create mode 100644 code/modules/ai/ai_holder_combat.dm create mode 100644 code/modules/ai/ai_holder_combat_unseen.dm create mode 100644 code/modules/ai/ai_holder_communication.dm create mode 100644 code/modules/ai/ai_holder_cooperation.dm create mode 100644 code/modules/ai/ai_holder_debug.dm create mode 100644 code/modules/ai/ai_holder_disabled.dm create mode 100644 code/modules/ai/ai_holder_fleeing.dm create mode 100644 code/modules/ai/ai_holder_follow.dm create mode 100644 code/modules/ai/ai_holder_movement.dm create mode 100644 code/modules/ai/ai_holder_pathfinding.dm create mode 100644 code/modules/ai/ai_holder_targeting.dm create mode 100644 code/modules/ai/interfaces.dm create mode 100644 code/modules/ai/say_list.dm create mode 100644 code/modules/mob/_modifiers/auras.dm delete mode 100644 code/modules/mob/living/simple_animal/constructs/constructs.dm delete mode 100644 code/modules/mob/living/simple_animal/humanoids/head.dm create mode 100644 code/modules/mob/living/simple_mob/appearance.dm create mode 100644 code/modules/mob/living/simple_mob/combat.dm create mode 100644 code/modules/mob/living/simple_mob/defense.dm create mode 100644 code/modules/mob/living/simple_mob/hands.dm create mode 100644 code/modules/mob/living/simple_mob/life.dm create mode 100644 code/modules/mob/living/simple_mob/on_click.dm create mode 100644 code/modules/mob/living/simple_mob/simple_hud.dm create mode 100644 code/modules/mob/living/simple_mob/simple_mob.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/animal.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_powers.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/farm animals/chicken.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/farm animals/cow.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/farm animals/goat.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/carrier.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/electric.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/frost.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/nurse.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/pepper.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/phorogenic.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/thermic.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/tunneler.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/webslinger.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/passive/crab.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/passive/fish.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/passive/lizard.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/passive/misc.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/passive/passive.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/passive/penguin.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/pets/bird.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/pets/cat.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/pets/parrot.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/sif/diyaab.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/sif/hooligan_crab.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/sif/savik.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/sif/shantak.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/sif/sif.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/space/alien.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/space/bats.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/space/bear.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/space/goose.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/animal/space/space.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/blob/blob.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/blob/spore.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/humanoid/clown.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/humanoid/humanoid.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/humanoid/russian.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/combat_drone.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/golem.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/tank.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/combat_mecha.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/durand.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/gygax.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/hoverpod.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/marauder.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/phazon.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/ripley.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/mechanical.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/viscerator.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/ward/monitor_ward.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/mechanical/ward/ward.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/occult/constructs/_construct.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/occult/constructs/artificer.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/occult/constructs/harvester.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/occult/constructs/shade.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/occult/constructs/wraith.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/occult/creature.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/occult/faithless.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/plant/tomato.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/plant/tree.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/slime/slime.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/slime/xenobio/combat.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/slime/xenobio/consumption.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/slime/xenobio/defense.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/slime/xenobio/discipline.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm create mode 100644 code/modules/mob/living/simple_mob/subtypes/slime/xenobio/xenobio.dm create mode 100644 html/changelogs/Neerti-AI.yml create mode 100644 sound/effects/break_stone.ogg create mode 100644 sound/effects/servostep.ogg create mode 100644 sound/effects/suitstep1.ogg create mode 100644 sound/effects/suitstep2.ogg diff --git a/code/ZAS/Airflow.dm b/code/ZAS/Airflow.dm index e5d2b7eb11..394fe72478 100644 --- a/code/ZAS/Airflow.dm +++ b/code/ZAS/Airflow.dm @@ -22,9 +22,6 @@ mob/proc/airflow_stun() mob/living/silicon/airflow_stun() return -mob/living/simple_animal/slime/airflow_stun() - return - mob/living/carbon/human/airflow_stun() if(shoes && (shoes.item_flags & NOSLIP)) to_chat(src, "Air suddenly rushes past you!") diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index 4d258cd91f..e37bef86d4 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -58,7 +58,10 @@ What is the naming convention for planes or layers? #define ATMOS_LAYER 2.4 // Pipe-like atmos machinery that goes on the floor, like filters. #define ABOVE_UTILITY 2.5 // Above stuff like pipes and wires #define TURF_PLANE -45 // Turfs themselves, most flooring - #define ABOVE_TURF_LAYER 2.1 // Snow and wallmounted/floormounted equipment + #define WATER_FLOOR_LAYER 2.0 // The 'bottom' of water tiles. + #define UNDERWATER_LAYER 2.5 // Anything on this layer will render under the water layer. + #define WATER_LAYER 3.0 // Layer for water overlays. + #define ABOVE_TURF_LAYER 3.1 // Snow and wallmounted/floormounted equipment #define DECAL_PLANE -44 // Permanent decals #define DIRTY_PLANE -43 // Nonpermanent decals #define BLOOD_PLANE -42 // Blood is really dirty, but we can do special stuff if we separate it diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index aa334224ad..527bf55d93 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -302,4 +302,4 @@ var/global/list/##LIST_NAME = list();\ #define RCD_SHEETS_PER_MATTER_UNIT 4 // Each physical material sheet is worth four matter units. -#define RCD_MAX_CAPACITY 30 * RCD_SHEETS_PER_MATTER_UNIT \ No newline at end of file +#define RCD_MAX_CAPACITY 30 * RCD_SHEETS_PER_MATTER_UNIT diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm index 679962b210..3221a595e4 100644 --- a/code/__defines/mobs.dm +++ b/code/__defines/mobs.dm @@ -27,6 +27,9 @@ #define BORGXRAY 0x4 #define BORGMATERIAL 8 +#define STANCE_ATTACK 11 // Backwards compatability +#define STANCE_ATTACKING 12 // Ditto +/* #define STANCE_IDLE 1 // Looking for targets if hostile. Does idle wandering. #define STANCE_ALERT 2 // Bears #define STANCE_ATTACK 3 // Attempting to get into attack position @@ -34,6 +37,20 @@ #define STANCE_TIRED 5 // Bears #define STANCE_FOLLOW 6 // Following somone #define STANCE_BUSY 7 // Do nothing on life ticks (Other code is running) +*/ +#define STANCE_SLEEP 0 // Doing (almost) nothing, to save on CPU because nobody is around to notice or the mob died. +#define STANCE_IDLE 1 // The more or less default state. Wanders around, looks for baddies, and spouts one-liners. +#define STANCE_ALERT 2 // A baddie is visible but not too close, and essentially we tell them to go away or die. +#define STANCE_APPROACH 3 // Attempting to get into range to attack them. +#define STANCE_FIGHT 4 // Actually fighting, with melee or ranged. +#define STANCE_BLINDFIGHT 5 // Fighting something that cannot be seen by the mob, from invisibility or out of sight. +#define STANCE_REPOSITION 6 // Relocating to a better position while in combat. Also used when moving away from a danger like grenades. +#define STANCE_MOVE 7 // Similar to above but for out of combat. If a baddie is seen, they'll cancel and fight them. +#define STANCE_FOLLOW 8 // Following somone, without trying to murder them. +#define STANCE_FLEE 9 // Run away from the target because they're too spooky/we're dying/some other reason. +#define STANCE_DISABLED 10 // Used when the holder is afflicted with certain status effects, such as stuns or confusion. + +#define STANCES_COMBAT list(STANCE_ALERT, STANCE_APPROACH, STANCE_FIGHT, STANCE_BLINDFIGHT, STANCE_REPOSITION) #define LEFT 0x1 #define RIGHT 0x2 @@ -279,11 +296,34 @@ #define SA_ROBOTIC 3 #define SA_HUMANOID 4 +// More refined version of SA_* ""intelligence"" seperators. +// Now includes bitflags, so to target two classes you just do 'MOB_CLASS_ANIMAL|MOB_CLASS_HUMANOID' +#define MOB_CLASS_NONE 0 // Default value, and used to invert for _ALL. + +#define MOB_CLASS_PLANT 1 // Unused at the moment. +#define MOB_CLASS_ANIMAL 2 // Animals and beasts like spiders, saviks, and bears. +#define MOB_CLASS_HUMANOID 4 // Non-robotic humanoids, including /simple_mob and /carbon/humans and their alien variants. +#define MOB_CLASS_SYNTHETIC 8 // Silicons, mechanical simple mobs, FBPs, and anything else that would pass is_synthetic() +#define MOB_CLASS_SLIME 16 // Everyone's favorite xenobiology specimen (and maybe prometheans?). +#define MOB_CLASS_ABERRATION 32 // Weird shit. +#define MOB_CLASS_DEMONIC 64 // Cult stuff. +#define MOB_CLASS_BOSS 128 // Future megafauna hopefully someday. +#define MOB_CLASS_ILLUSION 256 // Fake mobs, e.g. Technomancer illusions. +#define MOB_CLASS_PHOTONIC 512 // Holographic mobs like holocarp, similar to _ILLUSION, but that make no attempt to hide their true nature. + +#define MOB_CLASS_ALL (~MOB_CLASS_NONE) + // For slime commanding. Higher numbers allow for more actions. #define SLIME_COMMAND_OBEY 1 // When disciplined. #define SLIME_COMMAND_FACTION 2 // When in the same 'faction'. #define SLIME_COMMAND_FRIEND 3 // When befriended with a slime friendship agent. +// Threshold for mobs being able to damage things like airlocks or reinforced glass windows. +// If the damage is below this, nothing will happen besides a message saying that the attack was ineffective. +// Generally, this was not a define but was commonly set to 10, however 10 may be too low now since simple_mobs now attack twice as fast, +// at half damage compared to the old mob system, meaning mobs who could hurt structures may not be able to now, so now it is 5. +#define STRUCTURE_MIN_DAMAGE_THRESHOLD 5 + //Vision flags, for dealing with plane visibility #define VIS_FULLBRIGHT 1 #define VIS_LIGHTING 2 diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 5c2d9a8ca3..391aa3a117 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -60,6 +60,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define INIT_ORDER_OVERLAY -6 #define INIT_ORDER_XENOARCH -20 #define INIT_ORDER_CIRCUIT -21 +#define INIT_ORDER_AI -22 // Subsystem fire priority, from lowest to highest priority @@ -67,6 +68,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define FIRE_PRIORITY_SHUTTLES 5 #define FIRE_PRIORITY_ORBIT 8 #define FIRE_PRIORITY_VOTE 9 +#define FIRE_PRIORITY_AI 10 #define FIRE_PRIORITY_GARBAGE 15 #define FIRE_PRIORITY_AIRFLOW 30 #define FIRE_PRIORITY_AIR 35 diff --git a/code/_global_vars/misc.dm b/code/_global_vars/misc.dm index 5e7737f163..504246e304 100644 --- a/code/_global_vars/misc.dm +++ b/code/_global_vars/misc.dm @@ -2,4 +2,4 @@ GLOBAL_LIST_EMPTY(all_observable_events) GLOBAL_VAR_INIT(timezoneOffset, 0) // The difference betwen midnight (of the host computer) and 0 world.ticks. -GLOBAL_VAR_INIT(TAB, "    ") \ No newline at end of file +GLOBAL_VAR_INIT(TAB, "    ") diff --git a/code/_global_vars/mobs.dm b/code/_global_vars/mobs.dm index fac5a14036..a5099a68d2 100644 --- a/code/_global_vars/mobs.dm +++ b/code/_global_vars/mobs.dm @@ -2,4 +2,4 @@ GLOBAL_LIST_EMPTY(admins) //all clients whom are admins GLOBAL_PROTECT(admins) GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb. GLOBAL_LIST_EMPTY(stealthminID) -GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client \ No newline at end of file +GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index ce640731a6..8348796665 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -239,4 +239,4 @@ var/round_start_time = 0 else day = "[truncate ? "day" : "1 day"]" - return "[day][hour][minute][second]" \ No newline at end of file + return "[day][hour][minute][second]" diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index 7f1ff432e8..1836fbf30f 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -501,7 +501,7 @@ Turf and target are seperate in case you want to teleport some distance from a t moblist.Add(M) for(var/mob/new_player/M in sortmob) moblist.Add(M) - for(var/mob/living/simple_animal/M in sortmob) + for(var/mob/living/simple_mob/M in sortmob) moblist.Add(M) // for(var/mob/living/silicon/hivebot/M in sortmob) // mob_list.Add(M) diff --git a/code/_macros.dm b/code/_macros.dm index 9cd11a6c15..c977617d4a 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -10,7 +10,7 @@ #define isalien(A) istype(A, /mob/living/carbon/alien) -#define isanimal(A) istype(A, /mob/living/simple_animal) +#define isanimal(A) istype(A, /mob/living/simple_mob) #define isairlock(A) istype(A, /obj/machinery/door/airlock) @@ -18,7 +18,7 @@ #define iscarbon(A) istype(A, /mob/living/carbon) -#define iscorgi(A) istype(A, /mob/living/simple_animal/corgi) +#define iscorgi(A) istype(A, /mob/living/simple_mob/animal/passive/dog/corgi) #define isEye(A) istype(A, /mob/observer/eye) @@ -26,7 +26,7 @@ #define isliving(A) istype(A, /mob/living) -#define ismouse(A) istype(A, /mob/living/simple_animal/mouse) +#define ismouse(A) istype(A, /mob/living/simple_mob/animal/passive/mouse) #define isnewplayer(A) istype(A, /mob/new_player) @@ -42,11 +42,11 @@ #define isvoice(A) istype(A, /mob/living/voice) -#define isslime(A) istype(A, /mob/living/simple_animal/slime) +#define isslime(A) istype(A, /mob/living/simple_mob/slime) #define isbot(A) istype(A, /mob/living/bot) -#define isxeno(A) istype(A, /mob/living/simple_animal/xeno) +#define isxeno(A) istype(A, /mob/living/simple_mob/animal/space/alien) #define isopenspace(A) istype(A, /turf/simulated/open) diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index 94a067200a..2a92ba20bc 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -98,7 +98,7 @@ /obj/screen/fullscreen/flash icon = 'icons/mob/screen1.dmi' screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "flash" + icon_state = "flash_static" /obj/screen/fullscreen/flash/noise icon_state = "noise" diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 8b32d1447e..fef0301c81 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -316,8 +316,6 @@ datum/hud/New(mob/owner) mymob.instantiate_hud(src) else if(isalien(mymob)) larva_hud() - else if(isslime(mymob)) - slime_hud() else if(isAI(mymob)) ai_hud() else if(isobserver(mymob)) diff --git a/code/_onclick/hud/other_mobs.dm b/code/_onclick/hud/other_mobs.dm index f1b9165a26..b0f55f5503 100644 --- a/code/_onclick/hud/other_mobs.dm +++ b/code/_onclick/hud/other_mobs.dm @@ -23,7 +23,7 @@ mymob.client.screen += list(blobpwrdisplay, blobhealthdisplay) mymob.client.screen += mymob.client.void - +/* /datum/hud/proc/slime_hud(ui_style = 'icons/mob/screen1_Midnight.dmi') src.adding = list() @@ -92,10 +92,7 @@ mymob.client.screen += mymob.client.void return - - -/mob/living/simple_animal/construct/instantiate_hud(var/datum/hud/HUD) - ..(HUD) +*/ // HUD.construct_hud() //Archaic. /* diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index d1a61ec8f4..a1f2335a9d 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -60,7 +60,7 @@ avoid code duplication. This includes items that may sometimes act as a standard // Same as above but actually does useful things. // W is the item being used in the attack, if any. modifier is if the attack should be longer or shorter than usual, for whatever reason. /mob/living/get_attack_speed(var/obj/item/W) - var/speed = DEFAULT_ATTACK_COOLDOWN + var/speed = base_attack_cooldown if(W && istype(W)) speed = W.attackspeed for(var/datum/modifier/M in modifiers) diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 729330ddaa..e367769b07 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -68,58 +68,3 @@ */ /mob/new_player/ClickOn() return - -/* - Animals -*/ -/mob/living/simple_animal/UnarmedAttack(var/atom/A, var/proximity) - if(!(. = ..())) - return - - setClickCooldown(get_attack_speed()) - - if(has_hands && istype(A,/obj) && a_intent != I_HURT) - var/obj/O = A - return O.attack_hand(src) - - switch(a_intent) - if(I_HELP) - if(isliving(A)) - custom_emote(1,"[pick(friendly)] [A]!") - - if(I_HURT) - if(prob(spattack_prob)) - if(spattack_min_range <= 1) - SpecialAtkTarget() - - - else if(melee_damage_upper == 0 && istype(A,/mob/living)) - custom_emote(1,"[pick(friendly)] [A]!") - - - else - DoPunch(A) - - if(I_GRAB) - if(has_hands) - A.attack_hand(src) - - if(I_DISARM) - if(has_hands) - A.attack_hand(src) - -/mob/living/simple_animal/RangedAttack(var/atom/A) - setClickCooldown(get_attack_speed()) - var/distance = get_dist(src, A) - - if(prob(spattack_prob) && (distance >= spattack_min_range) && (distance <= spattack_max_range)) - target_mob = A - SpecialAtkTarget() - target_mob = null - return - - if(ranged && distance <= shoot_range) - target_mob = A - ShootTarget(A) - target_mob = null - diff --git a/code/controllers/subsystems/ai.dm b/code/controllers/subsystems/ai.dm new file mode 100644 index 0000000000..8977818755 --- /dev/null +++ b/code/controllers/subsystems/ai.dm @@ -0,0 +1,36 @@ +SUBSYSTEM_DEF(ai) + name = "AI" + init_order = INIT_ORDER_AI + priority = FIRE_PRIORITY_AI + wait = 5 // This gets run twice a second, however this is technically two loops in one, with the second loop being run every four iterations. + flags = SS_NO_INIT|SS_TICKER + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/processing = list() + var/list/currentrun = list() + +/datum/controller/subsystem/ai/stat_entry(msg_prefix) + var/list/msg = list(msg_prefix) + msg += "P:[processing.len]" + ..(msg.Join()) + +/datum/controller/subsystem/ai/fire(resumed = 0) + if (!resumed) + src.currentrun = processing.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while(currentrun.len) + // var/mob/living/L = currentrun[currentrun.len] + var/datum/ai_holder/A = currentrun[currentrun.len] + --currentrun.len + if(!A || QDELETED(A)) // Doesn't exist or won't exist soon. + continue + if(times_fired % 4 == 0 && A.holder.stat != DEAD) + A.handle_strategicals() + if(A.holder.stat != DEAD) // The /TG/ version checks stat twice, presumably in-case processing somehow got the mob killed in that instant. + A.handle_tactics() + + if(MC_TICK_CHECK) + return diff --git a/code/controllers/subsystems/vote.dm b/code/controllers/subsystems/vote.dm index d42cbe7f63..480e0da813 100644 --- a/code/controllers/subsystems/vote.dm +++ b/code/controllers/subsystems/vote.dm @@ -1,373 +1,373 @@ -SUBSYSTEM_DEF(vote) - name = "Vote" - wait = 10 - priority = FIRE_PRIORITY_VOTE - runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT - flags = SS_KEEP_TIMING | SS_NO_INIT - var/list/round_voters = list() - - //Current vote - var/initiator - var/started_time - var/time_remaining - var/duration - var/mode - var/question - var/list/choices = list() - var/list/gamemode_names = list() - var/list/voted = list() - var/list/current_votes = list() - var/list/additional_text = list() - -/datum/controller/subsystem/vote/fire(resumed) - if(mode) - time_remaining = round((started_time + duration - world.time)/10) - if(mode == VOTE_GAMEMODE && ticker.current_state >= GAME_STATE_SETTING_UP) - to_chat(world, "Gamemode vote aborted: Game has already started.") - reset() - return - if(time_remaining <= 0) - result() - reset() - -/datum/controller/subsystem/vote/proc/autotransfer() - initiate_vote(VOTE_CREW_TRANSFER, "the server", 1) - log_debug("The server has called a crew transfer vote.") - -/datum/controller/subsystem/vote/proc/autogamemode() - initiate_vote(VOTE_GAMEMODE, "the server", 1) - log_debug("The server has called a gamemode vote.") - -/datum/controller/subsystem/vote/proc/reset() - initiator = null - started_time = null - duration = null - time_remaining = null - mode = null - question = null - choices.Cut() - voted.Cut() - current_votes.Cut() - additional_text.Cut() - -/datum/controller/subsystem/vote/proc/get_result() // Get the highest number of votes - var/greatest_votes = 0 - var/total_votes = 0 - - for(var/option in choices) - var/votes = choices[option] - total_votes += votes - if(votes > greatest_votes) - greatest_votes = votes - - if(!config.vote_no_default && choices.len) // Default-vote for everyone who didn't vote - var/non_voters = (clients.len - total_votes) - if(non_voters > 0) - if(mode == VOTE_RESTART) - choices["Continue Playing"] += non_voters - if(choices["Continue Playing"] >= greatest_votes) - greatest_votes = choices["Continue Playing"] - else if(mode == VOTE_GAMEMODE) - if(master_mode in choices) - choices[master_mode] += non_voters - if(choices[master_mode] >= greatest_votes) - greatest_votes = choices[master_mode] - else if(mode == VOTE_CREW_TRANSFER) - var/factor = 0.5 - switch(world.time / (10 * 60)) // minutes - if(0 to 60) - factor = 0.5 - if(61 to 120) - factor = 0.8 - if(121 to 240) - factor = 1 - if(241 to 300) - factor = 1.2 - else - factor = 1.4 - choices["Initiate Crew Transfer"] = round(choices["Initiate Crew Transfer"] * factor) - world << "Crew Transfer Factor: [factor]" - greatest_votes = max(choices["Initiate Crew Transfer"], choices["Continue The Round"]) - - . = list() // Get all options with that many votes and return them in a list - if(greatest_votes) - for(var/option in choices) - if(choices[option] == greatest_votes) - . += option - -/datum/controller/subsystem/vote/proc/announce_result() - var/list/winners = get_result() - var/text - if(winners.len > 0) - if(winners.len > 1) - if(mode != VOTE_GAMEMODE || ticker.hide_mode == 0) // Here we are making sure we don't announce potential game modes - text = "Vote Tied Between:\n" - for(var/option in winners) - text += "\t[option]\n" - . = pick(winners) - - for(var/key in current_votes) - if(choices[current_votes[key]] == .) - round_voters += key // Keep track of who voted for the winning round. - if(mode != VOTE_GAMEMODE || . == "Extended" || ticker.hide_mode == 0) // Announce Extended gamemode, but not other gamemodes - text += "Vote Result: [mode == VOTE_GAMEMODE ? gamemode_names[.] : .]" - else - text += "The vote has ended." - - else - text += "Vote Result: Inconclusive - No Votes!" - if(mode == VOTE_ADD_ANTAGONIST) - antag_add_failed = 1 - log_vote(text) - to_chat(world, "[text]") - -/datum/controller/subsystem/vote/proc/result() - . = announce_result() - var/restart = 0 - if(.) - switch(mode) - if(VOTE_RESTART) - if(. == "Restart Round") - restart = 1 - if(VOTE_GAMEMODE) - if(master_mode != .) - world.save_mode(.) - if(ticker && ticker.mode) - restart = 1 - else - master_mode = . - if(VOTE_CREW_TRANSFER) - if(. == "Initiate Crew Transfer") - init_shift_change(null, 1) - if(VOTE_ADD_ANTAGONIST) - if(isnull(.) || . == "None") - antag_add_failed = 1 - else - additional_antag_types |= antag_names_to_ids[.] - - if(mode == VOTE_GAMEMODE) //fire this even if the vote fails. - if(!round_progressing) - round_progressing = 1 - world << "The round will start soon." - - if(restart) - world << "World restarting due to vote..." - feedback_set_details("end_error", "restart vote") - if(blackbox) - blackbox.save_all_data_to_sql() - sleep(50) - log_game("Rebooting due to restart vote") - world.Reboot() - -/datum/controller/subsystem/vote/proc/submit_vote(ckey, newVote) - if(mode) - if(config.vote_no_dead && usr.stat == DEAD && !usr.client.holder) - return - if(current_votes[ckey]) - choices[choices[current_votes[ckey]]]-- - if(newVote && newVote >= 1 && newVote <= choices.len) - choices[choices[newVote]]++ - current_votes[ckey] = newVote - else - current_votes[ckey] = null - -/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, automatic = FALSE, time = config.vote_period) - if(!mode) - if(started_time != null && !(check_rights(R_ADMIN) || automatic)) - var/next_allowed_time = (started_time + config.vote_delay) - if(next_allowed_time > world.time) - return 0 - - reset() - - switch(vote_type) - if(VOTE_RESTART) - choices.Add("Restart Round", "Continue Playing") - if(VOTE_GAMEMODE) - if(ticker.current_state >= GAME_STATE_SETTING_UP) - return 0 - choices.Add(config.votable_modes) - for(var/F in choices) - var/datum/game_mode/M = gamemode_cache[F] - if(!M) - continue - gamemode_names[M.config_tag] = capitalize(M.name) //It's ugly to put this here but it works - additional_text.Add("[M.required_players]") - gamemode_names["secret"] = "Secret" - if(VOTE_CREW_TRANSFER) - if(!check_rights(R_ADMIN|R_MOD, 0)) // The gods care not for the affairs of the mortals - if(get_security_level() == "red" || get_security_level() == "delta") - initiator_key << "The current alert status is too high to call for a crew transfer!" - return 0 - if(ticker.current_state <= GAME_STATE_SETTING_UP) - initiator_key << "The crew transfer button has been disabled!" - return 0 - question = "End the shift?" - choices.Add("Initiate Crew Transfer", "Continue The Round") - if(VOTE_ADD_ANTAGONIST) - if(!config.allow_extra_antags || ticker.current_state >= GAME_STATE_SETTING_UP) - return 0 - for(var/antag_type in all_antag_types) - var/datum/antagonist/antag = all_antag_types[antag_type] - if(!(antag.id in additional_antag_types) && antag.is_votable()) - choices.Add(antag.role_text) - choices.Add("None") - if(VOTE_CUSTOM) - question = sanitizeSafe(input(usr, "What is the vote for?") as text|null) - if(!question) - return 0 - for(var/i = 1 to 10) - var/option = capitalize(sanitize(input(usr, "Please enter an option or hit cancel to finish") as text|null)) - if(!option || mode || !usr.client) - break - choices.Add(option) - else - return 0 - - mode = vote_type - initiator = initiator_key - started_time = world.time - duration = time - var/text = "[capitalize(mode)] vote started by [initiator]." - if(mode == VOTE_CUSTOM) - text += "\n[question]" - - log_vote(text) - - world << "[text]\nType vote or click here to place your votes.\nYou have [config.vote_period / 10] seconds to vote." - if(vote_type == VOTE_CREW_TRANSFER || vote_type == VOTE_GAMEMODE || vote_type == VOTE_CUSTOM) - world << sound('sound/ambience/alarm4.ogg', repeat = 0, wait = 0, volume = 50, channel = 3) - - if(mode == VOTE_GAMEMODE && round_progressing) - round_progressing = 0 - world << "Round start has been delayed." - - time_remaining = round(config.vote_period / 10) - return 1 - return 0 - -/datum/controller/subsystem/vote/proc/interface(var/client/C) - if(!istype(C)) - return - var/admin = FALSE - if(C.holder) - if(C.holder.rights & R_ADMIN) - admin = TRUE - - . = "Voting Panel" - if(mode) - if(question) - . += "

Vote: '[question]'

" - else - . += "

Vote: [capitalize(mode)]

" - . += "Time Left: [time_remaining] s
" - . += "" - if(mode == VOTE_GAMEMODE) - .+= "" - - for(var/i = 1 to choices.len) - var/votes = choices[choices[i]] - if(!votes) - votes = 0 - . += "" - var/thisVote = (current_votes[C.ckey] == i) - if(mode == VOTE_GAMEMODE) - . += "" - else - . += "" - if (additional_text.len >= i) - . += additional_text[i] - . += "" - - . += "" - - . += "
ChoicesVotesMinimum Players
[thisVote ? "" : ""][gamemode_names[choices[i]]][thisVote ? "" : ""][votes][thisVote ? "" : ""][choices[i]][thisVote ? "" : ""][votes]
Unvote

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

Start a vote:



" - - . += "Close" - -/datum/controller/subsystem/vote/Topic(href, href_list[]) - if(!usr || !usr.client) - return - switch(href_list["vote"]) - if("close") - usr << browse(null, "window=vote") - return - - if("cancel") - if(usr.client.holder) - reset() - if("toggle_restart") - if(usr.client.holder) - config.allow_vote_restart = !config.allow_vote_restart - if("toggle_gamemode") - if(usr.client.holder) - config.allow_vote_mode = !config.allow_vote_mode - - if(VOTE_RESTART) - if(config.allow_vote_restart || usr.client.holder) - initiate_vote(VOTE_RESTART, usr.key) - if(VOTE_GAMEMODE) - if(config.allow_vote_mode || usr.client.holder) - initiate_vote(VOTE_GAMEMODE, usr.key) - if(VOTE_CREW_TRANSFER) - if(config.allow_vote_restart || usr.client.holder) - initiate_vote(VOTE_CREW_TRANSFER, usr.key) - if(VOTE_ADD_ANTAGONIST) - if(config.allow_extra_antags || usr.client.holder) - initiate_vote(VOTE_ADD_ANTAGONIST, usr.key) - if(VOTE_CUSTOM) - if(usr.client.holder) - initiate_vote(VOTE_CUSTOM, usr.key) - - if("unvote") - submit_vote(usr.ckey, null) - - else - var/t = round(text2num(href_list["vote"])) - if(t) // It starts from 1, so there's no problem - submit_vote(usr.ckey, t) - usr.client.vote() - -/client/verb/vote() - set category = "OOC" - set name = "Vote" - - if(SSvote) - src << browse(SSvote.interface(src), "window=vote;size=500x[300 + SSvote.choices.len * 25]") +SUBSYSTEM_DEF(vote) + name = "Vote" + wait = 10 + priority = FIRE_PRIORITY_VOTE + runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + flags = SS_KEEP_TIMING | SS_NO_INIT + var/list/round_voters = list() + + //Current vote + var/initiator + var/started_time + var/time_remaining + var/duration + var/mode + var/question + var/list/choices = list() + var/list/gamemode_names = list() + var/list/voted = list() + var/list/current_votes = list() + var/list/additional_text = list() + +/datum/controller/subsystem/vote/fire(resumed) + if(mode) + time_remaining = round((started_time + duration - world.time)/10) + if(mode == VOTE_GAMEMODE && ticker.current_state >= GAME_STATE_SETTING_UP) + to_chat(world, "Gamemode vote aborted: Game has already started.") + reset() + return + if(time_remaining <= 0) + result() + reset() + +/datum/controller/subsystem/vote/proc/autotransfer() + initiate_vote(VOTE_CREW_TRANSFER, "the server", 1) + log_debug("The server has called a crew transfer vote.") + +/datum/controller/subsystem/vote/proc/autogamemode() + initiate_vote(VOTE_GAMEMODE, "the server", 1) + log_debug("The server has called a gamemode vote.") + +/datum/controller/subsystem/vote/proc/reset() + initiator = null + started_time = null + duration = null + time_remaining = null + mode = null + question = null + choices.Cut() + voted.Cut() + current_votes.Cut() + additional_text.Cut() + +/datum/controller/subsystem/vote/proc/get_result() // Get the highest number of votes + var/greatest_votes = 0 + var/total_votes = 0 + + for(var/option in choices) + var/votes = choices[option] + total_votes += votes + if(votes > greatest_votes) + greatest_votes = votes + + if(!config.vote_no_default && choices.len) // Default-vote for everyone who didn't vote + var/non_voters = (clients.len - total_votes) + if(non_voters > 0) + if(mode == VOTE_RESTART) + choices["Continue Playing"] += non_voters + if(choices["Continue Playing"] >= greatest_votes) + greatest_votes = choices["Continue Playing"] + else if(mode == VOTE_GAMEMODE) + if(master_mode in choices) + choices[master_mode] += non_voters + if(choices[master_mode] >= greatest_votes) + greatest_votes = choices[master_mode] + else if(mode == VOTE_CREW_TRANSFER) + var/factor = 0.5 + switch(world.time / (10 * 60)) // minutes + if(0 to 60) + factor = 0.5 + if(61 to 120) + factor = 0.8 + if(121 to 240) + factor = 1 + if(241 to 300) + factor = 1.2 + else + factor = 1.4 + choices["Initiate Crew Transfer"] = round(choices["Initiate Crew Transfer"] * factor) + world << "Crew Transfer Factor: [factor]" + greatest_votes = max(choices["Initiate Crew Transfer"], choices["Continue The Round"]) + + . = list() // Get all options with that many votes and return them in a list + if(greatest_votes) + for(var/option in choices) + if(choices[option] == greatest_votes) + . += option + +/datum/controller/subsystem/vote/proc/announce_result() + var/list/winners = get_result() + var/text + if(winners.len > 0) + if(winners.len > 1) + if(mode != VOTE_GAMEMODE || ticker.hide_mode == 0) // Here we are making sure we don't announce potential game modes + text = "Vote Tied Between:\n" + for(var/option in winners) + text += "\t[option]\n" + . = pick(winners) + + for(var/key in current_votes) + if(choices[current_votes[key]] == .) + round_voters += key // Keep track of who voted for the winning round. + if(mode != VOTE_GAMEMODE || . == "Extended" || ticker.hide_mode == 0) // Announce Extended gamemode, but not other gamemodes + text += "Vote Result: [mode == VOTE_GAMEMODE ? gamemode_names[.] : .]" + else + text += "The vote has ended." + + else + text += "Vote Result: Inconclusive - No Votes!" + if(mode == VOTE_ADD_ANTAGONIST) + antag_add_failed = 1 + log_vote(text) + to_chat(world, "[text]") + +/datum/controller/subsystem/vote/proc/result() + . = announce_result() + var/restart = 0 + if(.) + switch(mode) + if(VOTE_RESTART) + if(. == "Restart Round") + restart = 1 + if(VOTE_GAMEMODE) + if(master_mode != .) + world.save_mode(.) + if(ticker && ticker.mode) + restart = 1 + else + master_mode = . + if(VOTE_CREW_TRANSFER) + if(. == "Initiate Crew Transfer") + init_shift_change(null, 1) + if(VOTE_ADD_ANTAGONIST) + if(isnull(.) || . == "None") + antag_add_failed = 1 + else + additional_antag_types |= antag_names_to_ids[.] + + if(mode == VOTE_GAMEMODE) //fire this even if the vote fails. + if(!round_progressing) + round_progressing = 1 + world << "The round will start soon." + + if(restart) + world << "World restarting due to vote..." + feedback_set_details("end_error", "restart vote") + if(blackbox) + blackbox.save_all_data_to_sql() + sleep(50) + log_game("Rebooting due to restart vote") + world.Reboot() + +/datum/controller/subsystem/vote/proc/submit_vote(ckey, newVote) + if(mode) + if(config.vote_no_dead && usr.stat == DEAD && !usr.client.holder) + return + if(current_votes[ckey]) + choices[choices[current_votes[ckey]]]-- + if(newVote && newVote >= 1 && newVote <= choices.len) + choices[choices[newVote]]++ + current_votes[ckey] = newVote + else + current_votes[ckey] = null + +/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, automatic = FALSE, time = config.vote_period) + if(!mode) + if(started_time != null && !(check_rights(R_ADMIN) || automatic)) + var/next_allowed_time = (started_time + config.vote_delay) + if(next_allowed_time > world.time) + return 0 + + reset() + + switch(vote_type) + if(VOTE_RESTART) + choices.Add("Restart Round", "Continue Playing") + if(VOTE_GAMEMODE) + if(ticker.current_state >= GAME_STATE_SETTING_UP) + return 0 + choices.Add(config.votable_modes) + for(var/F in choices) + var/datum/game_mode/M = gamemode_cache[F] + if(!M) + continue + gamemode_names[M.config_tag] = capitalize(M.name) //It's ugly to put this here but it works + additional_text.Add("[M.required_players]") + gamemode_names["secret"] = "Secret" + if(VOTE_CREW_TRANSFER) + if(!check_rights(R_ADMIN|R_MOD, 0)) // The gods care not for the affairs of the mortals + if(get_security_level() == "red" || get_security_level() == "delta") + initiator_key << "The current alert status is too high to call for a crew transfer!" + return 0 + if(ticker.current_state <= GAME_STATE_SETTING_UP) + initiator_key << "The crew transfer button has been disabled!" + return 0 + question = "End the shift?" + choices.Add("Initiate Crew Transfer", "Continue The Round") + if(VOTE_ADD_ANTAGONIST) + if(!config.allow_extra_antags || ticker.current_state >= GAME_STATE_SETTING_UP) + return 0 + for(var/antag_type in all_antag_types) + var/datum/antagonist/antag = all_antag_types[antag_type] + if(!(antag.id in additional_antag_types) && antag.is_votable()) + choices.Add(antag.role_text) + choices.Add("None") + if(VOTE_CUSTOM) + question = sanitizeSafe(input(usr, "What is the vote for?") as text|null) + if(!question) + return 0 + for(var/i = 1 to 10) + var/option = capitalize(sanitize(input(usr, "Please enter an option or hit cancel to finish") as text|null)) + if(!option || mode || !usr.client) + break + choices.Add(option) + else + return 0 + + mode = vote_type + initiator = initiator_key + started_time = world.time + duration = time + var/text = "[capitalize(mode)] vote started by [initiator]." + if(mode == VOTE_CUSTOM) + text += "\n[question]" + + log_vote(text) + + world << "[text]\nType vote or click here to place your votes.\nYou have [config.vote_period / 10] seconds to vote." + if(vote_type == VOTE_CREW_TRANSFER || vote_type == VOTE_GAMEMODE || vote_type == VOTE_CUSTOM) + world << sound('sound/ambience/alarm4.ogg', repeat = 0, wait = 0, volume = 50, channel = 3) + + if(mode == VOTE_GAMEMODE && round_progressing) + round_progressing = 0 + world << "Round start has been delayed." + + time_remaining = round(config.vote_period / 10) + return 1 + return 0 + +/datum/controller/subsystem/vote/proc/interface(var/client/C) + if(!istype(C)) + return + var/admin = FALSE + if(C.holder) + if(C.holder.rights & R_ADMIN) + admin = TRUE + + . = "Voting Panel" + if(mode) + if(question) + . += "

Vote: '[question]'

" + else + . += "

Vote: [capitalize(mode)]

" + . += "Time Left: [time_remaining] s
" + . += "" + if(mode == VOTE_GAMEMODE) + .+= "" + + for(var/i = 1 to choices.len) + var/votes = choices[choices[i]] + if(!votes) + votes = 0 + . += "" + var/thisVote = (current_votes[C.ckey] == i) + if(mode == VOTE_GAMEMODE) + . += "" + else + . += "" + if (additional_text.len >= i) + . += additional_text[i] + . += "" + + . += "" + + . += "
ChoicesVotesMinimum Players
[thisVote ? "" : ""][gamemode_names[choices[i]]][thisVote ? "" : ""][votes][thisVote ? "" : ""][choices[i]][thisVote ? "" : ""][votes]
Unvote

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

Start a vote:



" + + . += "Close" + +/datum/controller/subsystem/vote/Topic(href, href_list[]) + if(!usr || !usr.client) + return + switch(href_list["vote"]) + if("close") + usr << browse(null, "window=vote") + return + + if("cancel") + if(usr.client.holder) + reset() + if("toggle_restart") + if(usr.client.holder) + config.allow_vote_restart = !config.allow_vote_restart + if("toggle_gamemode") + if(usr.client.holder) + config.allow_vote_mode = !config.allow_vote_mode + + if(VOTE_RESTART) + if(config.allow_vote_restart || usr.client.holder) + initiate_vote(VOTE_RESTART, usr.key) + if(VOTE_GAMEMODE) + if(config.allow_vote_mode || usr.client.holder) + initiate_vote(VOTE_GAMEMODE, usr.key) + if(VOTE_CREW_TRANSFER) + if(config.allow_vote_restart || usr.client.holder) + initiate_vote(VOTE_CREW_TRANSFER, usr.key) + if(VOTE_ADD_ANTAGONIST) + if(config.allow_extra_antags || usr.client.holder) + initiate_vote(VOTE_ADD_ANTAGONIST, usr.key) + if(VOTE_CUSTOM) + if(usr.client.holder) + initiate_vote(VOTE_CUSTOM, usr.key) + + if("unvote") + submit_vote(usr.ckey, null) + + else + var/t = round(text2num(href_list["vote"])) + if(t) // It starts from 1, so there's no problem + submit_vote(usr.ckey, t) + usr.client.vote() + +/client/verb/vote() + set category = "OOC" + set name = "Vote" + + if(SSvote) + src << browse(SSvote.interface(src), "window=vote;size=500x[300 + SSvote.choices.len * 25]") diff --git a/code/datums/ghost_query.dm b/code/datums/ghost_query.dm index d041558a0c..2f7942a9b2 100644 --- a/code/datums/ghost_query.dm +++ b/code/datums/ghost_query.dm @@ -106,6 +106,13 @@ check_bans = list("AI", "Cyborg", "Syndicate") cutoff_number = 1 +/datum/ghost_query/borer + role_name = "Cortical Borer" + question = "A cortical borer has just been created on the facility. Would you like to play as them?" + be_special_flag = BE_ALIEN + check_bans = list("Syndicate", "Borer") + cutoff_number = 1 + // Surface stuff. /datum/ghost_query/lost_drone role_name = "Lost Drone" diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 5204964bda..fdca613460 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -502,7 +502,7 @@ if(!mind.assigned_role) mind.assigned_role = USELESS_JOB //defualt //VOREStation Edit - Visitor not Assistant //slime -/mob/living/simple_animal/slime/mind_initialize() +/mob/living/simple_mob/slime/mind_initialize() . = ..() mind.assigned_role = "slime" @@ -527,29 +527,30 @@ mind.special_role = "" //Animals -/mob/living/simple_animal/mind_initialize() +/mob/living/simple_mob/mind_initialize() . = ..() - mind.assigned_role = "Animal" + mind.assigned_role = "Simple Mob" -/mob/living/simple_animal/corgi/mind_initialize() +/mob/living/simple_mob/animal/passive/dog/corgi/mind_initialize() . = ..() mind.assigned_role = "Corgi" -/mob/living/simple_animal/shade/mind_initialize() +/mob/living/simple_mob/construct/shade/mind_initialize() . = ..() mind.assigned_role = "Shade" + mind.special_role = "Cultist" -/mob/living/simple_animal/construct/builder/mind_initialize() +/mob/living/simple_mob/construct/artificer/mind_initialize() . = ..() mind.assigned_role = "Artificer" mind.special_role = "Cultist" -/mob/living/simple_animal/construct/wraith/mind_initialize() +/mob/living/simple_mob/construct/wraith/mind_initialize() . = ..() mind.assigned_role = "Wraith" mind.special_role = "Cultist" -/mob/living/simple_animal/construct/armoured/mind_initialize() +/mob/living/simple_mob/construct/juggernaut/mind_initialize() . = ..() mind.assigned_role = "Juggernaut" mind.special_role = "Cultist" diff --git a/code/game/antagonist/alien/borer.dm b/code/game/antagonist/alien/borer.dm index e852bc8418..34b79271f7 100644 --- a/code/game/antagonist/alien/borer.dm +++ b/code/game/antagonist/alien/borer.dm @@ -5,7 +5,7 @@ var/datum/antagonist/borer/borers role_type = BE_ALIEN role_text = "Cortical Borer" role_text_plural = "Cortical Borers" - mob_path = /mob/living/simple_animal/borer + mob_path = /mob/living/simple_mob/animal/borer bantype = "Borer" welcome_text = "Use your Infest power to crawl into the ear of a host and fuse with their brain. You can only take control temporarily, and at risk of hurting your host, so be clever and careful; your host is encouraged to help you however they can. Talk to your fellow borers with :x." antag_indicator = "brainworm" @@ -40,7 +40,7 @@ var/datum/antagonist/borer/borers player.objectives += new /datum/objective/escape() /datum/antagonist/borer/place_mob(var/mob/living/mob) - var/mob/living/simple_animal/borer/borer = mob + var/mob/living/simple_mob/animal/borer/borer = mob if(istype(borer)) var/mob/living/carbon/human/host for(var/mob/living/carbon/human/H in mob_list) diff --git a/code/game/antagonist/station/cultist.dm b/code/game/antagonist/station/cultist.dm index e40085f90a..e5c7d8f652 100644 --- a/code/game/antagonist/station/cultist.dm +++ b/code/game/antagonist/station/cultist.dm @@ -111,12 +111,12 @@ var/datum/antagonist/cultist/cult . = ..() if(.) player << "You catch a glimpse of the Realm of Nar-Sie, the Geometer of Blood. You now see how flimsy the world is, you see that it should be open to the knowledge of That Which Waits. Assist your new compatriots in their dark dealings. Their goals are yours, and yours are theirs. You serve the Dark One above all else. Bring It back." - if(player.current && !istype(player.current, /mob/living/simple_animal/construct)) + if(player.current && !istype(player.current, /mob/living/simple_mob/construct)) player.current.add_language(LANGUAGE_CULT) /datum/antagonist/cultist/remove_antagonist(var/datum/mind/player, var/show_message, var/implanted) . = ..() - if(. && player.current && !istype(player.current, /mob/living/simple_animal/construct)) + if(. && player.current && !istype(player.current, /mob/living/simple_mob/construct)) player.current.remove_language(LANGUAGE_CULT) /datum/antagonist/cultist/can_become_antag(var/datum/mind/player) diff --git a/code/game/gamemodes/cult/construct_spells.dm b/code/game/gamemodes/cult/construct_spells.dm index f8ff57fc35..60a05e2e27 100644 --- a/code/game/gamemodes/cult/construct_spells.dm +++ b/code/game/gamemodes/cult/construct_spells.dm @@ -476,14 +476,14 @@ proc/findNullRod(var/atom/target) /obj/item/weapon/spell/construct/run_checks() if(owner) - if((iscultist(owner) || istype(owner, /mob/living/simple_animal/construct)) && (world.time >= (last_castcheck + cooldown))) //Are they a cultist or a construct, and has the cooldown time passed? + if((iscultist(owner) || istype(owner, /mob/living/simple_mob/construct)) && (world.time >= (last_castcheck + cooldown))) //Are they a cultist or a construct, and has the cooldown time passed? last_castcheck = world.time return 1 return 0 /obj/item/weapon/spell/construct/pay_energy(var/amount) if(owner) - if(istype(owner, /mob/living/simple_animal/construct)) + if(istype(owner, /mob/living/simple_mob/construct)) return 1 if(iscultist(owner) && pay_blood(amount)) return 1 @@ -607,7 +607,7 @@ proc/findNullRod(var/atom/target) name = "sphere of agony" desc = "Call forth a portal to a dimension of naught but pain at your target." - spawner_type = /obj/effect/temporary_effect/pulsar/agonizing_sphere + spawner_type = /obj/effect/temporary_effect/pulse/agonizing_sphere /obj/item/weapon/spell/construct/spawner/agonizing_sphere/on_ranged_cast(atom/hit_atom, mob/user) if(within_range(hit_atom) && pay_energy(10)) @@ -619,7 +619,7 @@ proc/findNullRod(var/atom/target) var/mob/living/L = hit_atom L.add_modifier(/datum/modifier/agonize, 10 SECONDS) -/obj/effect/temporary_effect/pulsar/agonizing_sphere +/obj/effect/temporary_effect/pulse/agonizing_sphere name = "agonizing sphere" desc = "A portal to some hellish place. Its screams wrack your body with pain.." icon_state = "red_static_sphere" @@ -628,19 +628,15 @@ proc/findNullRod(var/atom/target) light_power = 5 light_color = "#FF0000" pulses_remaining = 10 + pulse_delay = 1 SECOND -/obj/effect/temporary_effect/pulsar/agonizing_sphere/pulse_loop() - while(pulses_remaining) - sleep(1 SECONDS) - spawn() - for(var/mob/living/L in view(4,src)) - if(!iscultist(L) && !istype(L, /mob/living/simple_animal/construct)) - L.add_modifier(/datum/modifier/agonize, 2 SECONDS) - if(L.isSynthetic()) - to_chat(L, "Your chassis warps as the [src] pulses!") - L.adjustFireLoss(4) - pulses_remaining-- - qdel(src) +/obj/effect/temporary_effect/pulse/agonizing_sphere/on_pulse() + for(var/mob/living/L in view(4,src)) + if(!iscultist(L) && !istype(L, /mob/living/simple_mob/construct)) + L.add_modifier(/datum/modifier/agonize, 2 SECONDS) + if(L.isSynthetic()) + to_chat(L, "Your chassis warps as the [src] pulses!") + L.adjustFireLoss(4) //Artificer Heal @@ -659,7 +655,7 @@ proc/findNullRod(var/atom/target) L.add_modifier(/datum/modifier/mend_occult, 150) qdel(src) -//Juggernaut + Behemoth Slam +//Juggernaut Slam /obj/item/weapon/spell/construct/slam name = "slam" desc = "Empower your FIST, to send an opponent flying." @@ -672,8 +668,8 @@ proc/findNullRod(var/atom/target) /obj/item/weapon/spell/construct/slam/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) var/attack_message = "slams" - if(istype(user, /mob/living/simple_animal)) - var/mob/living/simple_animal/S = user + if(istype(user, /mob/living/simple_mob)) + var/mob/living/simple_mob/S = user attack_message = pick(S.attacktext) if(isliving(hit_atom)) var/mob/living/L = hit_atom diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm index 082404b271..7118aafe80 100644 --- a/code/game/gamemodes/cult/cult_items.dm +++ b/code/game/gamemodes/cult/cult_items.dm @@ -14,7 +14,7 @@ return /obj/item/weapon/melee/cultblade/attack(mob/living/M, mob/living/user, var/target_zone) - if(iscultist(user) && !istype(user, /mob/living/simple_animal/construct)) + if(iscultist(user) && !istype(user, /mob/living/simple_mob/construct)) return ..() var/zone = (user.hand ? "l_arm":"r_arm") @@ -25,7 +25,7 @@ //random amount of damage between half of the blade's force and the full force of the blade. user.apply_damage(rand(force/2, force), BRUTE, zone, 0, sharp=1, edge=1) user.Weaken(5) - else if(!istype(user, /mob/living/simple_animal/construct)) + else if(!istype(user, /mob/living/simple_mob/construct)) to_chat(user, "An inexplicable force rips through you, tearing the sword from your grasp!") else to_chat(user, "The blade hisses, forcing itself from your manipulators. \The [src] will only allow mortals to wield it against foes, not kin.") @@ -39,10 +39,10 @@ return 1 /obj/item/weapon/melee/cultblade/pickup(mob/living/user as mob) - if(!iscultist(user) && !istype(user, /mob/living/simple_animal/construct)) + if(!iscultist(user) && !istype(user, /mob/living/simple_mob/construct)) to_chat(user, "An overwhelming feeling of dread comes over you as you pick up the cultist's sword. It would be wise to be rid of this blade quickly.") user.make_dizzy(120) - if(istype(user, /mob/living/simple_animal/construct)) + if(istype(user, /mob/living/simple_mob/construct)) to_chat(user, "\The [src] hisses, as it is discontent with your acquisition of it. It would be wise to return it to a worthy mortal quickly.") /obj/item/clothing/head/culthood diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm index d588299cc5..e337ca8c60 100644 --- a/code/game/gamemodes/cult/cult_structures.dm +++ b/code/game/gamemodes/cult/cult_structures.dm @@ -108,18 +108,25 @@ light_range=5 light_color="#ff0000" spawnable=list( +<<<<<<< HEAD /mob/living/simple_animal/hostile/scarybat, /mob/living/simple_animal/hostile/creature/vore, /mob/living/simple_animal/hostile/faithless ) // Vorestation Edit +======= + /mob/living/simple_mob/animal/space/bats, + /mob/living/simple_mob/creature, + /mob/living/simple_mob/faithless + ) +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync /obj/effect/gateway/active/cult light_range=5 light_color="#ff0000" spawnable=list( - /mob/living/simple_animal/hostile/scarybat/cult, - /mob/living/simple_animal/hostile/creature/cult, - /mob/living/simple_animal/hostile/faithless/cult + /mob/living/simple_mob/animal/space/bats/cult, + /mob/living/simple_mob/creature/cult, + /mob/living/simple_mob/faithless/cult ) /obj/effect/gateway/active/cult/cultify() diff --git a/code/game/gamemodes/cult/cultify/mob.dm b/code/game/gamemodes/cult/cultify/mob.dm index 14bb380a19..d78956c40a 100644 --- a/code/game/gamemodes/cult/cultify/mob.dm +++ b/code/game/gamemodes/cult/cultify/mob.dm @@ -16,7 +16,7 @@ /mob/living/cultify() if(iscultist(src) && client) - var/mob/living/simple_animal/construct/harvester/C = new(get_turf(src)) + var/mob/living/simple_mob/construct/harvester/C = new(get_turf(src)) mind.transfer_to(C) C << "The Geometer of Blood is overjoyed to be reunited with its followers, and accepts your body in sacrifice. As reward, you have been gifted with the shell of an Harvester.
Your tendrils can use and draw runes without need for a tome, your eyes can see beings through walls, and your mind can open any door. Use these assets to serve Nar-Sie and bring him any remaining living human in the world.
You can teleport yourself back to Nar-Sie along with any being under yourself at any time using your \"Harvest\" spell.
" dust() diff --git a/code/modules/mob/living/simple_animal/constructs/soulstone.dm b/code/game/gamemodes/cult/soulstone.dm similarity index 88% rename from code/modules/mob/living/simple_animal/constructs/soulstone.dm rename to code/game/gamemodes/cult/soulstone.dm index 6f13789dfc..61db88917a 100644 --- a/code/modules/mob/living/simple_animal/constructs/soulstone.dm +++ b/code/game/gamemodes/cult/soulstone.dm @@ -1,242 +1,246 @@ -/obj/item/device/soulstone/cultify() - return - -/obj/item/device/soulstone - name = "Soul Stone Shard" - icon = 'icons/obj/wizard.dmi' - icon_state = "soulstone" - item_state = "electronic" - desc = "A fragment of the legendary treasure known simply as the 'Soul Stone'. The shard still flickers with a fraction of the full artefacts power." - w_class = ITEMSIZE_SMALL - slot_flags = SLOT_BELT - origin_tech = list(TECH_BLUESPACE = 4, TECH_MATERIAL = 4) - var/imprinted = "empty" - var/possible_constructs = list("Juggernaut","Wraith","Artificer","Harvester") - -//////////////////////////////Capturing//////////////////////////////////////////////////////// - -/obj/item/device/soulstone/attack(mob/living/carbon/human/M as mob, mob/user as mob) - if(!istype(M, /mob/living/carbon/human))//If target is not a human. - return ..() - if(istype(M, /mob/living/carbon/human/dummy)) - return..() - if(jobban_isbanned(M, "cultist")) - user << "This person's soul is too corrupt and cannot be captured!" - return..() - - if(M.has_brain_worms()) //Borer stuff - RR - user << "This being is corrupted by an alien intelligence and cannot be soul trapped." - return..() - - add_attack_logs(user,M,"Soulstone'd with [src.name]") - transfer_soul("VICTIM", M, user) - return - - -///////////////////Options for using captured souls/////////////////////////////////////// - -/obj/item/device/soulstone/attack_self(mob/user) - if (!in_range(src, user)) - return - user.set_machine(src) - var/dat = "Soul Stone
" - for(var/mob/living/simple_animal/shade/A in src) - dat += "Captured Soul: [A.name]
" - dat += {"Summon Shade"} - dat += "
" - dat += {" Close"} - user << browse(dat, "window=aicard") - onclose(user, "aicard") - return - - - - -/obj/item/device/soulstone/Topic(href, href_list) - var/mob/U = usr - if (!in_range(src, U)||U.machine!=src) - U << browse(null, "window=aicard") - U.unset_machine() - return - - add_fingerprint(U) - U.set_machine(src) - - switch(href_list["choice"])//Now we switch based on choice. - if ("Close") - U << browse(null, "window=aicard") - U.unset_machine() - return - - if ("Summon") - for(var/mob/living/simple_animal/shade/A in src) - A.status_flags &= ~GODMODE - A.canmove = 1 - A << "You have been released from your prison, but you are still bound to [U.name]'s will. Help them suceed in their goals at all costs." - A.forceMove(U.loc) - A.cancel_camera() - src.icon_state = "soulstone" - attack_self(U) - -///////////////////////////Transferring to constructs///////////////////////////////////////////////////// -/obj/structure/constructshell - name = "empty shell" - icon = 'icons/obj/wizard.dmi' - icon_state = "construct" - desc = "A wicked machine used by those skilled in magical arts. It is inactive." - -/obj/structure/constructshell/cultify() - return - -/obj/structure/constructshell/cult - icon_state = "construct-cult" - desc = "This eerie contraption looks like it would come alive if supplied with a missing ingredient." - -/obj/structure/constructshell/attackby(obj/item/O as obj, mob/user as mob) - if(istype(O, /obj/item/device/soulstone)) - var/obj/item/device/soulstone/S = O; - S.transfer_soul("CONSTRUCT",src,user) - - -////////////////////////////Proc for moving soul in and out off stone////////////////////////////////////// -/obj/item/device/soulstone/proc/transfer_human(var/mob/living/carbon/human/T,var/mob/U) - if(!istype(T)) - return; - if(src.imprinted != "empty") - U << "Capture failed!: The soul stone has already been imprinted with [src.imprinted]'s mind!" - return - if ((T.health + T.halloss) > config.health_threshold_crit && T.stat != DEAD) - U << "Capture failed!: Kill or maim the victim first!" - return - if(T.client == null) - U << "Capture failed!: The soul has already fled it's mortal frame." - return - if(src.contents.len) - U << "Capture failed!: The soul stone is full! Use or free an existing soul to make room." - return - - for(var/obj/item/W in T) - T.drop_from_inventory(W) - - new /obj/effect/decal/remains/human(T.loc) //Spawns a skeleton - T.invisibility = 101 - - var/atom/movable/overlay/animation = new /atom/movable/overlay( T.loc ) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - animation.master = T - flick("dust-h", animation) - qdel(animation) - - var/mob/living/simple_animal/shade/S = new /mob/living/simple_animal/shade( T.loc ) - S.forceMove(src) //put shade in stone - S.status_flags |= GODMODE //So they won't die inside the stone somehow - S.canmove = 0//Can't move out of the soul stone - S.name = "Shade of [T.real_name]" - S.real_name = "Shade of [T.real_name]" - S.icon = T.icon - S.icon_state = T.icon_state - S.overlays = T.overlays - S.color = rgb(254,0,0) - S.alpha = 127 - if (T.client) - T.client.mob = S - S.cancel_camera() - - - src.icon_state = "soulstone2" - src.name = "Soul Stone: [S.real_name]" - to_chat(S, "Your soul has been captured! You are now bound to [U.name]'s will, help them suceed in their goals at all costs.") - to_chat(U, "Capture successful! : [T.real_name]'s soul has been ripped from their body and stored within the soul stone.") - to_chat(U, "The soulstone has been imprinted with [S.real_name]'s mind, it will no longer react to other souls.") - src.imprinted = "[S.name]" - qdel(T) - -/obj/item/device/soulstone/proc/transfer_shade(var/mob/living/simple_animal/shade/T,var/mob/U) - if(!istype(T)) - return; - if (T.stat == DEAD) - to_chat(U, "Capture failed!: The shade has already been banished!") - return - if(src.contents.len) - to_chat(U, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") - return - if(T.name != src.imprinted) - to_chat(U, "Capture failed!: The soul stone has already been imprinted with [src.imprinted]'s mind!") - return - - T.forceMove(src) //put shade in stone - T.status_flags |= GODMODE - T.canmove = 0 - T.health = T.getMaxHealth() - src.icon_state = "soulstone2" - - to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form") - to_chat(U, "Capture successful! : [T.name]'s has been recaptured and stored within the soul stone.") - -/obj/item/device/soulstone/proc/transfer_construct(var/obj/structure/constructshell/T,var/mob/U) - var/mob/living/simple_animal/shade/A = locate() in src - if(!A) - to_chat(U,"Capture failed!: The soul stone is empty! Go kill someone!") - return; - var/construct_class = input(U, "Please choose which type of construct you wish to create.") as null|anything in possible_constructs - switch(construct_class) - if("Juggernaut") - var/mob/living/simple_animal/construct/armoured/Z = new /mob/living/simple_animal/construct/armoured (get_turf(T.loc)) - Z.key = A.key - if(iscultist(U)) - cult.add_antagonist(Z.mind) - qdel(T) - to_chat(Z,"You are playing a Juggernaut. Though slow, you can withstand extreme punishment, and rip apart enemies and walls alike.") - to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - Z.cancel_camera() - qdel(src) - if("Wraith") - var/mob/living/simple_animal/construct/wraith/Z = new /mob/living/simple_animal/construct/wraith (get_turf(T.loc)) - Z.key = A.key - if(iscultist(U)) - cult.add_antagonist(Z.mind) - qdel(T) - to_chat(Z,"You are playing a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls.") - to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - Z.cancel_camera() - qdel(src) - if("Artificer") - var/mob/living/simple_animal/construct/builder/Z = new /mob/living/simple_animal/construct/builder (get_turf(T.loc)) - Z.key = A.key - if(iscultist(U)) - cult.add_antagonist(Z.mind) - qdel(T) - to_chat(Z,"You are playing an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, repair allied constructs (by clicking on them), and even create new constructs") - to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - Z.cancel_camera() - qdel(src) - if("Harvester") - var/mob/living/simple_animal/construct/harvester/Z = new /mob/living/simple_animal/construct/harvester (get_turf(T.loc)) - Z.key = A.key - if(iscultist(U)) - cult.add_antagonist(Z.mind) - qdel(T) - to_chat(Z,"You are playing a Harvester. You are relatively weak, but your physical frailty is made up for by your ranged abilities.") - to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - Z.cancel_camera() - qdel(src) - if("Behemoth") - var/mob/living/simple_animal/construct/behemoth/Z = new /mob/living/simple_animal/construct/behemoth (get_turf(T.loc)) - Z.key = A.key - if(iscultist(U)) - cult.add_antagonist(Z.mind) - qdel(T) - to_chat(Z,"You are playing a Behemoth. You are incredibly slow, though your slowness is made up for by the fact your shell is far larger than any of your bretheren. You are the Unstoppable Force, and Immovable Object.") - to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - Z.cancel_camera() - qdel(src) - -/obj/item/device/soulstone/proc/transfer_soul(var/choice as text, var/target, var/mob/U as mob) - switch(choice) - if("VICTIM") - transfer_human(target,U) - if("SHADE") - transfer_shade(target,U) - if("CONSTRUCT") - transfer_construct(target,U) +///////////////////////// +// Soulstone +///////////////////////// + +/obj/item/device/soulstone + name = "Soul Stone Shard" + icon = 'icons/obj/wizard.dmi' + icon_state = "soulstone" + item_state = "electronic" + desc = "A fragment of the legendary treasure known simply as the 'Soul Stone'. The shard still flickers with a fraction of the full artefacts power." + w_class = ITEMSIZE_SMALL + slot_flags = SLOT_BELT + origin_tech = list(TECH_BLUESPACE = 4, TECH_MATERIAL = 4) + var/imprinted = "empty" + var/possible_constructs = list("Juggernaut","Wraith","Artificer","Harvester") + +/obj/item/device/soulstone/cultify() + return + +//////////////////////////////Capturing//////////////////////////////////////////////////////// + +/obj/item/device/soulstone/attack(mob/living/carbon/human/M as mob, mob/user as mob) + if(!istype(M, /mob/living/carbon/human))//If target is not a human. + return ..() + if(istype(M, /mob/living/carbon/human/dummy)) + return..() + if(jobban_isbanned(M, "cultist")) + user << "This person's soul is too corrupt and cannot be captured!" + return..() + + if(M.has_brain_worms()) //Borer stuff - RR + user << "This being is corrupted by an alien intelligence and cannot be soul trapped." + return..() + + add_attack_logs(user,M,"Soulstone'd with [src.name]") + transfer_soul("VICTIM", M, user) + return + + +///////////////////Options for using captured souls/////////////////////////////////////// + +/obj/item/device/soulstone/attack_self(mob/user) + if (!in_range(src, user)) + return + user.set_machine(src) + var/dat = "Soul Stone
" + for(var/mob/living/simple_mob/construct/shade/A in src) + dat += "Captured Soul: [A.name]
" + dat += {"Summon Shade"} + dat += "
" + dat += {" Close"} + user << browse(dat, "window=aicard") + onclose(user, "aicard") + return + + + + +/obj/item/device/soulstone/Topic(href, href_list) + var/mob/U = usr + if (!in_range(src, U)||U.machine!=src) + U << browse(null, "window=aicard") + U.unset_machine() + return + + add_fingerprint(U) + U.set_machine(src) + + switch(href_list["choice"])//Now we switch based on choice. + if ("Close") + U << browse(null, "window=aicard") + U.unset_machine() + return + + if ("Summon") + for(var/mob/living/simple_mob/construct/shade/A in src) + A.status_flags &= ~GODMODE + A.canmove = 1 + A << "You have been released from your prison, but you are still bound to [U.name]'s will. Help them suceed in their goals at all costs." + A.forceMove(U.loc) + A.cancel_camera() + src.icon_state = "soulstone" + attack_self(U) + +///////////////////////////Transferring to constructs///////////////////////////////////////////////////// +/obj/structure/constructshell + name = "empty shell" + icon = 'icons/obj/wizard.dmi' + icon_state = "construct" + desc = "A wicked machine used by those skilled in magical arts. It is inactive." + +/obj/structure/constructshell/cultify() + return + +/obj/structure/constructshell/cult + icon_state = "construct-cult" + desc = "This eerie contraption looks like it would come alive if supplied with a missing ingredient." + +/obj/structure/constructshell/attackby(obj/item/O as obj, mob/user as mob) + if(istype(O, /obj/item/device/soulstone)) + var/obj/item/device/soulstone/S = O; + S.transfer_soul("CONSTRUCT",src,user) + + +////////////////////////////Proc for moving soul in and out off stone////////////////////////////////////// +/obj/item/device/soulstone/proc/transfer_human(var/mob/living/carbon/human/T,var/mob/U) + if(!istype(T)) + return; + if(src.imprinted != "empty") + U << "Capture failed!: The soul stone has already been imprinted with [src.imprinted]'s mind!" + return + if ((T.health + T.halloss) > config.health_threshold_crit && T.stat != DEAD) + U << "Capture failed!: Kill or maim the victim first!" + return + if(T.client == null) + U << "Capture failed!: The soul has already fled it's mortal frame." + return + if(src.contents.len) + U << "Capture failed!: The soul stone is full! Use or free an existing soul to make room." + return + + for(var/obj/item/W in T) + T.drop_from_inventory(W) + + new /obj/effect/decal/remains/human(T.loc) //Spawns a skeleton + T.invisibility = 101 + + var/atom/movable/overlay/animation = new /atom/movable/overlay( T.loc ) + animation.icon_state = "blank" + animation.icon = 'icons/mob/mob.dmi' + animation.master = T + flick("dust-h", animation) + qdel(animation) + + var/mob/living/simple_mob/construct/shade/S = new /mob/living/simple_mob/construct/shade( T.loc ) + S.forceMove(src) //put shade in stone + S.status_flags |= GODMODE //So they won't die inside the stone somehow + S.canmove = 0//Can't move out of the soul stone + S.name = "Shade of [T.real_name]" + S.real_name = "Shade of [T.real_name]" + S.icon = T.icon + S.icon_state = T.icon_state + S.overlays = T.overlays + S.color = rgb(254,0,0) + S.alpha = 127 + if (T.client) + T.client.mob = S + S.cancel_camera() + + + src.icon_state = "soulstone2" + src.name = "Soul Stone: [S.real_name]" + to_chat(S, "Your soul has been captured! You are now bound to [U.name]'s will, help them suceed in their goals at all costs.") + to_chat(U, "Capture successful! : [T.real_name]'s soul has been ripped from their body and stored within the soul stone.") + to_chat(U, "The soulstone has been imprinted with [S.real_name]'s mind, it will no longer react to other souls.") + src.imprinted = "[S.name]" + qdel(T) + +/obj/item/device/soulstone/proc/transfer_shade(var/mob/living/simple_mob/construct/shade/T,var/mob/U) + if(!istype(T)) + return; + if (T.stat == DEAD) + to_chat(U, "Capture failed!: The shade has already been banished!") + return + if(src.contents.len) + to_chat(U, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") + return + if(T.name != src.imprinted) + to_chat(U, "Capture failed!: The soul stone has already been imprinted with [src.imprinted]'s mind!") + return + + T.forceMove(src) //put shade in stone + T.status_flags |= GODMODE + T.canmove = 0 + T.health = T.getMaxHealth() + src.icon_state = "soulstone2" + + to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form") + to_chat(U, "Capture successful! : [T.name]'s has been recaptured and stored within the soul stone.") + +/obj/item/device/soulstone/proc/transfer_construct(var/obj/structure/constructshell/T,var/mob/U) + var/mob/living/simple_mob/construct/shade/A = locate() in src + if(!A) + to_chat(U,"Capture failed!: The soul stone is empty! Go kill someone!") + return; + var/construct_class = input(U, "Please choose which type of construct you wish to create.") as null|anything in possible_constructs + switch(construct_class) + if("Juggernaut") + var/mob/living/simple_mob/construct/juggernaut/Z = new /mob/living/simple_mob/construct/juggernaut (get_turf(T.loc)) + Z.key = A.key + if(iscultist(U)) + cult.add_antagonist(Z.mind) + qdel(T) + to_chat(Z,"You are playing a Juggernaut. Though slow, you can withstand extreme punishment, and rip apart enemies and walls alike.") + to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + Z.cancel_camera() + qdel(src) + if("Wraith") + var/mob/living/simple_mob/construct/wraith/Z = new /mob/living/simple_mob/construct/wraith (get_turf(T.loc)) + Z.key = A.key + if(iscultist(U)) + cult.add_antagonist(Z.mind) + qdel(T) + to_chat(Z,"You are playing a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls.") + to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + Z.cancel_camera() + qdel(src) + if("Artificer") + var/mob/living/simple_mob/construct/artificer/Z = new /mob/living/simple_mob/construct/artificer (get_turf(T.loc)) + Z.key = A.key + if(iscultist(U)) + cult.add_antagonist(Z.mind) + qdel(T) + to_chat(Z,"You are playing an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, repair allied constructs (by clicking on them), and even create new constructs") + to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + Z.cancel_camera() + qdel(src) + if("Harvester") + var/mob/living/simple_mob/construct/harvester/Z = new /mob/living/simple_mob/construct/harvester (get_turf(T.loc)) + Z.key = A.key + if(iscultist(U)) + cult.add_antagonist(Z.mind) + qdel(T) + to_chat(Z,"You are playing a Harvester. You are relatively weak, but your physical frailty is made up for by your ranged abilities.") + to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + Z.cancel_camera() + qdel(src) + if("Behemoth") + var/mob/living/simple_mob/construct/juggernaut/behemoth/Z = new /mob/living/simple_mob/construct/juggernaut/behemoth (get_turf(T.loc)) + Z.key = A.key + if(iscultist(U)) + cult.add_antagonist(Z.mind) + qdel(T) + to_chat(Z,"You are playing a Behemoth. You are incredibly slow, though your slowness is made up for by the fact your shell is far larger than any of your bretheren. You are the Unstoppable Force, and Immovable Object.") + to_chat(Z,"You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + Z.cancel_camera() + qdel(src) + +/obj/item/device/soulstone/proc/transfer_soul(var/choice as text, var/target, var/mob/U as mob) + switch(choice) + if("VICTIM") + transfer_human(target,U) + if("SHADE") + transfer_shade(target,U) + if("CONSTRUCT") + transfer_construct(target,U) diff --git a/code/game/gamemodes/events.dm b/code/game/gamemodes/events.dm index 4b2ab0cd7d..573f10a9b4 100644 --- a/code/game/gamemodes/events.dm +++ b/code/game/gamemodes/events.dm @@ -206,7 +206,7 @@ var/hadevent = 0 /proc/carp_migration() // -- Darem for(var/obj/effect/landmark/C in landmarks_list) if(C.name == "carpspawn") - new /mob/living/simple_animal/hostile/carp(C.loc) + new /mob/living/simple_mob/animal/space/carp(C.loc) //sleep(100) spawn(rand(300, 600)) //Delayed announcements to keep the crew on their toes. command_announcement.Announce("Unknown biological entities have been detected near \the [station_name()], please stand-by.", "Lifesign Alert", new_sound = 'sound/AI/commandreport.ogg') diff --git a/code/game/gamemodes/events/holidays/Christmas.dm b/code/game/gamemodes/events/holidays/Christmas.dm index b2c4b68a5a..66e329ac1a 100644 --- a/code/game/gamemodes/events/holidays/Christmas.dm +++ b/code/game/gamemodes/events/holidays/Christmas.dm @@ -9,7 +9,7 @@ /proc/ChristmasEvent() for(var/obj/structure/flora/tree/pine/xmas in world) - var/mob/living/simple_animal/hostile/tree/evil_tree = new /mob/living/simple_animal/hostile/tree(xmas.loc) + var/mob/living/simple_mob/animal/space/tree/evil_tree = new /mob/living/simple_mob/animal/space/tree(xmas.loc) evil_tree.icon_state = xmas.icon_state evil_tree.icon_living = evil_tree.icon_state evil_tree.icon_dead = evil_tree.icon_state diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index ac9a16e8b6..a410ebfe24 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -808,7 +808,7 @@ datum/objective/heist/salvage /datum/objective/borer_survive/check_completion() if(owner) - var/mob/living/simple_animal/borer/B = owner + var/mob/living/simple_mob/animal/borer/B = owner if(istype(B) && B.stat < 2 && B.host && B.host.stat < 2) return 1 return 0 @@ -817,7 +817,7 @@ datum/objective/heist/salvage /datum/objective/borer_reproduce/check_completion() if(owner && owner.current) - var/mob/living/simple_animal/borer/B = owner.current + var/mob/living/simple_mob/animal/borer/B = owner.current if(istype(B) && B.has_reproduced) return 1 return 0 diff --git a/code/game/gamemodes/technomancer/assistance/golem.dm b/code/game/gamemodes/technomancer/assistance/golem.dm deleted file mode 100644 index 6b8654e719..0000000000 --- a/code/game/gamemodes/technomancer/assistance/golem.dm +++ /dev/null @@ -1,258 +0,0 @@ -//An AI-controlled 'companion' for the Technomancer. It's tough, strong, and can also use spells. -/mob/living/simple_animal/technomancer_golem - name = "G.O.L.E.M." - desc = "A rather unusual looking synthetic." - icon = 'icons/mob/mob.dmi' - icon_state = "technomancer_golem" - health = 250 - maxHealth = 250 - stop_automated_movement = 1 - wander = 0 - response_help = "pets" - response_disarm = "pushes away" - response_harm = "punches" - harm_intent_damage = 3 - - heat_damage_per_tick = 0 - cold_damage_per_tick = 0 - - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 - unsuitable_atoms_damage = 0 - speed = 0 - - melee_damage_lower = 30 // It has a built in esword. - melee_damage_upper = 30 - attack_sound = 'sound/weapons/blade1.ogg' - attacktext = list("slashed") - friendly = "hugs" - resistance = 0 - melee_miss_chance = 0 - - var/obj/item/weapon/technomancer_core/golem/core = null - var/obj/item/weapon/spell/active_spell = null // Shield and ranged spells - var/mob/living/master = null - - var/list/known_spells = list( - "beam" = /obj/item/weapon/spell/projectile/beam, - "chain lightning" = /obj/item/weapon/spell/projectile/chain_lightning, - "force missile" = /obj/item/weapon/spell/projectile/force_missile, - "ionic bolt" = /obj/item/weapon/spell/projectile/ionic_bolt, - "lightning" = /obj/item/weapon/spell/projectile/lightning, - "blink" = /obj/item/weapon/spell/blink, - "dispel" = /obj/item/weapon/spell/dispel, - "oxygenate" = /obj/item/weapon/spell/oxygenate, - "mend life" = /obj/item/weapon/spell/modifier/mend_life, - "mend synthetic" = /obj/item/weapon/spell/modifier/mend_synthetic, - "mend organs" = /obj/item/weapon/spell/mend_organs, - "purify" = /obj/item/weapon/spell/modifier/purify, - "resurrect" = /obj/item/weapon/spell/resurrect, - "passwall" = /obj/item/weapon/spell/passwall, - "repel missiles" = /obj/item/weapon/spell/modifier/repel_missiles, - "corona" = /obj/item/weapon/spell/modifier/corona, - "haste" = /obj/item/weapon/spell/modifier/haste - ) - - // Holds the overlays, when idle or attacking. - var/image/sword_image = null - var/image/spell_image = null - // These contain icon_states for each frame of an attack animation, which is swapped in and out manually, because BYOND. - // They are assoc lists, to hold the frame duration and the frame icon_state in one list. - var/list/spell_pre_attack_states = list( - "golem_spell_attack_1" = 1, - "golem_spell_attack_2" = 2, - "golem_spell_attack_3" = 2 - ) - var/list/spell_post_attack_states = list( - "golem_spell_attack_4" = 2, - "golem_spell_attack_5" = 3, - "golem_spell_attack_6" = 3 - ) - var/list/sword_pre_attack_states = list( - "golem_sword_attack_1" = 1, - "golem_sword_attack_2" = 5 - ) - var/list/sword_post_attack_states = list( - "golem_sword_attack_3" = 1, - "golem_sword_attack_4" = 3 - ) - -/mob/living/simple_animal/technomancer_golem/New() - ..() - core = new(src) - sword_image = image(icon, src, "golem_sword") - spell_image = image(icon, src, "golem_spell") - update_icon() - -/mob/living/simple_animal/technomancer_golem/Destroy() - qdel(core) - qdel(sword_image) - qdel(spell_image) - return ..() - -/mob/living/simple_animal/technomancer_golem/unref_spell() - active_spell = null - return ..() - -/mob/living/simple_animal/hostile/hivebot/death() - ..() - visible_message("\The [src] disintegrates!") - new /obj/effect/decal/cleanable/blood/gibs/robot(src.loc) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - qdel(src) - -/mob/living/simple_animal/technomancer_golem/update_icon() - overlays.Cut() - overlays += sword_image - overlays += spell_image - update_modifier_visuals() - -// Unfortunately, BYOND does not let you flick() images or other overlays, so we need to do this in a terrible way. -/atom/proc/manual_flick(var/list/frames, var/image/I, var/reset_to = null) - // Swap in and out each frame manually. - for(var/frame in frames) - overlays -= I - I.icon_state = frame - overlays += I - sleep(frames[frame]) - if(reset_to) - // One more time to reset it to what it was before. - overlays -= I - I.icon_state = reset_to - overlays += I - -/mob/living/simple_animal/technomancer_golem/proc/spellcast_pre_animation() - setClickCooldown(5) - manual_flick(spell_pre_attack_states, spell_image, reset_to = "golem_spell_attack_3") - -/mob/living/simple_animal/technomancer_golem/proc/spellcast_post_animation() - setClickCooldown(8) - manual_flick(spell_post_attack_states, spell_image, reset_to = "golem_spell") - -/mob/living/simple_animal/technomancer_golem/proc/sword_pre_animation() - setClickCooldown(6) - manual_flick(sword_pre_attack_states, sword_image) - -/mob/living/simple_animal/technomancer_golem/proc/sword_post_animation() - setClickCooldown(3) - manual_flick(sword_post_attack_states, sword_image, reset_to = "golem_sword") - -/mob/living/simple_animal/technomancer_golem/DoPunch(var/atom/A) - sword_pre_animation() - . = ..() // This does the actual attack and will check adjacency again. - sword_post_animation() - -/mob/living/simple_animal/technomancer_golem/isSynthetic() - return TRUE // So Mend Synthetic will work on them. - -/mob/living/simple_animal/technomancer_golem/speech_bubble_appearance() - return "synthetic_evil" - -/mob/living/simple_animal/technomancer_golem/place_spell_in_hand(var/path) - if(!path || !ispath(path)) - return 0 - - if(active_spell) - qdel(active_spell) // Get rid of our old spell. - - var/obj/item/weapon/spell/S = new path(src) - active_spell = S - -/mob/living/simple_animal/technomancer_golem/verb/test_giving_spells() - var/choice = input(usr, "What spell?", "Give spell") as null|anything in known_spells - if(choice) - place_spell_in_hand(known_spells[choice]) - else - qdel(active_spell) - -// Used to cast spells. -/mob/living/simple_animal/technomancer_golem/RangedAttack(var/atom/A, var/params) - if(active_spell) - spellcast_pre_animation() - if(active_spell.cast_methods & CAST_RANGED) - active_spell.on_ranged_cast(A, src) - spellcast_post_animation() - -/mob/living/simple_animal/technomancer_golem/UnarmedAttack(var/atom/A, var/proximity) - if(proximity) - if(active_spell) - spellcast_pre_animation() - if(!Adjacent(A)) // Need to check again since they might've moved while 'warming up'. - spellcast_post_animation() - return - var/effective_cooldown = round(active_spell.cooldown * core.cooldown_modifier, 5) - if(active_spell.cast_methods & CAST_MELEE) - active_spell.on_melee_cast(A, src) - else if(active_spell.cast_methods & CAST_RANGED) - active_spell.on_ranged_cast(A, src) - spellcast_post_animation() - src.setClickCooldown(effective_cooldown) - else - ..() - -/mob/living/simple_animal/technomancer_golem/get_technomancer_core() - return core - -/mob/living/simple_animal/technomancer_golem/proc/bind_to_mob(mob/user) - if(!user || master) - return - master = user - name = "[master]'s [initial(name)]" - -/mob/living/simple_animal/technomancer_golem/examine(mob/user) - ..() - if(user.mind && technomancers.is_antagonist(user.mind)) - user << "Your pride and joy. It's a very special synthetic robot, capable of using functions similar to you, and you built it \ - yourself! It'll always stand by your side, ready to help you out. You have no idea what GOLEM stands for, however..." - -/mob/living/simple_animal/technomancer_golem/Life() - ..() - handle_ai() - -// This is where the real spaghetti begins. -/mob/living/simple_animal/technomancer_golem/proc/handle_ai() - if(!master) - return - if(get_dist(src, master) > 6 || src.z != master.z) - targeted_blink(master) - - // Give our allies buffs and heals. - for(var/mob/living/L in view(src)) - if(L in friends) - support_friend(L) - return - -/mob/living/simple_animal/technomancer_golem/proc/support_friend(var/mob/living/L) - if(L.getBruteLoss() >= 10 || L.getFireLoss() >= 10) - if(L.isSynthetic() && !L.has_modifier_of_type(/datum/modifier/technomancer/mend_synthetic)) - place_spell_in_hand(known_spells["mend synthetic"]) - targeted_blink(L) - UnarmedAttack(L, 1) - else if(!L.has_modifier_of_type(/datum/modifier/technomancer/mend_life)) - place_spell_in_hand(known_spells["mend life"]) - targeted_blink(L) - UnarmedAttack(L, 1) - return - - - // Give them repel missiles if they lack it. - if(!L.has_modifier_of_type(/datum/modifier/technomancer/repel_missiles)) - place_spell_in_hand(known_spells["repel missiles"]) - RangedAttack(L) - return - -/mob/living/simple_animal/technomancer_golem/proc/targeted_blink(var/atom/target) - var/datum/effect/effect/system/spark_spread/spark_system = new() - spark_system.set_up(5, 0, get_turf(src)) - spark_system.start() - src.visible_message("\The [src] vanishes!") - src.forceMove(get_turf(target)) - return \ No newline at end of file diff --git a/code/game/gamemodes/technomancer/core_obj.dm b/code/game/gamemodes/technomancer/core_obj.dm index 1e7488258b..830de5a5f1 100644 --- a/code/game/gamemodes/technomancer/core_obj.dm +++ b/code/game/gamemodes/technomancer/core_obj.dm @@ -130,7 +130,7 @@ for(var/mob/living/L in summoned_mobs) summoned_mobs -= L qdel(L) - for(var/mob/living/simple_animal/ward/ward in wards_in_use) + for(var/mob/living/ward in wards_in_use) wards_in_use -= ward qdel(ward) diff --git a/code/game/gamemodes/technomancer/spell_objs_helpers.dm b/code/game/gamemodes/technomancer/spell_objs_helpers.dm index 105b3b3e9c..cf83640775 100644 --- a/code/game/gamemodes/technomancer/spell_objs_helpers.dm +++ b/code/game/gamemodes/technomancer/spell_objs_helpers.dm @@ -1,12 +1,17 @@ -//Returns 1 if the turf is dense, or if there's dense objects on it, unless told to ignore them. -/turf/proc/check_density(var/ignore_objs = 0) +//Returns 1 if the turf is dense, or if there's dense objects/mobs on it, unless told to ignore them. +/turf/proc/check_density(var/ignore_objs = FALSE, var/ignore_mobs = FALSE) if(density) - return 1 - if(!ignore_objs) + return TRUE + if(!ignore_objs || !ignore_mobs) for(var/atom/movable/stuff in contents) if(stuff.density) - return 1 - return 0 + if(ignore_objs && isobj(stuff)) + continue + else if(ignore_mobs && isliving(stuff)) // Ghosts aren't dense but keeping this limited to living type will probably save headaches in the future. + continue + else + return TRUE + return FALSE // Used to distinguish friend from foe. /obj/item/weapon/spell/proc/is_ally(var/mob/living/L) @@ -14,9 +19,9 @@ return 1 if(L.mind && technomancers.is_antagonist(L.mind)) // This should be done better since we might want opposing technomancers later. return 1 - if(istype(L, /mob/living/simple_animal/hostile)) // Mind controlled simple mobs count as allies too. - var/mob/living/simple_animal/SA = L - if(owner in SA.friends) + if(istype(L, /mob/living/simple_mob)) // Mind controlled simple mobs count as allies too. + var/mob/living/simple_mob/SM = L + if(owner in SM.friends) return 1 return 0 diff --git a/code/game/gamemodes/technomancer/spells/abjuration.dm b/code/game/gamemodes/technomancer/spells/abjuration.dm index 146c90a987..0eb566a637 100644 --- a/code/game/gamemodes/technomancer/spells/abjuration.dm +++ b/code/game/gamemodes/technomancer/spells/abjuration.dm @@ -16,12 +16,12 @@ /obj/item/weapon/spell/abjuration/on_ranged_cast(atom/hit_atom, mob/user) if(istype(hit_atom, /mob/living) && pay_energy(500) && within_range(hit_atom)) var/mob/living/L = hit_atom - var/mob/living/simple_animal/SA = null + var/mob/living/simple_mob/SM = null //Bit of a roundabout typecheck, in order to test for two variables from two different mob types in one line. - if(istype(L, /mob/living/simple_animal)) - SA = L - if(L.summoned || (SA && SA.supernatural) ) + if(istype(L, /mob/living/simple_mob)) + SM = L + if(L.summoned || (SM && SM.supernatural) ) if(L.client) // Player-controlled mobs are immune to being killed by this. user << "\The [L] resists your attempt to banish it!" L << "\The [user] tried to teleport you far away, but failed." @@ -29,8 +29,8 @@ else visible_message("\The [L] vanishes!") qdel(L) - else if(istype(L, /mob/living/simple_animal/construct)) - var/mob/living/simple_animal/construct/evil = L + else if(istype(L, /mob/living/simple_mob/construct)) + var/mob/living/simple_mob/construct/evil = L evil << "\The [user]'s abjuration purges your form!" evil.purge = 3 adjust_instability(5) diff --git a/code/game/gamemodes/technomancer/spells/control.dm b/code/game/gamemodes/technomancer/spells/control.dm index e76e63490c..f86fd79bf5 100644 --- a/code/game/gamemodes/technomancer/spells/control.dm +++ b/code/game/gamemodes/technomancer/spells/control.dm @@ -21,6 +21,7 @@ aspect = ASPECT_BIOMED //Not sure if this should be something else. var/image/control_overlay = null var/list/controlled_mobs = list() +<<<<<<< HEAD var/list/allowed_mobs = list( /mob/living/bot, /mob/living/simple_animal/cat, @@ -45,67 +46,70 @@ /mob/living/simple_animal/hostile/savik, /mob/living/simple_animal/hostile/shantak ) +======= + var/allowed_mob_classes = MOB_CLASS_ANIMAL|MOB_CLASS_SYNTHETIC +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync //This unfortunately is gonna be rather messy due to the various mobtypes involved. /obj/item/weapon/spell/control/proc/select(var/mob/living/L) - if(!(is_type_in_list(L, allowed_mobs))) - return 0 + if(!(L.mob_class & allowed_mob_classes)) + return FALSE - if(istype(L, /mob/living/simple_animal)) - var/mob/living/simple_animal/SA = L - SA.ai_inactive = 1 - SA.friends |= src.owner - SA.stance = STANCE_IDLE + if(!L.has_AI()) + return FALSE - L.overlays |= control_overlay + var/datum/ai_holder/AI = L.ai_holder + AI.hostile = FALSE // The Technomancer chooses the target, not the AI. + AI.retaliate = TRUE + AI.wander = FALSE + AI.forget_everything() + + if(istype(L, /mob/living/simple_mob)) + var/mob/living/simple_mob/SM = L + SM.friends |= src.owner + + L.add_overlay(control_overlay, TRUE) controlled_mobs |= L /obj/item/weapon/spell/control/proc/deselect(var/mob/living/L) if(!(L in controlled_mobs)) - return 0 + return FALSE - if(istype(L, /mob/living/simple_animal)) - var/mob/living/simple_animal/SA = L - SA.ai_inactive = 1 - if(istype(SA, /mob/living/simple_animal/hostile)) - var/mob/living/simple_animal/hostile/SAH = SA - SAH.friends.Remove(owner) + if(L.has_AI()) + var/datum/ai_holder/AI = L.ai_holder + AI.hostile = initial(AI.hostile) + AI.retaliate = initial(AI.retaliate) + AI.wander = initial(AI.wander) + AI.forget_everything() - L.overlays.Remove(control_overlay) + if(istype(L, /mob/living/simple_mob)) + var/mob/living/simple_mob/SM = L + SM.friends -= owner + + L.cut_overlay(control_overlay, TRUE) controlled_mobs.Remove(L) /obj/item/weapon/spell/control/proc/move_all(turf/T) - for(var/mob/living/living in controlled_mobs) - if(living.stat) - deselect(living) + for(var/mob/living/L in controlled_mobs) + if(!L.has_AI() || L.stat) + deselect(L) continue - if(istype(living, /mob/living/simple_animal)) - var/mob/living/simple_animal/SA = living - SA.target_mob = null - SA.stance = STANCE_IDLE - walk_towards(SA,T,SA.speed) - else - walk_towards(living,T,5) + L.ai_holder.give_destination(T, 0, TRUE) /obj/item/weapon/spell/control/proc/attack_all(mob/target) for(var/mob/living/L in controlled_mobs) - if(L.stat) + if(!L.has_AI() || L.stat) deselect(L) continue - if(istype(L, /mob/living/simple_animal/hostile)) - var/mob/living/simple_animal/hostile/SAH - SAH.target_mob = target - else if(istype(L, /mob/living/bot)) - var/mob/living/bot/B = L - B.UnarmedAttack(L) + L.ai_holder.give_target(target) -/obj/item/weapon/spell/control/New() +/obj/item/weapon/spell/control/initialize() control_overlay = image('icons/obj/spells.dmi',"controlled") - ..() + return ..() /obj/item/weapon/spell/control/Destroy() - for(var/mob/living/simple_animal/hostile/SM in controlled_mobs) - deselect(SM) + for(var/mob/living/L in controlled_mobs) + deselect(L) controlled_mobs = list() return ..() @@ -127,11 +131,14 @@ trying to use it on yourself, perhaps you're an exception? Regardless, nothing happens." return 0 - if(is_type_in_list(L, allowed_mobs)) + if(L.mob_class & allowed_mob_classes) if(!(L in controlled_mobs)) //Selecting if(L.client) user << "\The [L] seems to resist you!" return 0 + if(!L.has_AI()) + to_chat(user, span("warning", "\The [L] seems too dim for this to work on them.")) + return FALSE if(pay_energy(500)) select(L) user << "\The [L] is now under your (limited) control." diff --git a/code/game/gamemodes/technomancer/spells/gambit.dm b/code/game/gamemodes/technomancer/spells/gambit.dm index e0116073dc..5e03a8ca19 100644 --- a/code/game/gamemodes/technomancer/spells/gambit.dm +++ b/code/game/gamemodes/technomancer/spells/gambit.dm @@ -75,9 +75,9 @@ for(var/mob/living/L in view(owner)) // Spiders, carp... bears. - if(istype(L, /mob/living/simple_animal)) - var/mob/living/simple_animal/SM = L - if(!is_ally(SM) && SM.hostile) + if(istype(L, /mob/living/simple_mob)) + var/mob/living/simple_mob/SM = L + if(!is_ally(SM) && SM.has_AI() && SM.ai_holder.hostile) hostile_mobs++ if(SM.summoned || SM.supernatural) // Our creations might be trying to kill us. potential_spells |= /obj/item/weapon/spell/abjuration diff --git a/code/game/gamemodes/technomancer/spells/illusion.dm b/code/game/gamemodes/technomancer/spells/illusion.dm index 418df75539..8dccc2b9ec 100644 --- a/code/game/gamemodes/technomancer/spells/illusion.dm +++ b/code/game/gamemodes/technomancer/spells/illusion.dm @@ -14,7 +14,7 @@ aspect = ASPECT_LIGHT cast_methods = CAST_RANGED | CAST_USE var/atom/movable/copied = null - var/mob/living/simple_animal/illusion/illusion = null + var/mob/living/simple_mob/illusion/illusion = null /obj/item/weapon/spell/illusion/on_ranged_cast(atom/hit_atom, mob/user) if(istype(hit_atom, /atom/movable)) @@ -33,17 +33,13 @@ if(pay_energy(500)) illusion = new(T) illusion.copy_appearance(copied) - if(ishuman(copied)) - var/mob/living/carbon/human/H = copied - // This is to try to have the illusion move at the same rate the real mob world. - illusion.step_delay = max(H.movement_delay() + 4, 3) user << "An illusion of \the [copied] is made on \the [T]." user << 'sound/effects/pop.ogg' return 1 else if(pay_energy(100)) - spawn(1) - illusion.walk_loop(T) + var/datum/ai_holder/AI = illusion.ai_holder + AI.give_destination(T) /obj/item/weapon/spell/illusion/on_use_cast(mob/user) if(illusion) @@ -76,116 +72,3 @@ temp_image.transform = M // temp_image.pixel_y = 8 src.overlays.Add(temp_image) - - -/mob/living/simple_animal/illusion - name = "illusion" // gets overwritten - desc = "If you can read me, the game broke. Please report this to a coder." - resistance = 1000 // holograms are tough - wander = 0 - response_help = "pushes a hand through" - response_disarm = "tried to disarm" - response_harm = "tried to punch" - var/atom/movable/copying = null - universal_speak = 1 - var/realistic = 0 - var/list/path = list() //Used for AStar pathfinding. - var/walking = 0 - var/step_delay = 10 - -/mob/living/simple_animal/illusion/update_icon() // We don't want the appearance changing AT ALL unless by copy_appearance(). - return - -/mob/living/simple_animal/illusion/proc/copy_appearance(var/atom/movable/thing_to_copy) - if(!thing_to_copy) - return 0 - name = thing_to_copy.name - desc = thing_to_copy.desc - gender = thing_to_copy.gender - appearance = thing_to_copy.appearance - copying = thing_to_copy - return 1 - -// We use special movement code for illusions, because BYOND's default pathfinding will use diagonal movement if it results -// in the shortest path. As players are incapable of moving in diagonals, we must do this or else illusions will not be convincing. -/mob/living/simple_animal/illusion/proc/calculate_path(var/turf/targeted_loc) - if(!path.len || !path) - spawn(0) - path = AStar(loc, targeted_loc, /turf/proc/CardinalTurfs, /turf/proc/Distance, 0, 10, id = null) - if(!path) - path = list() - return - -/mob/living/simple_animal/illusion/proc/walk_path(var/turf/targeted_loc) - if(path && path.len) - step_to(src, path[1]) - path -= path[1] - return - else - if(targeted_loc) - calculate_path(targeted_loc) - -/mob/living/simple_animal/illusion/proc/walk_loop(var/turf/targeted_loc) - if(walking) //Already busy moving somewhere else. - return 0 - walking = 1 - calculate_path(targeted_loc) - if(!targeted_loc) - walking = 0 - return 0 - if(path.len == 0) - calculate_path(targeted_loc) - while(loc != targeted_loc) - walk_path(targeted_loc) - sleep(step_delay) - walking = 0 - return 1 - -// Because we can't perfectly duplicate some examine() output, we directly examine the AM it is copying. It's messy but -// this is to prevent easy checks from the opposing force. -/mob/living/simple_animal/illusion/examine(mob/user) - if(copying) - copying.examine(user) - return - ..() - -/mob/living/simple_animal/illusion/bullet_act(var/obj/item/projectile/P) - if(!P) - return - - if(realistic) - return ..() - - return PROJECTILE_FORCE_MISS - -/mob/living/simple_animal/illusion/attack_hand(mob/living/carbon/human/M) - if(!realistic) - playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) - visible_message("[M]'s hand goes through \the [src]!") - return - else - switch(M.a_intent) - - if(I_HELP) - var/datum/gender/T = gender_datums[src.get_visible_gender()] - M.visible_message("[M] hugs [src] to make [T.him] feel better!", \ - "You hug [src] to make [T.him] feel better!") // slightly redundant as at the moment most mobs still use the normal gender var, but it works and future-proofs it - playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - - if(I_DISARM) - playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) - visible_message("[M] attempted to disarm [src]!") - M.do_attack_animation(src) - - if(I_GRAB) - ..() - - if(I_HURT) - adjustBruteLoss(harm_intent_damage) - M.visible_message("[M] [response_harm] \the [src]") - M.do_attack_animation(src) - - return - -/mob/living/simple_animal/illusion/ex_act() - return diff --git a/code/game/gamemodes/technomancer/spells/modifier/modifier.dm b/code/game/gamemodes/technomancer/spells/modifier/modifier.dm index a8b9306945..3ea278286e 100644 --- a/code/game/gamemodes/technomancer/spells/modifier/modifier.dm +++ b/code/game/gamemodes/technomancer/spells/modifier/modifier.dm @@ -15,11 +15,13 @@ /obj/item/weapon/spell/modifier/on_melee_cast(atom/hit_atom, mob/user) if(istype(hit_atom, /mob/living)) - on_add_modifier(hit_atom) + return on_add_modifier(hit_atom) + return FALSE /obj/item/weapon/spell/modifier/on_ranged_cast(atom/hit_atom, mob/user) if(istype(hit_atom, /mob/living)) - on_add_modifier(hit_atom) + return on_add_modifier(hit_atom) + return FALSE /obj/item/weapon/spell/modifier/proc/on_add_modifier(var/mob/living/L) @@ -32,6 +34,7 @@ MT.spell_power = calculate_spell_power(1) log_and_message_admins("has casted [src] on [L].") qdel(src) + return TRUE // Technomancer specific subtype which keeps track of spell power and gets targeted specificially by Dispel. /datum/modifier/technomancer diff --git a/code/game/gamemodes/technomancer/spells/resurrect.dm b/code/game/gamemodes/technomancer/spells/resurrect.dm index 90a352c95f..2663a132a9 100644 --- a/code/game/gamemodes/technomancer/spells/resurrect.dm +++ b/code/game/gamemodes/technomancer/spells/resurrect.dm @@ -30,13 +30,13 @@ this point." return 0 user << "You stab \the [L] with a hidden integrated hypo, attempting to bring them back..." - if(istype(L, /mob/living/simple_animal)) - var/mob/living/simple_animal/SM = L + if(istype(L, /mob/living/simple_mob)) + var/mob/living/simple_mob/SM = L SM.health = SM.getMaxHealth() / 3 SM.stat = CONSCIOUS dead_mob_list -= SM living_mob_list += SM - SM.icon_state = SM.icon_living + SM.update_icon() adjust_instability(15) else if(ishuman(L)) var/mob/living/carbon/human/H = L diff --git a/code/game/gamemodes/technomancer/spells/spawner/pulsar.dm b/code/game/gamemodes/technomancer/spells/spawner/pulsar.dm index 5641df2f87..18e5785a05 100644 --- a/code/game/gamemodes/technomancer/spells/spawner/pulsar.dm +++ b/code/game/gamemodes/technomancer/spells/spawner/pulsar.dm @@ -11,7 +11,7 @@ icon_state = "radiance" cast_methods = CAST_RANGED | CAST_THROW aspect = ASPECT_EMP - spawner_type = /obj/effect/temporary_effect/pulsar + spawner_type = /obj/effect/temporary_effect/pulse/pulsar /obj/item/weapon/spell/spawner/pulsar/New() ..() @@ -25,7 +25,29 @@ /obj/item/weapon/spell/spawner/pulsar/on_throw_cast(atom/hit_atom, mob/user) empulse(hit_atom, 1, 1, 1, 1, log=1) -/obj/effect/temporary_effect/pulsar +// Does something every so often. Deletes itself when pulses_remaining hits zero. +/obj/effect/temporary_effect/pulse + var/pulses_remaining = 3 + var/pulse_delay = 2 SECONDS + +/obj/effect/temporary_effect/pulse/initialize() + spawn(0) + pulse_loop() + return ..() + +/obj/effect/temporary_effect/pulse/proc/pulse_loop() + while(pulses_remaining) + sleep(pulse_delay) + on_pulse() + pulses_remaining-- + qdel(src) + +// Override for specific effects. +/obj/effect/temporary_effect/pulse/proc/on_pulse() + + + +/obj/effect/temporary_effect/pulse/pulsar name = "pulsar" desc = "Not a real pulsar, but still emits loads of EMP." icon_state = "shield2" @@ -33,17 +55,14 @@ light_range = 4 light_power = 5 light_color = "#2ECCFA" - var/pulses_remaining = 3 + pulses_remaining = 3 + +/obj/effect/temporary_effect/pulse/pulsar/on_pulse() + empulse(src, 1, 1, 2, 2, log = 1) + + + + -/obj/effect/temporary_effect/pulsar/New() - ..() - spawn(0) - pulse_loop() -/obj/effect/temporary_effect/pulsar/proc/pulse_loop() - while(pulses_remaining) - sleep(2 SECONDS) - empulse(src, 1, 1, 2, 2, log = 1) - pulses_remaining-- - qdel(src) diff --git a/code/game/gamemodes/technomancer/spells/summon/summon_creature.dm b/code/game/gamemodes/technomancer/spells/summon/summon_creature.dm index 9a004d3ea3..7ac370433d 100644 --- a/code/game/gamemodes/technomancer/spells/summon/summon_creature.dm +++ b/code/game/gamemodes/technomancer/spells/summon/summon_creature.dm @@ -16,6 +16,7 @@ desc = "Chitter chitter." summoned_mob_type = null summon_options = list( +<<<<<<< HEAD "Mouse" = /mob/living/simple_animal/mouse, "Lizard" = /mob/living/simple_animal/lizard, "Chicken" = /mob/living/simple_animal/chicken, @@ -34,16 +35,34 @@ "CARP" = /mob/living/simple_animal/hostile/carp, "BEAR" = /mob/living/simple_animal/hostile/bear ) // Vorestation edits to add vore versions. +======= + "Mouse" = /mob/living/simple_mob/animal/passive/mouse, + "Lizard" = /mob/living/simple_mob/animal/passive/lizard, + "Chicken" = /mob/living/simple_mob/animal/passive/chicken, + "Chick" = /mob/living/simple_mob/animal/passive/chick, + "Crab" = /mob/living/simple_mob/animal/passive/crab, + "Parrot" = /mob/living/simple_mob/animal/passive/bird/parrot, + "Goat" = /mob/living/simple_mob/animal/goat, + "Cat" = /mob/living/simple_mob/animal/passive/cat, + "Kitten" = /mob/living/simple_mob/animal/passive/cat/kitten, + "Corgi" = /mob/living/simple_mob/animal/passive/dog/corgi, + "Corgi Pup" = /mob/living/simple_mob/animal/passive/dog/corgi/puppy, + "BAT" = /mob/living/simple_mob/animal/space/bats, + "SPIDER" = /mob/living/simple_mob/animal/giant_spider, + "SPIDER HUNTER" = /mob/living/simple_mob/animal/giant_spider/hunter, + "SPIDER NURSE" = /mob/living/simple_mob/animal/giant_spider/nurse, + "CARP" = /mob/living/simple_mob/animal/space/carp, + "BEAR" = /mob/living/simple_mob/animal/space/bear + ) +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync cooldown = 30 instability_cost = 10 energy_cost = 1000 -/obj/item/weapon/spell/summon/summon_creature/on_summon(var/mob/living/simple_animal/summoned) +/obj/item/weapon/spell/summon/summon_creature/on_summon(var/mob/living/simple_mob/summoned) if(check_for_scepter()) // summoned.faction = "technomancer" - if(istype(summoned, /mob/living/simple_animal/hostile)) - var/mob/living/simple_animal/SA = summoned - SA.friends.Add(owner) + summoned.friends += owner // Makes their new pal big and strong, if they have spell power. summoned.maxHealth = calculate_spell_power(summoned.maxHealth) @@ -51,15 +70,12 @@ summoned.melee_damage_lower = calculate_spell_power(summoned.melee_damage_lower) summoned.melee_damage_upper = calculate_spell_power(summoned.melee_damage_upper) // This makes the summon slower, so the crew has a chance to flee from massive monsters. - summoned.move_to_delay = calculate_spell_power(round(summoned.move_to_delay)) + summoned.movement_cooldown = calculate_spell_power(round(summoned.movement_cooldown)) var/new_size = calculate_spell_power(1) if(new_size != 1) - var/matrix/M = matrix() - M.Scale(new_size) - M.Translate(0, 16*(new_size-1)) - summoned.transform = M + adjust_scale(new_size) // Now we hurt their new pal, because being forcefully abducted by teleportation can't be healthy. - summoned.health = round(summoned.getMaxHealth() * 0.7) \ No newline at end of file + summoned.adjustBruteLoss(summoned.getMaxHealth() * 0.3) // Lose 30% of max health on arrival (but could be healed back up). \ No newline at end of file diff --git a/code/game/gamemodes/technomancer/spells/summon/summon_ward.dm b/code/game/gamemodes/technomancer/spells/summon/summon_ward.dm index c6e5a2804d..9500044581 100644 --- a/code/game/gamemodes/technomancer/spells/summon/summon_ward.dm +++ b/code/game/gamemodes/technomancer/spells/summon/summon_ward.dm @@ -1,9 +1,8 @@ /datum/technomancer/spell/summon_ward - name = "Summon Ward" - desc = "Teleports a prefabricated 'ward' drone to the target location, which will alert you and your allies when it sees entities \ - moving around it, or when it is attacked. They can see for up to five meters." - enhancement_desc = "Wards can detect invisibile entities, and are more specific in relaying information about what it sees. \ - Invisible entities that are spotted by it will be decloaked." + name = "Summon Monitor Ward" + desc = "Teleports a prefabricated 'ward' drone to the target location, which will alert you when it sees entities \ + moving around it, or when it is attacked. They can see for up to five meters. It can also see invisible entities, and \ + forcefully decloak them if close enough." cost = 25 obj_path = /obj/item/weapon/spell/summon/summon_ward category = UTILITY_SPELLS @@ -12,116 +11,10 @@ name = "summon ward" desc = "Finally, someone you can depend on to watch your back." cast_methods = CAST_RANGED - summoned_mob_type = /mob/living/simple_animal/ward + summoned_mob_type = /mob/living/simple_mob/mechanical/ward/monitor cooldown = 10 instability_cost = 5 energy_cost = 500 -/obj/item/weapon/spell/summon/summon_ward/on_summon(var/mob/living/simple_animal/ward/ward) - ward.creator = owner - if(check_for_scepter()) - ward.true_sight = 1 - ward.see_invisible = SEE_INVISIBLE_LEVEL_TWO - -/mob/living/simple_animal/ward - name = "ward" - desc = "It's a little flying drone that seems to be watching you..." - icon = 'icons/mob/critter.dmi' - icon_state = "ward" - resistance = 5 - wander = 0 - response_help = "pets the" - response_disarm = "swats away" - response_harm = "punches" - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 - minbodytemp = 0 - maxbodytemp = 0 - unsuitable_atoms_damage = 0 - heat_damage_per_tick = 0 - cold_damage_per_tick = 0 - - var/true_sight = 0 // If true, detects more than what the Technomancer normally can't. - var/mob/living/carbon/human/creator = null - var/list/seen_mobs = list() - -/mob/living/simple_animal/ward/death() - if(creator) - creator << "Your ward inside [get_area(src)] was killed!" - ..() - qdel(src) - -/mob/living/simple_animal/ward/proc/expire() - if(creator && src) - creator << "Your ward inside [get_area(src)] expired." - qdel(src) - -/mob/living/simple_animal/ward/Life() - ..() - detect_mobs() - update_icon() - -/mob/living/simple_animal/ward/proc/detect_mobs() - var/list/things_in_sight = view(5,src) - var/list/newly_seen_mobs = list() - for(var/mob/living/L in things_in_sight) - if(L == creator) // I really wish is_ally() was usable here. - continue - - if(istype(L, /mob/living/simple_animal/ward)) - continue - - if(istype(L, /mob/living/simple_animal)) - var/mob/living/simple_animal/SA = L - if(creator in SA.friends) - continue - - if(!true_sight) - var/turf/T = get_turf(L) - var/light_amount = T.get_lumcount() - if(light_amount <= 0.5) - continue // Too dark to see. - - if(L.alpha <= 127) - continue // Too transparent, as a mercy to camo lings. - - else - L.break_cloak() - - // Warn the Technomancer when it sees a new mob. - if(!(L in seen_mobs)) - seen_mobs.Add(L) - newly_seen_mobs.Add(L) - if(creator) - if(true_sight) - creator << "Your ward at [get_area(src)] detected [english_list(newly_seen_mobs)]." - else - creator << "Your ward at [get_area(src)] detected something." - - // Now get rid of old mobs that left vision. - for(var/mob/living/L in seen_mobs) - if(!(L in things_in_sight)) - seen_mobs.Remove(L) - - -/mob/living/simple_animal/ward/update_icon() - if(seen_mobs.len) - icon_state = "ward_spotted" - set_light(3, 3, l_color = "FF0000") - else - icon_state = "ward" - set_light(3, 3, l_color = "00FF00") - if(true_sight) - overlays.Cut() - var/image/I = image('icons/mob/critter.dmi',"ward_truesight") - overlays.Add(I) - -/mob/living/simple_animal/ward/invisible_detect - true_sight = 1 - see_invisible = SEE_INVISIBLE_LEVEL_TWO +/obj/item/weapon/spell/summon/summon_ward/on_summon(var/mob/living/simple_mob/mechanical/ward/monitor/my_ward) + my_ward.owner = owner diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm index 2fb2d48a82..2062f92db4 100644 --- a/code/game/machinery/adv_med.dm +++ b/code/game/machinery/adv_med.dm @@ -49,10 +49,9 @@ if(occupant) to_chat(user, "\The [src] is already occupied!") return - for(var/mob/living/simple_animal/slime/M in range(1, H.affecting)) - if(M.victim == H.affecting) - to_chat(user, "[H.affecting.name] has a slime attached to them, deal with that first.") - return + if(H.affecting.has_buckled_mobs()) + to_chat(user, span("warning", "\The [H.affecting] has other entities attached to it. Remove them first.")) + return var/mob/M = H.affecting if(M.abiotic()) to_chat(user, "Subject cannot have abiotic items on.") @@ -86,10 +85,9 @@ if(O.abiotic()) to_chat(user, "Subject cannot have abiotic items on.") return 0 - for(var/mob/living/simple_animal/slime/M in range(1, O)) - if(M.victim == O) - to_chat(user, "[O] has a slime attached to them, deal with that first.") - return 0 + if(O.has_buckled_mobs()) + to_chat(user, span("warning", "\The [O] has other entities attached to it. Remove them first.")) + return if(O == user) visible_message("[user] climbs into \the [src].") diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 20f5a617cf..20304fa2ba 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -143,7 +143,7 @@ /obj/machinery/camera/attack_generic(mob/user as mob) if(isanimal(user)) - var/mob/living/simple_animal/S = user + var/mob/living/simple_mob/S = user set_status(0) S.do_attack_animation(src) S.setClickCooldown(user.get_attack_speed()) diff --git a/code/game/machinery/computer/prisonshuttle.dm b/code/game/machinery/computer/prisonshuttle.dm index 8c435f5845..8fe6ffef8a 100644 --- a/code/game/machinery/computer/prisonshuttle.dm +++ b/code/game/machinery/computer/prisonshuttle.dm @@ -201,7 +201,7 @@ var/prison_shuttle_timeleft = 0 for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area... bug.gib() - for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug... + for(var/mob/living/simple_mob/pest in end_location) // And for the other kind of bug... pest.gib() start_location.move_contents_to(end_location) diff --git a/code/game/machinery/computer/specops_shuttle.dm b/code/game/machinery/computer/specops_shuttle.dm index 1f7c959db5..431d6278d6 100644 --- a/code/game/machinery/computer/specops_shuttle.dm +++ b/code/game/machinery/computer/specops_shuttle.dm @@ -81,7 +81,7 @@ var/specops_shuttle_timeleft = 0 for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area... bug.gib() - for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug... + for(var/mob/living/simple_mob/pest in end_location) // And for the other kind of bug... pest.gib() start_location.move_contents_to(end_location) diff --git a/code/game/machinery/computer/syndicate_specops_shuttle.dm b/code/game/machinery/computer/syndicate_specops_shuttle.dm index ba7193b69b..e02ab68474 100644 --- a/code/game/machinery/computer/syndicate_specops_shuttle.dm +++ b/code/game/machinery/computer/syndicate_specops_shuttle.dm @@ -166,7 +166,7 @@ var/syndicate_elite_shuttle_timeleft = 0 for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area... bug.gib() - for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug... + for(var/mob/living/simple_mob/pest in end_location) // And for the other kind of bug... pest.gib() start_location.move_contents_to(end_location) diff --git a/code/game/machinery/computer3/computers/HolodeckControl.dm b/code/game/machinery/computer3/computers/HolodeckControl.dm index fcff13c968..da3c2416fd 100644 --- a/code/game/machinery/computer3/computers/HolodeckControl.dm +++ b/code/game/machinery/computer3/computers/HolodeckControl.dm @@ -207,7 +207,7 @@ for(var/obj/effect/decal/cleanable/blood/B in linkedholodeck) qdel(B) - for(var/mob/living/simple_animal/hostile/carp/C in linkedholodeck) + for(var/mob/living/simple_mob/animal/space/carp/C in linkedholodeck) qdel(C) holographic_items = A.copy_contents_to(linkedholodeck , 1) @@ -228,7 +228,7 @@ T.temperature = 5000 T.hotspot_expose(50000,50000,1) if(L.name=="Holocarp Spawn") - new /mob/living/simple_animal/hostile/carp(L.loc) + new /mob/living/simple_mob/animal/space/carp(L.loc) /datum/file/program/holodeck/proc/emergencyShutdown() diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index 20a54046a6..6993d988dd 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -199,14 +199,13 @@ return if(occupant) to_chat(user,"\The [src] is already occupied by [occupant].") - for(var/mob/living/simple_animal/slime/M in range(1,grab.affecting)) - if(M.victim == grab.affecting) - to_chat(usr, "[grab.affecting.name] will not fit into the cryo because they have a slime latched onto their head.") - return + if(grab.affecting.has_buckled_mobs()) + to_chat(user, span("warning", "\The [grab.affecting] has other entities attached to it. Remove them first.")) + return var/mob/M = grab.affecting qdel(grab) put_mob(M) - + return /obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(var/mob/target, var/mob/user) //Allows borgs to put people into cryo without external assistance @@ -349,14 +348,14 @@ set name = "Move Inside" set category = "Object" set src in oview(1) - for(var/mob/living/simple_animal/slime/M in range(1,usr)) - if(M.victim == usr) - to_chat(usr, "You're too busy getting your life sucked out of you.") + if(isliving(usr)) + var/mob/living/L = usr + if(L.has_buckled_mobs()) + to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first.")) return - if(usr.stat != 0) - return - put_mob(usr) - return + if(L.stat != CONSCIOUS) + return + put_mob(L) /atom/proc/return_air_for_internal_lifeform(var/mob/living/lifeform) return return_air() diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index ffda46f7cf..fd1dbf529d 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -567,9 +567,10 @@ to_chat(usr, "\The [src] is in use.") return - for(var/mob/living/simple_animal/slime/M in range(1,usr)) - if(M.victim == usr) - to_chat(usr, "You're too busy getting your life sucked out of you.") + if(isliving(usr)) + var/mob/living/L = usr + if(L.has_buckled_mobs()) + to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first.")) return visible_message("[usr] [on_enter_visible_message] [src].", 3) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 541e461f27..fe82ca78a7 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -46,11 +46,12 @@ var/bolt_up_sound = 'sound/machines/boltsup.ogg' var/bolt_down_sound = 'sound/machines/boltsdown.ogg' -/obj/machinery/door/airlock/attack_generic(var/mob/user, var/damage) +/obj/machinery/door/airlock/attack_generic(var/mob/living/user, var/damage) if(stat & (BROKEN|NOPOWER)) - if(damage >= 10) + if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD) if(src.locked || src.welded) visible_message("\The [user] begins breaking into \the [src] internals!") + user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything. if(do_after(user,10 SECONDS,src)) src.locked = 0 src.welded = 0 @@ -58,6 +59,7 @@ open(1) if(prob(25)) src.shock(user, 100) + user.set_AI_busy(FALSE) else if(src.density) visible_message("\The [user] forces \the [src] open!") open(1) @@ -508,9 +510,6 @@ About the new airlock wires panel: return ..(user) -/obj/machinery/door/airlock/bumpopen(mob/living/simple_animal/user as mob) - ..(user) - /obj/machinery/door/airlock/proc/isElectrified() if(src.electrified_until != 0) return 1 diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index ddb2001639..01af732f06 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -39,8 +39,8 @@ /obj/machinery/door/attack_generic(var/mob/user, var/damage) if(isanimal(user)) - var/mob/living/simple_animal/S = user - if(damage >= 10) + var/mob/living/simple_mob/S = user + if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD) visible_message("\The [user] smashes into the [src]!") playsound(src, S.attack_sound, 75, 1) take_damage(damage) diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index fb7609ee05..ac5246d2da 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -216,22 +216,26 @@ return ..() -/obj/machinery/door/firedoor/attack_generic(var/mob/user, var/damage) +/obj/machinery/door/firedoor/attack_generic(var/mob/living/user, var/damage) if(stat & (BROKEN|NOPOWER)) - if(damage >= 10) + if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD) var/time_to_force = (2 + (2 * blocked)) * 5 if(src.density) visible_message("\The [user] starts forcing \the [src] open!") + user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything. if(do_after(user, time_to_force, src)) visible_message("\The [user] forces \the [src] open!") src.blocked = 0 open(1) + user.set_AI_busy(FALSE) else time_to_force = (time_to_force / 2) visible_message("\The [user] starts forcing \the [src] closed!") + user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything. if(do_after(user, time_to_force, src)) visible_message("\The [user] forces \the [src] closed!") close(1) + user.set_AI_busy(FALSE) else visible_message("\The [user] strains fruitlessly to force \the [src] [density ? "open" : "closed"].") return diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm index 07357ceb7b..4953236007 100644 --- a/code/game/machinery/portable_turret.dm +++ b/code/game/machinery/portable_turret.dm @@ -406,16 +406,16 @@ var/list/turret_icons attacked = 0 ..() -/obj/machinery/porta_turret/attack_generic(mob/user as mob, var/damage) - if(isanimal(user)) - var/mob/living/simple_animal/S = user - if(damage >= 10) +/obj/machinery/porta_turret/attack_generic(mob/living/L, damage) + if(isanimal(L)) + var/mob/living/simple_mob/S = L + if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD) var/incoming_damage = round(damage - (damage / 5)) //Turrets are slightly armored, assumedly. visible_message("\The [S] [pick(S.attacktext)] \the [src]!") take_damage(incoming_damage) S.do_attack_animation(src) return 1 - visible_message("\The [user] bonks \the [src]'s casing!") + visible_message("\The [L] bonks \the [src]'s casing!") return ..() /obj/machinery/porta_turret/emag_act(var/remaining_charges, var/mob/user) diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm index be31a5aa56..139fd8ecb9 100644 --- a/code/game/machinery/teleporter.dm +++ b/code/game/machinery/teleporter.dm @@ -69,7 +69,7 @@ for(var/obj/machinery/teleport/hub/H in range(1)) var/amount = rand(2,5) for(var/i=0;iKinda hard to climb in while handcuffed don't you think?" return - for(var/mob/living/simple_animal/slime/M in range(1,usr)) - if(M.victim == usr) - usr << "You're too busy getting your life sucked out of you." + if(isliving(usr)) + var/mob/living/L = usr + if(L.has_buckled_mobs()) + to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first.")) return //search for a valid passenger compartment @@ -1329,4 +1330,4 @@ /obj/item/mecha_parts/mecha_equipment/tool/jetpack/do_after_cooldown() sleep(equip_cooldown) wait = 0 - return 1 \ No newline at end of file + return 1 diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index d3bdbb4158..edb275a2fa 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -1142,10 +1142,12 @@ to_chat(usr,"Access denied") src.log_append_to_last("Permission denied.") return - for(var/mob/living/simple_animal/slime/M in range(1,usr)) - if(M.victim == usr) - to_chat(usr,"You're too busy getting your life sucked out of you.") + if(isliving(usr)) + var/mob/living/L = usr + if(L.has_buckled_mobs()) + to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first.")) return + // usr << "You start climbing into [src.name]" visible_message("\The [usr] starts to climb into [src.name]") diff --git a/code/game/mecha/mecha_wreckage.dm b/code/game/mecha/mecha_wreckage.dm index 73cee1223b..bc20f25b2e 100644 --- a/code/game/mecha/mecha_wreckage.dm +++ b/code/game/mecha/mecha_wreckage.dm @@ -99,6 +99,14 @@ name = "Dark Gygax wreckage" icon_state = "darkgygax-broken" +/obj/effect/decal/mecha_wreckage/gygax/adv + name = "Advanced Dark Gygax wreckage" + icon_state = "darkgygax_adv-broken" + +/obj/effect/decal/mecha_wreckage/gygax/medgax + name = "Medgax wreckage" + icon_state = "medgax-broken" + /obj/effect/decal/mecha_wreckage/marauder name = "Marauder wreckage" icon_state = "marauder-broken" @@ -198,6 +206,9 @@ parts -= part return +/obj/effect/decal/mecha_wreckage/odysseus/murdysseus + icon_state = "murdysseus-broken" + /obj/effect/decal/mecha_wreckage/hoverpod name = "Hover pod wreckage" icon_state = "engineering_pod-broken" diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index 8a1242d1a2..f7e886dd62 100644 --- a/code/game/mecha/medical/odysseus.dm +++ b/code/game/mecha/medical/odysseus.dm @@ -109,7 +109,7 @@ else if(foundVirus) holder.icon_state = "hudill" else if(patient.has_brain_worms()) - var/mob/living/simple_animal/borer/B = patient.has_brain_worms() + var/mob/living/simple_mob/animal/borer/B = patient.has_brain_worms() if(B.controlling) holder.icon_state = "hudbrainworm" else diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm index 7504b6e65f..056d9f7295 100644 --- a/code/game/objects/effects/overlays.dm +++ b/code/game/objects/effects/overlays.dm @@ -99,3 +99,13 @@ mouse_opacity = FALSE anchored = TRUE plane = ABOVE_PLANE + +// Similar to the tesla ball but doesn't actually do anything and is purely visual. +/obj/effect/overlay/energy_ball + name = "energy ball" + desc = "An energy ball." + icon = 'icons/obj/tesla_engine/energy_ball.dmi' + icon_state = "energy_ball" + plane = PLANE_LIGHTING_ABOVE + pixel_x = -32 + pixel_y = -32 diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 8e482cc1cb..f82bcefaa3 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -56,13 +56,15 @@ /obj/effect/spider/stickyweb icon_state = "stickyweb1" - New() - if(prob(50)) - icon_state = "stickyweb2" + +/obj/effect/spider/stickyweb/initialize() + if(prob(50)) + icon_state = "stickyweb2" + return ..() /obj/effect/spider/stickyweb/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) if(air_group || (height==0)) return 1 - if(istype(mover, /mob/living/simple_animal/hostile/giant_spider)) + if(istype(mover, /mob/living/simple_mob/animal/giant_spider)) return 1 else if(istype(mover, /mob/living)) if(prob(50)) @@ -80,10 +82,12 @@ var/spiders_min = 6 var/spiders_max = 24 var/spider_type = /obj/effect/spider/spiderling - New() - pixel_x = rand(3,-3) - pixel_y = rand(3,-3) - processing_objects |= src + +/obj/effect/spider/eggcluster/initialize() + pixel_x = rand(3,-3) + pixel_y = rand(3,-3) + processing_objects |= src + return ..() /obj/effect/spider/eggcluster/New(var/location, var/atom/parent) get_light_and_color(parent) @@ -129,10 +133,10 @@ var/amount_grown = -1 var/obj/machinery/atmospherics/unary/vent_pump/entry_vent var/travelling_in_vent = 0 - var/list/grow_as = list(/mob/living/simple_animal/hostile/giant_spider, /mob/living/simple_animal/hostile/giant_spider/nurse, /mob/living/simple_animal/hostile/giant_spider/hunter) + var/list/grow_as = list(/mob/living/simple_mob/animal/giant_spider, /mob/living/simple_mob/animal/giant_spider/nurse, /mob/living/simple_mob/animal/giant_spider/hunter) /obj/effect/spider/spiderling/frost - grow_as = list(/mob/living/simple_animal/hostile/giant_spider/frost) + grow_as = list(/mob/living/simple_mob/animal/giant_spider/frost) /obj/effect/spider/spiderling/New(var/location, var/atom/parent) pixel_x = rand(6,-6) diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index c2af338521..d700008220 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -107,7 +107,7 @@ return 0 if(!user.IsAdvancedToolUser() && isanimal(user)) - var/mob/living/simple_animal/S = user + var/mob/living/simple_mob/S = user if(!S.IsHumanoidToolUser(src)) return 0 diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 343a58ce1a..c35ec18e59 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -166,7 +166,7 @@ outmsg = "You missed the lens of [C] with [src]." //cats! - for(var/mob/living/simple_animal/cat/C in viewers(1,targloc)) + for(var/mob/living/simple_mob/animal/passive/cat/C in viewers(1,targloc)) if (!(C.stat || C.buckled)) if(prob(50) && !(C.client)) C.visible_message("[C] pounces on the light!", "You pounce on the light!") diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index feed24510b..97d352c3d7 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -451,15 +451,15 @@ HALOGEN COUNTER - Radcount on mobs matter = list(DEFAULT_WALL_MATERIAL = 30,"glass" = 20) /obj/item/device/slime_scanner/attack(mob/living/M as mob, mob/living/user as mob) - if(!isslime(M)) - to_chat(user, "This device can only scan slimes!") + if(!istype(M, /mob/living/simple_mob/slime/xenobio)) + to_chat(user, "This device can only scan lab-grown slimes!") return - var/mob/living/simple_animal/slime/S = M + var/mob/living/simple_mob/slime/xenobio/S = M user.show_message("Slime scan results:
[S.slime_color] [S.is_adult ? "adult" : "baby"] slime
Health: [S.health]
Mutation Probability: [S.mutation_chance]") var/list/mutations = list() for(var/potential_color in S.slime_mutation) - var/mob/living/simple_animal/slime/slime = potential_color + var/mob/living/simple_mob/slime/xenobio/slime = potential_color mutations.Add(initial(slime.slime_color)) user.show_message("Potental to mutate into [english_list(mutations)] colors.
Extract potential: [S.cores]
Nutrition: [S.nutrition]/[S.get_max_nutrition()]") @@ -469,12 +469,14 @@ HALOGEN COUNTER - Radcount on mobs user.show_message("Warning: Subject is hungry.") user.show_message("Electric change strength: [S.power_charge]") - if(S.resentment) - user.show_message("Warning: Subject is harboring resentment.") - if(S.docile) + if(S.has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = S.ai_holder + if(AI.resentment) + user.show_message("Warning: Subject is harboring resentment.") + if(AI.rabid) + user.show_message("Subject is enraged and extremely dangerous!") + if(S.harmless) user.show_message("Subject has been pacified.") - if(S.rabid) - user.show_message("Subject is enraged and extremely dangerous!") if(S.unity) user.show_message("Subject is friendly to other slime colors.") diff --git a/code/game/objects/items/glassjar.dm b/code/game/objects/items/glassjar.dm index c04b9b17dd..5de7ae3730 100644 --- a/code/game/objects/items/glassjar.dm +++ b/code/game/objects/items/glassjar.dm @@ -6,7 +6,7 @@ w_class = ITEMSIZE_SMALL matter = list("glass" = 200) flags = NOBLUDGEON - var/list/accept_mobs = list(/mob/living/simple_animal/lizard, /mob/living/simple_animal/mouse) + var/list/accept_mobs = list(/mob/living/simple_mob/animal/passive/lizard, /mob/living/simple_mob/animal/passive/mouse) var/contains = 0 // 0 = nothing, 1 = money, 2 = animal, 3 = spiderling /obj/item/glass_jar/New() diff --git a/code/game/objects/items/poi_items.dm b/code/game/objects/items/poi_items.dm index af1b6c9c7a..30b6e12763 100644 --- a/code/game/objects/items/poi_items.dm +++ b/code/game/objects/items/poi_items.dm @@ -47,14 +47,3 @@ processing_objects -= src return ..() - -//Crashed Cargo Shuttle PoI - -/obj/structure/largecrate/animal/crashedshuttle - name = "SCP" - -/obj/structure/largecrate/animal/crashedshuttle/initialize() - starts_with = list(pick(/mob/living/simple_animal/hostile/statue, /obj/item/cursed_marble, /obj/item/weapon/deadringer)) // Starts_with has to be a list - name = pick("Spicy Crust Pizzeria", "Soap and Care Products", "Sally's Computer Parts", "Steve's Chocolate Pastries", "Smith & Christian's Plastics","Standard Containers & Packaging Co.", "Sanitary Chemical Purgation (LTD)") - name += " delivery crate" - return ..() diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm index 2fa9ea63c7..180e261fc2 100644 --- a/code/game/objects/items/robot/robot_parts.dm +++ b/code/game/objects/items/robot/robot_parts.dm @@ -275,13 +275,6 @@ add_flashes(W,user) else add_flashes(W,user) - else if(istype(W, /obj/item/weapon/stock_parts/manipulator)) - to_chat(user, "You install some manipulators and modify the head, creating a functional spider-bot!") - new /mob/living/simple_animal/spiderbot(get_turf(loc)) - user.drop_item() - qdel(W) - qdel(src) - return return /obj/item/robot_parts/head/proc/add_flashes(obj/item/W as obj, mob/user as mob) //Made into a seperate proc to avoid copypasta diff --git a/code/game/objects/items/weapons/AI_modules.dm b/code/game/objects/items/weapons/AI_modules.dm old mode 100755 new mode 100644 index c5b637d9e5..13c4b29d45 --- a/code/game/objects/items/weapons/AI_modules.dm +++ b/code/game/objects/items/weapons/AI_modules.dm @@ -23,7 +23,7 @@ AI MODULES /obj/item/weapon/aiModule/proc/install(var/atom/movable/AM, var/mob/living/user) if(!user.IsAdvancedToolUser() && isanimal(user)) - var/mob/living/simple_animal/S = user + var/mob/living/simple_mob/S = user if(!S.IsHumanoidToolUser(src)) return 0 diff --git a/code/game/objects/items/weapons/grenades/spawnergrenade.dm b/code/game/objects/items/weapons/grenades/spawnergrenade.dm index 5a20c49ccd..a6ad6f4592 100644 --- a/code/game/objects/items/weapons/grenades/spawnergrenade.dm +++ b/code/game/objects/items/weapons/grenades/spawnergrenade.dm @@ -31,19 +31,25 @@ /obj/item/weapon/grenade/spawnergrenade/manhacks name = "manhack delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/viscerator + spawner_type = /mob/living/simple_mob/mechanical/viscerator deliveryamt = 5 origin_tech = list(TECH_MATERIAL = 3, TECH_MAGNET = 4, TECH_ILLEGAL = 4) +/obj/item/weapon/grenade/spawnergrenade/manhacks/mercenary + spawner_type = /mob/living/simple_mob/mechanical/viscerator/mercenary + +/obj/item/weapon/grenade/spawnergrenade/manhacks/raider + spawner_type = /mob/living/simple_mob/mechanical/viscerator/raider + /obj/item/weapon/grenade/spawnergrenade/spesscarp name = "carp delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/carp + spawner_type = /mob/living/simple_mob/animal/space/carp deliveryamt = 5 origin_tech = list(TECH_MATERIAL = 3, TECH_MAGNET = 4, TECH_ILLEGAL = 4) /obj/item/weapon/grenade/spawnergrenade/spider name = "spider delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/giant_spider/hunter + spawner_type = /mob/living/simple_mob/animal/giant_spider/hunter deliveryamt = 3 origin_tech = list(TECH_MATERIAL = 3, TECH_MAGNET = 4, TECH_ILLEGAL = 4) diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm index 40c074c939..f3e5d05fdf 100644 --- a/code/game/objects/items/weapons/handcuffs.dm +++ b/code/game/objects/items/weapons/handcuffs.dm @@ -336,4 +336,4 @@ var/last_chew = 0 target.m_intent = "walk" if(target.hud_used && user.hud_used.move_intent) target.hud_used.move_intent.icon_state = "walking" - return 1 \ No newline at end of file + return 1 diff --git a/code/game/objects/items/weapons/implants/implantchair.dm b/code/game/objects/items/weapons/implants/implantchair.dm index c05d52442c..5b1ba3f6c5 100644 --- a/code/game/objects/items/weapons/implants/implantchair.dm +++ b/code/game/objects/items/weapons/implants/implantchair.dm @@ -79,10 +79,9 @@ var/obj/item/weapon/grab/grab = G if(!ismob(grab.affecting)) return - for(var/mob/living/simple_animal/slime/M in range(1,grab.affecting)) - if(M.victim == grab.affecting) - usr << "[grab.affecting.name] will not fit into the [src.name] because they have a slime latched onto their head." - return + if(grab.affecting.has_buckled_mobs()) + to_chat(user, span("warning", "\The [grab.affecting] has other entities attached to them. Remove them first.")) + return var/mob/M = grab.affecting if(put_mob(M)) qdel(G) diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index ad30eb0a57..d70de28a7e 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -233,11 +233,10 @@ playsound(get_turf(target), 'sound/weapons/blade1.ogg', 100, 1) // Make lesser robots really mad at us. - if(istype(target, /mob/living/simple_animal)) - var/mob/living/simple_animal/SA = target - if(SA.intelligence_level == SA_ROBOTIC) - SA.taunt(user) - SA.adjustFireLoss(force * 6) // 30 Burn, for 50 total. + if(target.mob_class & MOB_CLASS_SYNTHETIC) + if(target.has_AI()) + target.taunt(user) + target.adjustFireLoss(force * 6) // 30 Burn, for 50 total. /* *Energy Blade diff --git a/code/game/objects/items/weapons/policetape.dm b/code/game/objects/items/weapons/policetape.dm index aec6a790b2..d9a886029d 100644 --- a/code/game/objects/items/weapons/policetape.dm +++ b/code/game/objects/items/weapons/policetape.dm @@ -292,7 +292,7 @@ var/list/tape_roll_applications = list() add_fingerprint(M) if (!allowed(M)) //only select few learn art of not crumpling the tape M << "You are not supposed to go past [src]..." - if(M.a_intent == I_HELP && !(istype(M, /mob/living/simple_animal))) + if(M.a_intent == I_HELP && !(istype(M, /mob/living/simple_mob))) return 0 crumple() return ..(mover) diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm index e37b2f9977..53c092d923 100644 --- a/code/game/objects/items/weapons/stunbaton.dm +++ b/code/game/objects/items/weapons/stunbaton.dm @@ -269,9 +269,8 @@ /obj/item/weapon/melee/baton/shocker/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone) ..(target, user, hit_zone) - if(istype(target, /mob/living/simple_animal) && status) - var/mob/living/simple_animal/SA = target - SA.taunt(user) + if(status && target.has_AI()) + target.taunt(user) // Borg version, for the lost module. /obj/item/weapon/melee/baton/shocker/robot diff --git a/code/game/objects/items/weapons/tools/screwdriver.dm b/code/game/objects/items/weapons/tools/screwdriver.dm index 86ac8f1267..249f49f529 100644 --- a/code/game/objects/items/weapons/tools/screwdriver.dm +++ b/code/game/objects/items/weapons/tools/screwdriver.dm @@ -122,4 +122,4 @@ counterpart.forceMove(get_turf(src)) src.forceMove(counterpart) user.put_in_active_hand(counterpart) - to_chat(user, "You attach the bolt driver bit to [src].") \ No newline at end of file + to_chat(user, "You attach the bolt driver bit to [src].") diff --git a/code/game/objects/items/weapons/tools/weldingtool.dm b/code/game/objects/items/weapons/tools/weldingtool.dm index 7c2f2ea26f..9cc09b4770 100644 --- a/code/game/objects/items/weapons/tools/weldingtool.dm +++ b/code/game/objects/items/weapons/tools/weldingtool.dm @@ -1,3 +1,7 @@ +<<<<<<< HEAD +======= + +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync #define WELDER_FUEL_BURN_INTERVAL 13 /* * Welding Tool @@ -317,7 +321,10 @@ var/obj/item/organ/internal/eyes/E = H.internal_organs_by_name[O_EYES] if(!E) return +<<<<<<< HEAD if(H.nif && H.nif.flag_check(NIF_V_UVFILTER,NIF_FLAGS_VISION)) return //VOREStation Add - NIF +======= +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync switch(safety) if(1) to_chat(usr, "Your eyes sting a little.") diff --git a/code/game/objects/items/weapons/tools/wrench.dm b/code/game/objects/items/weapons/tools/wrench.dm index e0a6957c3c..13cf3e51cf 100644 --- a/code/game/objects/items/weapons/tools/wrench.dm +++ b/code/game/objects/items/weapons/tools/wrench.dm @@ -68,4 +68,4 @@ counterpart.forceMove(get_turf(src)) src.forceMove(counterpart) user.put_in_active_hand(counterpart) - to_chat(user, "You attach the screw driver bit to [src].") \ No newline at end of file + to_chat(user, "You attach the screw driver bit to [src].") diff --git a/code/game/objects/random/mob.dm b/code/game/objects/random/mob.dm index 6daa10c0f9..966498a2bd 100644 --- a/code/game/objects/random/mob.dm +++ b/code/game/objects/random/mob.dm @@ -18,44 +18,47 @@ var/mob_retaliate = 0 /obj/random/mob/item_to_spawn() - return pick(prob(10);/mob/living/simple_animal/lizard, - prob(6);/mob/living/simple_animal/retaliate/diyaab, - prob(10);/mob/living/simple_animal/cat/fluff, - prob(6);/mob/living/simple_animal/cat/kitten, - prob(10);/mob/living/simple_animal/corgi, - prob(6);/mob/living/simple_animal/corgi/puppy, - prob(10);/mob/living/simple_animal/crab, - prob(10);/mob/living/simple_animal/chicken, - prob(6);/mob/living/simple_animal/chick, - prob(10);/mob/living/simple_animal/cow, - prob(6);/mob/living/simple_animal/retaliate/goat, - prob(10);/mob/living/simple_animal/penguin, - prob(10);/mob/living/simple_animal/mouse, - prob(10);/mob/living/simple_animal/yithian, - prob(10);/mob/living/simple_animal/tindalos, - prob(10);/mob/living/simple_animal/corgi/tamaskan, - prob(3);/mob/living/simple_animal/parrot, - prob(1);/mob/living/simple_animal/giant_crab) + return pick(prob(10);/mob/living/simple_mob/animal/passive/lizard, + prob(6);/mob/living/simple_mob/animal/sif/diyaab, + prob(10);/mob/living/simple_mob/animal/passive/cat, + prob(6);/mob/living/simple_mob/animal/passive/cat, + prob(10);/mob/living/simple_mob/animal/passive/dog/corgi, + prob(6);/mob/living/simple_mob/animal/passive/dog/corgi/puppy, + prob(10);/mob/living/simple_mob/animal/passive/crab, + prob(10);/mob/living/simple_mob/animal/passive/chicken, + prob(6);/mob/living/simple_mob/animal/passive/chick, + prob(10);/mob/living/simple_mob/animal/passive/cow, + prob(6);/mob/living/simple_mob/animal/goat, + prob(10);/mob/living/simple_mob/animal/passive/penguin, + prob(10);/mob/living/simple_mob/animal/passive/mouse, + prob(10);/mob/living/simple_mob/animal/passive/yithian, + prob(10);/mob/living/simple_mob/animal/passive/tindalos, + prob(10);/mob/living/simple_mob/animal/passive/dog/tamaskan, + prob(3);/mob/living/simple_mob/animal/passive/bird/parrot, + prob(1);/mob/living/simple_mob/animal/passive/crab) /obj/random/mob/spawn_item() //These should only ever have simple mobs. var/build_path = item_to_spawn() - var/mob/living/simple_animal/M = new build_path(src.loc) - M.ai_inactive = 1 //Don't fight eachother while we're still setting up! - if(mob_faction) - M.faction = mob_faction - M.returns_home = mob_returns_home - M.wander = mob_wander - M.wander_distance = mob_wander_distance - if(overwrite_hostility) - M.hostile = mob_hostile - M.retaliate = mob_retaliate - M.ai_inactive = 0 //Now you can kill eachother if your faction didn't override. + var/mob/living/simple_mob/M = new build_path(src.loc) + if(!istype(M)) + return + if(M.has_AI()) + var/datum/ai_holder/AI = M.ai_holder + AI.go_sleep() //Don't fight eachother while we're still setting up! + AI.returns_home = mob_returns_home + AI.wander = mob_wander + AI.max_home_distance = mob_wander_distance + if(overwrite_hostility) + AI.hostile = mob_hostile + AI.retaliate = mob_retaliate + AI.go_wake() //Now you can kill eachother if your faction didn't override. if(pixel_x || pixel_y) M.pixel_x = pixel_x M.pixel_y = pixel_y + /obj/random/mob/sif name = "Random Sif Animal" desc = "This is a random cold weather animal." @@ -65,14 +68,14 @@ mob_wander_distance = 10 /obj/random/mob/sif/item_to_spawn() - return pick(prob(30);/mob/living/simple_animal/retaliate/diyaab, - prob(15);/mob/living/simple_animal/crab, - prob(15);/mob/living/simple_animal/penguin, - prob(15);/mob/living/simple_animal/mouse, - prob(15);/mob/living/simple_animal/corgi/tamaskan, - prob(2);/mob/living/simple_animal/hostile/giant_spider/frost, - prob(1);/mob/living/simple_animal/hostile/goose, - prob(20);/mob/living/simple_animal/giant_crab) + return pick(prob(30);/mob/living/simple_mob/animal/sif/diyaab, + prob(15);/mob/living/simple_mob/animal/passive/crab, + prob(15);/mob/living/simple_mob/animal/passive/penguin, + prob(15);/mob/living/simple_mob/animal/passive/mouse, + prob(15);/mob/living/simple_mob/animal/passive/dog/tamaskan, + prob(2);/mob/living/simple_mob/animal/giant_spider/frost, + prob(1);/mob/living/simple_mob/animal/space/goose, + prob(20);/mob/living/simple_mob/animal/passive/crab) /obj/random/mob/sif/peaceful @@ -84,12 +87,12 @@ mob_wander_distance = 12 /obj/random/mob/sif/peaceful/item_to_spawn() - return pick(prob(30);/mob/living/simple_animal/retaliate/diyaab, - prob(15);/mob/living/simple_animal/crab, - prob(15);/mob/living/simple_animal/penguin, - prob(15);/mob/living/simple_animal/mouse, - prob(15);/mob/living/simple_animal/corgi/tamaskan, - prob(20);/mob/living/simple_animal/giant_crab) + return pick(prob(30);/mob/living/simple_mob/animal/sif/diyaab, + prob(15);/mob/living/simple_mob/animal/passive/crab, + prob(15);/mob/living/simple_mob/animal/passive/penguin, + prob(15);/mob/living/simple_mob/animal/passive/mouse, + prob(15);/mob/living/simple_mob/animal/passive/dog/tamaskan, + prob(20);/mob/living/simple_mob/animal/sif/hooligan_crab) /obj/random/mob/sif/hostile name = "Random Hostile Sif Animal" @@ -97,9 +100,9 @@ icon_state = "frost" /obj/random/mob/sif/hostile/item_to_spawn() - return pick(prob(22);/mob/living/simple_animal/hostile/savik, - prob(33);/mob/living/simple_animal/hostile/giant_spider/frost, - prob(45);/mob/living/simple_animal/hostile/shantak) + return pick(prob(22);/mob/living/simple_mob/animal/sif/savik, + prob(33);/mob/living/simple_mob/animal/giant_spider/frost, + prob(45);/mob/living/simple_mob/animal/sif/shantak) /obj/random/mob/spider name = "Random Spider" //Spiders should patrol where they spawn. @@ -110,9 +113,9 @@ mob_wander_distance = 4 /obj/random/mob/spider/item_to_spawn() - return pick(prob(22);/mob/living/simple_animal/hostile/giant_spider/nurse, - prob(33);/mob/living/simple_animal/hostile/giant_spider/hunter, - prob(45);/mob/living/simple_animal/hostile/giant_spider) + return pick(prob(22);/mob/living/simple_mob/animal/giant_spider/nurse, + prob(33);/mob/living/simple_mob/animal/giant_spider/hunter, + prob(45);/mob/living/simple_mob/animal/giant_spider) /obj/random/mob/spider/nurse name = "Random Nurse Spider" @@ -123,8 +126,8 @@ mob_wander_distance = 4 /obj/random/mob/spider/nurse/item_to_spawn() - return pick(prob(22);/mob/living/simple_animal/hostile/giant_spider/nurse/hat, - prob(45);/mob/living/simple_animal/hostile/giant_spider/nurse) + return pick(prob(22);/mob/living/simple_mob/animal/giant_spider/nurse/hat, + prob(45);/mob/living/simple_mob/animal/giant_spider/nurse) /obj/random/mob/spider/mutant name = "Random Mutant Spider" @@ -133,15 +136,15 @@ /obj/random/mob/spider/mutant/item_to_spawn() return pick(prob(5);/obj/random/mob/spider, - prob(10);/mob/living/simple_animal/hostile/giant_spider/webslinger, - prob(10);/mob/living/simple_animal/hostile/giant_spider/carrier, - prob(33);/mob/living/simple_animal/hostile/giant_spider/lurker, - prob(33);/mob/living/simple_animal/hostile/giant_spider/tunneler, - prob(40);/mob/living/simple_animal/hostile/giant_spider/pepper, - prob(20);/mob/living/simple_animal/hostile/giant_spider/thermic, - prob(40);/mob/living/simple_animal/hostile/giant_spider/electric, - prob(1);/mob/living/simple_animal/hostile/giant_spider/phorogenic, - prob(40);/mob/living/simple_animal/hostile/giant_spider/frost) + prob(10);/mob/living/simple_mob/animal/giant_spider/webslinger, + prob(10);/mob/living/simple_mob/animal/giant_spider/carrier, + prob(33);/mob/living/simple_mob/animal/giant_spider/lurker, + prob(33);/mob/living/simple_mob/animal/giant_spider/tunneler, + prob(40);/mob/living/simple_mob/animal/giant_spider/pepper, + prob(20);/mob/living/simple_mob/animal/giant_spider/thermic, + prob(40);/mob/living/simple_mob/animal/giant_spider/electric, + prob(1);/mob/living/simple_mob/animal/giant_spider/phorogenic, + prob(40);/mob/living/simple_mob/animal/giant_spider/frost) /obj/random/mob/robotic name = "Random Robot Mob" @@ -158,17 +161,18 @@ mob_retaliate = 1 /obj/random/mob/robotic/item_to_spawn() //Hivebots have a total number of 'lots' equal to the lesser drone, at 60. - return pick(prob(60);/mob/living/simple_animal/hostile/malf_drone/lesser, - prob(50);/mob/living/simple_animal/hostile/malf_drone, - prob(15);/mob/living/simple_animal/hostile/mecha/malf_drone, - prob(10);/mob/living/simple_animal/hostile/hivebot, - prob(15);/mob/living/simple_animal/hostile/hivebot/swarm, - prob(10);/mob/living/simple_animal/hostile/hivebot/range, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/rapid, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/ion, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/laser, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/strong, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/guard) + return pick(prob(60);/mob/living/simple_mob/mechanical/combat_drone/lesser, + prob(50);/mob/living/simple_mob/mechanical/combat_drone, + prob(15);/mob/living/simple_mob/mechanical/mecha/ripley, + prob(15);/mob/living/simple_mob/mechanical/mecha/odysseus, + prob(10);/mob/living/simple_mob/mechanical/hivebot, + prob(15);/mob/living/simple_mob/mechanical/hivebot/swarm, + prob(10);/mob/living/simple_mob/mechanical/hivebot/ranged_damage, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/rapid, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/ion, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/laser, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong/guard) /obj/random/mob/robotic/hivebot name = "Random Hivebot" @@ -178,14 +182,14 @@ mob_faction = "hivebot" /obj/random/mob/robotic/hivebot/item_to_spawn() - return pick(prob(10);/mob/living/simple_animal/hostile/hivebot, - prob(15);/mob/living/simple_animal/hostile/hivebot/swarm, - prob(10);/mob/living/simple_animal/hostile/hivebot/range, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/rapid, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/ion, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/laser, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/strong, - prob(5);/mob/living/simple_animal/hostile/hivebot/range/guard) + return pick(prob(10);/mob/living/simple_mob/mechanical/hivebot, + prob(15);/mob/living/simple_mob/mechanical/hivebot/swarm, + prob(10);/mob/living/simple_mob/mechanical/hivebot/ranged_damage, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/rapid, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/ion, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/laser, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong, + prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong/guard) //Mice @@ -195,7 +199,7 @@ icon_state = "mouse_gray" /obj/random/mob/mouse/item_to_spawn() - return pick(prob(15);/mob/living/simple_animal/mouse/white, - prob(30);/mob/living/simple_animal/mouse/brown, - prob(30);/mob/living/simple_animal/mouse/gray, + return pick(prob(15);/mob/living/simple_mob/animal/passive/mouse/white, + prob(30);/mob/living/simple_mob/animal/passive/mouse/brown, + prob(30);/mob/living/simple_mob/animal/passive/mouse/gray, prob(25);/obj/random/mouseremains) //because figuring out how to come up with it picking nothing is beyond my coding ability. diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index faa9f024f0..370b9b866e 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -176,8 +176,8 @@ return 0 return 1 -/obj/structure/attack_generic(var/mob/user, var/damage, var/attack_verb, var/wallbreaker) - if(!breakable || damage < 10 || !wallbreaker) +/obj/structure/attack_generic(var/mob/user, var/damage, var/attack_verb) + if(!breakable || damage < STRUCTURE_MIN_DAMAGE_THRESHOLD) return 0 visible_message("[user] [attack_verb] the [src] apart!") user.do_attack_animation(src) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 4a392c0bfe..1f6ca8395f 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -381,8 +381,8 @@ else icon_state = icon_opened -/obj/structure/closet/attack_generic(var/mob/user, var/damage, var/attack_message = "destroys", var/wallbreaker) - if(damage < 10 || !wallbreaker) +/obj/structure/closet/attack_generic(var/mob/user, var/damage, var/attack_message = "destroys") + if(damage < STRUCTURE_MIN_DAMAGE_THRESHOLD) return user.do_attack_animation(src) visible_message("[user] [attack_message] the [src]!") @@ -459,4 +459,4 @@ if(src.loc) if(istype(src.loc, /obj/structure/closet)) return (loc.return_air_for_internal_lifeform(L)) - return return_air() \ No newline at end of file + return return_air() diff --git a/code/game/objects/structures/crates_lockers/largecrate.dm b/code/game/objects/structures/crates_lockers/largecrate.dm index 2c739985b2..91642e434e 100644 --- a/code/game/objects/structures/crates_lockers/largecrate.dm +++ b/code/game/objects/structures/crates_lockers/largecrate.dm @@ -90,23 +90,23 @@ /obj/structure/largecrate/animal/corgi name = "corgi carrier" - starts_with = list(/mob/living/simple_animal/corgi) + starts_with = list(/mob/living/simple_mob/animal/passive/dog/corgi) /obj/structure/largecrate/animal/cow name = "cow crate" - starts_with = list(/mob/living/simple_animal/cow) + starts_with = list(/mob/living/simple_mob/animal/passive/cow) /obj/structure/largecrate/animal/goat name = "goat crate" - starts_with = list(/mob/living/simple_animal/retaliate/goat) + starts_with = list(/mob/living/simple_mob/animal/goat) /obj/structure/largecrate/animal/cat name = "cat carrier" - starts_with = list(/mob/living/simple_animal/cat) + starts_with = list(/mob/living/simple_mob/animal/passive/cat) /obj/structure/largecrate/animal/cat/bones - starts_with = list(/mob/living/simple_animal/cat/fluff/bones) + starts_with = list(/mob/living/simple_mob/animal/passive/cat/bones) /obj/structure/largecrate/animal/chick name = "chicken crate" - starts_with = list(/mob/living/simple_animal/chick = 5) + starts_with = list(/mob/living/simple_mob/animal/passive/chick = 5) diff --git a/code/game/objects/structures/ghost_pods/mysterious.dm b/code/game/objects/structures/ghost_pods/mysterious.dm index 2e6a11a8bb..cd34d91e6f 100644 --- a/code/game/objects/structures/ghost_pods/mysterious.dm +++ b/code/game/objects/structures/ghost_pods/mysterious.dm @@ -15,7 +15,7 @@ /obj/structure/ghost_pod/manual/corgi/create_occupant(var/mob/M) lightning_strike(get_turf(src), cosmetic = TRUE) density = FALSE - var/mob/living/simple_animal/corgi/R = new(get_turf(src)) + var/mob/living/simple_mob/animal/passive/dog/corgi/R = new(get_turf(src)) if(M.mind) M.mind.transfer_to(R) to_chat(M, "You are a Corgi! Woof!") @@ -47,4 +47,4 @@ R.ghost_inhabit(M) visible_message("The blade shines brightly for a brief moment as [usr] pulls it out of the stone!") log_and_message_admins("successfully acquired a cursed sword.") - ..() \ No newline at end of file + ..() diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 058398d850..4a858d5cce 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -83,8 +83,8 @@ health = (displaced_health - round(current_damage / 4)) cover = 25 -/obj/structure/girder/attack_generic(var/mob/user, var/damage, var/attack_message = "smashes apart", var/wallbreaker) - if(!damage || !wallbreaker) +/obj/structure/girder/attack_generic(var/mob/user, var/damage, var/attack_message = "smashes apart") + if(damage < STRUCTURE_MIN_DAMAGE_THRESHOLD) return 0 user.do_attack_animation(src) visible_message("[user] [attack_message] the [src]!") diff --git a/code/game/objects/structures/gravemarker.dm b/code/game/objects/structures/gravemarker.dm index 7ef0d49a51..5b39639a9e 100644 --- a/code/game/objects/structures/gravemarker.dm +++ b/code/game/objects/structures/gravemarker.dm @@ -126,7 +126,7 @@ src.set_dir(turn(src.dir, 90)) return else - if(istype(usr,/mob/living/simple_animal/mouse)) + if(ismouse(usr)) return if(!usr || !isturf(usr.loc)) return diff --git a/code/game/objects/structures/loot_piles.dm b/code/game/objects/structures/loot_piles.dm index f8d9977823..44f11b73fe 100644 --- a/code/game/objects/structures/loot_piles.dm +++ b/code/game/objects/structures/loot_piles.dm @@ -574,6 +574,7 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh icon = 'icons/mecha/mecha.dmi' icon_state = "engineering_pod-broken" density = TRUE + anchored = FALSE // In case a dead mecha-mob dies in a bad spot. chance_uncommon = 20 chance_rare = 10 @@ -615,7 +616,7 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh /obj/structure/loot_pile/mecha/ripley name = "ripley wreckage" desc = "The ruins of some unfortunate ripley. Perhaps something is salvageable." - icon_states_to_use = list("ripley-broken", "firefighter-broken", "ripley-broken-old") + icon_state = "ripley-broken" common_loot = list( /obj/random/tool, @@ -649,6 +650,12 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh /obj/item/mecha_parts/mecha_equipment/weapon/energy/flamer/rigged ) +/obj/structure/loot_pile/mecha/ripley/firefighter + icon_state = "firefighter-broken" + +/obj/structure/loot_pile/mecha/ripley/random_sprite + icon_states_to_use = list("ripley-broken", "firefighter-broken", "ripley-broken-old") + //Death-Ripley, same common, but more combat-exosuit-based /obj/structure/loot_pile/mecha/deathripley name = "strange ripley wreckage" @@ -719,6 +726,14 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh /obj/item/mecha_parts/mecha_equipment/shocker ) +/obj/structure/loot_pile/mecha/odysseus/murdysseus + icon_state = "murdysseus-broken" + +/obj/structure/loot_pile/mecha/hoverpod + name = "hoverpod wreckage" + desc = "The ruins of some unfortunate hoverpod. Perhaps something is salvageable." + icon_state = "engineering_pod" + /obj/structure/loot_pile/mecha/gygax name = "gygax wreckage" desc = "The ruins of some unfortunate gygax. Perhaps something is salvageable." @@ -759,6 +774,18 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh /obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy ) +/obj/structure/loot_pile/mecha/gygax/dark + icon_state = "darkgygax-broken" + +// Todo: Better loot. +/obj/structure/loot_pile/mecha/gygax/dark/adv + icon_state = "darkgygax_adv-broken" + icon_scale = 1.5 + pixel_y = 8 + +/obj/structure/loot_pile/mecha/gygax/medgax + icon_state = "medgax-broken" + /obj/structure/loot_pile/mecha/durand name = "durand wreckage" desc = "The ruins of some unfortunate durand. Perhaps something is salvageable." @@ -799,6 +826,22 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh /obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy ) +/obj/structure/loot_pile/mecha/marauder + name = "marauder wreckage" + desc = "The ruins of some unfortunate marauder. Perhaps something is salvagable." + icon_state = "marauder-broken" + // Todo: Better loot. + +/obj/structure/loot_pile/mecha/marauder/seraph + name = "seraph wreckage" + desc = "The ruins of some unfortunate seraph. Perhaps something is salvagable." + icon_state = "seraph-broken" + +/obj/structure/loot_pile/mecha/marauder/mauler + name = "mauler wreckage" + desc = "The ruins of some unfortunate mauler. Perhaps something is salvagable." + icon_state = "mauler-broken" + /obj/structure/loot_pile/mecha/phazon name = "phazon wreckage" desc = "The ruins of some unfortunate phazon. Perhaps something is salvageable." diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm index 95d2656ed5..26889106ac 100644 --- a/code/game/objects/structures/musician.dm +++ b/code/game/objects/structures/musician.dm @@ -321,7 +321,7 @@ set category = "Object" set src in oview(1) - if(istype(usr,/mob/living/simple_animal/mouse)) + if(istype(usr,/mob/living/simple_mob/animal/passive/mouse)) return else if(!usr || !isturf(usr.loc)) return diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm index 0340356e37..f328eb54fa 100644 --- a/code/game/objects/structures/plasticflaps.dm +++ b/code/game/objects/structures/plasticflaps.dm @@ -10,8 +10,8 @@ explosion_resistance = 5 var/list/mobs_can_pass = list( /mob/living/bot, - /mob/living/simple_animal/slime, - /mob/living/simple_animal/mouse, + /mob/living/simple_mob/slime/xenobio, + /mob/living/simple_mob/animal/passive/mouse, /mob/living/silicon/robot/drone ) diff --git a/code/game/objects/structures/props/nest.dm b/code/game/objects/structures/props/nest.dm index b6a4677f12..4b54d010cf 100644 --- a/code/game/objects/structures/props/nest.dm +++ b/code/game/objects/structures/props/nest.dm @@ -11,7 +11,7 @@ var/last_spawn var/spawn_delay = 150 var/randomize_spawning = FALSE - var/creature_types = list(/mob/living/simple_animal/retaliate/diyaab) + var/creature_types = list(/mob/living/simple_mob/animal/sif/diyaab) var/list/den_mobs var/den_faction //The faction of any spawned creatures. var/max_creatures = 3 //Maximum number of living creatures this nest can have at one time. diff --git a/code/game/objects/structures/simple_doors.dm b/code/game/objects/structures/simple_doors.dm index 4f430d3ec6..23e2ac1f76 100644 --- a/code/game/objects/structures/simple_doors.dm +++ b/code/game/objects/structures/simple_doors.dm @@ -206,6 +206,6 @@ /obj/structure/simple_door/cult/TryToSwitchState(atom/user) if(isliving(user)) var/mob/living/L = user - if(!iscultist(L) && !istype(L, /mob/living/simple_animal/construct)) + if(!iscultist(L) && !istype(L, /mob/living/simple_mob/construct)) return ..() diff --git a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm index 3e76682b19..f1c8217210 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm @@ -77,7 +77,7 @@ src.set_dir(turn(src.dir, 90)) return else - if(istype(usr,/mob/living/simple_animal/mouse)) + if(istype(usr,/mob/living/simple_mob/animal/passive/mouse)) return if(!usr || !isturf(usr.loc)) return diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 5c7adf8f0e..251e168293 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -206,7 +206,7 @@ user.setClickCooldown(user.get_attack_speed()) if(!damage) return - if(damage >= 10) + if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD) visible_message("[user] smashes into [src]!") if(reinf) damage = damage / 2 diff --git a/code/game/objects/weapons.dm b/code/game/objects/weapons.dm index 6cb93ab530..19fe4eb03c 100644 --- a/code/game/objects/weapons.dm +++ b/code/game/objects/weapons.dm @@ -27,16 +27,16 @@ cleaving = TRUE var/hit_mobs = 0 - for(var/mob/living/simple_animal/SA in range(get_turf(target), 1)) - if(SA.stat == DEAD) // Don't beat a dead horse. + for(var/mob/living/simple_mob/SM in range(get_turf(target), 1)) + if(SM.stat == DEAD) // Don't beat a dead horse. continue - if(SA == user) // Don't hit ourselves. Simple mobs shouldn't be able to do this but that might change later to be able to hit all mob/living-s. + if(SM == user) // Don't hit ourselves. Simple mobs shouldn't be able to do this but that might change later to be able to hit all mob/living-s. continue - if(SA == target) // We (presumably) already hit the target before cleave() was called. orange() should prevent this but just to be safe... + if(SM == target) // We (presumably) already hit the target before cleave() was called. orange() should prevent this but just to be safe... continue - if(!SA.Adjacent(user) || !SA.Adjacent(target)) // Cleaving only hits mobs near the target mob and user. + if(!SM.Adjacent(user) || !SM.Adjacent(target)) // Cleaving only hits mobs near the target mob and user. continue - if(resolve_attackby(SA, user, attack_modifier = 0.5)) // Hit them with the weapon. This won't cause recursive cleaving due to the cleaving variable being set to true. + if(resolve_attackby(SM, user, attack_modifier = 0.5)) // Hit them with the weapon. This won't cause recursive cleaving due to the cleaving variable being set to true. hit_mobs++ cleave_visual(user, target) @@ -55,4 +55,4 @@ // This is purely the visual effect of cleaving. /obj/item/weapon/proc/cleave_visual(var/mob/living/user, var/mob/living/target) var/obj/effect/temporary_effect/cleave_attack/E = new(get_turf(src)) - E.dir = get_dir(user, target) \ No newline at end of file + E.dir = get_dir(user, target) diff --git a/code/game/turfs/simulated/outdoors/outdoors.dm b/code/game/turfs/simulated/outdoors/outdoors.dm index 968816dc6a..6843a78264 100644 --- a/code/game/turfs/simulated/outdoors/outdoors.dm +++ b/code/game/turfs/simulated/outdoors/outdoors.dm @@ -132,4 +132,4 @@ var/list/turf_edge_cache = list() if(3) if(prob(66)) return - demote() \ No newline at end of file + demote() diff --git a/code/game/turfs/simulated/outdoors/snow.dm b/code/game/turfs/simulated/outdoors/snow.dm index 5fed8af66b..0f9be4ddbf 100644 --- a/code/game/turfs/simulated/outdoors/snow.dm +++ b/code/game/turfs/simulated/outdoors/snow.dm @@ -13,6 +13,9 @@ /turf/simulated/floor/outdoors/snow/Entered(atom/A) if(isliving(A)) + var/mob/living/L = A + if(L.hovering) // Flying things shouldn't make footprints. + return ..() var/mdir = "[A.dir]" crossed_dirs[mdir] = 1 update_icon() diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm index 02c6d0d8dc..bd6adfe32f 100644 --- a/code/game/turfs/simulated/wall_attacks.dm +++ b/code/game/turfs/simulated/wall_attacks.dm @@ -60,9 +60,9 @@ var/damage_lower = 25 var/damage_upper = 75 if(isanimal(user)) - var/mob/living/simple_animal/S = user + var/mob/living/simple_mob/S = user playsound(src, S.attack_sound, 75, 1) - if(!(S.melee_damage_upper >= 10)) + if(!(S.melee_damage_upper >= STRUCTURE_MIN_DAMAGE_THRESHOLD * 2)) to_chat(user, "You bounce against the wall.") return FALSE damage_lower = S.melee_damage_lower @@ -75,7 +75,7 @@ to_chat(user, "You smash through the wall!") user.do_attack_animation(src) if(isanimal(user)) - var/mob/living/simple_animal/S = user + var/mob/living/simple_mob/S = user playsound(src, S.attack_sound, 75, 1) spawn(1) dismantle_wall(1) @@ -115,12 +115,12 @@ try_touch(user, rotting) -/turf/simulated/wall/attack_generic(var/mob/user, var/damage, var/attack_message, var/wallbreaker) +/turf/simulated/wall/attack_generic(var/mob/user, var/damage, var/attack_message) radiate() user.setClickCooldown(user.get_attack_speed()) var/rotting = (locate(/obj/effect/overlay/wallrot) in src) - if(!damage || !wallbreaker) + if(damage < STRUCTURE_MIN_DAMAGE_THRESHOLD * 2) try_touch(user, rotting) return @@ -128,7 +128,7 @@ return success_smash(user) if(reinf_material) - if((wallbreaker == 2) || (damage >= max(material.hardness,reinf_material.hardness))) + if(damage >= max(material.hardness, reinf_material.hardness) ) return success_smash(user) else if(damage >= material.hardness) return success_smash(user) diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm index 6539348e5d..7ed1ad2965 100644 --- a/code/game/turfs/simulated/water.dm +++ b/code/game/turfs/simulated/water.dm @@ -9,6 +9,9 @@ edge_blending_priority = -1 movement_cost = 4 outdoors = TRUE + + layer = WATER_FLOOR_LAYER + can_dirty = FALSE // It's water var/depth = 1 // Higher numbers indicates deeper water. @@ -19,10 +22,11 @@ /turf/simulated/floor/water/update_icon() ..() // To get the edges. - icon_state = water_state - var/image/floorbed_sprite = image(icon = 'icons/turf/outdoors.dmi', icon_state = under_state) - underlays.Cut() // To clear the old underlay, so the list doesn't expand infinitely - underlays.Add(floorbed_sprite) + + icon_state = under_state // This isn't set at compile time in order for it to show as water in the map editor. + var/image/water_sprite = image(icon = 'icons/turf/outdoors.dmi', icon_state = water_state, layer = WATER_LAYER) + add_overlay(water_sprite) + update_icon_edge() /turf/simulated/floor/water/get_edge_icon_state() @@ -102,6 +106,8 @@ /mob/living/proc/check_submerged() if(buckled) return 0 + if(hovering) + return 0 var/turf/simulated/floor/water/T = loc if(istype(T)) return T.depth @@ -115,6 +121,8 @@ adjust_fire_stacks(-amount * 5) for(var/atom/movable/AM in contents) AM.water_act(amount) + remove_modifiers_of_type(/datum/modifier/fire) + inflict_water_damage(20 * amount) // Only things vulnerable to water will actually be harmed (slimes/prommies). var/list/shoreline_icon_cache = list() @@ -140,7 +148,13 @@ var/list/shoreline_icon_cache = list() var/icon/shoreline_water = icon(src.icon, "shoreline_water", src.dir) var/icon/shoreline_subtract = icon(src.icon, "[initial(icon_state)]_subtract", src.dir) shoreline_water.Blend(shoreline_subtract,ICON_SUBTRACT) + var/image/final = image(shoreline_water) + final.layer = WATER_LAYER - shoreline_icon_cache[cache_string] = shoreline_water + shoreline_icon_cache[cache_string] = final add_overlay(shoreline_icon_cache[cache_string]) +/turf/simulated/floor/water/is_safe_to_enter(mob/living/L) + if(L.get_water_protection() < 1) + return FALSE + return ..() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 0c18524346..005c8ac727 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -323,6 +323,10 @@ var/const/enterloopsanity = 100 /turf/AllowDrop() return TRUE +// Returns false if stepping into a tile would cause harm (e.g. open space while unable to fly, water tile while a slime, lava, etc). +/turf/proc/is_safe_to_enter(mob/living/L) + return TRUE + // This is all the way up here since its the common ancestor for things that need to get replaced with a floor when an RCD is used on them. // More specialized turfs like walls should instead override this. // The code for applying lattices/floor tiles onto lattices could also utilize something similar in the future. diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm index decd0f2eb3..5e5d3d9baf 100644 --- a/code/game/turfs/turf_changing.dm +++ b/code/game/turfs/turf_changing.dm @@ -28,7 +28,7 @@ if(N == /turf/space) var/turf/below = GetBelow(src) - if(istype(below) && (air_master.has_valid_zone(below) || air_master.has_valid_zone(src))) + if(istype(below) && !istype(below,/turf/space)) N = /turf/simulated/open var/obj/fire/old_fire = fire diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 4b0a1122e9..6fcb620163 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -280,21 +280,21 @@ if("larva") M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) if("nymph") M.change_mob_type( /mob/living/carbon/alien/diona , null, null, delmob ) if("human") M.change_mob_type( /mob/living/carbon/human , null, null, delmob, href_list["species"]) - if("slime") M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) + if("slime") M.change_mob_type( /mob/living/simple_mob/slime/xenobio , null, null, delmob ) if("monkey") M.change_mob_type( /mob/living/carbon/human/monkey , null, null, delmob ) if("robot") M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) - if("cat") M.change_mob_type( /mob/living/simple_animal/cat , null, null, delmob ) - if("runtime") M.change_mob_type( /mob/living/simple_animal/cat/fluff/Runtime , null, null, delmob ) - if("corgi") M.change_mob_type( /mob/living/simple_animal/corgi , null, null, delmob ) - if("ian") M.change_mob_type( /mob/living/simple_animal/corgi/Ian , null, null, delmob ) - if("crab") M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) - if("coffee") M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob ) - if("parrot") M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) - if("polyparrot") M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob ) - if("constructarmoured") M.change_mob_type( /mob/living/simple_animal/construct/armoured , null, null, delmob ) - if("constructbuilder") M.change_mob_type( /mob/living/simple_animal/construct/builder , null, null, delmob ) - if("constructwraith") M.change_mob_type( /mob/living/simple_animal/construct/wraith , null, null, delmob ) - if("shade") M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob ) + if("cat") M.change_mob_type( /mob/living/simple_mob/animal/passive/cat , null, null, delmob ) + if("runtime") M.change_mob_type( /mob/living/simple_mob/animal/passive/cat/runtime , null, null, delmob ) + if("corgi") M.change_mob_type( /mob/living/simple_mob/animal/passive/dog/corgi , null, null, delmob ) + if("ian") M.change_mob_type( /mob/living/simple_mob/animal/passive/dog/corgi/Ian , null, null, delmob ) + if("crab") M.change_mob_type( /mob/living/simple_mob/animal/passive/crab , null, null, delmob ) + if("coffee") M.change_mob_type( /mob/living/simple_mob/animal/passive/crab/Coffee , null, null, delmob ) + if("parrot") M.change_mob_type( /mob/living/simple_mob/animal/passive/bird/parrot , null, null, delmob ) + if("polyparrot") M.change_mob_type( /mob/living/simple_mob/animal/passive/bird/parrot/poly , null, null, delmob ) + if("constructarmoured") M.change_mob_type( /mob/living/simple_mob/construct/juggernaut , null, null, delmob ) + if("constructbuilder") M.change_mob_type( /mob/living/simple_mob/construct/artificer , null, null, delmob ) + if("constructwraith") M.change_mob_type( /mob/living/simple_mob/construct/wraith , null, null, delmob ) + if("shade") M.change_mob_type( /mob/living/simple_mob/construct/shade , null, null, delmob ) /////////////////////////////////////new ban stuff diff --git a/code/modules/admin/verbs/buildmode.dm b/code/modules/admin/verbs/buildmode.dm index a57b17d15f..4cfa952b67 100644 --- a/code/modules/admin/verbs/buildmode.dm +++ b/code/modules/admin/verbs/buildmode.dm @@ -127,6 +127,18 @@ usr << "Right Mouse Button on turf/obj/mob = Reset glowing" usr << "Right Mouse Button on buildmode button = Change glow properties" usr << "***********************************************************" + if(9) // Control mobs with ai_holders. + usr << "***********************************************************" + usr << "Left Mouse Button on AI mob = Select/Deselect mob" + usr << "Left Mouse Button + alt on AI mob = Toggle hostility on mob" + usr << "Left Mouse Button + ctrl on AI mob = Reset target/following/movement" + usr << "Right Mouse Button on enemy mob = Command selected mobs to attack mob" + usr << "Right Mouse Button on allied mob = Command selected mobs to follow mob" + usr << "Right Mouse Button + shift on any mob = Command selected mobs to follow mob regardless of faction" + usr << "Right Mouse Button on tile = Command selected mobs to move to tile (will cancel if enemies are seen)" + usr << "Right Mouse Button + shift on tile = Command selected mobs to reposition to tile (will not be inturrupted by enemies)" + usr << "Right Mouse Button + alt on obj/turfs = Command selected mobs to attack obj/turf" + usr << "***********************************************************" return 1 /obj/effect/bmode/buildquit @@ -146,6 +158,7 @@ var/obj/effect/bmode/buildmode/buildmode = null var/obj/effect/bmode/buildquit/buildquit = null var/atom/movable/throw_atom = null + var/list/selected_mobs = list() /obj/effect/bmode/buildholder/Destroy() qdel(builddir) @@ -157,9 +170,21 @@ qdel(buildquit) buildquit = null throw_atom = null + for(var/mob/living/unit in selected_mobs) + deselect_AI_mob(cl, unit) + selected_mobs.Cut() cl = null return ..() +/obj/effect/bmode/buildholder/proc/select_AI_mob(client/C, mob/living/unit) + selected_mobs += unit + C.images += unit.selected_image + +/obj/effect/bmode/buildholder/proc/deselect_AI_mob(client/C, mob/living/unit) + selected_mobs -= unit + C.images -= unit.selected_image + + /obj/effect/bmode/buildmode icon_state = "buildmode1" screen_loc = "NORTH,WEST+2" @@ -210,6 +235,9 @@ master.cl.buildmode = 8 src.icon_state = "buildmode8" if(8) + master.cl.buildmode = 9 + src.icon_state = "buildmode9" + if(9) master.cl.buildmode = 1 src.icon_state = "buildmode1" @@ -416,6 +444,86 @@ if(pa.Find("right")) if(object) object.set_light(0, 0, "#FFFFFF") + if(9) // AI control + if(pa.Find("left")) + if(isliving(object)) + var/mob/living/L = object + // Reset processes. + if(pa.Find("ctrl")) + if(!isnull(L.get_AI_stance())) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on. + var/datum/ai_holder/AI = L.ai_holder + AI.forget_everything() + to_chat(user, span("notice", "\The [L]'s AI has forgotten its target/movement destination/leader.")) + else + to_chat(user, span("warning", "\The [L] is not AI controlled.")) + return + + // Toggle hostility + if(pa.Find("alt")) + if(!isnull(L.get_AI_stance())) + var/datum/ai_holder/AI = L.ai_holder + AI.hostile = !AI.hostile + to_chat(user, span("notice", "\The [L] is now [AI.hostile ? "hostile" : "passive"].")) + else + to_chat(user, span("warning", "\The [L] is not AI controlled.")) + return + + // Select/Deselect + if(!isnull(L.get_AI_stance())) + if(L in holder.selected_mobs) + holder.deselect_AI_mob(user.client, L) + to_chat(user, span("notice", "Deselected \the [L].")) + else + holder.select_AI_mob(user.client, L) + to_chat(user, span("notice", "Selected \the [L].")) + else + to_chat(user, span("warning", "\The [L] is not AI controlled.")) + + if(pa.Find("right")) + if(istype(object, /atom)) // Force attack. + var/atom/A = object + + if(pa.Find("alt")) + var/i = 0 + for(var/mob/living/unit in holder.selected_mobs) + var/datum/ai_holder/AI = unit.ai_holder + AI.give_target(A) + i++ + to_chat(user, span("notice", "Commanded [i] mob\s to attack \the [A].")) + return + + if(isliving(object)) // Follow or attack. + var/mob/living/L = object + var/i = 0 // Attacking mobs. + var/j = 0 // Following mobs. + for(var/mob/living/unit in holder.selected_mobs) + var/datum/ai_holder/AI = unit.ai_holder + if(L.IIsAlly(unit) || !AI.hostile || pa.Find("shift")) + AI.set_follow(L) + j++ + else + AI.give_target(L) + i++ + var/message = "Commanded " + if(i) + message += "[i] mob\s to attack \the [L]" + if(j) + message += ", and " + else + message += "." + if(j) + message += "[j] mob\s to follow \the [L]." + to_chat(user, span("notice", message)) + + if(isturf(object)) // Move or reposition. + var/turf/T = object + var/i = 0 + for(var/mob/living/unit in holder.selected_mobs) + var/datum/ai_holder/AI = unit.ai_holder + AI.give_destination(T, 1, pa.Find("shift")) // If shift is held, the mobs will not stop moving to attack a visible enemy. + i++ + to_chat(user, span("notice", "Commanded [i] mob\s to move to \the [T].")) + /obj/effect/bmode/buildmode/proc/get_path_from_partial_text(default_path) var/desired_path = input("Enter full or partial typepath.","Typepath","[default_path]") diff --git a/code/modules/admin/verbs/lightning_strike.dm b/code/modules/admin/verbs/lightning_strike.dm index 16cb6b06dd..6adb122d4a 100644 --- a/code/modules/admin/verbs/lightning_strike.dm +++ b/code/modules/admin/verbs/lightning_strike.dm @@ -1,115 +1,99 @@ -/client/proc/admin_lightning_strike() - set name = "Lightning Strike" - set desc = "Causes lightning to strike on your tile. This will hurt things on or nearby it severely." - set category = "Fun" - - if(!check_rights(R_FUN)) - return - - var/result = alert(src, "Really strike your tile with lightning?", "Confirm Badmin" , "No", "Yes (Cosmetic)", "Yes (Real)") - - if(result == "No") - return - var/fake_lightning = result == "Yes (Cosmetic)" - - lightning_strike(get_turf(usr), fake_lightning) - log_and_message_admins("[key_name(src)] has caused [fake_lightning ? "cosmetic":"harmful"] lightning to strike at their position ([src.mob.x], [src.mob.y], [src.mob.z]). \ - (JMP)") - -#define LIGHTNING_REDIRECT_RANGE 28 // How far in tiles certain things draw lightning from. -#define LIGHTNING_ZAP_RANGE 3 // How far the tesla effect zaps, as well as the bad effects from a direct strike. -#define LIGHTNING_POWER 20000 // How much 'zap' is in a strike, used for tesla_zap(). - -// The real lightning proc. -// This is global until I can figure out a better place for it. -// T is the turf that is being struck. If cosmetic is true, the lightning won't actually hurt anything. -/proc/lightning_strike(turf/T, cosmetic = FALSE) - // First, visuals. - - // Do a lightning flash for the whole planet, if the turf belongs to a planet. - var/datum/planet/P = null - P = SSplanets.z_to_planet[T.z] - if(P) - var/datum/weather_holder/holder = P.weather_holder - flick("lightning_flash", holder.special_visuals) - - // Before we do the other visuals, we need to see if something is going to hijack our intended target. - var/obj/machinery/power/grounding_rod/ground = null // Most of the bad effects of lightning will get negated if a grounding rod is nearby. - var/obj/machinery/power/tesla_coil/coil = null // However a tesla coil has higher priority and the strike will bounce. - - for(var/obj/machinery/power/thing in range(LIGHTNING_REDIRECT_RANGE, T)) - if(istype(thing, /obj/machinery/power/tesla_coil)) - var/turf/simulated/coil_turf = get_turf(thing) - if(istype(coil_turf) && thing.anchored && coil_turf.outdoors) - coil = thing - break - - if(istype(thing, /obj/machinery/power/grounding_rod)) - var/turf/simulated/rod_turf = get_turf(thing) - if(istype(rod_turf) && thing.anchored && rod_turf.outdoors) - ground = thing - - if(coil) // Coil gets highest priority. - T = coil.loc - else if(ground) - T = ground.loc - - // Now make the lightning strike sprite. It will fade and delete itself in a second. - new /obj/effect/temporary_effect/lightning_strike(T) - - // For those close up. - playsound(T, 'sound/effects/lightningbolt.ogg', 100, 1) - - // And for those far away. If the strike happens on a planet, everyone on the planet will hear it. - // Otherwise only those on the current z-level will hear it. - var/sound = get_sfx("thunder") - for(var/mob/M in player_list) - if((P && M.z in P.expected_z_levels) || M.z == T.z) - M.playsound_local(get_turf(M), soundin = sound, vol = 70, vary = FALSE, is_global = TRUE) - - if(cosmetic) // Everything beyond here involves potentially damaging things. If we don't want to do that, stop now. - return - - if(ground) // All is well. - ground.tesla_act(LIGHTNING_POWER, FALSE) - return - - else if(coil) // Otherwise lets bounce off the tesla coil. - coil.tesla_act(LIGHTNING_POWER, TRUE) - - else // Striking the turf directly. - tesla_zap(T, zap_range = LIGHTNING_ZAP_RANGE, power = LIGHTNING_POWER, explosive = FALSE, stun_mobs = TRUE) - - // Some extra effects. - // Some apply to those within zap range, others if they were a bit farther away. - for(var/mob/living/L in view(5, T)) - if(get_dist(L, T) <= LIGHTNING_ZAP_RANGE) // They probably got zapped. - // The actual damage/electrocution is handled by tesla_zap(). - L.Paralyse(5) - L.stuttering += 20 - L.make_jittery(20) - L.emp_act(1) - to_chat(L, span("critical", "You've been struck by lightning!")) - - // If a non-player simplemob was struck, inflict huge damage. - // If the damage is fatal, the SA is turned to ash. - if(istype(L, /mob/living/simple_animal) && !L.key) - var/mob/living/simple_animal/SA = L - SA.adjustFireLoss(200) - SA.updatehealth() - if(SA.health <= 0) // Might be best to check/give simple_mobs siemens when this gets ported to new mobs. - SA.visible_message(span("critical", "\The [SA] disintegrates into ash!")) - SA.ash() - continue // No point deafening something that wont exist. - - // Deafen them. - if(L.get_ear_protection() < 2) - L.AdjustSleeping(-100) - if(iscarbon(L)) - var/mob/living/carbon/C = L - C.ear_deaf += 10 - to_chat(L, span("danger", "Lightning struck nearby, and the thunderclap is deafening!")) - -#undef GROUNDING_ROD_RANGE -#undef LIGHTNING_ZAP_RANGE -#undef LIGHTNING_POWER \ No newline at end of file +/client/proc/admin_lightning_strike() + set name = "Lightning Strike" + set desc = "Causes lightning to strike on your tile. This will hurt things on or nearby it severely." + set category = "Fun" + + if(!check_rights(R_FUN)) + return + + var/result = alert(src, "Really strike your tile with lightning?", "Confirm Badmin" , "No", "Yes (Cosmetic)", "Yes (Real)") + + if(result == "No") + return + var/fake_lightning = result == "Yes (Cosmetic)" + + lightning_strike(get_turf(usr), fake_lightning) + log_and_message_admins("[key_name(src)] has caused [fake_lightning ? "cosmetic":"harmful"] lightning to strike at their position ([src.mob.x], [src.mob.y], [src.mob.z]). \ + (JMP)") + +#define LIGHTNING_REDIRECT_RANGE 28 // How far in tiles certain things draw lightning from. +#define LIGHTNING_ZAP_RANGE 3 // How far the tesla effect zaps, as well as the bad effects from a direct strike. +#define LIGHTNING_POWER 20000 // How much 'zap' is in a strike, used for tesla_zap(). + +// The real lightning proc. +// This is global until I can figure out a better place for it. +// T is the turf that is being struck. If cosmetic is true, the lightning won't actually hurt anything. +/proc/lightning_strike(turf/T, cosmetic = FALSE) + // First, visuals. + + // Do a lightning flash for the whole planet, if the turf belongs to a planet. + var/datum/planet/P = null + P = SSplanets.z_to_planet[T.z] + if(P) + var/datum/weather_holder/holder = P.weather_holder + flick("lightning_flash", holder.special_visuals) + + // Before we do the other visuals, we need to see if something is going to hijack our intended target. + var/obj/machinery/power/grounding_rod/ground = null // Most of the bad effects of lightning will get negated if a grounding rod is nearby. + var/obj/machinery/power/tesla_coil/coil = null // However a tesla coil has higher priority and the strike will bounce. + + for(var/obj/machinery/power/thing in range(LIGHTNING_REDIRECT_RANGE, T)) + if(istype(thing, /obj/machinery/power/tesla_coil)) + var/turf/simulated/coil_turf = get_turf(thing) + if(istype(coil_turf) && thing.anchored && coil_turf.outdoors) + coil = thing + break + + if(istype(thing, /obj/machinery/power/grounding_rod)) + var/turf/simulated/rod_turf = get_turf(thing) + if(istype(rod_turf) && thing.anchored && rod_turf.outdoors) + ground = thing + + if(coil) // Coil gets highest priority. + T = coil.loc + else if(ground) + T = ground.loc + + // Now make the lightning strike sprite. It will fade and delete itself in a second. + new /obj/effect/temporary_effect/lightning_strike(T) + + // For those close up. + playsound(T, 'sound/effects/lightningbolt.ogg', 100, 1) + + // And for those far away. If the strike happens on a planet, everyone on the planet will hear it. + // Otherwise only those on the current z-level will hear it. + var/sound = get_sfx("thunder") + for(var/mob/M in player_list) + if((P && M.z in P.expected_z_levels) || M.z == T.z) + M.playsound_local(get_turf(M), soundin = sound, vol = 70, vary = FALSE, is_global = TRUE) + + if(cosmetic) // Everything beyond here involves potentially damaging things. If we don't want to do that, stop now. + return + + if(ground) // All is well. + ground.tesla_act(LIGHTNING_POWER, FALSE) + return + + else if(coil) // Otherwise lets bounce off the tesla coil. + coil.tesla_act(LIGHTNING_POWER, TRUE) + + else // Striking the turf directly. + tesla_zap(T, zap_range = LIGHTNING_ZAP_RANGE, power = LIGHTNING_POWER, explosive = FALSE, stun_mobs = TRUE) + + // Some extra effects. + // Some apply to those within zap range, others if they were a bit farther away. + for(var/mob/living/L in view(5, T)) + if(get_dist(L, T) <= LIGHTNING_ZAP_RANGE) // They probably got zapped. + L.lightning_act() + + // Deafen them. + if(L.get_ear_protection() < 2) + L.AdjustSleeping(-100) + if(iscarbon(L)) + var/mob/living/carbon/C = L + C.ear_deaf += 10 + to_chat(L, span("danger", "Lightning struck nearby, and the thunderclap is deafening!")) + +#undef GROUNDING_ROD_RANGE +#undef LIGHTNING_ZAP_RANGE +#undef LIGHTNING_POWER diff --git a/code/modules/ai/__readme.dm b/code/modules/ai/__readme.dm new file mode 100644 index 0000000000..85b49ec6cb --- /dev/null +++ b/code/modules/ai/__readme.dm @@ -0,0 +1,201 @@ +/* +[Summary] + +This module contains an AI implementation designed to be (at the base level) mobtype-agnostic, +by being held inside a datum instead of being written into the mob directly. More specialized +subtypes of the base AI may be designed with a specific mob type in mind, but the base system +should be compatible with most types of mobs which have the needed Interfaces in place to +support them. + +When designing a new mob, all that is needed to give a mob an AI is to set +its 'ai_holder_type' variable to the path of the AI that is desired. + + +[Seperation] + +In previous iterations of AI systems, the AI is generally written into the mob's code directly, +which has some advantages, but often makes the code rigid, and also tied the speed of the AI +to the mob's own ticker, meaning it could only decide every two seconds. + +Instead, this version has the code for the AI held inside an /datum/ai_holder object, +which is carried by the mob it controls. This gives some advantages; + All /mob/living mobs can potentially have an AI applied to them, and utilize the + same base code while adding specialized code on top. + + Interfaces allow the base AI code to not need to know what particular mode it's controlling. + + The processing of the AI is independant of the mob's Life() cycle, which allows for a + different clock rate. + + Seperating the AI from the mob simplies the mob's code greatly. + + It is more logical to think that a mob is the 'body', where as its ai_holder is + the 'mind'. + + AIs can be applied or disabled on the fly by instantiating or deleting the + ai_holder, if needed. + + +The current implementation also has some disadvantages, but they can perhaps be resolved +in the future. + AI-driven mob movement and attack speed is tied to half-second delays due to the + AI subsystem ticking at that rate. Porting the timer subsystem and integrating + callbacks into basic AI actions (moving, attacking) can potentially resolve that. + + It can be difficult to modify AI variables at mob instantiation without an ugly + delay, as the ai_holder might not exist yet. + + +[Flow of Processing] + +Terrible visual representation here; +AI Subsystem -> Every 0.5s -> /datum/ai_holder/handle_tactics() -> switch(stance)... + -> Every 2.0s -> /datum/ai_holder/handle_strategicals() -> switch(stance)... + +The AI datum is not processed by the mob itself, but instead it is directly processed +by a new AI subsystem. The AI subsystem contains a list of all active ai_holder +objects, which is iterated every tick to process each individual ai_holder +object attached to a mob. + +Each ai_holder actually has two 'tracks' for processing, a 'fast' track +and a 'slow' track. + +The fast track is named handle_tactics(), and is called every 0.5 seconds. + +The slow track is named handle_strategicals(), and is called every 2 seconds. + +When an ai_holder is iterated on inside the AI subsystem's list, it first +calls that ai_holder's handle_tactics(). It will then call that ai_holder's +handle_strategicals() every fourth tick, effectively doing so every two seconds. + +Both functions do different things depending on which 'stance' the +ai_holder is in. See the Stances section for more information. + +The fast track is for 'cheap' processing that needs to happen fast, such as +walking along a path, initiating an attack, or firing a gun. The rate that +it is called allows for the ai_holder to interact with the world through +its mob very often, giving a more convincing appearance of intelligence, +allowing for faster reaction times to certain events, and allowing for +variable attack speeds that would not be possible when bound to a +two second Life() cycle. + +The slow track, on the other hand, is for 'expensive' processing that might +be too demanding on the CPU to do every half a second, such as +re/calculating an A* path (if the mob uses A*), or running a complicated +tension assessment to determine how brave the mob is feeling. This is the +same delay used for certain tasks in the old implementation, but it is less +noticable due to the mob appearing to do things inbetween those two seconds. + +The purpose of having two tracks is to allow for 'fast' and 'slow' actions +to be more easily encapsulated, and ensures that all ai_holders are syncronized +with each other, as opposed to having individual tick counters inside all of +the ai_holder instances. It should be noted that handle_tactics() is always +called first, before handle_strategicals() every two seconds. + +[Process Skipping] + +An ai_holder object can choose to enter a 'busy' state, or a 'sleep' state, +in order to avoid processing. + +When busy, the AI subsystem will skip over the ai_holder until it is no +longer busy. The busy state is intended to be short-term, and is usually +toggled by the mob when doing something with a delay, so that the ai_holder +does not accidentally do something to inturrupt something important, like +a special attack. + +The longer term alternative to the busy state is the sleep state. Unlike +being busy, an ai_holder set to sleep will remove itself from the +AI subsystem's list, meaning it will no longer process until something +else 'wakes' it. This is usually done when the mob dies or a client +logs into an AI-controlled mob (and the AI is not set to ignore that, +with the autopilot variable). If the mob is revived, the AI will be +awakened automatically. + +The ai_holder functions, and mob functions that are called by the +ai_holder, should not be sleep()ed, as it will block the AI Subsystem +from processing the other ai_holders until the sleep() finishes. +Delays on the mob typically have set waitfor = FALSE, or spawn() is used. + + +[Stances] + +The AI has a large number of states that it can be in, called stances. +The AI will act in a specific way depending on which stance it is in, +and only one stance can be active at a time. This effectively creates +a state pattern. + +To change the stance, set_stance() is used, with the new stance as +the first argument. It should be noted that the change is not immediate, +and it will react to the change next tick instead of immediately switching +to the new stance and acting on that in the same tick. This is done to help +avoid infinite loops (I.E. Stance A switches to Stance B, which then +switches to Stance A, and so on...), and the delay is very short so +it should not be an issue. + +See code/__defines/mob.dm for a list of stance defines, and descriptions +about their purpose. Generally, each stance has its own file in the AI +module folder and are mostly self contained, however some files instead +deal with general things that other stances may require, such as targeting +or movement. + +[Interfaces] + +Interfaces are a concept that is used to help bridge the gap between +the ai_holder, and its mob. Because the (base) ai_holder is explicitly +designed to not be specific to any type of mob, all that it knows is +that it is controlling a /mob/living mob. Some mobs work very differently, +between mob types such as /mob/living/simple_mob, /mob/living/silicon/robot, +/mob/living/carbon/human, and more. + +The solution to the vast differences between mob types is to have the +mob itself deal with how to handle a specific task, such as attacking +something, talking, moving, etc. Interfaces exist to do this. + +Interfaces are applied on the mob-side, and are generally specific to +that mob type. This lets the ai_holder not have to worry about specific +implementations and instead just tell the Interface that it wants to attack +something, or move into a tile. The AI does not need to know if the mob its +controlling has hands, instead that is the mob's responsibility. + +Interface functions have an uppercase I at the start of the function name, +and then the function they are bridging between the AI and the mob +(if it exists), e.g. IMove(), IAttack(), ISay(). + +Interfaces are also used for the AI to ask its mob if it can do certain +things, without having to actually know what type of mob it is attached to. +For example, ICheckRangedAttack() tells the AI if it is possible to do a +ranged attack. For simple_mobs, they can if a ranged projectile type was set, +where as for a human mob, it could check if a gun is in a hand. For a borg, +it could check if a gun is inside their current module. + +[Say List] + +A /datum/say_list is a very light datum that holds a list of strings for the +AI to have their mob say based on certain conditions, such as when threatening +to kill another mob. Despite the name, a say_list also can contain emotes +and some sounds. + +The reason that it is in a seperate datum is to allow for multiple mob types +to have the same text, even when inheritence cannot do that, such as +mercenaries and fake piloted mecha mobs. + +The say_list datum is applied to the mob itself and not held inside the AI datum. + +[Subtypes] + +Some subtypes of ai_holder are more specialized, but remain compatible with +most mob types. There are many different subtypes that make the AI act different +by overriding a function, such as kiting their target, moving up close while +using ranged attacks, or running away if not cloaked. + +Other subtypes are very specific about what kind of mob it controls, and trying +to apply them to a different type of mob will likely result in a lot of bugs +or ASSERT() failures. The xenobio slime AI is an example of the latter. + +To use a specific subtype on a mob, all that is needed is setting the mob's +ai_holder_type to the subtype desired, and it will create that subtype when +the mob is initialize()d. Switching to a subtype 'live' will require additional +effort on the coder. + + +*/ \ No newline at end of file diff --git a/code/modules/ai/_defines.dm b/code/modules/ai/_defines.dm new file mode 100644 index 0000000000..e94d26b3c3 --- /dev/null +++ b/code/modules/ai/_defines.dm @@ -0,0 +1,29 @@ +// Defines for the ai_intelligence var. +// Controls if the mob will do 'advanced tactics' like running from grenades. +#define AI_DUMB 1 // Be dumber than usual. +#define AI_NORMAL 2 // Default level. +#define AI_SMART 3 // Will do more processing to be a little smarter, like not walking while confused if it could risk stepping randomly onto a bad tile. + +#define ai_log(M,V) if(debug_ai) ai_log_output(M,V) + +// Logging level defines. +#define AI_LOG_OFF 0 // Don't show anything. +#define AI_LOG_ERROR 1 // Show logs of things likely causing the mob to not be functioning correctly. +#define AI_LOG_WARNING 2 // Show less serious but still helpful to know issues that might be causing things to work incorrectly. +#define AI_LOG_INFO 3 // Important regular events, like selecting a target or switching stances. +#define AI_LOG_DEBUG 4 // More detailed information about the flow of execution. +#define AI_LOG_TRACE 5 // Even more detailed than the last. Will absolutely flood your chatlog. + +// Results of pre-movement checks. +// Todo: Move outside AI code? +#define MOVEMENT_ON_COOLDOWN -1 // Recently moved and needs to try again soon. +#define MOVEMENT_FAILED 0 // Move() returned false for whatever reason and the mob didn't move. +#define MOVEMENT_SUCCESSFUL 1 // Move() returned true and the mob hopefully moved. + +// Reasons for targets to not be valid. Based on why, the AI responds differently. +#define AI_TARGET_VALID 0 // We can fight them. +#define AI_TARGET_INVIS 1 // They were in field of view but became invisible. Switch to STANCE_BLINDFIGHT if no other viable targets exist. +#define AI_TARGET_NOSIGHT 2 // No longer in field of view. Go STANCE_REPOSITION to their last known location if no other targets are seen. +#define AI_TARGET_ALLY 3 // They are an ally. Find a new target. +#define AI_TARGET_DEAD 4 // They're dead. Find a new target. +#define AI_TARGET_INVINCIBLE 5 // Target is currently unable to receive damage for whatever reason. Find a new target or wait. diff --git a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm new file mode 100644 index 0000000000..f2d1f05e13 --- /dev/null +++ b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm @@ -0,0 +1,136 @@ +// Base AIs for simple mobs. +// Mob-specific AIs are in their mob's file. + +/datum/ai_holder/simple_mob + hostile = TRUE // The majority of simplemobs are hostile. + cooperative = TRUE + returns_home = FALSE + can_flee = FALSE + speak_chance = 1 // If the mob's saylist is empty, nothing will happen. + wander = TRUE + base_wander_delay = 4 + +// For non-hostile animals, and pets like Ian and Runtime. +/datum/ai_holder/simple_mob/passive + hostile = FALSE + can_flee = TRUE + violent_breakthrough = FALSE + +// Won't wander away, ideal for event-spawned mobs like carp or drones. +/datum/ai_holder/simple_mob/event + wander = FALSE + +// Doesn't really act until told to by something on the outside. +/datum/ai_holder/simple_mob/inert + hostile = FALSE + retaliate = FALSE + can_flee = FALSE + wander = FALSE + speak_chance = 0 + cooperative = FALSE + violent_breakthrough = FALSE // So it can open doors but not attack windows and shatter the literal illusion. + +// Used for technomancer illusions, to resemble player movement better. +/datum/ai_holder/simple_mob/inert/astar + use_astar = TRUE + +// Ranged mobs. + +/datum/ai_holder/simple_mob/ranged +// ranged = TRUE + +// Tries to not waste ammo. +/datum/ai_holder/simple_mob/ranged/careful + conserve_ammo = TRUE + +/datum/ai_holder/simple_mob/ranged/pointblank + pointblank = TRUE + +// Runs away from its target if within a certain distance. +/datum/ai_holder/simple_mob/ranged/kiting + pointblank = TRUE // So we don't need to copypaste post_melee_attack(). + var/run_if_this_close = 4 // If anything gets within this range, it'll try to move away. + var/moonwalk = TRUE // If true, mob turns to face the target while kiting, otherwise they turn in the direction they moved towards. + +/datum/ai_holder/simple_mob/ranged/kiting/threatening + threaten = TRUE + threaten_delay = 1 SECOND // Less of a threat and more of pre-attack notice. + threaten_timeout = 30 SECONDS + conserve_ammo = TRUE + +// For event-spawned malf drones. +/datum/ai_holder/simple_mob/ranged/kiting/threatening/event + wander = FALSE + +/datum/ai_holder/simple_mob/ranged/kiting/no_moonwalk + moonwalk = FALSE + +/datum/ai_holder/simple_mob/ranged/kiting/on_engagement(atom/A) + if(get_dist(holder, A) < run_if_this_close) + holder.IMove(get_step_away(holder, A, run_if_this_close)) + if(moonwalk) + holder.face_atom(A) + +// Closes distance from the target even while in range. +/datum/ai_holder/simple_mob/ranged/aggressive + pointblank = TRUE + var/closest_distance = 1 // How close to get to the target. By default they will get into melee range (and then pointblank them). + +/datum/ai_holder/simple_mob/ranged/aggressive/on_engagement(atom/A) + if(get_dist(holder, A) > closest_distance) + holder.IMove(get_step_towards(holder, A)) + holder.face_atom(A) + +// Yakkity saxes while firing at you. +/datum/ai_holder/hostile/ranged/robust/on_engagement(atom/movable/AM) + step_rand(holder) + holder.face_atom(AM) + +// Switches intents based on specific criteria. +// Used for special mobs who do different things based on intents (and aren't slimes). +// Intent switching is generally done in pre_[ranged/special]_attack(), so that the mob can use the right attack for the right time. +/datum/ai_holder/simple_mob/intentional + + +// These try to avoid collateral damage. +/datum/ai_holder/simple_mob/restrained + violent_breakthrough = FALSE + conserve_ammo = TRUE + +// Melee mobs. + +/datum/ai_holder/simple_mob/melee + +// Dances around the enemy its fighting, making it harder to fight back. +/datum/ai_holder/simple_mob/melee/evasive + +/datum/ai_holder/simple_mob/melee/evasive/post_melee_attack(atom/A) + if(holder.Adjacent(A)) + holder.IMove(get_step(holder, pick(alldirs))) + holder.face_atom(A) + + + +// This AI hits something, then runs away for awhile. +// It will (almost) always flee if they are uncloaked, AND their target is not stunned. +/datum/ai_holder/simple_mob/melee/hit_and_run + can_flee = TRUE + +// Used for the 'running' part of hit and run. +/datum/ai_holder/simple_mob/melee/hit_and_run/special_flee_check() + if(!holder.is_cloaked()) + if(isliving(target)) + var/mob/living/L = target + return !L.incapacitated(INCAPACITATION_DISABLED) // Don't flee if our target is stunned in some form, even if uncloaked. This is so the mob keeps attacking a stunned opponent. + return TRUE // We're out in the open, uncloaked, and our target isn't stunned, so lets flee. + return FALSE + + +// Simple mobs that aren't hostile, but will fight back. +/datum/ai_holder/simple_mob/retaliate + hostile = FALSE + retaliate = TRUE + +// Simple mobs that retaliate and support others in their faction who get attacked. +/datum/ai_holder/simple_mob/retaliate/cooperative + cooperative = TRUE \ No newline at end of file diff --git a/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm b/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm new file mode 100644 index 0000000000..c859a523cc --- /dev/null +++ b/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm @@ -0,0 +1,262 @@ +// Specialized AI for slime simplemobs. +// Unlike the parent AI code, this will probably break a lot of things if you put it on something that isn't /mob/living/simple_mob/slime/xenobio + +/datum/ai_holder/simple_mob/xenobio_slime + hostile = TRUE + cooperative = TRUE + firing_lanes = TRUE + var/rabid = FALSE // Will attack regardless of discipline. + var/discipline = 0 // Beating slimes makes them less likely to lash out. In theory. + var/resentment = 0 // 'Unjustified' beatings make this go up, and makes it more likely for abused slimes to go rabid. + var/obedience = 0 // Conversely, 'justified' beatings make this go up, and makes discipline decay slower, potentially making it not decay at all. + + var/always_stun = FALSE // If true, the slime will elect to attempt to permastun the target. + +/datum/ai_holder/simple_mob/xenobio_slime/sapphire + always_stun = TRUE // They know that stuns are godly. + intelligence_level = AI_SMART // Also knows not to walk while confused if it risks death. + +/datum/ai_holder/simple_mob/xenobio_slime/light_pink + discipline = 5 + obedience = 5 + +/datum/ai_holder/simple_mob/xenobio_slime/passive/New() // For Kendrick. + ..() + pacify() + +/datum/ai_holder/simple_mob/xenobio_slime/New() + ..() + ASSERT(istype(holder, /mob/living/simple_mob/slime/xenobio)) + +// Checks if disciplining the slime would be 'justified' right now. +/datum/ai_holder/simple_mob/xenobio_slime/proc/is_justified_to_discipline() + if(rabid) + return TRUE + if(target) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(istype(H.species, /datum/species/monkey)) + return FALSE // Attacking monkeys is okay. + return TRUE // Otherwise attacking other things is bad. + return FALSE // Not attacking anything. + +/datum/ai_holder/simple_mob/xenobio_slime/proc/can_command(mob/living/commander) + if(rabid) + return FALSE + if(!hostile) + return SLIME_COMMAND_OBEY +// if(commander in friends) +// return SLIME_COMMAND_FRIEND + if(holder.IIsAlly(commander)) + return SLIME_COMMAND_FACTION + if(discipline > resentment && obedience >= 5) + return SLIME_COMMAND_OBEY + return FALSE + +/datum/ai_holder/simple_mob/xenobio_slime/proc/adjust_discipline(amount, silent) + var/mob/living/simple_mob/slime/xenobio/my_slime = holder + if(amount > 0) + if(rabid) + return + var/justified = is_justified_to_discipline() + lost_target() // Stop attacking. + + if(justified) + obedience++ + if(!silent) + holder.say(pick("Fine...", "Okay...", "Sorry...", "I yield...", "Mercy...")) + else + if(prob(resentment * 20)) + enrage() + holder.say(pick("Evil...", "Kill...", "Tyrant...")) + else + if(!silent) + holder.say(pick("Why...?", "I don't understand...?", "Cruel...", "Stop...", "Nooo...")) + resentment++ // Done after check so first time will never enrage. + + discipline = between(0, discipline + amount, 10) + my_slime.update_mood() + +// This slime always enrages if disciplined. +/datum/ai_holder/simple_mob/xenobio_slime/red/adjust_discipline(amount, silent) + if(amount > 0 && !rabid) + holder.say("Grrr...") + holder.add_modifier(/datum/modifier/berserk, 30 SECONDS) + enrage() + +/datum/ai_holder/simple_mob/xenobio_slime/handle_special_strategical() + discipline_decay() + +// Handles decay of discipline. +/datum/ai_holder/simple_mob/xenobio_slime/proc/discipline_decay() + if(discipline > 0) + if(!prob(75 + (obedience * 5))) + adjust_discipline(-1) + +/datum/ai_holder/simple_mob/xenobio_slime/handle_special_tactic() + evolve_and_reproduce() + +// Hit the correct verbs to keep the slime species going. +/datum/ai_holder/simple_mob/xenobio_slime/proc/evolve_and_reproduce() + var/mob/living/simple_mob/slime/xenobio/my_slime = holder + if(my_slime.amount_grown >= 10) + // Press the correct verb when we can. + if(my_slime.is_adult) + my_slime.reproduce() // Splits into four new baby slimes. + else + my_slime.evolve() // Turns our holder into an adult slime. + + +// Called when pushed too far (or a red slime core was used). +/datum/ai_holder/simple_mob/xenobio_slime/proc/enrage() + var/mob/living/simple_mob/slime/xenobio/my_slime = holder + if(my_slime.harmless) + return + rabid = TRUE + my_slime.update_mood() + my_slime.visible_message(span("danger", "\The [src] enrages!")) + +// Called when using a pacification agent (or it's Kendrick being initalized). +/datum/ai_holder/simple_mob/xenobio_slime/proc/pacify() + lost_target() // So it stops trying to kill them. + rabid = FALSE + hostile = FALSE + retaliate = FALSE + cooperative = FALSE + +// The holder's attack changes based on intent. This lets the AI choose what effect is desired. +/datum/ai_holder/simple_mob/xenobio_slime/pre_melee_attack(atom/A) + if(istype(A, /mob/living)) + var/mob/living/L = A + var/mob/living/simple_mob/slime/xenobio/my_slime = holder + + if( (!L.lying && prob(30 + (my_slime.power_charge * 7) ) || (!L.lying && always_stun) )) + my_slime.a_intent = I_DISARM // Stun them first. + else if(my_slime.can_consume(L) && L.lying) + my_slime.a_intent = I_GRAB // Then eat them. + else + my_slime.a_intent = I_HURT // Otherwise robust them. + +/datum/ai_holder/simple_mob/xenobio_slime/closest_distance(atom/movable/AM) + if(istype(AM, /mob/living)) + var/mob/living/L = AM + if(ishuman(L)) + var/mob/living/carbon/human/H = L + if(istype(H.species, /datum/species/monkey)) + return 1 // Otherwise ranged slimes will eat a lot less often. + if(L.stat >= UNCONSCIOUS) + return 1 // Melee (eat) the target if dead/dying, don't shoot it. + return ..() + +/datum/ai_holder/simple_mob/xenobio_slime/can_attack(atom/movable/AM) + . = ..() + if(.) // Do some additional checks because we have Special Code(tm). + if(ishuman(AM)) + var/mob/living/carbon/human/H = AM + if(istype(H.species, /datum/species/monkey)) // istype() is so they'll eat the alien monkeys too. + return TRUE // Monkeys are always food (sorry Pun Pun). + else if(H.species && H.species.name == SPECIES_PROMETHEAN) + return FALSE // Prometheans are always our friends. + if(discipline && !rabid) + return FALSE // We're a good slime. + +// Commands, reactions, etc +/datum/ai_holder/simple_mob/xenobio_slime/on_hear_say(mob/living/speaker, message) + ai_log("xenobio_slime/on_hear_say([speaker], [message]) : Entered.", AI_LOG_DEBUG) + var/mob/living/simple_mob/slime/xenobio/my_slime = holder + + if((findtext(message, num2text(my_slime.number)) || findtext(message, my_slime.name) || findtext(message, "slimes"))) // Talking to us. + + // First, make sure it's actually a player saying something and not an AI, or else we risk infinite loops. + if(!speaker.client) + return + + // Are all slimes being referred to? + // var/mass_order = FALSE + // if(findtext(message, "slimes")) + // mass_order = TRUE + + // Say hello back. + if(findtext(message, "hello") || findtext(message, "hi") || findtext(message, "greetings")) + delayed_say(pick("Hello...", "Hi..."), speaker) + + // Follow request. + if(findtext(message, "follow") || findtext(message, "come with me")) + if(!can_command(speaker)) + delayed_say(pick("No...", "I won't follow..."), speaker) + return + + delayed_say("Yes... I follow \the [speaker]...", speaker) + set_follow(speaker) + + // Squish request. + if(findtext(message , "squish")) + if(!can_command(speaker)) + delayed_say("No...", speaker) + return + + spawn(rand(1 SECOND, 2 SECONDS)) + if(!src || !holder || !can_act()) // We might've died/got deleted/etc in the meantime. + return + my_slime.squish() + + + // Stop request. + if(findtext(message, "stop") || findtext(message, "halt") || findtext(message, "cease")) + if(my_slime.victim) // We're being asked to stop eatting someone. + if(!can_command(speaker) || !is_justified_to_discipline()) + delayed_say("No...", speaker) + return + else + delayed_say("Fine...", speaker) + adjust_discipline(1, TRUE) + my_slime.stop_consumption() + + if(target) // We're being asked to stop chasing someone. + if(!can_command(speaker) || !is_justified_to_discipline()) + delayed_say("No...", speaker) + return + else + delayed_say("Fine...", speaker) + adjust_discipline(1, TRUE) // This must come before losing the target or it will be unjustified. + lost_target() + + + if(leader) // We're being asked to stop following someone. + if(can_command(speaker) == SLIME_COMMAND_FRIEND || leader == speaker) + delayed_say("Yes... I'll stop...", speaker) + lose_follow() + else + delayed_say("No... I'll keep following \the [leader]...", speaker) + + /* // Commented out since its mostly useless now due to slimes refusing to attack if it would make them naughty. + // Murder request + if(findtext(message, "harm") || findtext(message, "attack") || findtext(message, "kill") || findtext(message, "murder") || findtext(message, "eat") || findtext(message, "consume") || findtext(message, "absorb")) + if(can_command(speaker) < SLIME_COMMAND_FACTION) + delayed_say("No...", speaker) + return + + for(var/mob/living/L in view(7, my_slime) - list(my_slime, speaker)) + if(L == src) + continue // Don't target ourselves. + var/list/valid_names = splittext(L.name, " ") // Should output list("John", "Doe") as an example. + for(var/line in valid_names) // Check each part of someone's name. + if(findtext(message, lowertext(line))) // If part of someone's name is in the command, the slime targets them if allowed to. + if(!(mass_order && line == "slime")) //don't think random other slimes are target + if(can_attack(L)) + delayed_say("Okay... I attack \the [L]...", speaker) + give_target(L) + return + else + delayed_say("No... I won't attack \the [L].", speaker) + return + + // If we're here, it couldn't find anyone with that name. + delayed_say("No... I don't know who to attack...", speaker) + */ + ai_log("xenobio_slime/on_hear_say() : Exited.", AI_LOG_DEBUG) + +/datum/ai_holder/simple_mob/xenobio_slime/can_violently_breakthrough() + if(discipline && !rabid) // Good slimes don't shatter the windows because their buddy in an adjacent cell decided to piss off Slimesky. + return FALSE + return ..() \ No newline at end of file diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm new file mode 100644 index 0000000000..2c7e02ecef --- /dev/null +++ b/code/modules/ai/ai_holder.dm @@ -0,0 +1,290 @@ +// This is a datum-based artificial intelligence for simple mobs (and possibly others) to use. +// The neat thing with having this here instead of on the mob is that it is independant of Life(), and that different mobs +// can use a more or less complex AI by giving it a different datum. + +/mob/living + var/datum/ai_holder/ai_holder = null + var/ai_holder_type = null // Which ai_holder datum to give to the mob when initialized. If null, nothing happens. + +/mob/living/initialize() + if(ai_holder_type) + ai_holder = new ai_holder_type(src) + return ..() + +/mob/living/Destroy() + QDEL_NULL(ai_holder) + return ..() + +/datum/ai_holder + var/mob/living/holder = null // The mob this datum is going to control. + var/stance = STANCE_IDLE // Determines if the mob should be doing a specific thing, e.g. attacking, following, standing around, etc. + var/intelligence_level = AI_NORMAL // Adjust to make the AI be intentionally dumber, or make it more robust (e.g. dodging grenades). + var/autopilot = FALSE // If true, the AI won't be deactivated if a client gets attached to the AI's mob. + var/busy = FALSE // If true, the ticker will skip processing this mob until this is false. Good for if you need the + // mob to stay still (e.g. delayed attacking). If you need the mob to be inactive for an extended period of time, + // consider sleeping the AI instead. + + + +/datum/ai_holder/hostile + hostile = TRUE + +/datum/ai_holder/retaliate + hostile = TRUE + retaliate = TRUE + +/datum/ai_holder/New(var/new_holder) + ASSERT(new_holder) + holder = new_holder + SSai.processing += src + home_turf = get_turf(holder) + ..() + +/datum/ai_holder/Destroy() + holder = null + SSai.processing -= src // We might've already been asleep and removed, but byond won't care if we do this again and it saves a conditional. + home_turf = null + return ..() + + +// Now for the actual AI stuff. + +// Makes this ai holder not get processed. +// Called automatically when the host mob is killed. +// Potential future optimization would be to sleep AIs which mobs that are far away from in-round players. +/datum/ai_holder/proc/go_sleep() + if(stance == STANCE_SLEEP) + return + forget_everything() // If we ever wake up, its really unlikely that our current memory will be of use. + set_stance(STANCE_SLEEP) + SSai.processing -= src + +// Reverses the above proc. +// Revived mobs will wake their AI if they have one. +/datum/ai_holder/proc/go_wake() + if(stance != STANCE_SLEEP) + return + if(!should_wake()) + return + set_stance(STANCE_IDLE) + SSai.processing += src + +/datum/ai_holder/proc/should_wake() + if(holder.client && !autopilot) + return FALSE + if(holder.stat >= DEAD) + return FALSE + return TRUE + +// Resets a lot of 'memory' vars. +/datum/ai_holder/proc/forget_everything() + // Some of these might be redundant, but hopefully this prevents future bugs if that changes. + lose_follow() + lose_target() + lose_target_position() + give_up_movement() + +// 'Tactical' processes such as moving a step, meleeing an enemy, firing a projectile, and other fairly cheap actions that need to happen quickly. +/datum/ai_holder/proc/handle_tactics() + if(busy) + return + handle_special_tactic() + handle_stance_tactical() + +// 'Strategical' processes that are more expensive on the CPU and so don't get run as often as the above proc, such as A* pathfinding or robust targeting. +/datum/ai_holder/proc/handle_strategicals() + if(busy) + return + handle_special_strategical() + handle_stance_strategical() + +// Override these for special things without polluting the main loop. +/datum/ai_holder/proc/handle_special_tactic() + +/datum/ai_holder/proc/handle_special_strategical() + +/* + //AI Actions + if(!ai_inactive) + //Stanceyness + handle_stance() + + //Movement + if(!stop_automated_movement && wander && !anchored) //Allowed to move? + handle_wander_movement() + + //Speaking + if(speak_chance && stance == STANCE_IDLE) // Allowed to chatter? + handle_idle_speaking() + + //Resisting out buckles + if(stance != STANCE_IDLE && incapacitated(INCAPACITATION_BUCKLED_PARTIALLY)) + handle_resist() + + //Resisting out of closets + if(istype(loc,/obj/structure/closet)) + var/obj/structure/closet/C = loc + if(C.welded) + resist() + else + C.open() +*/ + +// For setting the stance WITHOUT processing it +/datum/ai_holder/proc/set_stance(var/new_stance) + ai_log("set_stance() : Setting stance from [stance] to [new_stance].", AI_LOG_INFO) + stance = new_stance + if(stance_coloring) // For debugging or really weird mobs. + stance_color() + +// This is called every half a second. +/datum/ai_holder/proc/handle_stance_tactical() + ai_log("========= Fast Process Beginning ==========", AI_LOG_TRACE) // This is to make it easier visually to disinguish between 'blocks' of what a tick did. + ai_log("handle_stance_tactical() : Called.", AI_LOG_TRACE) + + if(stance == STANCE_SLEEP) + ai_log("handle_stance_tactical() : Going to sleep.", AI_LOG_TRACE) + go_sleep() + return + + if(target && can_see_target(target)) + track_target_position() + + if(stance != STANCE_DISABLED && is_disabled()) // Stunned/confused/etc + ai_log("handle_stance_tactical() : Disabled.", AI_LOG_TRACE) + set_stance(STANCE_DISABLED) + return + + if(stance in STANCES_COMBAT) + // Should resist? We check this before fleeing so that we can actually flee and not be trapped in a chair. + if(holder.incapacitated(INCAPACITATION_BUCKLED_PARTIALLY)) + ai_log("handle_stance_tactical() : Going to handle_resist().", AI_LOG_TRACE) + handle_resist() + + else if(istype(holder.loc, /obj/structure/closet)) + var/obj/structure/closet/C = holder.loc + ai_log("handle_stance_tactical() : Inside a closet. Going to attempt escape.", AI_LOG_TRACE) + if(C.sealed) + holder.resist() + else + C.open() + + // Should we flee? + if(should_flee()) + ai_log("handle_stance_tactical() : Going to flee.", AI_LOG_TRACE) + set_stance(STANCE_FLEE) + return + + switch(stance) + if(STANCE_IDLE) + if(should_go_home()) + ai_log("handle_stance_tactical() : STANCE_IDLE, going to go home.", AI_LOG_TRACE) + go_home() + + else if(should_follow_leader()) + ai_log("handle_stance_tactical() : STANCE_IDLE, going to follow leader.", AI_LOG_TRACE) + set_stance(STANCE_FOLLOW) + + else if(should_wander()) + ai_log("handle_stance_tactical() : STANCE_IDLE, going to wander randomly.", AI_LOG_TRACE) + handle_wander_movement() + + if(STANCE_ALERT) + ai_log("handle_stance_tactical() : STANCE_ALERT, going to threaten_target().", AI_LOG_TRACE) + threaten_target() + + if(STANCE_APPROACH) + ai_log("handle_stance_tactical() : STANCE_APPROACH, going to walk_to_target().", AI_LOG_TRACE) + walk_to_target() + + if(STANCE_FIGHT) + ai_log("handle_stance_tactical() : STANCE_FIGHT, going to engage_target().", AI_LOG_TRACE) + engage_target() + + if(STANCE_MOVE) + ai_log("handle_stance_tactical() : STANCE_MOVE, going to walk_to_destination().", AI_LOG_TRACE) + walk_to_destination() + + if(STANCE_REPOSITION) // This is the same as above but doesn't stop if an enemy is visible since its an 'in-combat' move order. + ai_log("handle_stance_tactical() : STANCE_REPOSITION, going to walk_to_destination().", AI_LOG_TRACE) + walk_to_destination() + + if(STANCE_FOLLOW) + ai_log("handle_stance_tactical() : STANCE_FOLLOW, going to walk_to_leader().", AI_LOG_TRACE) + walk_to_leader() + + if(STANCE_FLEE) + ai_log("handle_stance_tactical() : STANCE_FLEE, going to flee_from_target().", AI_LOG_TRACE) + flee_from_target() + + if(STANCE_DISABLED) + ai_log("handle_stance_tactical() : STANCE_DISABLED.", AI_LOG_TRACE) + if(!is_disabled()) + ai_log("handle_stance_tactical() : No longer disabled.", AI_LOG_TRACE) + set_stance(STANCE_IDLE) + else + handle_disabled() + + ai_log("handle_stance_tactical() : Exiting.", AI_LOG_TRACE) + ai_log("========= Fast Process Ending ==========", AI_LOG_TRACE) + +// This is called every two seconds. +/datum/ai_holder/proc/handle_stance_strategical() + ai_log("++++++++++ Slow Process Beginning ++++++++++", AI_LOG_TRACE) + ai_log("handle_stance_strategical() : Called.", AI_LOG_TRACE) + + switch(stance) + if(STANCE_IDLE) + + if(speak_chance) // In the long loop since otherwise it wont shut up. + handle_idle_speaking() + + if(hostile) + ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE) + find_target() + if(STANCE_APPROACH) + if(target) + ai_log("handle_stance_strategical() : STANCE_APPROACH, going to calculate_path([target]).", AI_LOG_TRACE) + calculate_path(target) + if(STANCE_MOVE) + if(hostile && find_target()) // This will switch its stance. + ai_log("handle_stance_strategical() : STANCE_MOVE, found target and was inturrupted.", AI_LOG_TRACE) + if(STANCE_FOLLOW) + if(hostile && find_target()) // This will switch its stance. + ai_log("handle_stance_strategical() : STANCE_FOLLOW, found target and was inturrupted.", AI_LOG_TRACE) + else if(leader) + ai_log("handle_stance_strategical() : STANCE_FOLLOW, going to calculate_path([leader]).", AI_LOG_TRACE) + calculate_path(leader) + + ai_log("handle_stance_strategical() : Exiting.", AI_LOG_TRACE) + ai_log("++++++++++ Slow Process Ending ++++++++++", AI_LOG_TRACE) + + +// Helper proc to turn AI 'busy' mode on or off without having to check if there is an AI, to simplify writing code. +/mob/living/proc/set_AI_busy(value) + if(ai_holder) + ai_holder.busy = value + +/mob/living/proc/is_AI_busy() + if(!ai_holder) + return FALSE + return ai_holder.busy + +// Helper proc to check for the AI's stance. +// Returns null if there's no AI holder, or the mob has a player and autopilot is not on. +// Otherwise returns the stance. +/mob/living/proc/get_AI_stance() + if(!ai_holder) + return null + if(client && !ai_holder.autopilot) + return null + return ai_holder.stance + +// Similar to above but only returns 1 or 0. +/mob/living/proc/has_AI() + return get_AI_stance() ? TRUE : FALSE + +// 'Taunts' the AI into attacking the taunter. +/mob/living/proc/taunt(atom/movable/taunter, force_target_switch = FALSE) + if(ai_holder) + ai_holder.receive_taunt(taunter, force_target_switch) \ No newline at end of file diff --git a/code/modules/ai/ai_holder_combat.dm b/code/modules/ai/ai_holder_combat.dm new file mode 100644 index 0000000000..4a8fbdff69 --- /dev/null +++ b/code/modules/ai/ai_holder_combat.dm @@ -0,0 +1,308 @@ +// This file is for actual fighting. Targeting is in a seperate file. + +/datum/ai_holder + var/firing_lanes = FALSE // If ture, tries to refrain from shooting allies or the wall. + var/conserve_ammo = FALSE // If true, the mob will avoid shooting anything that does not have a chance to hit a mob. Requires firing_lanes to be true. + var/pointblank = FALSE // If ranged is true, and this is true, people adjacent to the mob will suffer the ranged instead of using a melee attack. + + var/can_breakthrough = TRUE // If false, the AI will not try to open a path to its goal, like opening doors. + var/violent_breakthrough = TRUE // If false, the AI is not allowed to destroy things like windows or other structures in the way. Requires above var to be true. + + var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range. + + +// This does the actual attacking. +/datum/ai_holder/proc/engage_target() + ai_log("engage_target() : Entering.", AI_LOG_DEBUG) + + // Can we still see them? +// if(!target || !can_attack(target) || (!(target in list_targets())) ) + if(!target || !can_attack(target)) + ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE) + lose_target() // We lost them. + + if(!find_target()) // If we can't get a new one, then wait for a bit and then time out. + set_stance(STANCE_IDLE) + lost_target() + ai_log("engage_target() : No more targets. Exiting.", AI_LOG_DEBUG) + return + // if(lose_target_time + lose_target_timeout < world.time) + // ai_log("engage_target() : Unseen enemy timed out.", AI_LOG_TRACE) + // set_stance(STANCE_IDLE) // It must've been the wind. + // lost_target() + // ai_log("engage_target() : Exiting.", AI_LOG_DEBUG) + // return + + // // But maybe we do one last ditch effort. + // if(!target_last_seen_turf || intelligence_level < AI_SMART) + // ai_log("engage_target() : No last known position or is too dumb to fight unseen enemies.", AI_LOG_TRACE) + // set_stance(STANCE_IDLE) + // else + // ai_log("engage_target() : Fighting unseen enemy.", AI_LOG_TRACE) + // engage_unseen_enemy() + else + ai_log("engage_target() : Got new target ([target]).", AI_LOG_TRACE) + + var/distance = get_dist(holder, target) + ai_log("engage_target() : Distance to target ([target]) is [distance].", AI_LOG_TRACE) + holder.face_atom(target) + last_conflict_time = world.time + + request_help() // Call our allies. + + // Do a 'special' attack, if one is allowed. +// if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range)) + if(holder.ICheckSpecialAttack(target)) + ai_log("engage_target() : Attempting a special attack.", AI_LOG_TRACE) + on_engagement(target) + if(special_attack(target)) // If this fails, then we try a regular melee/ranged attack. + ai_log("engage_target() : Successful special attack. Exiting.", AI_LOG_DEBUG) + return + + // Stab them. + else if(distance <= 1 && !pointblank) + ai_log("engage_target() : Attempting a melee attack.", AI_LOG_TRACE) + on_engagement(target) + melee_attack(target) + + // Shoot them. + else if(holder.ICheckRangedAttack(target) && (distance <= max_range(target)) ) + on_engagement(target) + if(firing_lanes && !test_projectile_safety(target)) + // Nudge them a bit, maybe they can shoot next time. + step_rand(holder) + holder.face_atom(target) + ai_log("engage_target() : Could not safely fire at target. Exiting.", AI_LOG_DEBUG) + return + + ai_log("engage_target() : Attempting a ranged attack.", AI_LOG_TRACE) + ranged_attack(target) + + // Run after them. + else if(!stand_ground) + ai_log("engage_target() : Target ([target]) too far away. Exiting.", AI_LOG_DEBUG) + set_stance(STANCE_APPROACH) + +// We're not entirely sure how holder will do melee attacks since any /mob/living could be holder, but we don't have to care because Interfaces. +/datum/ai_holder/proc/melee_attack(atom/A) + pre_melee_attack(A) + . = holder.IAttack(A) + if(.) + post_melee_attack(A) + +// Ditto. +/datum/ai_holder/proc/ranged_attack(atom/A) + pre_ranged_attack(A) + . = holder.IRangedAttack(A) + if(.) + post_ranged_attack(A) + +// Most mobs probably won't have this defined but we don't care. +/datum/ai_holder/proc/special_attack(atom/movable/AM) + pre_special_attack(AM) + . = holder.ISpecialAttack(AM) + if(.) + post_special_attack(AM) + +// Called when within striking/shooting distance, however cooldown is not considered. +// Override to do things like move in a random step for evasiveness. +// Note that this is called BEFORE the attack. +/datum/ai_holder/proc/on_engagement(atom/A) + +// Called before a ranged attack is attempted. +/datum/ai_holder/proc/pre_ranged_attack(atom/A) + +// Called before a melee attack is attempted. +/datum/ai_holder/proc/pre_melee_attack(atom/A) + +// Called before a 'special' attack is attempted. +/datum/ai_holder/proc/pre_special_attack(atom/A) + +// Called after a successful (IE not on cooldown) ranged attack. +// Note that this is not whether the projectile actually hit, just that one was launched. +/datum/ai_holder/proc/post_ranged_attack(atom/A) + +// Ditto but for melee. +/datum/ai_holder/proc/post_melee_attack(atom/A) + +// And one more for special snowflake attacks. +/datum/ai_holder/proc/post_special_attack(atom/A) + +// Used to make sure projectiles will probably hit the target and not the wall or a friend. +/datum/ai_holder/proc/test_projectile_safety(atom/movable/AM) + var/mob/living/L = check_trajectory(AM, holder) // This isn't always reliable but its better than the previous method. +// world << "Checked trajectory, would hit [L]." + + if(istype(L)) // Did we hit a mob? +// world << "Hit [L]." + if(holder.IIsAlly(L)) +// world << "Would hit ally, canceling." + return FALSE // We would hit a friend! +// world << "Won't threaten ally, firing." + return TRUE // Otherwise we don't care, even if its not the intended target. + else + if(!isliving(AM)) // If the original target was an object, then let it happen if it doesn't threaten an ally. +// world << "Targeting object, ignoring and firing." + return TRUE +// world << "Not sure." + + return !conserve_ammo // If we have infinite ammo than shooting the wall isn't so bad, but otherwise lets not. + +// Test if we are within range to attempt an attack, melee or ranged. +/datum/ai_holder/proc/within_range(atom/movable/AM) + var/distance = get_dist(holder, AM) + if(distance <= 1) + return TRUE // Can melee. + else if(holder.ICheckRangedAttack(AM) && distance <= max_range(AM)) + return TRUE // Can shoot. + return FALSE + +// Determines how close the AI will move to its target. +/datum/ai_holder/proc/closest_distance(atom/movable/AM) + return max(max_range(AM) - 1, 1) // Max range -1 just because we don't want to constantly get kited + +// Can be used to conditionally do a ranged or melee attack. +/datum/ai_holder/proc/max_range(atom/movable/AM) + return holder.ICheckRangedAttack(AM) ? 7 : 1 + +// Goes to the target, to attack them. +// Called when in STANCE_APPROACH. +/datum/ai_holder/proc/walk_to_target() + ai_log("walk_to_target() : Entering.", AI_LOG_DEBUG) + // Make sure we can still chase/attack them. + if(!target || !can_attack(target)) + ai_log("walk_to_target() : Lost target.", AI_LOG_INFO) + if(!find_target()) + lost_target() + ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG) + return + else + ai_log("walk_to_target() : Found new target ([target]).", AI_LOG_INFO) + + // Find out where we're going. + var/get_to = closest_distance(target) + var/distance = get_dist(holder, target) + ai_log("walk_to_target() : get_to is [get_to].", AI_LOG_TRACE) + + // We're here! + // Special case: Our holder has a special attack that is ranged, but normally the holder uses melee. + // If that happens, we'll switch to STANCE_FIGHT so they can use it. If the special attack is limited, they'll likely switch back next tick. + if(distance <= get_to || holder.ICheckSpecialAttack(target)) + ai_log("walk_to_target() : Within range.", AI_LOG_INFO) + forget_path() + set_stance(STANCE_FIGHT) + ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG) + return + + + // Otherwise keep walking. + if(!stand_ground) + walk_path(target, get_to) + + ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG) + +// Resists out of things. +// Sometimes there are times you want your mob to be buckled to something, so override this for when that is needed. +/datum/ai_holder/proc/handle_resist() + holder.resist() + +// Used to break through windows and barriers to a target on the other side. +// This does two passes, so that if its just a public access door, the windows nearby don't need to be smashed. +/datum/ai_holder/proc/breakthrough(atom/target_atom) + ai_log("breakthrough() : Entering", AI_LOG_TRACE) + + if(!can_breakthrough) + ai_log("breakthrough() : Not allowed to breakthrough. Exiting.", AI_LOG_TRACE) + return FALSE + + if(!isturf(holder.loc)) + ai_log("breakthrough() : Trapped inside \the [holder.loc]. Exiting.", AI_LOG_TRACE) + return FALSE + + var/dir_to_target = get_dir(holder, target_atom) + holder.face_atom(target_atom) + ai_log("breakthrough() : Exiting", AI_LOG_DEBUG) + + // Sometimes the mob will try to hit something diagonally, and generally this fails. + // So instead we will try two more times with some adjustments if the attack fails. + var/list/directions_to_try = list( + dir_to_target, + turn(dir_to_target, 45), + turn(dir_to_target, -45) + ) + + ai_log("breakthrough() : Starting peaceful pass.", AI_LOG_DEBUG) + + var/result = FALSE + + // First, we will try to peacefully make a path, I.E opening a door we have access to. + for(var/direction in directions_to_try) + result = destroy_surroundings(direction, violent = FALSE) + if(result) + break + + // Alright, lets smash some shit instead, if it didn't work and we're allowed to be violent. + if(!result && can_violently_breakthrough()) + ai_log("breakthrough() : Starting violent pass.", AI_LOG_DEBUG) + for(var/direction in directions_to_try) + result = destroy_surroundings(direction, violent = TRUE) + if(result) + break + + ai_log("breakthrough() : Exiting with [result].", AI_LOG_TRACE) + return result + +// Despite the name, this can also be used to help clear a path without any destruction. +/datum/ai_holder/proc/destroy_surroundings(direction, violent = TRUE) + ai_log("destroy_surroundings() : Entering.", AI_LOG_TRACE) + if(!direction) + direction = pick(cardinal) // FLAIL WILDLY + ai_log("destroy_surroundings() : No direction given, picked [direction] randomly.", AI_LOG_DEBUG) + + var/turf/problem_turf = get_step(holder, direction) + + // First, give peace a chance. + if(!violent) + ai_log("destroy_surroundings() : Going to try to peacefully clear [problem_turf].", AI_LOG_DEBUG) + for(var/obj/machinery/door/D in problem_turf) + if(D.density && holder.Adjacent(D) && D.allowed(holder) && D.operable()) + // First, try to open the door if possible without smashing it. We might have access. + ai_log("destroy_surroundings() : Opening closed door.", AI_LOG_INFO) + return D.open() + + // Peace has failed us, can we just smash the things in the way? + else + ai_log("destroy_surroundings() : Going to try to violently clear [problem_turf].", AI_LOG_DEBUG) + // First, kill windows in the way. + for(var/obj/structure/window/W in problem_turf) + if(W.dir == reverse_dir[holder.dir]) // So that windows get smashed in the right order + ai_log("destroy_surroundings() : Attacking side window.", AI_LOG_INFO) + return holder.IAttack(W) + + else if(W.is_fulltile()) + ai_log("destroy_surroundings() : Attacking full tile window.", AI_LOG_INFO) + return holder.IAttack(W) + + // Kill hull shields in the way. + for(var/obj/effect/energy_field/shield in problem_turf) + if(shield.density) // Don't attack shields that are already down. + ai_log("destroy_surroundings() : Attacking hull shield.", AI_LOG_INFO) + return holder.IAttack(shield) + + // Kill common obstacle in the way like tables. + var/obj/structure/obstacle = locate(/obj/structure, problem_turf) + if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille)) + ai_log("destroy_surroundings() : Attacking generic structure.", AI_LOG_INFO) + return holder.IAttack(obstacle) + + for(var/obj/machinery/door/D in problem_turf) // Required since firelocks take up the same turf. + if(D.density) + ai_log("destroy_surroundings() : Attacking closed door.", AI_LOG_INFO) + return holder.IAttack(D) + + ai_log("destroy_surroundings() : Exiting due to nothing to attack.", AI_LOG_INFO) + return FALSE // Nothing to attack. + +// Override for special behaviour. +/datum/ai_holder/proc/can_violently_breakthrough() + return violent_breakthrough \ No newline at end of file diff --git a/code/modules/ai/ai_holder_combat_unseen.dm b/code/modules/ai/ai_holder_combat_unseen.dm new file mode 100644 index 0000000000..0cb518f08c --- /dev/null +++ b/code/modules/ai/ai_holder_combat_unseen.dm @@ -0,0 +1,43 @@ +// Used for fighting invisible things. + +// Used when a target is out of sight or invisible. +/datum/ai_holder/proc/engage_unseen_enemy() + // Lets do some last things before giving up. + if(!ranged) + if(get_dist(holder, target_last_seen_turf > 1)) // We last saw them over there. + // Go to where you last saw the enemy. + give_destination(target_last_seen_turf, 1, TRUE) // This will set it to STANCE_REPOSITION. + else // We last saw them next to us, so do a blind attack on that tile. + melee_on_tile(target_last_seen_turf) + + else if(!conserve_ammo) + shoot_near_turf(target_last_seen_turf) + +// This shoots semi-randomly near a specific turf. +/datum/ai_holder/proc/shoot_near_turf(turf/targeted_turf) + if(!ranged) + return // Can't shoot. + if(get_dist(holder, targeted_turf) > max_range(targeted_turf)) + return // Too far to shoot. + + var/turf/T = pick(RANGE_TURFS(2, targeted_turf)) // The turf we're actually gonna shoot at. + on_engagement(T) + if(firing_lanes && !test_projectile_safety(T)) + step_rand(holder) + holder.face_atom(T) + return + + ranged_attack(T) + +// Attempts to attack something on a specific tile. +// TODO: Put on mob/living? +/datum/ai_holder/proc/melee_on_tile(turf/T) + var/mob/living/L = locate() in T + if(!L) + T.visible_message("\The [holder] attacks nothing around \the [T].") + return + + if(holder.IIsAlly(L)) // Don't hurt our ally. + return + + melee_attack(L) \ No newline at end of file diff --git a/code/modules/ai/ai_holder_communication.dm b/code/modules/ai/ai_holder_communication.dm new file mode 100644 index 0000000000..93267d19f4 --- /dev/null +++ b/code/modules/ai/ai_holder_communication.dm @@ -0,0 +1,134 @@ +// Contains code for speaking and emoting. + +/datum/ai_holder + var/threaten = FALSE // If hostile and sees a valid target, gives a 'warning' to the target before beginning the attack. + var/threatening = FALSE // If the mob actually gave the warning, checked so it doesn't constantly yell every tick. + var/threaten_delay = 3 SECONDS // How long a 'threat' lasts, until actual fighting starts. If null, the mob never starts the fight but still does the threat. + var/threaten_timeout = 1 MINUTE // If the mob threatens someone, they leave, and then come back before this timeout period, the mob escalates to fighting immediately. + var/last_conflict_time = null // Last occurance of fighting being used, in world.time. + var/last_threaten_time = null // Ditto but only for threats. + + var/speak_chance = 0 // Probability that the mob talks (this is 'X in 200' chance since even 1/100 is pretty noisy) + + +/datum/ai_holder/proc/should_threaten() + if(!threaten) + return FALSE // We don't negotiate. + if(target in attackers) + return FALSE // They (or someone like them) attacked us before, escalate immediately. + if(!will_threaten(target)) + return FALSE // Pointless to threaten an animal, a mindless drone, or an object. + if(stance in STANCES_COMBAT) + return FALSE // We're probably already fighting or recently fought if not in these stances. + if(last_threaten_time && threaten_delay && last_conflict_time + threaten_timeout > world.time) + return FALSE // We threatened someone recently, so lets show them we mean business. + return TRUE // Lets give them a chance to choose wisely and walk away. + +/datum/ai_holder/proc/threaten_target() + holder.face_atom(target) // Constantly face the target. + + if(!threatening) // First tick. + threatening = TRUE + last_threaten_time = world.time + + if(holder.say_list) + holder.ISay(safepick(holder.say_list.say_threaten)) + playsound(holder.loc, holder.say_list.threaten_sound, 50, 1) // We do this twice to make the sound -very- noticable to the target. + playsound(target.loc, holder.say_list.threaten_sound, 50, 1) // Actual aim-mode also does that so at least it's consistant. + else // Otherwise we are waiting for them to go away or to wait long enough for escalate. + if(target in list_targets()) // Are they still visible? + var/should_escalate = FALSE + + if(threaten_delay && last_threaten_time + threaten_delay < world.time) // Waited too long. + should_escalate = TRUE + else if(last_conflict_time + threaten_timeout > world.time) // We got attacked while threatening them. + should_escalate = TRUE + + if(should_escalate) + threatening = FALSE + set_stance(STANCE_APPROACH) + if(holder.say_list) + holder.ISay(safepick(holder.say_list.say_escalate)) + else + return // Wait a bit. + + else // They left, or so we think. + threatening = FALSE + set_stance(STANCE_IDLE) + if(holder.say_list) + holder.ISay(safepick(holder.say_list.say_stand_down)) + playsound(holder.loc, holder.say_list.stand_down_sound, 50, 1) // We do this twice to make the sound -very- noticable to the target. + playsound(target.loc, holder.say_list.stand_down_sound, 50, 1) // Actual aim-mode also does that so at least it's consistant. + +// Determines what is deserving of a warning when STANCE_ALERT is active. +/datum/ai_holder/proc/will_threaten(mob/living/the_target) + if(!isliving(the_target)) + return FALSE // Turrets don't give a fuck so neither will we. + /* + // Find a nice way of doing this later. + if(istype(the_target, /mob/living/simple_mob) && istype(holder, /mob/living/simple_mob)) + var/mob/living/simple_mob/us = holder + var/mob/living/simple_mob/them = target + + if(them.intelligence_level < us.intelligence_level) // Todo: Bitflag these. + return FALSE // Humanoids don't care about drones/animals/etc. Drones don't care about animals, and so on. + */ + return TRUE + +// Temp defines to make the below code a bit more readable. +#define COMM_SAY "say" +#define COMM_AUDIBLE_EMOTE "audible emote" +#define COMM_VISUAL_EMOTE "visual emote" + +/datum/ai_holder/proc/handle_idle_speaking() + if(rand(0,200) < speak_chance) + // Check if anyone is around to 'appreciate' what we say. + var/alone = TRUE + for(var/m in viewers(holder)) + var/mob/M = m + if(M.client) + alone = FALSE + break + if(alone) // Forever alone. No point doing anything else. + return + + var/list/comm_types = list() // What kinds of things can we do? + if(!holder.say_list) + return + + if(holder.say_list.speak.len) + comm_types += COMM_SAY + if(holder.say_list.emote_hear.len) + comm_types += COMM_AUDIBLE_EMOTE + if(holder.say_list.emote_see.len) + comm_types += COMM_VISUAL_EMOTE + + if(!comm_types.len) + return // All the relevant lists are empty, so do nothing. + + switch(pick(comm_types)) + if(COMM_SAY) + holder.ISay(safepick(holder.say_list.speak)) + if(COMM_AUDIBLE_EMOTE) + holder.audible_emote(safepick(holder.say_list.emote_hear)) + if(COMM_VISUAL_EMOTE) + holder.visible_emote(safepick(holder.say_list.emote_see)) + +#undef COMM_SAY +#undef COMM_AUDIBLE_EMOTE +#undef COMM_VISUAL_EMOTE + +// Handles the holder hearing a mob's say() +// Does nothing by default, override this proc for special behavior. +/datum/ai_holder/proc/on_hear_say(mob/living/speaker, message) + return + +// This is to make responses feel a bit more natural and not instant. +/datum/ai_holder/proc/delayed_say(var/message, var/mob/speak_to) + spawn(rand(1 SECOND, 2 SECONDS)) + if(!src || !holder || !can_act()) // We might've died/got deleted/etc in the meantime. + return + + if(speak_to) + holder.face_atom(speak_to) + holder.ISay(message) diff --git a/code/modules/ai/ai_holder_cooperation.dm b/code/modules/ai/ai_holder_cooperation.dm new file mode 100644 index 0000000000..0f6b0bcfa2 --- /dev/null +++ b/code/modules/ai/ai_holder_cooperation.dm @@ -0,0 +1,115 @@ +// Involves cooperating with other ai_holders. +/datum/ai_holder + var/cooperative = FALSE // If true, asks allies to help when fighting something. + var/call_distance = 14 // How far away calls for help will go for. + var/last_helpask_time = 0 // world.time when a mob asked for help. + var/list/faction_friends = list() // List of all mobs inside the faction with ai_holders that have cooperate on, to call for help without using range(). + // Note that this is only used for sending calls out. Receiving calls doesn't care about this list, only if the mob is in the faction. + // This means the AI could respond to a player's call for help, if a way to do so was implemented. + + // These vars don't do anything currently. They did before but an optimization made them nonfunctional. + // It was probably worth it. + var/call_players = FALSE // (Currently nonfunctional) If true, players get notified of an allied mob calling for help. + var/called_player_message = "needs help!" // (Currently nonfunctional) Part of a message used when above var is true. Full message is "\The [holder] [called_player_message]" + +/datum/ai_holder/New(new_holder) + ..() + if(cooperative) + build_faction_friends() + +/datum/ai_holder/Destroy() + if(faction_friends.len) //This list is shared amongst the faction + faction_friends -= src + return ..() + +// Handles everything about that list. +// Call on initialization or if something weird happened like the mob switched factions. +/datum/ai_holder/proc/build_faction_friends() + if(faction_friends.len) // Already have a list. + // Assume we're moving to a new faction. + faction_friends -= src // Get us out of the current list shared by everyone else. + faction_friends = list() // Then make our list empty and unshared in case we become a loner. + + // Find another AI-controlled mob in the same faction if possible. + var/mob/living/first_friend + for(var/mob/living/L in living_mob_list) + if(L.faction == holder.faction && L.ai_holder) + first_friend = L + break + + if(first_friend) // Joining an already established faction. + faction_friends = first_friend.ai_holder.faction_friends + faction_friends |= holder + else // We're the 'founder' (first and/or only member) of this faction. + faction_friends |= holder + +// Requests help in combat from other mobs possessing ai_holders. +/datum/ai_holder/proc/request_help() + ai_log("request_help() : Entering.", AI_LOG_DEBUG) + if(!cooperative || ((world.time - last_helpask_time) < 10 SECONDS)) + return + + ai_log("request_help() : Asking for help.", AI_LOG_INFO) + last_helpask_time = world.time + +// for(var/mob/living/L in range(call_distance, holder)) + for(var/mob/living/L in faction_friends) + if(L == holder) // Lets not call ourselves. + continue + if(holder.z != L.z) // On seperate z-level. + continue + if(get_dist(L, holder) > call_distance) // Too far to 'hear' the call for help. + continue + + if(holder.IIsAlly(L)) + // This will currently never run sadly, until faction_friends is made to accept players too. + // That might be for the best since I can imagine it getting spammy in a big fight. + if(L.client && call_players) // Dealing with a player. + ai_log("request_help() : Asking [L] (Player) for help.", AI_LOG_INFO) + to_chat(L, "\The [holder] [called_player_message]") + + else if(L.ai_holder) // Dealing with an AI. + ai_log("request_help() : Asking [L] (AI) for help.", AI_LOG_INFO) + L.ai_holder.help_requested(holder) + + ai_log("request_help() : Exiting.", AI_LOG_DEBUG) + +// What allies receive when someone else is calling for help. +/datum/ai_holder/proc/help_requested(mob/living/friend) + ai_log("help_requested() : Entering.", AI_LOG_DEBUG) + if(stance == STANCE_SLEEP) + ai_log("help_requested() : Help requested by [friend] but we are asleep.", AI_LOG_INFO) + return + if(!cooperative) + ai_log("help_requested() : Help requested by [friend] but we're not cooperative.", AI_LOG_INFO) + return + if(stance in STANCES_COMBAT) + ai_log("help_requested() : Help requested by [friend] but we are busy fighting something else.", AI_LOG_INFO) + return + if(!can_act()) + ai_log("help_requested() : Help requested by [friend] but cannot act (stunned or dead).", AI_LOG_INFO) + return + if(!holder.IIsAlly(friend)) // Extra sanity. + ai_log("help_requested() : Help requested by [friend] but we hate them.", AI_LOG_INFO) + return + if(friend.ai_holder && friend.ai_holder.target && !can_attack(friend.ai_holder.target)) + ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO) + return + if(get_dist(holder, friend) <= follow_distance) + ai_log("help_requested() : Help requested by [friend] but we're already here.", AI_LOG_INFO) + return + if(get_dist(holder, friend) <= vision_range) // Within our sight. + ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO) + if(friend.ai_holder) // AI calling for help. + if(friend.ai_holder.target && can_attack(friend.ai_holder.target)) // Friend wants us to attack their target. + last_conflict_time = world.time // So we attack immediately and not threaten. + give_target(friend.ai_holder.target) // This will set us to the appropiate stance. + ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG) + return + + // Otherwise they're outside our sight, lack a target, or aren't AI controlled, but within call range. + // So assuming we're AI controlled, we'll go to them and see whats wrong. + ai_log("help_requested() : Help requested by [friend], going to go to friend.", AI_LOG_INFO) + set_follow(friend, 10 SECONDS) + ai_log("help_requested() : Exiting.", AI_LOG_DEBUG) + diff --git a/code/modules/ai/ai_holder_debug.dm b/code/modules/ai/ai_holder_debug.dm new file mode 100644 index 0000000000..beee998af8 --- /dev/null +++ b/code/modules/ai/ai_holder_debug.dm @@ -0,0 +1,89 @@ +// Contains settings to make it easier to debug things. + +/datum/ai_holder + var/path_display = FALSE // Displays a visual path when A* is being used. + var/path_icon = 'icons/misc/debug_group.dmi' // What icon to use for the overlay + var/path_icon_state = "red" // What state to use for the overlay + var/image/path_overlay // A reference to the overlay + + var/last_turf_display = FALSE // Similar to above, but shows the target's last known turf visually. + var/last_turf_icon_state = "green" // A seperate icon_state from the previous. + var/image/last_turf_overlay // Another reference for an overlay. + + var/stance_coloring = FALSE // Colors the mob depending on its stance. + + var/debug_ai = AI_LOG_OFF // The level of debugging information to display to people who can see log_debug(). + +/datum/ai_holder/New() + ..() + path_overlay = new(path_icon,path_icon_state) + last_turf_overlay = new(path_icon, last_turf_icon_state) + +/datum/ai_holder/Destroy() + path_overlay = null + last_turf_overlay = null + return ..() + +//For debug purposes! +/datum/ai_holder/proc/ai_log_output(var/msg = "missing message", var/ver = AI_LOG_INFO) + var/span_type + switch(ver) + if(AI_LOG_OFF) + return + if(AI_LOG_ERROR) + span_type = "debug_error" + if(AI_LOG_WARNING) + span_type = "debug_warning" + if(AI_LOG_INFO) + span_type = "debug_info" + if(AI_LOG_DEBUG) + span_type = "debug_debug" // RAS syndrome at work. + if(AI_LOG_TRACE) + span_type = "debug_trace" + if(ver <= debug_ai) + log_debug("AI: ([holder]:\ref[holder] | [holder.x],[holder.y],[holder.z])(@[world.time]): [msg] ") + +// Colors the mob based on stance, to visually tell what stance it is for debugging. +// Probably not something you want for regular use. +/datum/ai_holder/proc/stance_color() + var/new_color = null + switch(stance) + if(STANCE_SLEEP) + new_color = "#FFFFFF" // White + if(STANCE_IDLE) + new_color = "#00FF00" // Green + if(STANCE_ALERT) + new_color = "#FFFF00" // Yellow + if(STANCE_APPROACH) + new_color = "#FF9933" // Orange + if(STANCE_FIGHT) + new_color = "#FF0000" // Red + if(STANCE_MOVE) + new_color = "#0000FF" // Blue + if(STANCE_REPOSITION) + new_color = "#FF00FF" // Purple + if(STANCE_FOLLOW) + new_color = "#00FFFF" // Cyan + if(STANCE_FLEE) + new_color = "#666666" // Grey + if(STANCE_DISABLED) + new_color = "#000000" // Black + holder.color = new_color + +// Turns on all the debugging stuff. +/datum/ai_holder/proc/debug() + stance_coloring = TRUE + path_display = TRUE + last_turf_display = TRUE + debug_ai = AI_LOG_INFO + +/datum/ai_holder/hostile/debug + wander = FALSE + conserve_ammo = FALSE + intelligence_level = AI_SMART + + stance_coloring = TRUE + path_display = TRUE + last_turf_display = TRUE + debug_ai = AI_LOG_INFO + diff --git a/code/modules/ai/ai_holder_disabled.dm b/code/modules/ai/ai_holder_disabled.dm new file mode 100644 index 0000000000..e7cd6aa1f8 --- /dev/null +++ b/code/modules/ai/ai_holder_disabled.dm @@ -0,0 +1,95 @@ +// Handles AI while stunned or otherwise disabled. + +/datum/ai_holder + var/respect_confusion = TRUE // If false, the mob won't wander around recklessly. + +// If our holder is able to do anything. +/datum/ai_holder/proc/can_act() + if(holder.stat) // Dead or unconscious. + ai_log("can_act() : Stat was non-zero ([holder.stat]).", AI_LOG_TRACE) + return FALSE + if(holder.incapacitated(INCAPACITATION_DISABLED)) // Stunned in some form. + ai_log("can_act() : Incapacited.", AI_LOG_TRACE) + return FALSE + return TRUE + +// Test if we should switch to STANCE_DISABLE. +// Currently tests for death, stuns, and confusion. +/datum/ai_holder/proc/is_disabled() + if(!can_act()) + return TRUE + if(is_confused()) + return TRUE + return FALSE + +/datum/ai_holder/proc/is_confused() + return holder.confused > 0 && respect_confusion + +// Called by the main loop. +/datum/ai_holder/proc/handle_disabled() + if(!can_act()) + return // Just sit there and take it. + else if(is_confused()) + dangerous_wander() // Let's bump into allies and hit them. + +// Similar to normal wander, but will walk into tiles that are harmful, and attack anything they bump into, including allies. +// Occurs when confused. +/datum/ai_holder/proc/dangerous_wander() + ai_log("dangerous_wander() : Entered.", AI_LOG_DEBUG) + if(isturf(holder.loc) && can_act()) + // Test if we should refrain from falling/attacking allies, if we're smart enough to realize that. + if(intelligence_level > AI_NORMAL) + var/unsafe = FALSE + + tile_test: + for(var/dir_tested in cardinal) + var/turf/turf_tested = get_step(holder, dir_tested) + // Look for unsafe tiles. + if(!turf_tested.is_safe_to_enter(holder)) + unsafe = TRUE + break + + // Look for allies. + for(var/mob/living/L in turf_tested) + if(holder.IIsAlly(L)) + unsafe = TRUE + break tile_test + + + if(unsafe) + ai_log("dangerous_wander() : Staying still due to risk of harm to self or allies.", AI_LOG_TRACE) + return // Just stay still. + + var/moving_to = 0 + moving_to = pick(cardinal) + var/turf/T = get_step(holder, moving_to) + + var/mob/living/L = locate() in T + if(L) + // Attack whoever's on the tile. Even if it's an ally. + ai_log("dangerous_wander() : Going to confuse-attack [L].", AI_LOG_TRACE) + melee_attack(L) + else + // Move to the tile. Even if it's unsafe. + ai_log("dangerous_wander() : Going to confuse-walk to [T] ([T.x],[T.y],[T.z]).", AI_LOG_TRACE) + holder.IMove(T, safety = FALSE) + ai_log("dangerous_wander() : Exited.", AI_LOG_DEBUG) + +/* +// Wanders randomly in cardinal directions. +/datum/ai_holder/proc/handle_wander_movement() + ai_log("handle_wander_movement() : Entered.", AI_LOG_DEBUG) + if(isturf(holder.loc) && can_act()) + wander_delay-- + if(wander_delay <= 0) + if(!wander_when_pulled && holder.pulledby) + ai_log("handle_wander_movement() : Being pulled and cannot wander. Exiting.", AI_LOG_DEBUG) + return + + var/moving_to = 0 // Apparently this is required or it always picks 4, according to the previous developer for simplemob AI. + moving_to = pick(cardinal) + holder.set_dir(moving_to) + holder.IMove(get_step(holder,moving_to)) + wander_delay = base_wander_delay + ai_log("handle_wander_movement() : Exited.", AI_LOG_DEBUG) +*/ \ No newline at end of file diff --git a/code/modules/ai/ai_holder_fleeing.dm b/code/modules/ai/ai_holder_fleeing.dm new file mode 100644 index 0000000000..83a7be94b8 --- /dev/null +++ b/code/modules/ai/ai_holder_fleeing.dm @@ -0,0 +1,45 @@ +// This code handles what to do inside STANCE_FLEE. + +/datum/ai_holder + var/can_flee = TRUE // If they're even allowed to flee. + var/flee_when_dying = TRUE // If they should flee when low on health. + var/dying_threshold = 0.3 // How low on health the holder needs to be before fleeing. Defaults to 30% or lower health. + var/flee_when_outmatched = FALSE // If they should flee upon reaching a specific tension threshold. + var/outmatched_threshold = 200 // The tension threshold needed for a mob to decide it should run away. + + + +/datum/ai_holder/proc/should_flee(force = FALSE) + if(holder.has_modifier_of_type(/datum/modifier/berserk)) // Berserked mobs will never flee, even if 'forced' to. + return FALSE + if(force) + return TRUE + + if(can_flee) + if(special_flee_check()) + return TRUE + if(!hostile && !retaliate) + return TRUE // We're not hostile and someone attacked us first. + if(flee_when_dying && (holder.health / holder.getMaxHealth()) <= dying_threshold) + return TRUE // We're gonna die! + else if(flee_when_outmatched && holder.get_tension() >= outmatched_threshold) + return TRUE // We're fighting something way way stronger then us. + return FALSE + +// Override for special fleeing conditionally. +/datum/ai_holder/proc/special_flee_check() + return FALSE + +/datum/ai_holder/proc/flee_from_target() + ai_log("flee_from_target() : Entering.", AI_LOG_DEBUG) + + if(!target || !should_flee() || !can_attack(target)) // can_attack() is used since it checks the same things we would need to anyways. + ai_log("flee_from_target() : Lost target to flee from.", AI_LOG_INFO) + lose_target() + set_stance(STANCE_IDLE) + ai_log("flee_from_target() : Exiting.", AI_LOG_DEBUG) + return + + ai_log("flee_from_target() : Stepping away.", AI_LOG_TRACE) + step_away(holder, target, vision_range) + ai_log("flee_from_target() : Exiting.", AI_LOG_DEBUG) \ No newline at end of file diff --git a/code/modules/ai/ai_holder_follow.dm b/code/modules/ai/ai_holder_follow.dm new file mode 100644 index 0000000000..1e7bb0875d --- /dev/null +++ b/code/modules/ai/ai_holder_follow.dm @@ -0,0 +1,68 @@ +// This handles following a specific atom/movable, without violently murdering it. + +/datum/ai_holder + // Following. + var/atom/movable/leader = null // The movable atom that the mob wants to follow. + var/follow_distance = 2 // How far leader must be to start moving towards them. + var/follow_until_time = 0 // world.time when the mob will stop following leader. 0 means it won't time out. + +/datum/ai_holder/proc/walk_to_leader() + ai_log("walk_to_leader() : Entering.",AI_LOG_TRACE) + if(!leader) + ai_log("walk_to_leader() : No leader.", AI_LOG_WARNING) + forget_path() + set_stance(STANCE_IDLE) + ai_log("walk_to_leader() : Exiting.", AI_LOG_TRACE) + return + + // Did we time out? + if(follow_until_time && world.time > follow_until_time) + ai_log("walk_to_leader() : Follow timed out, losing leader.", AI_LOG_INFO) + lose_follow() + set_stance(STANCE_IDLE) + ai_log("walk_to_leader() : Exiting.", AI_LOG_TRACE) + return + + var/get_to = follow_distance + var/distance = get_dist(holder, leader) + ai_log("walk_to_leader() : get_to is [get_to].", AI_LOG_TRACE) + + // We're here! + if(distance <= get_to) + give_up_movement() + set_stance(STANCE_IDLE) + ai_log("walk_to_leader() : Within range, exiting.", AI_LOG_INFO) + return + + ai_log("walk_to_leader() : Walking.", AI_LOG_TRACE) + walk_path(leader, get_to) + ai_log("walk_to_leader() : Exiting.",AI_LOG_DEBUG) + +/datum/ai_holder/proc/set_follow(mob/living/L, follow_for = 0) + ai_log("set_follow() : Entered.", AI_LOG_DEBUG) + if(!L) + ai_log("set_follow() : Was told to follow a nonexistant mob.", AI_LOG_ERROR) + return FALSE + + leader = L + follow_until_time = !follow_for ? 0 : world.time + follow_for + ai_log("set_follow() : Exited.", AI_LOG_DEBUG) + return TRUE + +/datum/ai_holder/proc/lose_follow() + ai_log("lose_follow() : Entered.", AI_LOG_DEBUG) + ai_log("lose_follow() : Going to lose leader [leader].", AI_LOG_INFO) + leader = null + give_up_movement() + ai_log("lose_follow() : Exited.", AI_LOG_DEBUG) + +/datum/ai_holder/proc/should_follow_leader() + if(!leader) + return FALSE + if(follow_until_time && world.time > follow_until_time) + lose_follow() + set_stance(STANCE_IDLE) + return FALSE + if(get_dist(holder, leader) > follow_distance) + return TRUE + return FALSE \ No newline at end of file diff --git a/code/modules/ai/ai_holder_movement.dm b/code/modules/ai/ai_holder_movement.dm new file mode 100644 index 0000000000..55a1098b90 --- /dev/null +++ b/code/modules/ai/ai_holder_movement.dm @@ -0,0 +1,154 @@ +/datum/ai_holder + // General. + var/turf/destination = null // The targeted tile the mob wants to walk to. + var/min_distance_to_destination = 1 // Holds how close the mob should go to destination until they're done. + + // Home. + var/turf/home_turf = null // The mob's 'home' turf. It will try to stay near it if told to do so. This is the turf the AI was initialized on by default. + var/returns_home = FALSE // If true, makes the mob go to its 'home' if it strays too far. + var/home_low_priority = FALSE // If true, the mob will not go home unless it has nothing better to do, e.g. its following someone. + var/max_home_distance = 3 // How far the mob can go away from its home before being told to go_home(). + // Note that there is a 'BYOND cap' of 14 due to limitations of get_/step_to(). + + // Wandering. + var/wander = FALSE // If true, the mob will randomly move in the four cardinal directions when idle. + var/wander_delay = 0 // How many ticks until the mob can move a tile in handle_wander_movement(). + var/base_wander_delay = 2 // What the above var gets set to when it wanders. Note that a tick happens every half a second. + var/wander_when_pulled = FALSE // If the mob will refrain from wandering if someone is pulling it. + + +/datum/ai_holder/proc/walk_to_destination() + ai_log("walk_to_destination() : Entering.",AI_LOG_TRACE) + if(!destination) + ai_log("walk_to_destination() : No destination.", AI_LOG_WARNING) + forget_path() + set_stance(stance == STANCE_REPOSITION ? STANCE_APPROACH : STANCE_IDLE) + ai_log("walk_to_destination() : Exiting.", AI_LOG_TRACE) + return + + var/get_to = min_distance_to_destination + var/distance = get_dist(holder, destination) + ai_log("walk_to_destination() : get_to is [get_to].", AI_LOG_TRACE) + + // We're here! + if(distance <= get_to) + give_up_movement() + set_stance(stance == STANCE_REPOSITION ? STANCE_APPROACH : STANCE_IDLE) + ai_log("walk_to_destination() : Destination reached. Exiting.", AI_LOG_INFO) + return + + ai_log("walk_to_destination() : Walking.", AI_LOG_TRACE) + walk_path(destination, get_to) + ai_log("walk_to_destination() : Exiting.",AI_LOG_TRACE) + +/datum/ai_holder/proc/should_go_home() + if(!returns_home || !home_turf) + return FALSE + if(get_dist(holder, home_turf) > max_home_distance) + if(!home_low_priority) + return TRUE + else if(!leader && !target) + return TRUE + return FALSE +// return (returns_home && home_turf) && (get_dist(holder, home_turf) > max_home_distance) + +/datum/ai_holder/proc/go_home() + if(home_turf) + ai_log("go_home() : Telling holder to go home.", AI_LOG_INFO) + lose_follow() // So they don't try to path back and forth. + give_destination(home_turf, max_home_distance) + else + ai_log("go_home() : Told to go home without home_turf.", AI_LOG_ERROR) + +/datum/ai_holder/proc/give_destination(turf/new_destination, min_distance = 1, combat = FALSE) + ai_log("give_destination() : Entering.", AI_LOG_DEBUG) + + destination = new_destination + min_distance_to_destination = min_distance + + if(new_destination != null) + ai_log("give_destination() : Going to new destination.", AI_LOG_INFO) + set_stance(combat ? STANCE_REPOSITION : STANCE_MOVE) + return TRUE + else + ai_log("give_destination() : Given null destination.", AI_LOG_ERROR) + + ai_log("give_destination() : Exiting.", AI_LOG_DEBUG) + + +// Walk towards whatever. +/datum/ai_holder/proc/walk_path(atom/A, get_to = 1) + ai_log("walk_path() : Entered.", AI_LOG_TRACE) + + if(use_astar) + if(!path.len) // If we're missing a path, make a new one. + ai_log("walk_path() : No path. Attempting to calculate path.", AI_LOG_DEBUG) + calculate_path(A, get_to) + + if(!path.len) // If we still don't have one, then the target's probably somewhere inaccessible to us. Get as close as we can. + ai_log("walk_path() : Failed to obtain path to target. Using get_step_to() instead.", AI_LOG_INFO) + // step_to(holder, A) + if(holder.IMove(get_step_to(holder, A)) == MOVEMENT_FAILED) + ai_log("walk_path() : Failed to move, attempting breakthrough.", AI_LOG_INFO) + breakthrough(A) // We failed to move, time to smash things. + return + + if(move_once() == FALSE) // Start walking the path. + ai_log("walk_path() : Failed to step.", AI_LOG_TRACE) + ++failed_steps + if(failed_steps > 3) // We're probably stuck. + ai_log("walk_path() : Too many failed_steps.", AI_LOG_DEBUG) + forget_path() // So lets try again with a new path. + failed_steps = 0 + + else + // step_to(holder, A) + ai_log("walk_path() : Going to IMove().", AI_LOG_TRACE) + if(holder.IMove(get_step_to(holder, A)) == MOVEMENT_FAILED ) + ai_log("walk_path() : Failed to move, attempting breakthrough.", AI_LOG_INFO) + breakthrough(A) // We failed to move, time to smash things. + + ai_log("walk_path() : Exited.", AI_LOG_TRACE) + + +//Take one step along a path +/datum/ai_holder/proc/move_once() + ai_log("move_once() : Entered.", AI_LOG_TRACE) + if(!path.len) + return + + if(path_display) + var/turf/T = src.path[1] + T.overlays -= path_overlay + +// step_towards(holder, src.path[1]) + if(holder.IMove(get_step_towards(holder, src.path[1])) != MOVEMENT_ON_COOLDOWN) + if(holder.loc != src.path[1]) + ai_log("move_once() : Failed step. Exiting.", AI_LOG_TRACE) + return MOVEMENT_FAILED + else + path -= src.path[1] + ai_log("move_once() : Successful step. Exiting.", AI_LOG_TRACE) + return MOVEMENT_SUCCESSFUL + ai_log("move_once() : Mob movement on cooldown. Exiting.", AI_LOG_TRACE) + return MOVEMENT_ON_COOLDOWN + +/datum/ai_holder/proc/should_wander() + return wander && !leader + +// Wanders randomly in cardinal directions. +/datum/ai_holder/proc/handle_wander_movement() + ai_log("handle_wander_movement() : Entered.", AI_LOG_TRACE) + if(isturf(holder.loc) && can_act()) + wander_delay-- + if(wander_delay <= 0) + if(!wander_when_pulled && holder.pulledby) + ai_log("handle_wander_movement() : Being pulled and cannot wander. Exiting.", AI_LOG_DEBUG) + return + + var/moving_to = 0 // Apparently this is required or it always picks 4, according to the previous developer for simplemob AI. + moving_to = pick(cardinal) + holder.set_dir(moving_to) + holder.IMove(get_step(holder,moving_to)) + wander_delay = base_wander_delay + ai_log("handle_wander_movement() : Exited.", AI_LOG_TRACE) diff --git a/code/modules/ai/ai_holder_pathfinding.dm b/code/modules/ai/ai_holder_pathfinding.dm new file mode 100644 index 0000000000..1a20c6f682 --- /dev/null +++ b/code/modules/ai/ai_holder_pathfinding.dm @@ -0,0 +1,58 @@ +// This handles obtaining a (usually A*) path towards something, such as a target, destination, or leader. +// This interacts heavily with code inside ai_holder_movement.dm + +/datum/ai_holder + // Pathfinding. + var/use_astar = FALSE // Do we use the more expensive A* implementation or stick with BYOND's default step_to()? + var/list/path = list() // A list of tiles that A* gave us as a solution to reach the target. + var/list/obstacles = list() // Things A* will try to avoid. + var/astar_adjacent_proc = /turf/proc/CardinalTurfsWithAccess // Proc to use when A* pathfinding. Default makes them bound to cardinals. + var/failed_steps = 0 // If move_once() fails to move the mob onto the correct tile, this increases. When it reaches 3, the path is recalc'd since they're probably stuck. + +// This clears the stored A* path. +/datum/ai_holder/proc/forget_path() + ai_log("forget_path() : Entering.", AI_LOG_DEBUG) + if(path_display) + for(var/turf/T in path) + T.overlays -= path_overlay + path.Cut() + ai_log("forget_path() : Exiting.", AI_LOG_DEBUG) + +/datum/ai_holder/proc/give_up_movement() + ai_log("give_up_movement() : Entering.", AI_LOG_DEBUG) + forget_path() + destination = null + ai_log("give_up_movement() : Exiting.", AI_LOG_DEBUG) + +/datum/ai_holder/proc/calculate_path(atom/A, get_to = 1) + ai_log("calculate_path([A],[get_to]) : Entering.", AI_LOG_DEBUG) + if(!A) + ai_log("calculate_path() : Called without an atom. Exiting.",AI_LOG_WARNING) + return + + if(!use_astar) // If we don't use A* then this is pointless. + ai_log("calculate_path() : Not using A*, Exiting.", AI_LOG_DEBUG) + return + + get_path(get_turf(A), get_to) + + ai_log("calculate_path() : Exiting.", AI_LOG_DEBUG) + +//A* now, try to a path to a target +/datum/ai_holder/proc/get_path(var/turf/target,var/get_to = 1, var/max_distance = world.view*6) + ai_log("get_path() : Entering.",AI_LOG_DEBUG) + forget_path() + var/list/new_path = AStar(get_turf(holder.loc), target, astar_adjacent_proc, /turf/proc/Distance, min_target_dist = get_to, max_node_depth = max_distance, id = holder.IGetID(), exclude = obstacles) + + if(new_path && new_path.len) + path = new_path + ai_log("get_path() : Made new path.",AI_LOG_DEBUG) + if(path_display) + for(var/turf/T in path) + T.overlays |= path_overlay + else + ai_log("get_path() : Failed to make new path. Exiting.",AI_LOG_DEBUG) + return 0 + + ai_log("get_path() : Exiting.", AI_LOG_DEBUG) + return path.len \ No newline at end of file diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm new file mode 100644 index 0000000000..acb3da2309 --- /dev/null +++ b/code/modules/ai/ai_holder_targeting.dm @@ -0,0 +1,237 @@ +// Used for assigning a target for attacking. + +/datum/ai_holder + var/hostile = FALSE // Do we try to hurt others? + var/retaliate = FALSE // Attacks whatever struck it first. Mobs will still attack back if this is false but hostile is true. + + var/atom/movable/target = null // The thing (mob or object) we're trying to kill. + var/atom/movable/preferred_target = null// If set, and if given the chance, we will always prefer to target this over other options. + var/turf/target_last_seen_turf = null // Where the mob last observed the target being, used if the target disappears but the mob wants to keep fighting. + + var/vision_range = 7 // How far the targeting system will look for things to kill. Note that values higher than 7 are 'offscreen' and might be unsporting. + var/respect_alpha = TRUE // If true, mobs with a sufficently low alpha will be treated as invisible. + var/alpha_vision_threshold = 127 // Targets with an alpha less or equal to this will be considered invisible. Requires above var to be true. + + var/lose_target_time = 0 // world.time when a target was lost. + var/lose_target_timeout = 5 SECONDS // How long until a mob 'times out' and stops trying to find the mob that disappeared. + + var/list/attackers = list() // List of strings of names of people who attacked us before in our life. + // This uses strings and not refs to allow for disguises, and to avoid needing to use weakrefs. + +// A lot of this is based off of /TG/'s AI code. + +// Step 1, find out what we can see. +/datum/ai_holder/proc/list_targets() + . = hearers(vision_range, holder) - src // Remove ourselves to prevent suicidal decisions. + + var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha)) + + for(var/HM in typecache_filter_list(range(vision_range, holder), hostile_machines)) + if(can_see(holder, HM, vision_range)) + . += HM + +// Step 2, filter down possible targets to things we actually care about. +/datum/ai_holder/proc/find_target(var/list/possible_targets, var/has_targets_list = FALSE) + if(!hostile) // So retaliating mobs only attack the thing that hit it. + return null + . = list() + if(!has_targets_list) + possible_targets = list_targets() + for(var/possible_target in possible_targets) + var/atom/A = possible_target + if(found(A)) // In case people want to override this. + . = list(A) + break + if(can_attack(A)) // Can we attack it? + . += A + continue + + var/new_target = pick_target(.) + give_target(new_target) + return new_target + +// Step 3, pick among the possible, attackable targets. +/datum/ai_holder/proc/pick_target(list/targets) + if(target != null) // If we already have a target, but are told to pick again, calculate the lowest distance between all possible, and pick from the lowest distance targets. + targets = target_filter_distance(targets) +// for(var/possible_target in targets) +// var/atom/A = possible_target +// var/target_dist = get_dist(holder, target) +// var/possible_target_distance = get_dist(holder, A) +// if(target_dist < possible_target_distance) +// targets -= A + if(!targets.len) // We found nothing. + return + + var/chosen_target + if(preferred_target && preferred_target in targets) + chosen_target = preferred_target + else + chosen_target = pick(targets) + return chosen_target + +// Step 4, give us our selected target. +/datum/ai_holder/proc/give_target(new_target) + target = new_target + if(target != null) + if(should_threaten()) + set_stance(STANCE_ALERT) + else + set_stance(STANCE_APPROACH) + return TRUE + +// Filters return one or more 'preferred' targets. + +// This one is for closest targets. +/datum/ai_holder/proc/target_filter_distance(list/targets) + for(var/possible_target in targets) + var/atom/A = possible_target + var/target_dist = get_dist(holder, target) + var/possible_target_distance = get_dist(holder, A) + if(target_dist < possible_target_distance) + targets -= A + return targets + +/datum/ai_holder/proc/can_attack(atom/movable/the_target) + if(!can_see_target(the_target)) + return FALSE + + if(istype(the_target, /mob/zshadow)) + return FALSE // no + + if(isliving(the_target)) + var/mob/living/L = the_target + if(L.stat == DEAD) + return FALSE + if(holder.IIsAlly(L)) + return FALSE + return TRUE + + if(istype(the_target, /obj/mecha)) + var/obj/mecha/M = the_target + if(M.occupant) + return can_attack(M.occupant) + + if(istype(the_target, /obj/machinery/porta_turret)) + var/obj/machinery/porta_turret/P = the_target + if(P.stat & BROKEN) + return FALSE // Already dead. + if(P.faction == holder.faction) + return FALSE // Don't shoot allied turrets. + if(!P.raised && !P.raising) + return FALSE // Turrets won't get hurt if they're still in their cover. + return TRUE + + return TRUE +// return FALSE + +// Override this for special targeting criteria. +// If it returns true, the mob will always select it as the target. +/datum/ai_holder/proc/found(atom/movable/the_target) + return FALSE + +//We can't see the target, go look or attack where they were last seen. +/datum/ai_holder/proc/lose_target() + if(target) + target = null + lose_target_time = world.time + + give_up_movement() + + +//Target is no longer valid (?) +/datum/ai_holder/proc/lost_target() + set_stance(STANCE_IDLE) + lose_target_position() + lose_target() + +// Check if target is visible to us. +/datum/ai_holder/proc/can_see_target(atom/movable/the_target, view_range = vision_range) + ai_log("can_see_target() : Entering.", AI_LOG_TRACE) + + if(!the_target) // Nothing to target. + ai_log("can_see_target() : There is no target. Exiting.", AI_LOG_WARNING) + return FALSE + + if(holder.see_invisible < the_target.invisibility) // Real invis. + ai_log("can_see_target() : Target ([the_target]) was invisible to holder. Exiting.", AI_LOG_TRACE) + return FALSE + + if(respect_alpha && the_target.alpha <= alpha_vision_threshold) // Fake invis. + ai_log("can_see_target() : Target ([the_target]) was sufficently transparent to holder and is hidden. Exiting.", AI_LOG_TRACE) + return FALSE + + if(get_dist(holder, the_target) > view_range) // Too far away. + ai_log("can_see_target() : Target ([the_target]) was too far from holder. Exiting.", AI_LOG_TRACE) + return FALSE + + if(!can_see(holder, the_target, view_range)) + ai_log("can_see_target() : Target ([the_target]) failed can_see(). Exiting.", AI_LOG_TRACE) + return FALSE + + ai_log("can_see_target() : Target ([the_target]) can be seen. Exiting.", AI_LOG_TRACE) + return TRUE + +// Updates the last known position of the target. +/datum/ai_holder/proc/track_target_position() + if(!target) + lose_target_position() + + if(last_turf_display && target_last_seen_turf) + target_last_seen_turf.overlays -= last_turf_overlay + + target_last_seen_turf = get_turf(target) + + if(last_turf_display) + target_last_seen_turf.overlays += last_turf_overlay + +// Resets the last known position to null. +/datum/ai_holder/proc/lose_target_position() + if(last_turf_display && target_last_seen_turf) + target_last_seen_turf.overlays -= last_turf_overlay + ai_log("lose_target_position() : Last position is being reset.", AI_LOG_INFO) + target_last_seen_turf = null + +// Responds to a hostile action against its mob. +/datum/ai_holder/proc/react_to_attack(atom/movable/attacker) + if(holder.stat) // We're dead. + ai_log("react_to_attack() : Was attacked by [attacker], but we are dead/unconscious.", AI_LOG_TRACE) + return FALSE + if(!hostile && !retaliate) // Not allowed to defend ourselves. + ai_log("react_to_attack() : Was attacked by [attacker], but we are not allowed to attack back.", AI_LOG_TRACE) + return FALSE + if(holder.IIsAlly(attacker)) // I'll overlook it THIS time... + ai_log("react_to_attack() : Was attacked by [attacker], but they were an ally.", AI_LOG_TRACE) + return FALSE + if(target) // Already fighting someone. Switching every time we get hit would impact our combat performance. + ai_log("react_to_attack() : Was attacked by [attacker], but we already have a target.", AI_LOG_TRACE) + on_attacked(attacker) // So we attack immediately and not threaten. + return FALSE + + if(stance == STANCE_SLEEP) // If we're asleep, try waking up if someone's wailing on us. + ai_log("react_to_attack() : AI is asleep. Waking up.", AI_LOG_TRACE) + go_wake() + + ai_log("react_to_attack() : Was attacked by [attacker].", AI_LOG_INFO) + on_attacked(attacker) // So we attack immediately and not threaten. + return give_target(attacker) // Also handles setting the appropiate stance. + +// Sets a few vars so mobs that threaten will react faster to an attacker or someone who attacked them before. +/datum/ai_holder/proc/on_attacked(atom/movable/AM) + last_conflict_time = world.time + if(isliving(AM)) + var/mob/living/L = AM + attackers |= L.name + +// Causes targeting to prefer targeting the taunter if possible. +// This generally occurs if more than one option is within striking distance, including the taunter. +// Otherwise the default filter will prefer the closest target. +/datum/ai_holder/proc/receive_taunt(atom/movable/taunter, force_target_switch = FALSE) + ai_log("receive_taunt() : Was taunted by [taunter].", AI_LOG_INFO) + preferred_target = taunter + if(force_target_switch) + give_target(taunter) + +/datum/ai_holder/proc/lose_taunt() + ai_log("lose_taunt() : Resetting preferred_target.", AI_LOG_INFO) + preferred_target = null \ No newline at end of file diff --git a/code/modules/ai/interfaces.dm b/code/modules/ai/interfaces.dm new file mode 100644 index 0000000000..dbadac8757 --- /dev/null +++ b/code/modules/ai/interfaces.dm @@ -0,0 +1,92 @@ +// 'Interfaces' are procs that the ai_holder datum uses to communicate its will to the mob its attached. +// The reason for using this proc in the middle is to ensure the AI has some form of compatibility with most mob types, +// since some actions work very differently between mob types (e.g. executing an attack as a simple animal compared to a human). +// The AI can just call holder.IAttack(target) and the mob is responsible for determining how to actually attack the target. + +/mob/living/proc/IAttack(atom/A) + return FALSE + +/mob/living/simple_mob/IAttack(atom/A) + if(!canClick()) // Still on cooldown from a "click". + return FALSE + return attack_target(A) // This will set click cooldown. + +/mob/living/proc/IRangedAttack(atom/A) + return FALSE + +/mob/living/simple_mob/IRangedAttack(atom/A) + if(!canClick()) // Still on cooldown from a "click". + return FALSE + return shoot_target(A) + +// Test if the AI is allowed to attempt a ranged attack. +/mob/living/proc/ICheckRangedAttack(atom/A) + return FALSE + +/mob/living/simple_mob/ICheckRangedAttack(atom/A) + if(needs_reload) + if(reload_count >= reload_max) + try_reload() + return FALSE + return projectiletype ? TRUE : FALSE + +/mob/living/proc/ISpecialAttack(atom/A) + return FALSE + +/mob/living/simple_mob/ISpecialAttack(atom/A) + return special_attack_target(A) + +// Is the AI allowed to attempt to do it? +/mob/living/proc/ICheckSpecialAttack(atom/A) + return FALSE + +/mob/living/simple_mob/ICheckSpecialAttack(atom/A) + return can_special_attack(A) && should_special_attack(A) // Just because we can doesn't mean we should. + +/mob/living/proc/ISay(message) + return say(message) + +/mob/living/proc/IIsAlly(mob/living/L) + return src.faction == L.faction + +/mob/living/simple_mob/IIsAlly(mob/living/L) + . = ..() + if(!.) // Outside the faction, try to see if they're friends. + return L in friends + +/mob/living/proc/IGetID() + +/mob/living/simple_mob/IGetID() + if(myid) + return myid.GetID() + +// Respects move cooldowns as if it had a client. +// Also tries to avoid being superdumb with moving into certain tiles (unless that's desired). +/mob/living/proc/IMove(turf/newloc, safety = TRUE) + if(check_move_cooldown()) +// if(!newdir) +// newdir = get_dir(get_turf(src), newloc) + + // Check to make sure moving to newloc won't actually kill us. e.g. we're a slime and trying to walk onto water. + if(istype(newloc)) + if(safety && !newloc.is_safe_to_enter(src)) + return MOVEMENT_FAILED + + // Move()ing to another tile successfully returns 32 because BYOND. Would rather deal with TRUE/FALSE-esque terms. + // Note that moving to the same tile will be 'successful'. + var/turf/old_T = get_turf(src) + + // An adjacency check to avoid mobs phasing diagonally past windows. + // This might be better in general movement code but I'm too scared to add it, and most things don't move diagonally anyways. + if(!old_T.Adjacent(newloc)) + return MOVEMENT_FAILED + + . = SelfMove(newloc) ? MOVEMENT_SUCCESSFUL : MOVEMENT_FAILED + if(. == MOVEMENT_SUCCESSFUL) + set_dir(get_dir(old_T, newloc)) + // Apply movement delay. + // Player movement has more factors but its all in the client and fixing that would be its own project. + setMoveCooldown(movement_delay()) + return + + . = MOVEMENT_ON_COOLDOWN // To avoid superfast mobs that aren't meant to be superfast. Is actually -1. diff --git a/code/modules/ai/say_list.dm b/code/modules/ai/say_list.dm new file mode 100644 index 0000000000..43732abf0c --- /dev/null +++ b/code/modules/ai/say_list.dm @@ -0,0 +1,119 @@ +// A simple datum that just holds many lists of lines for mobs to pick from. +// This is its own datum in order to be able to have different types of mobs be able to use the same lines if desired, +// even when inheritence wouldn't be able to do so. + +// Also note this also contains emotes, despite its name. +// and now sounds because its probably better that way. + +/mob/living + var/datum/say_list/say_list = null + var/say_list_type = /datum/say_list // Type to give us on initialization. Default has empty lists, so the mob will be silent. + +/mob/living/initialize() + if(say_list_type) + say_list = new say_list_type(src) + return ..() + +/mob/living/Destroy() + QDEL_NULL(say_list) + return ..() + + +/datum/say_list + var/list/speak = list() // Things the mob might say if it talks while idle. + var/list/emote_hear = list() // Hearable emotes it might perform + var/list/emote_see = list() // Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps + + var/list/say_understood = list() // When accepting an order. + var/list/say_cannot = list() // When they cannot comply. + var/list/say_maybe_target = list() // When they briefly see something. + var/list/say_got_target = list() // When a target is first assigned. + var/list/say_threaten = list() // When threatening someone. + var/list/say_stand_down = list() // When the threatened thing goes away. + var/list/say_escalate = list() // When the threatened thing doesn't go away. + + var/threaten_sound = null // Sound file played when the mob's AI calls threaten_target() for the first time. + var/stand_down_sound = null // Sound file played when the mob's AI loses sight of the threatened target. + + + + + + + +// Subtypes. + +// This one's pretty dumb, but pirates are dumb anyways and it makes for a good test. +/datum/say_list/pirate + speak = list("Yarr!") + + say_understood = list("Alright, matey.") + say_cannot = list("No, matey.") + say_maybe_target = list("Eh?") + say_got_target = list("Yarrrr!") + say_threaten = list("You best leave, this booty is mine.", "No plank to walk on, just walk away.") + say_stand_down = list("Good.") + say_escalate = list("Yarr! The booty is mine!") + +// Mercs! +/datum/say_list/merc + speak = list("When are we gonna get out of this chicken-shit outfit?", + "Wish I had better equipment...", + "I knew I should have been a line chef...", + "Fuckin' helmet keeps fogging up.", + "Anyone else smell that?") + emote_see = list("sniffs", "coughs", "taps his foot", "looks around", "checks his equipment") + + say_understood = list("Understood!", "Affirmative!") + say_cannot = list("Negative!") + say_maybe_target = list("Who's there?") + say_got_target = list("Engaging!") + say_threaten = list("Get out of here!", "Hey! Private Property!") + say_stand_down = list("Good.") + say_escalate = list("Your funeral!", "Bring it!") + +/datum/say_list/malf_drone + speak = list("ALERT.","Hostile-ile-ile entities dee-twhoooo-wected.","Threat parameterszzzz- szzet.","Bring sub-sub-sub-systems uuuup to combat alert alpha-a-a.") + emote_see = list("beeps menacingly","whirrs threateningly","scans its immediate vicinity") + + say_understood = list("Affirmative.", "Positive.") + say_cannot = list("Denied.", "Negative.") + say_maybe_target = list("Possible threat detected. Investigating.", "Motion detected.", "Investigating.") + say_got_target = list("Threat detected.", "New task: Remove threat.", "Threat removal engaged.", "Engaging target.") + say_threaten = list("Motion detected, judging target...") + say_stand_down = list("Visual lost.", "Error: Target not found.") + say_escalate = list("Viable target found. Removing.", "Engaging target.", "Target judgement complete. Removal required.") + + threaten_sound = 'sound/effects/turret/move1.wav' + stand_down_sound = 'sound/effects/turret/move2.wav' + +/datum/say_list/mercenary + threaten_sound = 'sound/weapons/TargetOn.ogg' + stand_down_sound = 'sound/weapons/TargetOff.ogg' + + +/datum/say_list/crab + emote_hear = list("clicks") + emote_see = list("clacks") + +/datum/say_list/spider + emote_hear = list("chitters") + +/datum/say_list/hivebot + speak = list( + "Resuming task: Protect area.", + "No threats found.", + "Error: No targets found." + ) + emote_hear = list("hums ominously", "whirrs softly", "grinds a gear") + emote_see = list("looks around the area", "turns from side to side") + say_understood = list("Affirmative.", "Positive.") + say_cannot = list("Denied.", "Negative.") + say_maybe_target = list("Possible threat detected. Investigating.", "Motion detected.", "Investigating.") + say_got_target = list("Threat detected.", "New task: Remove threat.", "Threat removal engaged.", "Engaging target.") + +/datum/say_list/lizard + emote_hear = list("hisses") + +/datum/say_list/crab + emote_hear = list("hisses") \ No newline at end of file diff --git a/code/modules/artifice/deadringer.dm b/code/modules/artifice/deadringer.dm index e12c0b0045..c19da40873 100644 --- a/code/modules/artifice/deadringer.dm +++ b/code/modules/artifice/deadringer.dm @@ -76,13 +76,14 @@ return /obj/item/weapon/deadringer/proc/deathprevent() - for(var/mob/living/simple_animal/D in oviewers(7, src)) - D.LoseTarget() + for(var/mob/living/simple_mob/D in oviewers(7, src)) + if(!D.has_AI()) + continue + D.ai_holder.lose_target() + watchowner.emote("deathgasp") watchowner.alpha = 15 makeacorpse(watchowner) - for(var/mob/living/simple_animal/D in oviewers(7, src)) - D.LoseTarget() return /obj/item/weapon/deadringer/proc/uncloak() diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index e45c6d8beb..7d1c5346c0 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -40,7 +40,7 @@ H.UpdateDamageIcon() H.updatehealth() else if(ismouse(target)) - var/mob/living/simple_animal/mouse/M = target + var/mob/living/simple_mob/animal/passive/mouse/M = target visible_message("SPLAT!") M.splat() playsound(target.loc, 'sound/effects/snap.ogg', 50, 1) diff --git a/code/modules/blob2/blobs/factory.dm b/code/modules/blob2/blobs/factory.dm index 9577f3c14e..08d378f1d0 100644 --- a/code/modules/blob2/blobs/factory.dm +++ b/code/modules/blob2/blobs/factory.dm @@ -15,7 +15,7 @@ var/spore_cooldown = 8 SECONDS /obj/structure/blob/factory/Destroy() - for(var/mob/living/simple_animal/hostile/blob/spore/spore in spores) + for(var/mob/living/simple_mob/blob/spore/spore in spores) if(istype(spore) && spore.factory == src) spore.factory = null else @@ -31,7 +31,7 @@ return flick("blob_factory_glow", src) spore_delay = world.time + spore_cooldown - var/mob/living/simple_animal/hostile/blob/spore/S = null + var/mob/living/simple_mob/blob/spore/S = null if(overmind) S = new overmind.blob_type.spore_type(src.loc, src) S.faction = "blob" @@ -39,10 +39,8 @@ S.overmind = overmind overmind.blob_mobs.Add(S) if(overmind.blob_type.ranged_spores) - S.ranged = TRUE S.projectiletype = overmind.blob_type.spore_projectile S.projectilesound = overmind.blob_type.spore_firesound - S.shoot_range = overmind.blob_type.spore_range else //Other mobs don't add themselves in New. Ew. S.nest = src spores += S diff --git a/code/modules/blob2/overmind/overmind.dm b/code/modules/blob2/overmind/overmind.dm index 8ba73eb1e6..6477c86f22 100644 --- a/code/modules/blob2/overmind/overmind.dm +++ b/code/modules/blob2/overmind/overmind.dm @@ -53,7 +53,7 @@ var/list/overminds = list() B.update_icon() //reset anything that was ours for(var/BLO in blob_mobs) - var/mob/living/simple_animal/hostile/blob/BM = BLO + var/mob/living/simple_mob/blob/spore/BM = BLO if(BM) BM.overmind = null BM.update_icons() diff --git a/code/modules/blob2/overmind/types.dm b/code/modules/blob2/overmind/types.dm index c8aca03cf6..6049ccfc72 100644 --- a/code/modules/blob2/overmind/types.dm +++ b/code/modules/blob2/overmind/types.dm @@ -32,7 +32,7 @@ var/can_build_resources = FALSE // Ditto, for resource blobs. var/can_build_nodes = TRUE // Ditto, for nodes. - var/spore_type = /mob/living/simple_animal/hostile/blob/spore + var/spore_type = /mob/living/simple_mob/blob/spore var/ranged_spores = FALSE // For proper spores of the type above. var/spore_firesound = 'sound/effects/slime_squish.ogg' var/spore_range = 7 // The range the spore can fire. @@ -72,7 +72,7 @@ return // Spore things -/datum/blob_type/proc/on_spore_death(mob/living/simple_animal/hostile/blob/spore/S) +/datum/blob_type/proc/on_spore_death(mob/living/simple_mob/blob/spore/S) return @@ -120,7 +120,7 @@ brute_multiplier = 0.25 burn_multiplier = 0.6 ai_aggressiveness = 50 //Really doesn't like you near it. - spore_type = /mob/living/simple_animal/hostile/hivebot/swarm + spore_type = /mob/living/simple_mob/mechanical/hivebot/swarm /datum/blob_type/fabrication_swarm/on_received_damage(var/obj/structure/blob/B, damage, damage_type, mob/living/attacker) if(istype(B, /obj/structure/blob/normal)) @@ -227,9 +227,9 @@ burn_multiplier = 3 ai_aggressiveness = 40 can_build_factories = TRUE - spore_type = /mob/living/simple_animal/hostile/blob/spore/infesting + spore_type = /mob/living/simple_mob/blob/spore/infesting -/datum/blob_type/fungal_bloom/on_spore_death(mob/living/simple_animal/hostile/blob/spore/S) +/datum/blob_type/fungal_bloom/on_spore_death(mob/living/simple_mob/blob/spore/S) if(S.is_infesting) return // Don't make blobs if they were on someone's head. var/turf/T = get_turf(S) @@ -258,11 +258,11 @@ brute_multiplier = 1.5 ai_aggressiveness = 30 // The spores do most of the fighting. can_build_factories = TRUE - spore_type = /mob/living/simple_animal/hostile/blob/spore/weak + spore_type = /mob/living/simple_mob/blob/spore/weak /datum/blob_type/fulminant_organism/on_expand(var/obj/structure/blob/B, var/obj/structure/blob/new_B, var/turf/T, var/mob/observer/blob/O) if(prob(10)) // 10% chance to make a weak spore when expanding. - var/mob/living/simple_animal/hostile/blob/S = new spore_type(T) + var/mob/living/simple_mob/blob/spore/S = new spore_type(T) if(istype(S)) S.overmind = O O.blob_mobs.Add(S) @@ -272,7 +272,7 @@ /datum/blob_type/fulminant_organism/on_death(obj/structure/blob/B) if(prob(33)) // 33% chance to make a spore when dying. - var/mob/living/simple_animal/hostile/blob/S = new spore_type(get_turf(B)) + var/mob/living/simple_mob/blob/spore/S = new spore_type(get_turf(B)) B.visible_message("\The [S] floats free from the [name]!") if(istype(S)) S.overmind = B.overmind @@ -614,7 +614,7 @@ attack_verb = "crashes against" can_build_factories = TRUE can_build_resources = TRUE - spore_type = /mob/living/simple_animal/hostile/blob/spore/weak + spore_type = /mob/living/simple_mob/blob/spore/weak ranged_spores = TRUE spore_range = 3 spore_projectile = /obj/item/projectile/energy/blob/splattering diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 807faab47f..e711006b2a 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -18,7 +18,7 @@ //OTHER// ///////// var/datum/preferences/prefs = null - var/move_delay = 1 + //var/move_delay = 1 var/moving = null var/adminobs = null var/area = null diff --git a/code/modules/client/preference_setup/loadout/loadout_head.dm b/code/modules/client/preference_setup/loadout/loadout_head.dm index e051daf585..32450813bf 100644 --- a/code/modules/client/preference_setup/loadout/loadout_head.dm +++ b/code/modules/client/preference_setup/loadout/loadout_head.dm @@ -370,4 +370,4 @@ /datum/gear/head/circuitry display_name = "headwear, circuitry (empty)" - path = /obj/item/clothing/head/circuitry \ No newline at end of file + path = /obj/item/clothing/head/circuitry diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm index d0f9a8165c..b30a5d4898 100644 --- a/code/modules/events/carp_migration.dm +++ b/code/modules/events/carp_migration.dm @@ -37,15 +37,15 @@ while (i <= num_groups) var/group_size = rand(group_size_min, group_size_max) for (var/j = 1, j <= group_size, j++) - spawned_carp.Add(new /mob/living/simple_animal/hostile/carp(spawn_locations[i])) + spawned_carp.Add(new /mob/living/simple_mob/animal/space/carp/event(spawn_locations[i])) i++ /datum/event/carp_migration/end() spawn(0) - for(var/mob/living/simple_animal/hostile/C in spawned_carp) - if(!C.stat) - var/turf/T = get_turf(C) + for(var/mob/living/simple_mob/SM in spawned_carp) + if(!SM.stat) + var/turf/T = get_turf(SM) if(istype(T, /turf/space)) if(prob(75)) - qdel(C) + qdel(SM) sleep(1) \ No newline at end of file diff --git a/code/modules/events/infestation.dm b/code/modules/events/infestation.dm index 16f2b78521..3800bc999f 100644 --- a/code/modules/events/infestation.dm +++ b/code/modules/events/infestation.dm @@ -65,11 +65,11 @@ vermin = rand(0,2) switch(vermin) if(VERM_MICE) - spawn_types = list(/mob/living/simple_animal/mouse/gray, /mob/living/simple_animal/mouse/brown, /mob/living/simple_animal/mouse/white) + spawn_types = list(/mob/living/simple_mob/animal/passive/mouse/gray, /mob/living/simple_mob/animal/passive/mouse/brown, /mob/living/simple_mob/animal/passive/mouse/white) max_number = 12 vermstring = "mice" if(VERM_LIZARDS) - spawn_types = list(/mob/living/simple_animal/lizard) + spawn_types = list(/mob/living/simple_mob/animal/passive/lizard) max_number = 6 vermstring = "lizards" if(VERM_SPIDERS) diff --git a/code/modules/events/rogue_drones.dm b/code/modules/events/rogue_drones.dm index 768628f8fb..9cfcc648b5 100644 --- a/code/modules/events/rogue_drones.dm +++ b/code/modules/events/rogue_drones.dm @@ -16,10 +16,8 @@ else num = rand(2,6) for(var/i=0, iThe gibber is locked and running, wait for it to finish." - return - else - src.startgibbing(user) - -/obj/machinery/gibber/examine() - ..() - usr << "The safety guard is [emagged ? "disabled" : "enabled"]." - -/obj/machinery/gibber/emag_act(var/remaining_charges, var/mob/user) - emagged = !emagged - user << "You [emagged ? "disable" : "enable"] the gibber safety guard." - return 1 - -/obj/machinery/gibber/attackby(var/obj/item/W, var/mob/user) - var/obj/item/weapon/grab/G = W - - if(default_unfasten_wrench(user, W, 40)) - return - - if(!istype(G)) - return ..() - - if(G.state < 2) - user << "You need a better grip to do that!" - return - - move_into_gibber(user,G.affecting) - // Grab() process should clean up the grab item, no need to del it. - -/obj/machinery/gibber/MouseDrop_T(mob/target, mob/user) - if(user.stat || user.restrained()) - return - move_into_gibber(user,target) - -/obj/machinery/gibber/proc/move_into_gibber(var/mob/user,var/mob/living/victim) - - if(src.occupant) - user << "The gibber is full, empty it first!" - return - - if(operating) - user << "The gibber is locked and running, wait for it to finish." - return - - if(!(istype(victim, /mob/living/carbon)) && !(istype(victim, /mob/living/simple_animal)) ) - user << "This is not suitable for the gibber!" - return - - if(istype(victim,/mob/living/carbon/human) && !emagged) - user << "The gibber safety guard is engaged!" - return - - - if(victim.abiotic(1)) - user << "Subject may not have abiotic items on." - return - - user.visible_message("[user] starts to put [victim] into the gibber!") - src.add_fingerprint(user) - if(do_after(user, 30) && victim.Adjacent(src) && user.Adjacent(src) && victim.Adjacent(user) && !occupant) - user.visible_message("[user] stuffs [victim] into the gibber!") - if(victim.client) - victim.client.perspective = EYE_PERSPECTIVE - victim.client.eye = src - victim.loc = src - src.occupant = victim - update_icon() - -/obj/machinery/gibber/verb/eject() - set category = "Object" - set name = "Empty Gibber" - set src in oview(1) - - if (usr.stat != 0) - return - src.go_out() - add_fingerprint(usr) - return - -/obj/machinery/gibber/proc/go_out() - if(operating || !src.occupant) - return - for(var/obj/O in src) - O.loc = src.loc - if (src.occupant.client) - src.occupant.client.eye = src.occupant.client.mob - src.occupant.client.perspective = MOB_PERSPECTIVE - src.occupant.loc = src.loc - src.occupant = null - update_icon() - return - - -/obj/machinery/gibber/proc/startgibbing(mob/user as mob) - if(src.operating) - return - if(!src.occupant) - visible_message("You hear a loud metallic grinding sound.") - return - - use_power(1000) - visible_message("You hear a loud [occupant.isSynthetic() ? "metallic" : "squelchy"] grinding sound.") - src.operating = 1 - update_icon() - - var/slab_name = occupant.name - var/slab_count = 3 - var/slab_type = /obj/item/weapon/reagent_containers/food/snacks/meat - var/slab_nutrition = src.occupant.nutrition / 15 - - // Some mobs have specific meat item types. - if(istype(src.occupant,/mob/living/simple_animal)) - var/mob/living/simple_animal/critter = src.occupant - if(critter.meat_amount) - slab_count = critter.meat_amount - if(critter.meat_type) - slab_type = critter.meat_type - else if(istype(src.occupant,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = occupant - slab_name = src.occupant.real_name - slab_type = H.isSynthetic() ? /obj/item/stack/material/steel : H.species.meat_type - - // Small mobs don't give as much nutrition. - if(issmall(src.occupant)) - slab_nutrition *= 0.5 - slab_nutrition /= slab_count - - for(var/i=1 to slab_count) - var/obj/item/weapon/reagent_containers/food/snacks/meat/new_meat = new slab_type(src, rand(3,8)) - if(istype(new_meat)) - new_meat.name = "[slab_name] [new_meat.name]" - new_meat.reagents.add_reagent("nutriment",slab_nutrition) - if(src.occupant.reagents) - src.occupant.reagents.trans_to_obj(new_meat, round(occupant.reagents.total_volume/slab_count,1)) - - add_attack_logs(user,occupant,"Used [src] to gib") - - src.occupant.ghostize() - - spawn(gib_time) - - src.operating = 0 - src.occupant.gib() - qdel(src.occupant) - - playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) - operating = 0 - for (var/obj/item/thing in contents) - // There's a chance that the gibber will fail to destroy some evidence. - if(istype(thing,/obj/item/organ) && prob(80)) - qdel(thing) - continue - thing.forceMove(get_turf(thing)) // Drop it onto the turf for throwing. - thing.throw_at(get_edge_target_turf(src,gib_throw_dir),rand(0,3),emagged ? 100 : 50) // Being pelted with bits of meat and bone would hurt. - - update_icon() - - + +/obj/machinery/gibber + name = "gibber" + desc = "The name isn't descriptive enough?" + icon = 'icons/obj/kitchen.dmi' + icon_state = "grinder" + density = 1 + anchored = 1 + req_access = list(access_kitchen,access_morgue) + + var/operating = 0 //Is it on? + var/dirty = 0 // Does it need cleaning? + var/mob/living/occupant // Mob who has been put inside + var/gib_time = 40 // Time from starting until meat appears + var/gib_throw_dir = WEST // Direction to spit meat and gibs in. + + use_power = 1 + idle_power_usage = 2 + active_power_usage = 500 + +//auto-gibs anything that bumps into it +/obj/machinery/gibber/autogibber + var/turf/input_plate + +/obj/machinery/gibber/autogibber/New() + ..() + spawn(5) + for(var/i in cardinal) + var/obj/machinery/mineral/input/input_obj = locate( /obj/machinery/mineral/input, get_step(src.loc, i) ) + if(input_obj) + if(isturf(input_obj.loc)) + input_plate = input_obj.loc + gib_throw_dir = i + qdel(input_obj) + break + + if(!input_plate) + log_misc("a [src] didn't find an input plate.") + return + +/obj/machinery/gibber/autogibber/Bumped(var/atom/A) + if(!input_plate) return + + if(ismob(A)) + var/mob/M = A + + if(M.loc == input_plate + ) + M.loc = src + M.gib() + + +/obj/machinery/gibber/New() + ..() + src.overlays += image('icons/obj/kitchen.dmi', "grjam") + +/obj/machinery/gibber/update_icon() + overlays.Cut() + if (dirty) + src.overlays += image('icons/obj/kitchen.dmi', "grbloody") + if(stat & (NOPOWER|BROKEN)) + return + if (!occupant) + src.overlays += image('icons/obj/kitchen.dmi', "grjam") + else if (operating) + src.overlays += image('icons/obj/kitchen.dmi', "gruse") + else + src.overlays += image('icons/obj/kitchen.dmi', "gridle") + +/obj/machinery/gibber/relaymove(mob/user as mob) + src.go_out() + return + +/obj/machinery/gibber/attack_hand(mob/user as mob) + if(stat & (NOPOWER|BROKEN)) + return + if(operating) + user << "The gibber is locked and running, wait for it to finish." + return + else + src.startgibbing(user) + +/obj/machinery/gibber/examine() + ..() + usr << "The safety guard is [emagged ? "disabled" : "enabled"]." + +/obj/machinery/gibber/emag_act(var/remaining_charges, var/mob/user) + emagged = !emagged + user << "You [emagged ? "disable" : "enable"] the gibber safety guard." + return 1 + +/obj/machinery/gibber/attackby(var/obj/item/W, var/mob/user) + var/obj/item/weapon/grab/G = W + + if(default_unfasten_wrench(user, W, 40)) + return + + if(!istype(G)) + return ..() + + if(G.state < 2) + user << "You need a better grip to do that!" + return + + move_into_gibber(user,G.affecting) + // Grab() process should clean up the grab item, no need to del it. + +/obj/machinery/gibber/MouseDrop_T(mob/target, mob/user) + if(user.stat || user.restrained()) + return + move_into_gibber(user,target) + +/obj/machinery/gibber/proc/move_into_gibber(var/mob/user,var/mob/living/victim) + + if(src.occupant) + user << "The gibber is full, empty it first!" + return + + if(operating) + user << "The gibber is locked and running, wait for it to finish." + return + + if(!(istype(victim, /mob/living/carbon)) && !(istype(victim, /mob/living/simple_mob)) ) + user << "This is not suitable for the gibber!" + return + + if(istype(victim,/mob/living/carbon/human) && !emagged) + user << "The gibber safety guard is engaged!" + return + + + if(victim.abiotic(1)) + user << "Subject may not have abiotic items on." + return + + user.visible_message("[user] starts to put [victim] into the gibber!") + src.add_fingerprint(user) + if(do_after(user, 30) && victim.Adjacent(src) && user.Adjacent(src) && victim.Adjacent(user) && !occupant) + user.visible_message("[user] stuffs [victim] into the gibber!") + if(victim.client) + victim.client.perspective = EYE_PERSPECTIVE + victim.client.eye = src + victim.loc = src + src.occupant = victim + update_icon() + +/obj/machinery/gibber/verb/eject() + set category = "Object" + set name = "Empty Gibber" + set src in oview(1) + + if (usr.stat != 0) + return + src.go_out() + add_fingerprint(usr) + return + +/obj/machinery/gibber/proc/go_out() + if(operating || !src.occupant) + return + for(var/obj/O in src) + O.loc = src.loc + if (src.occupant.client) + src.occupant.client.eye = src.occupant.client.mob + src.occupant.client.perspective = MOB_PERSPECTIVE + src.occupant.loc = src.loc + src.occupant = null + update_icon() + return + + +/obj/machinery/gibber/proc/startgibbing(mob/user as mob) + if(src.operating) + return + if(!src.occupant) + visible_message("You hear a loud metallic grinding sound.") + return + + use_power(1000) + visible_message("You hear a loud [occupant.isSynthetic() ? "metallic" : "squelchy"] grinding sound.") + src.operating = 1 + update_icon() + + var/slab_name = occupant.name + var/slab_count = 3 + var/slab_type = /obj/item/weapon/reagent_containers/food/snacks/meat + var/slab_nutrition = src.occupant.nutrition / 15 + + // Some mobs have specific meat item types. + if(istype(src.occupant,/mob/living/simple_mob)) + var/mob/living/simple_mob/critter = src.occupant + if(critter.meat_amount) + slab_count = critter.meat_amount + if(critter.meat_type) + slab_type = critter.meat_type + else if(istype(src.occupant,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = occupant + slab_name = src.occupant.real_name + slab_type = H.isSynthetic() ? /obj/item/stack/material/steel : H.species.meat_type + + // Small mobs don't give as much nutrition. + if(issmall(src.occupant)) + slab_nutrition *= 0.5 + slab_nutrition /= slab_count + + for(var/i=1 to slab_count) + var/obj/item/weapon/reagent_containers/food/snacks/meat/new_meat = new slab_type(src, rand(3,8)) + if(istype(new_meat)) + new_meat.name = "[slab_name] [new_meat.name]" + new_meat.reagents.add_reagent("nutriment",slab_nutrition) + if(src.occupant.reagents) + src.occupant.reagents.trans_to_obj(new_meat, round(occupant.reagents.total_volume/slab_count,1)) + + add_attack_logs(user,occupant,"Used [src] to gib") + + src.occupant.ghostize() + + spawn(gib_time) + + src.operating = 0 + src.occupant.gib() + qdel(src.occupant) + + playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) + operating = 0 + for (var/obj/item/thing in contents) + // There's a chance that the gibber will fail to destroy some evidence. + if(istype(thing,/obj/item/organ) && prob(80)) + qdel(thing) + continue + thing.forceMove(get_turf(thing)) // Drop it onto the turf for throwing. + thing.throw_at(get_edge_target_turf(src,gib_throw_dir),rand(0,3),emagged ? 100 : 50) // Being pelted with bits of meat and bone would hurt. + + update_icon() + + diff --git a/code/modules/food/kitchen/microwave.dm b/code/modules/food/kitchen/microwave.dm index 7357dd5568..1ece9225b1 100644 --- a/code/modules/food/kitchen/microwave.dm +++ b/code/modules/food/kitchen/microwave.dm @@ -1,392 +1,392 @@ -/obj/machinery/microwave - name = "microwave" - icon = 'icons/obj/kitchen.dmi' - icon_state = "mw" - density = 1 - anchored = 1 - use_power = 1 - idle_power_usage = 5 - active_power_usage = 100 - flags = OPENCONTAINER | NOREACT - circuit = /obj/item/weapon/circuitboard/microwave - var/operating = 0 // Is it on? - var/dirty = 0 // = {0..100} Does it need cleaning? - var/broken = 0 // ={0,1,2} How broken is it??? - var/global/list/datum/recipe/available_recipes // List of the recipes you can use - var/global/list/acceptable_items // List of the items you can put in - var/global/list/acceptable_reagents // List of the reagents you can put in - var/global/max_n_of_items = 0 - - -// see code/modules/food/recipes_microwave.dm for recipes - -/******************* -* Initialising -********************/ - -/obj/machinery/microwave/New() - ..() - reagents = new/datum/reagents(100) - reagents.my_atom = src - - component_parts = list() - component_parts += new /obj/item/weapon/stock_parts/console_screen(src) - component_parts += new /obj/item/weapon/stock_parts/motor(src) - component_parts += new /obj/item/weapon/stock_parts/capacitor(src) - - if (!available_recipes) - available_recipes = new - for (var/type in (typesof(/datum/recipe)-/datum/recipe)) - available_recipes+= new type - acceptable_items = new - acceptable_reagents = new - for (var/datum/recipe/recipe in available_recipes) - for (var/item in recipe.items) - acceptable_items |= item - for (var/reagent in recipe.reagents) - acceptable_reagents |= reagent - if (recipe.items) - max_n_of_items = max(max_n_of_items,recipe.items.len) - // This will do until I can think of a fun recipe to use dionaea in - - // will also allow anything using the holder item to be microwaved into - // impure carbon. ~Z - acceptable_items |= /obj/item/weapon/holder - acceptable_items |= /obj/item/weapon/reagent_containers/food/snacks/grown - - RefreshParts() - -/******************* -* Item Adding -********************/ - -/obj/machinery/microwave/attackby(var/obj/item/O as obj, var/mob/user as mob) - if(src.broken > 0) - if(src.broken == 2 && O.is_screwdriver()) // If it's broken and they're using a screwdriver - user.visible_message( \ - "\The [user] starts to fix part of the microwave.", \ - "You start to fix part of the microwave." \ - ) - playsound(src, O.usesound, 50, 1) - if (do_after(user,20 * O.toolspeed)) - user.visible_message( \ - "\The [user] fixes part of the microwave.", \ - "You have fixed part of the microwave." \ - ) - src.broken = 1 // Fix it a bit - else if(src.broken == 1 && O.is_wrench()) // If it's broken and they're doing the wrench - user.visible_message( \ - "\The [user] starts to fix part of the microwave.", \ - "You start to fix part of the microwave." \ - ) - if (do_after(user,20 * O.toolspeed)) - user.visible_message( \ - "\The [user] fixes the microwave.", \ - "You have fixed the microwave." \ - ) - src.icon_state = "mw" - src.broken = 0 // Fix it! - src.dirty = 0 // just to be sure - src.flags = OPENCONTAINER | NOREACT - else - to_chat(user, "It's broken!") - return 1 - else if(default_deconstruction_screwdriver(user, O)) - return - else if(default_deconstruction_crowbar(user, O)) - return - else if(default_unfasten_wrench(user, O, 10)) - return - - else if(src.dirty==100) // The microwave is all dirty so can't be used! - if(istype(O, /obj/item/weapon/reagent_containers/spray/cleaner) || istype(O, /obj/item/weapon/soap)) // If they're trying to clean it then let them - user.visible_message( \ - "\The [user] starts to clean the microwave.", \ - "You start to clean the microwave." \ - ) - if (do_after(user,20)) - user.visible_message( \ - "\The [user] has cleaned the microwave.", \ - "You have cleaned the microwave." \ - ) - src.dirty = 0 // It's clean! - src.broken = 0 // just to be sure - src.icon_state = "mw" - src.flags = OPENCONTAINER | NOREACT - else //Otherwise bad luck!! - to_chat(user, "It's dirty!") - return 1 - else if(is_type_in_list(O,acceptable_items)) - if (contents.len>=(max_n_of_items + component_parts.len + 1)) //Adds component_parts to the maximum number of items. The 1 is from the circuit - to_chat(user, "This [src] is full of ingredients, you cannot put more.") - return 1 - if(istype(O, /obj/item/stack) && O:get_amount() > 1) // This is bad, but I can't think of how to change it - var/obj/item/stack/S = O - new O.type (src) - S.use(1) - user.visible_message( \ - "\The [user] has added one of [O] to \the [src].", \ - "You add one of [O] to \the [src].") - return - else - // user.remove_from_mob(O) //This just causes problems so far as I can tell. -Pete - user.drop_item() - O.loc = src - user.visible_message( \ - "\The [user] has added \the [O] to \the [src].", \ - "You add \the [O] to \the [src].") - return - else if(istype(O,/obj/item/weapon/reagent_containers/glass) || \ - istype(O,/obj/item/weapon/reagent_containers/food/drinks) || \ - istype(O,/obj/item/weapon/reagent_containers/food/condiment) \ - ) - if (!O.reagents) - return 1 - for (var/datum/reagent/R in O.reagents.reagent_list) - if (!(R.id in acceptable_reagents)) - to_chat(user, "Your [O] contains components unsuitable for cookery.") - return 1 - return - else if(istype(O,/obj/item/weapon/grab)) - var/obj/item/weapon/grab/G = O - to_chat(user, "This is ridiculous. You can not fit \the [G.affecting] in this [src].") - return 1 - else - to_chat(user, "You have no idea what you can cook with this [O].") - ..() - src.updateUsrDialog() - -/obj/machinery/microwave/attack_ai(mob/user as mob) - if(istype(user, /mob/living/silicon/robot) && Adjacent(user)) - attack_hand(user) - -/obj/machinery/microwave/attack_hand(mob/user as mob) - user.set_machine(src) - interact(user) - -/******************* -* Microwave Menu -********************/ - -/obj/machinery/microwave/interact(mob/user as mob) // The microwave Menu - var/dat = "" - if(src.broken > 0) - dat = {"Bzzzzttttt"} - else if(src.operating) - dat = {"Microwaving in progress!
Please wait...!
"} - else if(src.dirty==100) - dat = {"This microwave is dirty!
Please clean it before use!
"} - else - var/list/items_counts = new - var/list/items_measures = new - var/list/items_measures_p = new - for (var/obj/O in ((contents - component_parts) - circuit)) - var/display_name = O.name - if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/egg)) - items_measures[display_name] = "egg" - items_measures_p[display_name] = "eggs" - if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/tofu)) - items_measures[display_name] = "tofu chunk" - items_measures_p[display_name] = "tofu chunks" - if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/meat)) //any meat - items_measures[display_name] = "slab of meat" - items_measures_p[display_name] = "slabs of meat" - if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/donkpocket)) - display_name = "Turnovers" - items_measures[display_name] = "turnover" - items_measures_p[display_name] = "turnovers" - if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/carpmeat)) - items_measures[display_name] = "fillet of meat" - items_measures_p[display_name] = "fillets of meat" - items_counts[display_name]++ - for (var/O in items_counts) - var/N = items_counts[O] - if (!(O in items_measures)) - dat += {"[capitalize(O)]: [N] [lowertext(O)]\s
"} - else - if (N==1) - dat += {"[capitalize(O)]: [N] [items_measures[O]]
"} - else - dat += {"[capitalize(O)]: [N] [items_measures_p[O]]
"} - - for (var/datum/reagent/R in reagents.reagent_list) - var/display_name = R.name - if (R.id == "capsaicin") - display_name = "Hotsauce" - if (R.id == "frostoil") - display_name = "Coldsauce" - dat += {"[display_name]: [R.volume] unit\s
"} - - if (items_counts.len==0 && reagents.reagent_list.len==0) - dat = {"The microwave is empty
"} - else - dat = {"Ingredients:
[dat]"} - dat += {"

\ -Turn on!
\ -
Eject ingredients!
\ -"} - - to_chat(user, browse("Microwave Controls[dat]", "window=microwave")) - onclose(user, "microwave") - return - - - -/*********************************** -* Microwave Menu Handling/Cooking -************************************/ - -/obj/machinery/microwave/proc/cook() - if(stat & (NOPOWER|BROKEN)) - return - start() - if (reagents.total_volume==0 && !(locate(/obj) in ((contents - component_parts) - circuit))) //dry run - if (!wzhzhzh(10)) - abort() - return - stop() - return - - var/datum/recipe/recipe = select_recipe(available_recipes,src) - var/obj/cooked - if (!recipe) - dirty += 1 - if (prob(max(10,dirty*5))) - if (!wzhzhzh(4)) - abort() - return - muck_start() - wzhzhzh(4) - muck_finish() - cooked = fail() - cooked.loc = src.loc - return - else if (has_extra_item()) - if (!wzhzhzh(4)) - abort() - return - broke() - cooked = fail() - cooked.loc = src.loc - return - else - if (!wzhzhzh(10)) - abort() - return - stop() - cooked = fail() - cooked.loc = src.loc - return - else - var/halftime = round(recipe.time/10/2) - if (!wzhzhzh(halftime)) - abort() - return - if (!wzhzhzh(halftime)) - abort() - cooked = fail() - cooked.loc = src.loc - return - cooked = recipe.make_food(src) - stop() - if(cooked) - cooked.loc = src.loc - return - -/obj/machinery/microwave/proc/wzhzhzh(var/seconds as num) // Whoever named this proc is fucking literally Satan. ~ Z - for (var/i=1 to seconds) - if (stat & (NOPOWER|BROKEN)) - return 0 - use_power(500) - sleep(10) - return 1 - -/obj/machinery/microwave/proc/has_extra_item() - for (var/obj/O in ((contents - component_parts) - circuit)) - if ( \ - !istype(O,/obj/item/weapon/reagent_containers/food) && \ - !istype(O, /obj/item/weapon/grown) \ - ) - return 1 - return 0 - -/obj/machinery/microwave/proc/start() - src.visible_message("The microwave turns on.", "You hear a microwave.") - src.operating = 1 - src.icon_state = "mw1" - src.updateUsrDialog() - -/obj/machinery/microwave/proc/abort() - src.operating = 0 // Turn it off again aferwards - src.icon_state = "mw" - src.updateUsrDialog() - -/obj/machinery/microwave/proc/stop() - playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) - src.operating = 0 // Turn it off again aferwards - src.icon_state = "mw" - src.updateUsrDialog() - -/obj/machinery/microwave/proc/dispose() - for (var/obj/O in ((contents-component_parts)-circuit)) - O.loc = src.loc - if (src.reagents.total_volume) - src.dirty++ - src.reagents.clear_reagents() - usr << "You dispose of the microwave contents." - src.updateUsrDialog() - -/obj/machinery/microwave/proc/muck_start() - playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) // Play a splat sound - src.icon_state = "mwbloody1" // Make it look dirty!! - -/obj/machinery/microwave/proc/muck_finish() - playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) - src.visible_message("The microwave gets covered in muck!") - src.dirty = 100 // Make it dirty so it can't be used util cleaned - src.flags = null //So you can't add condiments - src.icon_state = "mwbloody" // Make it look dirty too - src.operating = 0 // Turn it off again aferwards - src.updateUsrDialog() - -/obj/machinery/microwave/proc/broke() - var/datum/effect/effect/system/spark_spread/s = new - s.set_up(2, 1, src) - s.start() - src.icon_state = "mwb" // Make it look all busted up and shit - src.visible_message("The microwave breaks!") //Let them know they're stupid - src.broken = 2 // Make it broken so it can't be used util fixed - src.flags = null //So you can't add condiments - src.operating = 0 // Turn it off again aferwards - src.updateUsrDialog() - -/obj/machinery/microwave/proc/fail() - var/obj/item/weapon/reagent_containers/food/snacks/badrecipe/ffuu = new(src) - var/amount = 0 - for (var/obj/O in (((contents - ffuu) - component_parts) - circuit)) - amount++ - if (O.reagents) - var/id = O.reagents.get_master_reagent_id() - if (id) - amount+=O.reagents.get_reagent_amount(id) - qdel(O) - src.reagents.clear_reagents() - ffuu.reagents.add_reagent("carbon", amount) - ffuu.reagents.add_reagent("toxin", amount/10) - return ffuu - -/obj/machinery/microwave/Topic(href, href_list) - if(..()) - return - - usr.set_machine(src) - if(src.operating) - src.updateUsrDialog() - return - - switch(href_list["action"]) - if ("cook") - cook() - - if ("dispose") - dispose() - return +/obj/machinery/microwave + name = "microwave" + icon = 'icons/obj/kitchen.dmi' + icon_state = "mw" + density = 1 + anchored = 1 + use_power = 1 + idle_power_usage = 5 + active_power_usage = 100 + flags = OPENCONTAINER | NOREACT + circuit = /obj/item/weapon/circuitboard/microwave + var/operating = 0 // Is it on? + var/dirty = 0 // = {0..100} Does it need cleaning? + var/broken = 0 // ={0,1,2} How broken is it??? + var/global/list/datum/recipe/available_recipes // List of the recipes you can use + var/global/list/acceptable_items // List of the items you can put in + var/global/list/acceptable_reagents // List of the reagents you can put in + var/global/max_n_of_items = 0 + + +// see code/modules/food/recipes_microwave.dm for recipes + +/******************* +* Initialising +********************/ + +/obj/machinery/microwave/New() + ..() + reagents = new/datum/reagents(100) + reagents.my_atom = src + + component_parts = list() + component_parts += new /obj/item/weapon/stock_parts/console_screen(src) + component_parts += new /obj/item/weapon/stock_parts/motor(src) + component_parts += new /obj/item/weapon/stock_parts/capacitor(src) + + if (!available_recipes) + available_recipes = new + for (var/type in (typesof(/datum/recipe)-/datum/recipe)) + available_recipes+= new type + acceptable_items = new + acceptable_reagents = new + for (var/datum/recipe/recipe in available_recipes) + for (var/item in recipe.items) + acceptable_items |= item + for (var/reagent in recipe.reagents) + acceptable_reagents |= reagent + if (recipe.items) + max_n_of_items = max(max_n_of_items,recipe.items.len) + // This will do until I can think of a fun recipe to use dionaea in - + // will also allow anything using the holder item to be microwaved into + // impure carbon. ~Z + acceptable_items |= /obj/item/weapon/holder + acceptable_items |= /obj/item/weapon/reagent_containers/food/snacks/grown + + RefreshParts() + +/******************* +* Item Adding +********************/ + +/obj/machinery/microwave/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(src.broken > 0) + if(src.broken == 2 && O.is_screwdriver()) // If it's broken and they're using a screwdriver + user.visible_message( \ + "\The [user] starts to fix part of the microwave.", \ + "You start to fix part of the microwave." \ + ) + playsound(src, O.usesound, 50, 1) + if (do_after(user,20 * O.toolspeed)) + user.visible_message( \ + "\The [user] fixes part of the microwave.", \ + "You have fixed part of the microwave." \ + ) + src.broken = 1 // Fix it a bit + else if(src.broken == 1 && O.is_wrench()) // If it's broken and they're doing the wrench + user.visible_message( \ + "\The [user] starts to fix part of the microwave.", \ + "You start to fix part of the microwave." \ + ) + if (do_after(user,20 * O.toolspeed)) + user.visible_message( \ + "\The [user] fixes the microwave.", \ + "You have fixed the microwave." \ + ) + src.icon_state = "mw" + src.broken = 0 // Fix it! + src.dirty = 0 // just to be sure + src.flags = OPENCONTAINER | NOREACT + else + to_chat(user, "It's broken!") + return 1 + else if(default_deconstruction_screwdriver(user, O)) + return + else if(default_deconstruction_crowbar(user, O)) + return + else if(default_unfasten_wrench(user, O, 10)) + return + + else if(src.dirty==100) // The microwave is all dirty so can't be used! + if(istype(O, /obj/item/weapon/reagent_containers/spray/cleaner) || istype(O, /obj/item/weapon/soap)) // If they're trying to clean it then let them + user.visible_message( \ + "\The [user] starts to clean the microwave.", \ + "You start to clean the microwave." \ + ) + if (do_after(user,20)) + user.visible_message( \ + "\The [user] has cleaned the microwave.", \ + "You have cleaned the microwave." \ + ) + src.dirty = 0 // It's clean! + src.broken = 0 // just to be sure + src.icon_state = "mw" + src.flags = OPENCONTAINER | NOREACT + else //Otherwise bad luck!! + to_chat(user, "It's dirty!") + return 1 + else if(is_type_in_list(O,acceptable_items)) + if (contents.len>=(max_n_of_items + component_parts.len + 1)) //Adds component_parts to the maximum number of items. The 1 is from the circuit + to_chat(user, "This [src] is full of ingredients, you cannot put more.") + return 1 + if(istype(O, /obj/item/stack) && O:get_amount() > 1) // This is bad, but I can't think of how to change it + var/obj/item/stack/S = O + new O.type (src) + S.use(1) + user.visible_message( \ + "\The [user] has added one of [O] to \the [src].", \ + "You add one of [O] to \the [src].") + return + else + // user.remove_from_mob(O) //This just causes problems so far as I can tell. -Pete + user.drop_item() + O.loc = src + user.visible_message( \ + "\The [user] has added \the [O] to \the [src].", \ + "You add \the [O] to \the [src].") + return + else if(istype(O,/obj/item/weapon/reagent_containers/glass) || \ + istype(O,/obj/item/weapon/reagent_containers/food/drinks) || \ + istype(O,/obj/item/weapon/reagent_containers/food/condiment) \ + ) + if (!O.reagents) + return 1 + for (var/datum/reagent/R in O.reagents.reagent_list) + if (!(R.id in acceptable_reagents)) + to_chat(user, "Your [O] contains components unsuitable for cookery.") + return 1 + return + else if(istype(O,/obj/item/weapon/grab)) + var/obj/item/weapon/grab/G = O + to_chat(user, "This is ridiculous. You can not fit \the [G.affecting] in this [src].") + return 1 + else + to_chat(user, "You have no idea what you can cook with this [O].") + ..() + src.updateUsrDialog() + +/obj/machinery/microwave/attack_ai(mob/user as mob) + if(istype(user, /mob/living/silicon/robot) && Adjacent(user)) + attack_hand(user) + +/obj/machinery/microwave/attack_hand(mob/user as mob) + user.set_machine(src) + interact(user) + +/******************* +* Microwave Menu +********************/ + +/obj/machinery/microwave/interact(mob/user as mob) // The microwave Menu + var/dat = "" + if(src.broken > 0) + dat = {"Bzzzzttttt"} + else if(src.operating) + dat = {"Microwaving in progress!
Please wait...!
"} + else if(src.dirty==100) + dat = {"This microwave is dirty!
Please clean it before use!
"} + else + var/list/items_counts = new + var/list/items_measures = new + var/list/items_measures_p = new + for (var/obj/O in ((contents - component_parts) - circuit)) + var/display_name = O.name + if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/egg)) + items_measures[display_name] = "egg" + items_measures_p[display_name] = "eggs" + if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/tofu)) + items_measures[display_name] = "tofu chunk" + items_measures_p[display_name] = "tofu chunks" + if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/meat)) //any meat + items_measures[display_name] = "slab of meat" + items_measures_p[display_name] = "slabs of meat" + if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/donkpocket)) + display_name = "Turnovers" + items_measures[display_name] = "turnover" + items_measures_p[display_name] = "turnovers" + if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/carpmeat)) + items_measures[display_name] = "fillet of meat" + items_measures_p[display_name] = "fillets of meat" + items_counts[display_name]++ + for (var/O in items_counts) + var/N = items_counts[O] + if (!(O in items_measures)) + dat += {"[capitalize(O)]: [N] [lowertext(O)]\s
"} + else + if (N==1) + dat += {"[capitalize(O)]: [N] [items_measures[O]]
"} + else + dat += {"[capitalize(O)]: [N] [items_measures_p[O]]
"} + + for (var/datum/reagent/R in reagents.reagent_list) + var/display_name = R.name + if (R.id == "capsaicin") + display_name = "Hotsauce" + if (R.id == "frostoil") + display_name = "Coldsauce" + dat += {"[display_name]: [R.volume] unit\s
"} + + if (items_counts.len==0 && reagents.reagent_list.len==0) + dat = {"The microwave is empty
"} + else + dat = {"Ingredients:
[dat]"} + dat += {"

\ +
Turn on!
\ +
Eject ingredients!
\ +"} + + to_chat(user, browse("Microwave Controls[dat]", "window=microwave")) + onclose(user, "microwave") + return + + + +/*********************************** +* Microwave Menu Handling/Cooking +************************************/ + +/obj/machinery/microwave/proc/cook() + if(stat & (NOPOWER|BROKEN)) + return + start() + if (reagents.total_volume==0 && !(locate(/obj) in ((contents - component_parts) - circuit))) //dry run + if (!wzhzhzh(10)) + abort() + return + stop() + return + + var/datum/recipe/recipe = select_recipe(available_recipes,src) + var/obj/cooked + if (!recipe) + dirty += 1 + if (prob(max(10,dirty*5))) + if (!wzhzhzh(4)) + abort() + return + muck_start() + wzhzhzh(4) + muck_finish() + cooked = fail() + cooked.loc = src.loc + return + else if (has_extra_item()) + if (!wzhzhzh(4)) + abort() + return + broke() + cooked = fail() + cooked.loc = src.loc + return + else + if (!wzhzhzh(10)) + abort() + return + stop() + cooked = fail() + cooked.loc = src.loc + return + else + var/halftime = round(recipe.time/10/2) + if (!wzhzhzh(halftime)) + abort() + return + if (!wzhzhzh(halftime)) + abort() + cooked = fail() + cooked.loc = src.loc + return + cooked = recipe.make_food(src) + stop() + if(cooked) + cooked.loc = src.loc + return + +/obj/machinery/microwave/proc/wzhzhzh(var/seconds as num) // Whoever named this proc is fucking literally Satan. ~ Z + for (var/i=1 to seconds) + if (stat & (NOPOWER|BROKEN)) + return 0 + use_power(500) + sleep(10) + return 1 + +/obj/machinery/microwave/proc/has_extra_item() + for (var/obj/O in ((contents - component_parts) - circuit)) + if ( \ + !istype(O,/obj/item/weapon/reagent_containers/food) && \ + !istype(O, /obj/item/weapon/grown) \ + ) + return 1 + return 0 + +/obj/machinery/microwave/proc/start() + src.visible_message("The microwave turns on.", "You hear a microwave.") + src.operating = 1 + src.icon_state = "mw1" + src.updateUsrDialog() + +/obj/machinery/microwave/proc/abort() + src.operating = 0 // Turn it off again aferwards + src.icon_state = "mw" + src.updateUsrDialog() + +/obj/machinery/microwave/proc/stop() + playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) + src.operating = 0 // Turn it off again aferwards + src.icon_state = "mw" + src.updateUsrDialog() + +/obj/machinery/microwave/proc/dispose() + for (var/obj/O in ((contents-component_parts)-circuit)) + O.loc = src.loc + if (src.reagents.total_volume) + src.dirty++ + src.reagents.clear_reagents() + usr << "You dispose of the microwave contents." + src.updateUsrDialog() + +/obj/machinery/microwave/proc/muck_start() + playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) // Play a splat sound + src.icon_state = "mwbloody1" // Make it look dirty!! + +/obj/machinery/microwave/proc/muck_finish() + playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) + src.visible_message("The microwave gets covered in muck!") + src.dirty = 100 // Make it dirty so it can't be used util cleaned + src.flags = null //So you can't add condiments + src.icon_state = "mwbloody" // Make it look dirty too + src.operating = 0 // Turn it off again aferwards + src.updateUsrDialog() + +/obj/machinery/microwave/proc/broke() + var/datum/effect/effect/system/spark_spread/s = new + s.set_up(2, 1, src) + s.start() + src.icon_state = "mwb" // Make it look all busted up and shit + src.visible_message("The microwave breaks!") //Let them know they're stupid + src.broken = 2 // Make it broken so it can't be used util fixed + src.flags = null //So you can't add condiments + src.operating = 0 // Turn it off again aferwards + src.updateUsrDialog() + +/obj/machinery/microwave/proc/fail() + var/obj/item/weapon/reagent_containers/food/snacks/badrecipe/ffuu = new(src) + var/amount = 0 + for (var/obj/O in (((contents - ffuu) - component_parts) - circuit)) + amount++ + if (O.reagents) + var/id = O.reagents.get_master_reagent_id() + if (id) + amount+=O.reagents.get_reagent_amount(id) + qdel(O) + src.reagents.clear_reagents() + ffuu.reagents.add_reagent("carbon", amount) + ffuu.reagents.add_reagent("toxin", amount/10) + return ffuu + +/obj/machinery/microwave/Topic(href, href_list) + if(..()) + return + + usr.set_machine(src) + if(src.operating) + src.updateUsrDialog() + return + + switch(href_list["action"]) + if ("cook") + cook() + + if ("dispose") + dispose() + return diff --git a/code/modules/food/kitchen/smartfridge.dm b/code/modules/food/kitchen/smartfridge.dm index 005f7749b6..b7c3ac443f 100644 --- a/code/modules/food/kitchen/smartfridge.dm +++ b/code/modules/food/kitchen/smartfridge.dm @@ -10,7 +10,11 @@ idle_power_usage = 5 active_power_usage = 100 flags = NOREACT +<<<<<<< HEAD var/max_n_of_items = 999 // Sorry but the BYOND infinite loop detector doesn't look things over 1000. //VOREStation Edit - Non-global +======= + var/global/max_n_of_items = 999 // Sorry but the BYOND infinite loop detector doesn't look things over 1000. +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync var/icon_on = "smartfridge" var/icon_off = "smartfridge-off" var/icon_panel = "smartfridge-panel" diff --git a/code/modules/gamemaster/actions/carp_migration.dm b/code/modules/gamemaster/actions/carp_migration.dm index 7c76a5119c..7bf7d80ee3 100644 --- a/code/modules/gamemaster/actions/carp_migration.dm +++ b/code/modules/gamemaster/actions/carp_migration.dm @@ -52,12 +52,12 @@ while (i <= carp_amount) var/group_size = rand(group_size_min, group_size_max) for (var/j = 1, j <= group_size, j++) - spawned_carp.Add(new /mob/living/simple_animal/hostile/carp(spawn_locations[i])) + spawned_carp.Add(new /mob/living/simple_mob/animal/space/carp/event(spawn_locations[i])) i++ message_admins("[spawned_carp.len] carp spawned by event.") /datum/gm_action/carp_migration/end() - for(var/mob/living/simple_animal/hostile/carp/C in spawned_carp) + for(var/mob/living/simple_mob/animal/space/carp/C in spawned_carp) if(!C.stat) var/turf/T = get_turf(C) if(istype(T, /turf/space)) diff --git a/code/modules/gamemaster/actions/surprise_carp_attack.dm b/code/modules/gamemaster/actions/surprise_carp_attack.dm index e78dd72343..14c4b03e0e 100644 --- a/code/modules/gamemaster/actions/surprise_carp_attack.dm +++ b/code/modules/gamemaster/actions/surprise_carp_attack.dm @@ -47,7 +47,5 @@ spawning_turf = space break if(spawning_turf) - var/mob/living/simple_animal/hostile/carp/C = new(spawning_turf) - C.target_mob = victim - C.stance = STANCE_ATTACK + new /mob/living/simple_mob/animal/space/carp(spawning_turf) number_of_carp-- \ No newline at end of file diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm index cbeff5fb34..f5b6b28e11 100644 --- a/code/modules/games/cards.dm +++ b/code/modules/games/cards.dm @@ -238,7 +238,7 @@ /obj/item/weapon/deck/MouseDrop(mob/user as mob) // Code from Paper bin, so you can still pick up the deck if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr)))))) - if(!istype(usr, /mob/living/simple_animal)) + if(!istype(usr, /mob/living/simple_mob)) if( !usr.get_active_hand() ) //if active hand is empty var/mob/living/carbon/human/H = user var/obj/item/organ/external/temp = H.organs_by_name["r_hand"] @@ -256,7 +256,7 @@ /obj/item/weapon/deck/verb_pickup(mob/user as mob) // Snowflaked so pick up verb work as intended if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr)))))) - if(!istype(usr, /mob/living/simple_animal)) + if(!istype(usr, /mob/living/simple_mob)) if( !usr.get_active_hand() ) //if active hand is empty var/mob/living/carbon/human/H = user var/obj/item/organ/external/temp = H.organs_by_name["r_hand"] diff --git a/code/modules/holodeck/HolodeckControl.dm b/code/modules/holodeck/HolodeckControl.dm index e5c9b81d99..845b4701bd 100644 --- a/code/modules/holodeck/HolodeckControl.dm +++ b/code/modules/holodeck/HolodeckControl.dm @@ -177,7 +177,7 @@ for(var/obj/item/weapon/holo/esword/H in linkedholodeck) H.damtype = initial(H.damtype) - for(var/mob/living/simple_animal/hostile/carp/holodeck/C in holographic_mobs) + for(var/mob/living/simple_mob/animal/space/carp/holodeck/C in holographic_mobs) C.set_safety(!safety_disabled) if (last_to_emag) C.friends = list(last_to_emag) @@ -210,7 +210,7 @@ derez(item, 0) if (!safety_disabled) - for(var/mob/living/simple_animal/hostile/carp/holodeck/C in holographic_mobs) + for(var/mob/living/simple_mob/animal/space/carp/holodeck/C in holographic_mobs) if (get_area(C.loc) != linkedholodeck) holographic_mobs -= C C.derez() @@ -306,7 +306,7 @@ for(var/item in holographic_objs) derez(item) - for(var/mob/living/simple_animal/hostile/carp/holodeck/C in holographic_mobs) + for(var/mob/living/simple_mob/animal/space/carp/holodeck/C in holographic_mobs) holographic_mobs -= C C.derez() @@ -340,11 +340,11 @@ T.temperature = 5000 T.hotspot_expose(50000,50000,1) if(L.name=="Holocarp Spawn") - holographic_mobs += new /mob/living/simple_animal/hostile/carp/holodeck(L.loc) + holographic_mobs += new /mob/living/simple_mob/animal/space/carp/holodeck(L.loc) if(L.name=="Holocarp Spawn Random") if (prob(4)) //With 4 spawn points, carp should only appear 15% of the time. - holographic_mobs += new /mob/living/simple_animal/hostile/carp/holodeck(L.loc) + holographic_mobs += new /mob/living/simple_mob/animal/space/carp/holodeck(L.loc) update_projections() diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm index 8e6d20eab9..8dd32997a7 100644 --- a/code/modules/holodeck/HolodeckObjects.dm +++ b/code/modules/holodeck/HolodeckObjects.dm @@ -419,7 +419,7 @@ //Holocarp -/mob/living/simple_animal/hostile/carp/holodeck +/mob/living/simple_mob/animal/space/carp/holodeck icon = 'icons/mob/AI.dmi' icon_state = "holo4" icon_living = "holo4" @@ -429,31 +429,27 @@ meat_amount = 0 meat_type = null -/mob/living/simple_animal/hostile/carp/holodeck/New() +/mob/living/simple_mob/animal/space/carp/holodeck/New() ..() set_light(2) //hologram lighting -/mob/living/simple_animal/hostile/carp/holodeck/proc/set_safety(var/safe) +/mob/living/simple_mob/animal/space/carp/holodeck/proc/set_safety(var/safe) if (safe) faction = "neutral" melee_damage_lower = 0 melee_damage_upper = 0 - environment_smash = 0 - destroy_surroundings = 0 else faction = "carp" melee_damage_lower = initial(melee_damage_lower) melee_damage_upper = initial(melee_damage_upper) - environment_smash = initial(environment_smash) - destroy_surroundings = initial(destroy_surroundings) -/mob/living/simple_animal/hostile/carp/holodeck/gib() +/mob/living/simple_mob/animal/space/carp/holodeck/gib() derez() //holograms can't gib -/mob/living/simple_animal/hostile/carp/holodeck/death() +/mob/living/simple_mob/animal/space/carp/holodeck/death() ..() derez() -/mob/living/simple_animal/hostile/carp/holodeck/proc/derez() +/mob/living/simple_mob/animal/space/carp/holodeck/proc/derez() visible_message("\The [src] fades away!") qdel(src) diff --git a/code/modules/hydroponics/seed.dm b/code/modules/hydroponics/seed.dm index 5c6053aba4..0118088b27 100644 --- a/code/modules/hydroponics/seed.dm +++ b/code/modules/hydroponics/seed.dm @@ -104,10 +104,10 @@ return if(!istype(target)) - if(istype(target, /mob/living/simple_animal/mouse)) + if(istype(target, /mob/living/simple_mob/animal/passive/mouse)) new /obj/effect/decal/remains/mouse(get_turf(target)) qdel(target) - else if(istype(target, /mob/living/simple_animal/lizard)) + else if(istype(target, /mob/living/simple_mob/animal/passive/lizard)) new /obj/effect/decal/remains/lizard(get_turf(target)) qdel(target) return diff --git a/code/modules/hydroponics/seed_datums.dm b/code/modules/hydroponics/seed_datums.dm index b70a02af4c..eab78597b1 100644 --- a/code/modules/hydroponics/seed_datums.dm +++ b/code/modules/hydroponics/seed_datums.dm @@ -184,7 +184,7 @@ display_name = "killer tomato plant" mutants = null can_self_harvest = 1 - has_mob_product = /mob/living/simple_animal/hostile/tomato + has_mob_product = /mob/living/simple_mob/tomato /datum/seed/tomato/killer/New() ..() diff --git a/code/modules/integrated_electronics/core/assemblies/device.dm b/code/modules/integrated_electronics/core/assemblies/device.dm index 63e0bef562..bcdd0e9bea 100644 --- a/code/modules/integrated_electronics/core/assemblies/device.dm +++ b/code/modules/integrated_electronics/core/assemblies/device.dm @@ -1,84 +1,84 @@ -/obj/item/device/assembly/electronic_assembly - name = "electronic device" - desc = "It's a case for building electronics with. It can be attached to other small devices." - icon_state = "setup_device" - var/opened = 0 - - var/obj/item/device/electronic_assembly/device/EA - -/obj/item/device/assembly/electronic_assembly/New() - EA = new(src) - EA.holder = src - ..() - -/obj/item/device/assembly/electronic_assembly/attackby(obj/item/I as obj, mob/user as mob) - if (I.is_crowbar()) - toggle_open(user) - else if (opened) - EA.attackby(I, user) - else - ..() - -/obj/item/device/assembly/electronic_assembly/proc/toggle_open(mob/user) - playsound(get_turf(src), 'sound/items/Crowbar.ogg', 50, 1) - opened = !opened - EA.opened = opened - to_chat(user, "You [opened ? "opened" : "closed"] \the [src].") - secured = 1 - update_icon() - -/obj/item/device/assembly/electronic_assembly/update_icon() - if(EA) - icon_state = initial(icon_state) - else - icon_state = initial(icon_state)+"0" - if(opened) - icon_state = icon_state + "-open" - -/obj/item/device/assembly/electronic_assembly/attack_self(mob/user as mob) - if(EA) - EA.attack_self(user) - -/obj/item/device/assembly/electronic_assembly/pulsed(var/radio = 0) //Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs - if(EA) - for(var/obj/item/integrated_circuit/built_in/device_input/I in EA.contents) - I.do_work() - return - -/obj/item/device/assembly/electronic_assembly/examine(mob/user) - .=..(user, 1) - if(EA) - for(var/obj/item/integrated_circuit/IC in EA.contents) - IC.external_examine(user) - -/obj/item/device/assembly/electronic_assembly/verb/toggle() - set src in usr - set category = "Object" - set name = "Open/Close Device Assembly" - set desc = "Open or close device assembly!" - - toggle_open(usr) - - -/obj/item/device/electronic_assembly/device - name = "electronic device" - icon_state = "setup_device" - desc = "It's a tiny electronic device with specific use for attaching to other devices." - var/obj/item/device/assembly/electronic_assembly/holder - w_class = ITEMSIZE_TINY - max_components = IC_COMPONENTS_BASE * 3/4 - max_complexity = IC_COMPLEXITY_BASE * 3/4 - - -/obj/item/device/electronic_assembly/device/New() - ..() - var/obj/item/integrated_circuit/built_in/device_input/input = new(src) - var/obj/item/integrated_circuit/built_in/device_output/output = new(src) - input.assembly = src - output.assembly = src - -/obj/item/device/electronic_assembly/device/check_interactivity(mob/user) - if(!CanInteract(user, state = deep_inventory_state)) - return 0 - return 1 - +/obj/item/device/assembly/electronic_assembly + name = "electronic device" + desc = "It's a case for building electronics with. It can be attached to other small devices." + icon_state = "setup_device" + var/opened = 0 + + var/obj/item/device/electronic_assembly/device/EA + +/obj/item/device/assembly/electronic_assembly/New() + EA = new(src) + EA.holder = src + ..() + +/obj/item/device/assembly/electronic_assembly/attackby(obj/item/I as obj, mob/user as mob) + if (I.is_crowbar()) + toggle_open(user) + else if (opened) + EA.attackby(I, user) + else + ..() + +/obj/item/device/assembly/electronic_assembly/proc/toggle_open(mob/user) + playsound(get_turf(src), 'sound/items/Crowbar.ogg', 50, 1) + opened = !opened + EA.opened = opened + to_chat(user, "You [opened ? "opened" : "closed"] \the [src].") + secured = 1 + update_icon() + +/obj/item/device/assembly/electronic_assembly/update_icon() + if(EA) + icon_state = initial(icon_state) + else + icon_state = initial(icon_state)+"0" + if(opened) + icon_state = icon_state + "-open" + +/obj/item/device/assembly/electronic_assembly/attack_self(mob/user as mob) + if(EA) + EA.attack_self(user) + +/obj/item/device/assembly/electronic_assembly/pulsed(var/radio = 0) //Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs + if(EA) + for(var/obj/item/integrated_circuit/built_in/device_input/I in EA.contents) + I.do_work() + return + +/obj/item/device/assembly/electronic_assembly/examine(mob/user) + .=..(user, 1) + if(EA) + for(var/obj/item/integrated_circuit/IC in EA.contents) + IC.external_examine(user) + +/obj/item/device/assembly/electronic_assembly/verb/toggle() + set src in usr + set category = "Object" + set name = "Open/Close Device Assembly" + set desc = "Open or close device assembly!" + + toggle_open(usr) + + +/obj/item/device/electronic_assembly/device + name = "electronic device" + icon_state = "setup_device" + desc = "It's a tiny electronic device with specific use for attaching to other devices." + var/obj/item/device/assembly/electronic_assembly/holder + w_class = ITEMSIZE_TINY + max_components = IC_COMPONENTS_BASE * 3/4 + max_complexity = IC_COMPLEXITY_BASE * 3/4 + + +/obj/item/device/electronic_assembly/device/New() + ..() + var/obj/item/integrated_circuit/built_in/device_input/input = new(src) + var/obj/item/integrated_circuit/built_in/device_output/output = new(src) + input.assembly = src + output.assembly = src + +/obj/item/device/electronic_assembly/device/check_interactivity(mob/user) + if(!CanInteract(user, state = deep_inventory_state)) + return 0 + return 1 + diff --git a/code/modules/mob/_modifiers/auras.dm b/code/modules/mob/_modifiers/auras.dm new file mode 100644 index 0000000000..fcc023ee2e --- /dev/null +++ b/code/modules/mob/_modifiers/auras.dm @@ -0,0 +1,18 @@ +/* +'Aura' modifiers are semi-permanent, in that they do not have a set duration, but will expire if out of range of the 'source' of the aura. +Note: The source is defined as an argument in New(), and if not specified, it is assumed the holder is the source, +making it not expire ever, which is likely not what you want. +*/ + +/datum/modifier/aura + var/aura_max_distance = 5 // If more than this many tiles away from the source, the modifier expires next tick. + +/datum/modifier/aura/check_if_valid() + if(!origin) + expire() + var/atom/A = origin.resolve() + if(istype(A)) // Make sure we're not null. + if(get_dist(holder, A) > aura_max_distance) + expire() + else + expire() // Source got deleted or something. diff --git a/code/modules/mob/_modifiers/modifiers.dm b/code/modules/mob/_modifiers/modifiers.dm index 4f449b723b..115acb1b3e 100644 --- a/code/modules/mob/_modifiers/modifiers.dm +++ b/code/modules/mob/_modifiers/modifiers.dm @@ -149,6 +149,13 @@ /mob/living/proc/remove_specific_modifier(var/datum/modifier/M, var/silent = FALSE) M.expire(silent) +// Removes one modifier of a type +/mob/living/proc/remove_a_modifier_of_type(var/modifier_type, var/silent = FALSE) + for(var/datum/modifier/M in modifiers) + if(ispath(M.type, modifier_type)) + M.expire(silent) + break + // Removes all modifiers of a type /mob/living/proc/remove_modifiers_of_type(var/modifier_type, var/silent = FALSE) for(var/datum/modifier/M in modifiers) diff --git a/code/modules/mob/_modifiers/modifiers_misc.dm b/code/modules/mob/_modifiers/modifiers_misc.dm index fbe9e35624..af5be943d1 100644 --- a/code/modules/mob/_modifiers/modifiers_misc.dm +++ b/code/modules/mob/_modifiers/modifiers_misc.dm @@ -29,7 +29,7 @@ Berserk is a somewhat rare modifier to obtain freely (and for good reason), howe - Red Slimes will berserk if they go rabid. - Red slime core reactions will berserk slimes that can see the user in addition to making them go rabid. - Red slime core reactions will berserk prometheans that can see the user. -- Bears will berserk when losing a fight. +- Saviks will berserk when losing a fight. - Changelings can evolve a 2 point ability to use a changeling-specific variant of Berserk, that replaces the text with a 'we' variant. Recursive Enhancement allows the changeling to instead used an improved variant that features less exhaustion time and less nutrition drain. - Xenoarch artifacts may have forced berserking as one of their effects. This is especially fun if an artifact that makes hostile mobs is nearby. @@ -180,3 +180,70 @@ the artifact triggers the rage. accuracy = -75 // Aiming requires focus. accuracy_dispersion = 3 // Ditto. evasion = -45 // Too angry to dodge. + + + + +// Ignition, but confined to the modifier system. +// This makes it more predictable and thus, easier to balance. +/datum/modifier/fire + name = "on fire" + desc = "You are on fire! You will be harmed until the fire goes out or you extinguish it with water." + mob_overlay_state = "on_fire" + + on_created_text = "You combust into flames!" + on_expired_text = "The fire starts to fade." + stacks = MODIFIER_STACK_ALLOWED // Multiple instances will hurt a lot. + var/damage_per_tick = 5 + +/datum/modifier/fire/tick() + holder.inflict_heat_damage(damage_per_tick) + + +// Applied when near something very cold. +// Reduces mobility, attack speed. +/datum/modifier/chilled + name = "chilled" + desc = "You feel yourself freezing up. Its hard to move." + mob_overlay_state = "chilled" + + on_created_text = "You feel like you're going to freeze! It's hard to move." + on_expired_text = "You feel somewhat warmer and more mobile now." + stacks = MODIFIER_STACK_EXTEND + + slowdown = 2 + evasion = -40 + attack_speed_percent = 1.4 + disable_duration_percent = 1.2 + + +// Similar to being on fire, except poison tends to be more long term. +// Antitoxins will remove stacks over time. +// Synthetics can't receive this. +/datum/modifier/poisoned + name = "poisoned" + desc = "You have poison inside of you. It will cause harm over a long span of time if not cured." + mob_overlay_state = "poisoned" + + on_created_text = "You feel sick..." + on_expired_text = "You feel a bit better." + stacks = MODIFIER_STACK_ALLOWED // Multiple instances will hurt a lot. + var/damage_per_tick = 1 + +/datum/modifier/poisoned/weak + damage_per_tick = 0.5 + +/datum/modifier/poisoned/strong + damage_per_tick = 2 + +/datum/modifier/poisoned/tick() + if(holder.stat == DEAD) + expire(silent = TRUE) + holder.inflict_poison_damage(damage_per_tick) + +/datum/modifier/poisoned/can_apply(mob/living/L) + if(L.isSynthetic()) + return FALSE + if(L.get_poison_protection() >= 1) + return FALSE + return TRUE \ No newline at end of file diff --git a/code/modules/mob/_modifiers/traits_phobias.dm b/code/modules/mob/_modifiers/traits_phobias.dm index 0fbe099c22..bd30891fbe 100644 --- a/code/modules/mob/_modifiers/traits_phobias.dm +++ b/code/modules/mob/_modifiers/traits_phobias.dm @@ -207,8 +207,8 @@ if(istype(thing, /obj/structure/snowman/spider)) //Snow spiders are also spooky so people can be assholes with those too. fear_amount += 1 - if(istype(thing, /mob/living/simple_animal/hostile/giant_spider)) // Actual giant spiders are the scariest of them all. - var/mob/living/simple_animal/hostile/giant_spider/S = thing + if(istype(thing, /mob/living/simple_mob/animal/giant_spider)) // Actual giant spiders are the scariest of them all. + var/mob/living/simple_mob/animal/giant_spider/S = thing if(S.stat == DEAD) // Dead giant spiders are less scary than alive ones. fear_amount += 4 else @@ -425,14 +425,18 @@ if(istype(thing, /obj/item/clothing/head/collectable/slime)) // Some hats are spooky so people can be assholes with them. fear_amount += 1 - if(istype(thing, /mob/living/simple_animal/slime)) // An actual predatory specimen! - var/mob/living/simple_animal/slime/S = thing + if(istype(thing, /mob/living/simple_mob/slime)) // An actual predatory specimen! + var/mob/living/simple_mob/slime/S = thing if(S.stat == DEAD) // Dead slimes are somewhat less spook. fear_amount += 4 - if(S.is_adult == TRUE) //big boy - fear_amount += 8 + if(istype(S, /mob/living/simple_mob/slime/xenobio)) + var/mob/living/simple_mob/slime/xenobio/X = S + if(X.is_adult == TRUE) //big boy + fear_amount += 8 + else + fear_amount += 6 else - fear_amount += 6 + fear_amount += 10 // It's huge and feral. if(istype(thing, /mob/living/carbon/human)) var/mob/living/carbon/human/S = thing diff --git a/code/modules/mob/_modifiers/unholy.dm b/code/modules/mob/_modifiers/unholy.dm index cc4a23aa40..0eadabc83a 100644 --- a/code/modules/mob/_modifiers/unholy.dm +++ b/code/modules/mob/_modifiers/unholy.dm @@ -71,7 +71,7 @@ /datum/modifier/repair_aura/tick() spawn() - for(var/mob/living/simple_animal/construct/T in view(4,holder)) + for(var/mob/living/simple_mob/construct/T in view(4,holder)) T.adjustBruteLoss(rand(-10,-15)) T.adjustFireLoss(rand(-10,-15)) @@ -110,7 +110,7 @@ spawn() if(isliving(holder)) var/mob/living/L = holder - if(istype(L, /mob/living/simple_animal/construct)) + if(istype(L, /mob/living/simple_mob/construct)) L.adjustBruteLoss(rand(-5,-10)) L.adjustFireLoss(rand(-5,-10)) else diff --git a/code/modules/mob/animations.dm b/code/modules/mob/animations.dm index c5bded9765..9b9ce3957f 100644 --- a/code/modules/mob/animations.dm +++ b/code/modules/mob/animations.dm @@ -180,8 +180,33 @@ note dizziness decrements automatically in the mob's Life() proc. pixel_z = default_pixel_z alpha = initial_alpha -/atom/movable/proc/do_attack_animation(atom/A) +// Similar to attack animations, but in reverse and is longer to act as a telegraph. +/atom/movable/proc/do_windup_animation(atom/A, windup_time) + var/pixel_x_diff = 0 + var/pixel_y_diff = 0 + var/direction = get_dir(src, A) + if(direction & NORTH) + pixel_y_diff = -8 + else if(direction & SOUTH) + pixel_y_diff = 8 + if(direction & EAST) + pixel_x_diff = -8 + else if(direction & WEST) + pixel_x_diff = 8 + + var/default_pixel_x = initial(pixel_x) + var/default_pixel_y = initial(pixel_y) + var/mob/mob = src + if(istype(mob)) + default_pixel_x = mob.default_pixel_x + default_pixel_y = mob.default_pixel_y + + animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = windup_time - 2) + animate(pixel_x = default_pixel_x, pixel_y = default_pixel_y, time = 2) + + +/atom/movable/proc/do_attack_animation(atom/A) var/pixel_x_diff = 0 var/pixel_y_diff = 0 var/direction = get_dir(src, A) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 7345e7e9b1..fa021509fb 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -498,7 +498,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp //find a viable mouse candidate - var/mob/living/simple_animal/mouse/host + var/mob/living/simple_mob/animal/passive/mouse/host var/obj/machinery/atmospherics/unary/vent_pump/vent_found var/list/found_vents = list() for(var/obj/machinery/atmospherics/unary/vent_pump/v in machines) @@ -506,7 +506,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp found_vents.Add(v) if(found_vents.len) vent_found = pick(found_vents) - host = new /mob/living/simple_animal/mouse(vent_found) + host = new /mob/living/simple_mob/animal/passive/mouse(vent_found) else src << "Unable to find any unwelded vents to spawn mice at." diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm index 0ecdb2fc20..f67f70eb87 100644 --- a/code/modules/mob/emote.dm +++ b/code/modules/mob/emote.dm @@ -52,6 +52,13 @@ if(O) O.see_emote(src, message, m_type) +// Shortcuts for above proc +/mob/proc/visible_emote(var/act_desc) + custom_emote(1, act_desc) + +/mob/proc/audible_emote(var/act_desc) + custom_emote(2, act_desc) + /mob/proc/emote_dead(var/message) if(client.prefs.muted & MUTE_DEADCHAT) diff --git a/code/modules/mob/freelook/mask/chunk.dm b/code/modules/mob/freelook/mask/chunk.dm index f98a9b936b..b540a8023b 100644 --- a/code/modules/mob/freelook/mask/chunk.dm +++ b/code/modules/mob/freelook/mask/chunk.dm @@ -25,10 +25,10 @@ /mob/living/silicon/seen_cult_turfs() return list() -/mob/living/simple_animal/seen_cult_turfs() +/mob/living/simple_mob/seen_cult_turfs() return seen_turfs_in_range(src, 1) -/mob/living/simple_animal/shade/narsie/seen_cult_turfs() +/mob/living/simple_mob/construct/shade/seen_cult_turfs() return view(2, src) /proc/seen_turfs_in_range(var/source, var/range) diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index 1e862472a2..e7c097c2b3 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -78,6 +78,12 @@ var/turf/source = speaker? get_turf(speaker) : get_turf(src) src.playsound_local(source, speech_sound, sound_vol, 1) +// Done here instead of on_hear_say() since that is NOT called if the mob is clientless (which includes most AI mobs). +/mob/living/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "",var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol) + ..() + if(has_AI()) // Won't happen if no ai_holder exists or there's a player inside w/o autopilot active. + ai_holder.on_hear_say(speaker, message) + /mob/proc/on_hear_say(var/message) to_chat(src, message) if(teleop) @@ -158,17 +164,10 @@ if(!(language && (language.flags & INNATE))) // skip understanding checks for INNATE languages if(!say_understands(speaker,language)) - if(istype(speaker,/mob/living/simple_animal)) - var/mob/living/simple_animal/S = speaker - if(S.speak && S.speak.len) - message = pick(S.speak) - else - return + if(language) + message = language.scramble(message) else - if(language) - message = language.scramble(message) - else - message = stars(message) + message = stars(message) if(hard_to_hear) message = stars(message) diff --git a/code/modules/mob/language/outsider.dm b/code/modules/mob/language/outsider.dm index fdd719df89..de78a9c1b5 100644 --- a/code/modules/mob/language/outsider.dm +++ b/code/modules/mob/language/outsider.dm @@ -27,16 +27,16 @@ /datum/language/corticalborer/broadcast(var/mob/living/speaker,var/message,var/speaker_mask) - var/mob/living/simple_animal/borer/B + var/mob/living/simple_mob/animal/borer/B if(istype(speaker,/mob/living/carbon)) var/mob/living/carbon/M = speaker B = M.has_brain_worms() - else if(istype(speaker,/mob/living/simple_animal/borer)) + else if(istype(speaker,/mob/living/simple_mob/animal/borer)) B = speaker if(B) - speaker_mask = B.truename + speaker_mask = B.true_name ..(speaker,message,speaker_mask) /datum/language/vox diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm index ba8c0945f0..23008d126a 100644 --- a/code/modules/mob/living/bot/secbot.dm +++ b/code/modules/mob/living/bot/secbot.dm @@ -215,7 +215,7 @@ else if(declare_arrests) var/action = arrest_type ? "detaining" : "arresting" - if(istype(target, /mob/living/simple_animal)) + if(istype(target, /mob/living/simple_mob)) action = "fighting" global_announcer.autosay("[src] is [action] a level [threat] [action != "fighting" ? "suspect" : "threat"] [target_name(target)] in [get_area(src)].", "[src]", "Security") UnarmedAttack(target) @@ -269,8 +269,8 @@ C.handcuffed = new /obj/item/weapon/handcuffs(C) C.update_inv_handcuffed() busy = 0 - else if(istype(M, /mob/living/simple_animal)) - var/mob/living/simple_animal/S = M + else if(istype(M, /mob/living/simple_mob)) + var/mob/living/simple_mob/S = M S.Weaken(xeno_stun_strength) S.adjustBruteLoss(xeno_harm_strength) do_attack_animation(M) @@ -286,8 +286,8 @@ /mob/living/bot/secbot/slime/UnarmedAttack(var/mob/living/L, var/proximity) ..() - if(istype(L, /mob/living/simple_animal/slime)) - var/mob/living/simple_animal/slime/S = L + if(istype(L, /mob/living/simple_mob/slime/xenobio)) + var/mob/living/simple_mob/slime/xenobio/S = L S.adjust_discipline(2) diff --git a/code/modules/mob/living/carbon/alien/diona/diona_powers.dm b/code/modules/mob/living/carbon/alien/diona/diona_powers.dm index 585b421c7f..aea0054418 100644 --- a/code/modules/mob/living/carbon/alien/diona/diona_powers.dm +++ b/code/modules/mob/living/carbon/alien/diona/diona_powers.dm @@ -63,5 +63,5 @@ if(istype(M)) for(var/atom/A in M.contents) - if(istype(A,/mob/living/simple_animal/borer) || istype(A,/obj/item/weapon/holder)) + if(istype(A,/mob/living/simple_mob/animal/borer) || istype(A,/obj/item/weapon/holder)) return diff --git a/code/modules/mob/living/carbon/brain/brain.dm b/code/modules/mob/living/carbon/brain/brain.dm index 7165c36fa8..561dd0c139 100644 --- a/code/modules/mob/living/carbon/brain/brain.dm +++ b/code/modules/mob/living/carbon/brain/brain.dm @@ -46,7 +46,7 @@ return 1 if (istype(other, /mob/living/carbon/human)) return 1 - if (istype(other, /mob/living/simple_animal/slime)) + if (istype(other, /mob/living/simple_mob/slime)) return 1 return ..() diff --git a/code/modules/mob/living/carbon/brain/life.dm b/code/modules/mob/living/carbon/brain/life.dm index b631e8ae13..4ab220083d 100644 --- a/code/modules/mob/living/carbon/brain/life.dm +++ b/code/modules/mob/living/carbon/brain/life.dm @@ -76,7 +76,6 @@ if(ingested) ingested.metabolize() if(bloodstr) bloodstr.metabolize() - AdjustConfused(-1) // decrement dizziness counter, clamped to 0 if(resting) dizziness = max(0, dizziness - 5) diff --git a/code/modules/mob/living/carbon/carbon_powers.dm b/code/modules/mob/living/carbon/carbon_powers.dm index f1360504ef..337f0b0ff1 100644 --- a/code/modules/mob/living/carbon/carbon_powers.dm +++ b/code/modules/mob/living/carbon/carbon_powers.dm @@ -5,7 +5,7 @@ set name = "Release Control" set desc = "Release control of your host's body." - var/mob/living/simple_animal/borer/B = has_brain_worms() + var/mob/living/simple_mob/animal/borer/B = has_brain_worms() if(B && B.host_brain) src << "You withdraw your probosci, releasing control of [B.host_brain]" @@ -25,7 +25,7 @@ set name = "Torment host" set desc = "Punish your host with agony." - var/mob/living/simple_animal/borer/B = has_brain_worms() + var/mob/living/simple_mob/animal/borer/B = has_brain_worms() if(!B) return @@ -43,7 +43,7 @@ set name = "Reproduce" set desc = "Spawn several young." - var/mob/living/simple_animal/borer/B = has_brain_worms() + var/mob/living/simple_mob/animal/borer/B = has_brain_worms() if(!B) return @@ -55,7 +55,7 @@ B.has_reproduced = 1 vomit(1) - new /mob/living/simple_animal/borer(get_turf(src)) + new /mob/living/simple_mob/animal/borer(get_turf(src)) else src << "You do not have enough chemicals stored to reproduce." diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 24a5bac586..2244cdac51 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -61,11 +61,11 @@ //Handle brain slugs. var/obj/item/organ/external/Hd = get_organ(BP_HEAD) - var/mob/living/simple_animal/borer/B + var/mob/living/simple_mob/animal/borer/B if(Hd) for(var/I in Hd.implants) - if(istype(I,/mob/living/simple_animal/borer)) + if(istype(I,/mob/living/simple_mob/animal/borer)) B = I if(B) if(!B.ckey && ckey && B.controlling) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index c5f259e0b4..57cb8d0f58 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -135,6 +135,27 @@ emp_act return siemens_coefficient +// Similar to above but is for the mob's overall protection, being the average of all slots. +/mob/living/carbon/human/proc/get_siemens_coefficient_average() + var/siemens_value = 0 + var/total = 0 + for(var/organ_name in organs_by_name) + if(organ_name in organ_rel_size) + var/obj/item/organ/external/organ = organs_by_name[organ_name] + if(organ) + var/weight = organ_rel_size[organ_name] + siemens_value += get_siemens_coefficient_organ(organ) * weight + total += weight + + if(fire_stacks < 0) // Water makes you more conductive. + siemens_value *= 1.5 + + return (siemens_value/max(total, 1)) + +// Returns a number between 0 to 1, with 1 being total protection. +/mob/living/carbon/human/get_shock_protection() + return between(0, 1-get_siemens_coefficient_average(), 1) + // Returns a list of clothing that is currently covering def_zone. /mob/living/carbon/human/proc/get_clothing_list_organ(var/obj/item/organ/external/def_zone, var/type) var/list/results = list() @@ -542,6 +563,20 @@ emp_act return perm +// This is for preventing harm by being covered in water, which only prometheans need to deal with. +// This is not actually used for now since the code for prometheans gets changed a lot. +/mob/living/carbon/human/get_water_protection() + var/protection = ..() // Todo: Replace with species var later. + if(protection == 1) // No point doing permeability checks if it won't matter. + return protection + // Wearing clothing with a low permeability_coefficient can protect from water. + + var/converted_protection = 1 - protection + var/perm = reagent_permeability() + converted_protection *= perm + return 1-converted_protection + + /mob/living/carbon/human/shank_attack(obj/item/W, obj/item/weapon/grab/G, mob/user, hit_zone) if(!..()) diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 2589b1c9da..24a63bf1a4 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -42,6 +42,16 @@ if(cloaker.active) cloaker.deactivate() +/mob/living/carbon/human/is_cloaked() + if(mind && mind.changeling && mind.changeling.cloaked) // Ling camo. + return TRUE + else if(istype(back, /obj/item/weapon/rig)) //Ninja cloak + var/obj/item/weapon/rig/suit = back + for(var/obj/item/rig_module/stealth_field/cloaker in suit.installed_modules) + if(cloaker.active) + return TRUE + return ..() + /mob/living/carbon/human/get_ear_protection() var/sum = 0 if(istype(l_ear, /obj/item/clothing/ears)) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 8a4fe6a18b..e9cbc80d95 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1047,8 +1047,6 @@ sleeping += 1 Paralyse(5) - confused = max(0, confused - 1) - // If you're dirty, your gloves will become dirty, too. if(gloves && germ_level > gloves.germ_level && prob(10)) gloves.germ_level += 1 @@ -1602,7 +1600,7 @@ else if(foundVirus) holder.icon_state = "hudill" else if(has_brain_worms()) - var/mob/living/simple_animal/borer/B = has_brain_worms() + var/mob/living/simple_mob/animal/borer/B = has_brain_worms() if(B.controlling) holder.icon_state = "hudbrainworm" else diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm index d42c26880b..7f0e42e599 100644 --- a/code/modules/mob/living/carbon/human/say.dm +++ b/code/modules/mob/living/carbon/human/say.dm @@ -64,7 +64,7 @@ return 1 if (istype(other, /mob/living/carbon/brain)) return 1 - if (istype(other, /mob/living/simple_animal/slime)) + if (istype(other, /mob/living/simple_mob/slime)) return 1 //This is already covered by mob/say_understands() diff --git a/code/modules/mob/living/carbon/human/species/station/prometheans.dm b/code/modules/mob/living/carbon/human/species/station/prometheans.dm index b78d530707..1a24ad9e1b 100644 --- a/code/modules/mob/living/carbon/human/species/station/prometheans.dm +++ b/code/modules/mob/living/carbon/human/species/station/prometheans.dm @@ -116,7 +116,7 @@ var/datum/species/shapeshifter/promethean/prometheans /datum/species/shapeshifter/promethean/equip_survival_gear(var/mob/living/carbon/human/H) var/boxtype = pick(typesof(/obj/item/weapon/storage/toolbox/lunchbox)) var/obj/item/weapon/storage/toolbox/lunchbox/L = new boxtype(get_turf(H)) - var/mob/living/simple_animal/mouse/mouse = new (L) + var/mob/living/simple_mob/animal/passive/mouse/mouse = new (L) var/obj/item/weapon/holder/holder = new (L) mouse.forceMove(holder) holder.sync(mouse) diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index a5da193678..17d6b693d4 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -1,5 +1,6 @@ /mob/living/death() clear_fullscreens() +<<<<<<< HEAD reveal(TRUE) //Silently reveal the mob if they were hidden. //VOREStation Edit - Mob spawner stuff if(source_spawner) @@ -7,6 +8,11 @@ source_spawner = null //VOREStation Edit End . = ..() +======= + + if(ai_holder) + ai_holder.go_sleep() +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync if(nest) //Ew. if(istype(nest, /obj/structure/prop/nest)) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 02131c8cd0..24d56945b8 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -110,6 +110,7 @@ handle_silent() handle_drugged() handle_slurring() + handle_confused() /mob/living/proc/handle_stunned() if(stunned) @@ -146,6 +147,11 @@ AdjustParalysis(-1) return paralysis +/mob/living/proc/handle_confused() + if(confused) + AdjustConfused(-1) + return confused + /mob/living/proc/handle_disabilities() //Eyes if(sdisabilities & BLIND || stat) //blindness from disability or unconsciousness doesn't get better on its own diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 3bcfbe7b60..3a65cb2d2d 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -16,6 +16,8 @@ dsma.blend_mode = BLEND_ADD dsoverlay.appearance = dsma + selected_image = image(icon = 'icons/mob/screen1.dmi', loc = src, icon_state = "centermarker") + /mob/living/Destroy() dsoverlay.loc = null //I'll take my coat with me dsoverlay = null @@ -29,6 +31,7 @@ nest = null if(buckled) buckled.unbuckle_mob(src, TRUE) + qdel(selected_image) return ..() //mob verbs are faster than object verbs. See mob/verb/examine. @@ -550,6 +553,36 @@ default behaviour is: // ++++ROCKDTBEN++++ MOB PROCS //END +// Applies direct "cold" damage while checking protection against the cold. +/mob/living/proc/inflict_cold_damage(amount) + amount *= 1 - get_cold_protection(50) // Within spacesuit protection. + if(amount > 0) + adjustFireLoss(amount) + +// Ditto, but for "heat". +/mob/living/proc/inflict_heat_damage(amount) + amount *= 1 - get_heat_protection(10000) // Within firesuit protection. + if(amount > 0) + adjustFireLoss(amount) + +// and one for electricity because why not +/mob/living/proc/inflict_shock_damage(amount) + electrocute_act(amount, null, 1 - get_shock_protection()) + +// also one for water (most things resist it entirely, except for slimes) +/mob/living/proc/inflict_water_damage(amount) + amount *= 1 - get_water_protection() + if(amount > 0) + adjustToxLoss(amount) + +// one for abstracted away ""poison"" (mostly because simplemobs shouldn't handle reagents) +/mob/living/proc/inflict_poison_damage(amount) + if(isSynthetic()) + return + amount *= 1 - get_poison_protection() + if(amount > 0) + adjustToxLoss(amount) + /mob/proc/get_contents() @@ -663,6 +696,8 @@ default behaviour is: BITSET(hud_updateflag, LIFE_HUD) ExtinguishMob() fire_stacks = 0 + if(ai_holder) // AI gets told to sleep when killed. Since they're not dead anymore, wake it up. + ai_holder.go_wake() /mob/living/proc/rejuvenate() if(reagents) @@ -891,7 +926,7 @@ default behaviour is: // Update whether or not this mob needs to pass emotes to contents. for(var/atom/A in M.contents) - if(istype(A,/mob/living/simple_animal/borer) || istype(A,/obj/item/weapon/holder)) + if(istype(A,/mob/living/simple_mob/animal/borer) || istype(A,/obj/item/weapon/holder)) return else if(istype(H.loc,/obj/item/clothing/accessory/holster)) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 08d35e5de2..23507d88bc 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -92,6 +92,13 @@ /mob/living/proc/getsoak(var/def_zone, var/type) return 0 +// Clicking with an empty hand +/mob/living/attack_hand(mob/living/L) + ..() + if(istype(L) && L.a_intent != I_HELP) + if(ai_holder) // Using disarm, grab, or harm intent is considered a hostile action to the mob's AI. + ai_holder.react_to_attack(L) + /mob/living/bullet_act(var/obj/item/projectile/P, var/def_zone) //Being hit while using a deadman switch @@ -102,6 +109,9 @@ src.visible_message("[src] triggers their deadman's switch!") signaler.signal() + if(ai_holder && P.firer) + ai_holder.react_to_attack(P.firer) + //Armor var/soaked = get_armor_soak(def_zone, P.check_armour, P.armor_penetration) var/absorb = run_armor_check(def_zone, P.check_armour, P.armor_penetration) @@ -267,6 +277,8 @@ var/client/assailant = M.client if(assailant) add_attack_logs(M,src,"Hit by thrown [O.name]") + if(ai_holder) + ai_holder.react_to_attack(O.thrower) // Begin BS12 momentum-transfer code. var/mass = 1.5 @@ -324,12 +336,13 @@ // End BS12 momentum-transfer code. /mob/living/attack_generic(var/mob/user, var/damage, var/attack_message) - if(!damage) return adjustBruteLoss(damage) - add_attack_logs(user,src,"Generic attack (probably animal)", admin_notify = FALSE) //Usually due to simple_animal attacks + add_attack_logs(user,src,"Generic attack (probably animal)", admin_notify = FALSE) //Usually due to simple_mob attacks + if(ai_holder) + ai_holder.react_to_attack(user) src.visible_message("[user] has [attack_message] [src]!") user.do_attack_animation(src) spawn(1) updatehealth() @@ -412,6 +425,15 @@ /mob/living/proc/get_heat_protection() return 0 +/mob/living/proc/get_shock_protection() + return 0 + +/mob/living/proc/get_water_protection() + return 1 // Water won't hurt most things. + +/mob/living/proc/get_poison_protection() + return 0 + //Finds the effective temperature that the mob is burning at. /mob/living/proc/fire_burn_temperature() if (fire_stacks <= 0) @@ -421,6 +443,15 @@ //lower limit of 700 K, same as matches and roughly the temperature of a cool flame. return max(2.25*round(FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE*(fire_stacks/FIRE_MAX_FIRESUIT_STACKS)**2), 700) +// Called when struck by lightning. +/mob/living/proc/lightning_act() + // The actual damage/electrocution is handled by the tesla_zap() that accompanies this. + Paralyse(5) + stuttering += 20 + make_jittery(150) + emp_act(1) + to_chat(src, span("critical", "You've been struck by lightning!")) + /mob/living/proc/reagent_permeability() return 1 return round(FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE*(fire_stacks/FIRE_MAX_FIRESUIT_STACKS)**2) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 9486eb9960..8e3b9fb9a4 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -5,6 +5,8 @@ var/maxHealth = 100 //Maximum health that should be possible. Avoid adjusting this if you can, and instead use modifiers datums. var/health = 100 //A mob's health + var/mob_class = null // A mob's "class", e.g. human, mechanical, animal, etc. Used for certain projectile effects. See __defines/mob.dm for available classes. + var/hud_updateflag = 0 //Damage related vars, NOTE: THESE SHOULD ONLY BE MODIFIED BY PROCS @@ -20,6 +22,7 @@ var/list/atom/hallucinations = list() //A list of hallucinated people that try to attack the mob. See /obj/effect/fake_attacker in hallucinations.dm var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. + var/base_attack_cooldown = DEFAULT_ATTACK_COOLDOWN var/t_phoron = null var/t_oxygen = null @@ -64,4 +67,5 @@ var/makes_dirt = TRUE //FALSE if the mob shouldn't be making dirt on the ground when it walks var/looking_elsewhere = FALSE //If the mob's view has been relocated to somewhere else, like via a camera or with binocs - \ No newline at end of file + + var/image/selected_image = null // Used for buildmode AI control stuff. \ No newline at end of file diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index 275e88be08..96d59dc330 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -8,4 +8,9 @@ update_antag_icons(mind) client.screen |= global_hud.darksight client.images |= dsoverlay + + if(ai_holder && !ai_holder.autopilot) + ai_holder.go_sleep() + to_chat(src,"Mob AI disabled while you are controlling the mob.") + return . diff --git a/code/modules/mob/living/logout.dm b/code/modules/mob/living/logout.dm index 577221a03e..fa04b0b317 100644 --- a/code/modules/mob/living/logout.dm +++ b/code/modules/mob/living/logout.dm @@ -1,6 +1,11 @@ /mob/living/Logout() ..() - if (mind) + if (mind) //Per BYOND docs key remains set if the player DCs, becomes null if switching bodies. - if(!key) //key and mind have become seperated. + if(!key) //key and mind have become seperated. mind.active = 0 //This is to stop say, a mind.transfer_to call on a corpse causing a ghost to re-enter its body. + + spawn(15 SECONDS) //15 seconds to get back into the mob before it goes wild + if(src && !src.client) + if(ai_holder) + ai_holder.go_wake() diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm index ac78f5aab9..20087b19bb 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm @@ -344,7 +344,7 @@ var/grabbed_something = 0 for(var/mob/M in T) - if(istype(M,/mob/living/simple_animal/lizard) || istype(M,/mob/living/simple_animal/mouse)) + if(istype(M,/mob/living/simple_mob/animal/passive/lizard) || istype(M,/mob/living/simple_mob/animal/passive/mouse)) src.loc.visible_message("[src.loc] sucks [M] into its decompiler. There's a horrible crunching noise.","It's a bit of a struggle, but you manage to suck [M] into your decompiler. It makes a series of visceral crunching noises.") new/obj/effect/decal/cleanable/blood/splatter(get_turf(src)) qdel(M) diff --git a/code/modules/mob/living/simple_animal/aliens/faithless.dm b/code/modules/mob/living/simple_animal/aliens/faithless.dm index af2e7da48d..3b5e520fcd 100644 --- a/code/modules/mob/living/simple_animal/aliens/faithless.dm +++ b/code/modules/mob/living/simple_animal/aliens/faithless.dm @@ -72,7 +72,6 @@ melee_damage_lower = 13 melee_damage_upper = 28 - /mob/living/simple_animal/hostile/faithless/strong/cult faction = "cult" supernatural = 1 diff --git a/code/modules/mob/living/simple_animal/aliens/hivebot.dm b/code/modules/mob/living/simple_animal/aliens/hivebot.dm index e322198815..6d53b30cfa 100644 --- a/code/modules/mob/living/simple_animal/aliens/hivebot.dm +++ b/code/modules/mob/living/simple_animal/aliens/hivebot.dm @@ -1,6 +1,6 @@ // Hivebots are tuned towards how many default lasers are needed to kill them. // As such, if laser damage is ever changed, you should change this define. -#define LASERS_TO_KILL *30 +#define LASERS_TO_KILL * 40 // Default hivebot is melee, and a bit more meaty, so it can meatshield for their ranged friends. /mob/living/simple_animal/hostile/hivebot @@ -238,3 +238,5 @@ /obj/item/projectile/bullet/hivebot damage = 10 damage_type = BRUTE + +#undef LASERS_TO_KILL \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/animals/cat.dm b/code/modules/mob/living/simple_animal/animals/cat.dm index 8877ea8959..30d23e0105 100644 --- a/code/modules/mob/living/simple_animal/animals/cat.dm +++ b/code/modules/mob/living/simple_animal/animals/cat.dm @@ -1,3 +1,4 @@ +<<<<<<< HEAD //Cat /mob/living/simple_animal/cat name = "cat" @@ -202,3 +203,203 @@ /obj/item/weapon/holder/cat/kitten icon_state = "kitten" w_class = ITEMSIZE_SMALL +======= +//Cat +/mob/living/simple_animal/cat + name = "cat" + desc = "A domesticated, feline pet. Has a tendency to adopt crewmembers." + tt_desc = "E Felis silvestris catus" + intelligence_level = SA_ANIMAL + icon_state = "cat2" + item_state = "cat2" + icon_living = "cat2" + icon_dead = "cat2_dead" + icon_rest = "cat2_rest" + + investigates = 1 + specific_targets = 1 //Only targets with Found() + run_at_them = 0 //DOMESTICATED + view_range = 5 + + turns_per_move = 5 + see_in_dark = 6 + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + + min_oxy = 16 //Require atleast 16kPA oxygen + minbodytemp = 223 //Below -50 Degrees Celcius + maxbodytemp = 323 //Above 50 Degrees Celcius + + holder_type = /obj/item/weapon/holder/cat + mob_size = MOB_SMALL + + has_langs = list("Cat") + speak_chance = 1 + speak = list("Meow!","Esp!","Purr!","HSSSSS") + speak_emote = list("purrs", "meows") + emote_hear = list("meows","mews") + emote_see = list("shakes their head", "shivers") + say_maybe_target = list("Meow?","Mew?","Mao?") + say_got_target = list("MEOW!","HSSSS!","REEER!") + + meat_amount = 1 + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + var/turns_since_scan = 0 + var/mob/flee_target + +/mob/living/simple_animal/cat/Life() + . = ..() + if(!.) return + + if(prob(2)) //spooky + var/mob/observer/dead/spook = locate() in range(src,5) + if(spook) + var/turf/T = spook.loc + var/list/visible = list() + for(var/obj/O in T.contents) + if(!O.invisibility && O.name) + visible += O + if(visible.len) + var/atom/A = pick(visible) + visible_emote("suddenly stops and stares at something unseen[istype(A) ? " near [A]":""].") + + handle_flee_target() + +/mob/living/simple_animal/cat/PunchTarget() + if(ismouse(target_mob)) + var/mob/living/simple_mob/animal/passive/mouse/mouse = target_mob + mouse.splat() + visible_emote(pick("bites \the [mouse]!","toys with \the [mouse].","chomps on \the [mouse]!")) + return mouse + else + ..() + +/mob/living/simple_animal/cat/Found(var/atom/found_atom) + if(ismouse(found_atom) && SA_attackable(found_atom)) + return found_atom + +/mob/living/simple_animal/cat/proc/handle_flee_target() + //see if we should stop fleeing + if (flee_target && !(flee_target in ListTargets(view_range))) + flee_target = null + GiveUpMoving() + + if (flee_target && !stat && !buckled) + if (resting) + lay_down() + if(prob(25)) say("HSSSSS") + stop_automated_movement = 1 + walk_away(src, flee_target, 7, 2) + +/mob/living/simple_animal/cat/react_to_attack(var/atom/A) + if(A == src) return + flee_target = A + turns_since_scan = 5 + +/mob/living/simple_animal/cat/ex_act() + . = ..() + react_to_attack(src.loc) + +//Basic friend AI +/mob/living/simple_animal/cat/fluff + var/mob/living/carbon/human/friend + var/befriend_job = null + +/mob/living/simple_animal/cat/fluff/Life() + . = ..() + if(!. || ai_inactive || !friend) return + + var/friend_dist = get_dist(src,friend) + + if (friend_dist <= 4) + if(stance == STANCE_IDLE) + if(set_follow(friend)) + handle_stance(STANCE_FOLLOW) + + if (friend_dist <= 1) + if (friend.stat >= DEAD || friend.health <= config.health_threshold_softcrit) + if (prob((friend.stat < DEAD)? 50 : 15)) + var/verb = pick("meows", "mews", "mrowls") + audible_emote(pick("[verb] in distress.", "[verb] anxiously.")) + else + if (prob(5)) + visible_emote(pick("nuzzles [friend].", + "brushes against [friend].", + "rubs against [friend].", + "purrs.")) + else if (friend.health <= 50) + if (prob(10)) + var/verb = pick("meows", "mews", "mrowls") + audible_emote("[verb] anxiously.") + +/mob/living/simple_animal/cat/fluff/verb/become_friends() + set name = "Become Friends" + set category = "IC" + set src in view(1) + + if(!friend) + var/mob/living/carbon/human/H = usr + if(istype(H) && (!befriend_job || H.job == befriend_job)) + friend = usr + . = 1 + else if(usr == friend) + . = 1 //already friends, but show success anyways + + if(.) + set_dir(get_dir(src, friend)) + visible_emote(pick("nuzzles [friend].", + "brushes against [friend].", + "rubs against [friend].", + "purrs.")) + else + usr << "[src] ignores you." + return + +//RUNTIME IS ALIVE! SQUEEEEEEEE~ +/mob/living/simple_animal/cat/fluff/Runtime + name = "Runtime" + desc = "Her fur has the look and feel of velvet, and her tail quivers occasionally." + tt_desc = "E Felis silvestris medicalis" //a hypoallergenic breed produced by NT for... medical purposes? Sure. + gender = FEMALE + icon_state = "cat" + item_state = "cat" + icon_living = "cat" + icon_dead = "cat_dead" + icon_rest = "cat_rest" + befriend_job = "Chief Medical Officer" + +/mob/living/simple_animal/cat/kitten + name = "kitten" + desc = "D'aaawwww" + icon_state = "kitten" + item_state = "kitten" + icon_living = "kitten" + icon_dead = "kitten_dead" + gender = NEUTER + +// Leaving this here for now. +/obj/item/weapon/holder/cat/fluff/bones + name = "Bones" + desc = "It's Bones! Meow." + gender = MALE + icon_state = "cat3" + +/mob/living/simple_animal/cat/fluff/bones + name = "Bones" + desc = "That's Bones the cat. He's a laid back, black cat. Meow." + gender = MALE + icon_state = "cat3" + item_state = "cat3" + icon_living = "cat3" + icon_dead = "cat3_dead" + icon_rest = "cat3_rest" + holder_type = /obj/item/weapon/holder/cat/fluff/bones + var/friend_name = "Erstatz Vryroxes" + +/mob/living/simple_animal/cat/kitten/New() + gender = pick(MALE, FEMALE) + ..() +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync diff --git a/code/modules/mob/living/simple_animal/animals/corgi.dm b/code/modules/mob/living/simple_animal/animals/corgi.dm index 33bda8b2e0..6cc1f80523 100644 --- a/code/modules/mob/living/simple_animal/animals/corgi.dm +++ b/code/modules/mob/living/simple_animal/animals/corgi.dm @@ -207,6 +207,7 @@ set_dir(i) sleep(1) + //Technically this should be like, its own file or something or a subset of dog but whatever. Not a coder. /mob/living/simple_animal/corgi/tamaskan name = "tamaskan" diff --git a/code/modules/mob/living/simple_animal/animals/mouse.dm b/code/modules/mob/living/simple_animal/animals/mouse.dm index bf5d03c52c..370001a7ca 100644 --- a/code/modules/mob/living/simple_animal/animals/mouse.dm +++ b/code/modules/mob/living/simple_animal/animals/mouse.dm @@ -16,10 +16,10 @@ see_in_dark = 6 universal_understand = 1 - mob_size = MOB_MINISCULE + mob_size = MOB_SMALL pass_flags = PASSTABLE - can_pull_size = ITEMSIZE_TINY - can_pull_mobs = MOB_PULL_NONE +// can_pull_size = ITEMSIZE_TINY +// can_pull_mobs = MOB_PULL_NONE layer = MOB_LAYER density = 0 diff --git a/code/modules/mob/living/simple_animal/animals/spiderbot.dm b/code/modules/mob/living/simple_animal/animals/spiderbot.dm index b76351b976..9145e1923f 100644 --- a/code/modules/mob/living/simple_animal/animals/spiderbot.dm +++ b/code/modules/mob/living/simple_animal/animals/spiderbot.dm @@ -46,7 +46,7 @@ /obj/item/device/radio/borg, /obj/item/weapon/holder, /obj/machinery/camera, - /mob/living/simple_animal/borer, + /mob/living/simple_mob/animal/borer, /obj/item/device/mmi, ) diff --git a/code/modules/mob/living/simple_animal/borer/borer.dm b/code/modules/mob/living/simple_animal/borer/borer.dm index e4add4d749..43edd45602 100644 --- a/code/modules/mob/living/simple_animal/borer/borer.dm +++ b/code/modules/mob/living/simple_animal/borer/borer.dm @@ -1,4 +1,4 @@ -/mob/living/simple_animal/borer +/mob/living/simple_mob/animal/borer name = "cortical borer" real_name = "cortical borer" desc = "A small, quivering sluglike creature." @@ -35,15 +35,15 @@ can_be_antagged = TRUE -/mob/living/simple_animal/borer/roundstart +/mob/living/simple_mob/animal/borer/roundstart roundstart = 1 -/mob/living/simple_animal/borer/Login() +/mob/living/simple_mob/animal/borer/Login() ..() if(mind) borers.add_antagonist(mind) -/mob/living/simple_animal/borer/New() +/mob/living/simple_mob/animal/borer/New() ..() add_language("Cortical Link") @@ -53,7 +53,7 @@ truename = "[pick("Primary","Secondary","Tertiary","Quaternary")] [rand(1000,9999)]" if(!roundstart) request_player() -/mob/living/simple_animal/borer/Life() +/mob/living/simple_mob/animal/borer/Life() ..() @@ -91,7 +91,7 @@ if(prob(host.brainloss/20)) host.say("*[pick(list("blink","blink_r","choke","aflap","drool","twitch","twitch_v","gasp"))]") -/mob/living/simple_animal/borer/Stat() +/mob/living/simple_mob/animal/borer/Stat() ..() statpanel("Status") @@ -103,7 +103,7 @@ if (client.statpanel == "Status") stat("Chemicals", chemicals) -/mob/living/simple_animal/borer/proc/detatch() +/mob/living/simple_mob/animal/borer/proc/detatch() if(!host || !controlling) return @@ -154,7 +154,7 @@ qdel(host_brain) -/mob/living/simple_animal/borer/proc/leave_host() +/mob/living/simple_mob/animal/borer/proc/leave_host() if(!host) return @@ -172,7 +172,7 @@ return //Procs for grabbing players. -/mob/living/simple_animal/borer/proc/request_player() +/mob/living/simple_mob/animal/borer/proc/request_player() for(var/mob/observer/dead/O in player_list) if(jobban_isbanned(O, "Borer")) continue @@ -180,7 +180,7 @@ if(O.client.prefs.be_special & BE_ALIEN) question(O.client) -/mob/living/simple_animal/borer/proc/question(var/client/C) +/mob/living/simple_mob/animal/borer/proc/question(var/client/C) spawn(0) if(!C) return var/response = alert(C, "A cortical borer needs a player. Are you interested?", "Cortical borer request", "Yes", "No", "Never for this round") @@ -191,7 +191,7 @@ else if (response == "Never for this round") C.prefs.be_special ^= BE_ALIEN -/mob/living/simple_animal/borer/proc/transfer_personality(var/client/candidate) +/mob/living/simple_mob/animal/borer/proc/transfer_personality(var/client/candidate) if(!candidate || !candidate.mob || !candidate.mob.mind) return @@ -209,5 +209,5 @@ your host and your eventual spawn safe and warm." src << "You can speak to your victim with say, to other borers with say :x, and use your Abilities tab to access powers." -/mob/living/simple_animal/borer/cannot_use_vents() +/mob/living/simple_mob/animal/borer/cannot_use_vents() return \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/borer/borer_captive.dm b/code/modules/mob/living/simple_animal/borer/borer_captive.dm index c34dca00e8..2e5af8be9a 100644 --- a/code/modules/mob/living/simple_animal/borer/borer_captive.dm +++ b/code/modules/mob/living/simple_animal/borer/borer_captive.dm @@ -10,7 +10,7 @@ src << "You cannot speak in IC (muted)." return - if(istype(src.loc,/mob/living/simple_animal/borer)) + if(istype(src.loc,/mob/living/simple_mob/animal/borer)) message = sanitize(message) if (!message) @@ -19,7 +19,7 @@ if (stat == 2) return say_dead(message) - var/mob/living/simple_animal/borer/B = src.loc + var/mob/living/simple_mob/animal/borer/B = src.loc src << "You whisper silently, \"[message]\"" B.host << "The captive mind of [src] whispers, \"[message]\"" @@ -34,8 +34,8 @@ /mob/living/captive_brain/process_resist() //Resisting control by an alien mind. - if(istype(src.loc,/mob/living/simple_animal/borer)) - var/mob/living/simple_animal/borer/B = src.loc + if(istype(src.loc,/mob/living/simple_mob/animal/borer)) + var/mob/living/simple_mob/animal/borer/B = src.loc var/mob/living/captive_brain/H = src H << "You begin doggedly resisting the parasite's control (this will take approximately sixty seconds)." diff --git a/code/modules/mob/living/simple_animal/borer/borer_powers.dm b/code/modules/mob/living/simple_animal/borer/borer_powers.dm index b75da20cc4..c12736a307 100644 --- a/code/modules/mob/living/simple_animal/borer/borer_powers.dm +++ b/code/modules/mob/living/simple_animal/borer/borer_powers.dm @@ -1,4 +1,4 @@ -/mob/living/simple_animal/borer/verb/release_host() +/mob/living/simple_mob/animal/borer/verb/release_host() set category = "Abilities" set name = "Release Host" set desc = "Slither out of your host." @@ -38,7 +38,7 @@ detatch() leave_host() -/mob/living/simple_animal/borer/verb/infest() +/mob/living/simple_mob/animal/borer/verb/infest() set category = "Abilities" set name = "Infest" set desc = "Infest a suitable humanoid host." @@ -126,7 +126,7 @@ return /* -/mob/living/simple_animal/borer/verb/devour_brain() +/mob/living/simple_mob/animal/borer/verb/devour_brain() set category = "Abilities" set name = "Devour Brain" set desc = "Take permanent control of a dead host." @@ -152,7 +152,7 @@ */ // BRAIN WORM ZOMBIES AAAAH. -/mob/living/simple_animal/borer/proc/replace_brain() +/mob/living/simple_mob/animal/borer/proc/replace_brain() var/mob/living/carbon/human/H = host @@ -198,7 +198,7 @@ if(!H.lastKnownIP) H.lastKnownIP = s2h_ip -/mob/living/simple_animal/borer/verb/secrete_chemicals() +/mob/living/simple_mob/animal/borer/verb/secrete_chemicals() set category = "Abilities" set name = "Secrete Chemicals" set desc = "Push some chemicals into your host's bloodstream." @@ -226,7 +226,7 @@ host.reagents.add_reagent(chem, 10) chemicals -= 50 -/mob/living/simple_animal/borer/verb/dominate_victim() +/mob/living/simple_mob/animal/borer/verb/dominate_victim() set category = "Abilities" set name = "Paralyze Victim" set desc = "Freeze the limbs of a potential host with supernatural fear." @@ -266,7 +266,7 @@ used_dominate = world.time -/mob/living/simple_animal/borer/verb/bond_brain() +/mob/living/simple_mob/animal/borer/verb/bond_brain() set category = "Abilities" set name = "Assume Control" set desc = "Fully connect to the brain of your host." @@ -335,20 +335,3 @@ host.verbs += /mob/living/carbon/proc/spawn_larvae return - -/mob/living/carbon/human/proc/jumpstart() - set category = "Abilities" - set name = "Revive Host" - set desc = "Send a jolt of electricity through your host, reviving them." - - if(stat != 2) - usr << "Your host is already alive." - return - - verbs -= /mob/living/carbon/human/proc/jumpstart - visible_message("With a hideous, rattling moan, [src] shudders back to life!") - - rejuvenate() - restore_blood() - fixblood() - update_canmove() \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/borer/say.dm b/code/modules/mob/living/simple_animal/borer/say.dm index 440dfa7ff7..f4c12a7458 100644 --- a/code/modules/mob/living/simple_animal/borer/say.dm +++ b/code/modules/mob/living/simple_animal/borer/say.dm @@ -1,4 +1,4 @@ -/mob/living/simple_animal/borer/say(var/message) +/mob/living/simple_mob/animal/borer/say(var/message) message = sanitize(message) message = capitalize(message) diff --git a/code/modules/mob/living/simple_animal/constructs/constructs.dm b/code/modules/mob/living/simple_animal/constructs/constructs.dm deleted file mode 100644 index 26ab3a1260..0000000000 --- a/code/modules/mob/living/simple_animal/constructs/constructs.dm +++ /dev/null @@ -1,488 +0,0 @@ -/mob/living/simple_animal/construct - name = "Construct" - real_name = "Construct" - var/construct_type = "shade" - desc = "" - speak_emote = list("hisses") - emote_hear = list("wails","screeches") - - ui_icons = 'icons/mob/screen1_construct.dmi' - has_hands = 1 - hand_form = "stone manipulators" - - response_help = "thinks better of touching" - response_disarm = "flailed at" - response_harm = "punched" - - intelligence_level = SA_HUMANOID // Player controlled. - - hovering = TRUE - softfall = TRUE //Beings made of Hellmarble and powered by the tears of the damned are not concerned with mortal things such as 'gravity'. - parachuting = TRUE - - icon_dead = "shade_dead" - var/do_glow = 1 - - speed = -1 - a_intent = I_HURT - stop_automated_movement = 1 - - status_flags = CANPUSH - - universal_speak = 0 - universal_understand = 1 - - attack_sound = 'sound/weapons/spiderlunge.ogg' - - min_oxy = 0 - max_oxy = 0 - min_tox = 0 - max_tox = 0 - min_co2 = 0 - max_co2 = 0 - min_n2 = 0 - max_n2 = 0 - - minbodytemp = 0 - show_stat_health = 1 - - faction = "cult" - supernatural = 1 - - see_invisible = SEE_INVISIBLE_NOLIGHTING - see_in_dark = 7 - - var/nullblock = 0 - - mob_swap_flags = HUMAN|SIMPLE_ANIMAL|SLIME|MONKEY - mob_push_flags = ALLMOBS - - var/list/construct_spells = list() - - can_be_antagged = TRUE - - taser_kill = 0 // no - - shock_resistance = 0.9 //Electricity isn't very effective on stone, especially that from hell. - - armor = list( - "melee" = 10, - "bullet" = 10, - "laser" = 10, - "energy" = 10, - "bomb" = 10, - "bio" = 100, - "rad" = 100) - -/mob/living/simple_animal/construct/place_spell_in_hand(var/path) - if(!path || !ispath(path)) - return 0 - - //var/obj/item/weapon/spell/S = new path(src) - var/obj/item/weapon/spell/construct/S = new path(src) - - //No hands needed for innate casts. - if(S.cast_methods & CAST_INNATE) - if(S.run_checks()) - S.on_innate_cast(src) - - if(l_hand && r_hand) //Make sure our hands aren't full. - if(istype(r_hand, /obj/item/weapon/spell)) //If they are full, perhaps we can still be useful. - var/obj/item/weapon/spell/r_spell = r_hand - if(r_spell.aspect == ASPECT_CHROMATIC) //Check if we can combine the new spell with one in our hands. - r_spell.on_combine_cast(S, src) - else if(istype(l_hand, /obj/item/weapon/spell)) - var/obj/item/weapon/spell/l_spell = l_hand - if(l_spell.aspect == ASPECT_CHROMATIC) //Check the other hand too. - l_spell.on_combine_cast(S, src) - else //Welp - to_chat(src, "You require a free manipulator to use this power.") - return 0 - - if(S.run_checks()) - put_in_hands(S) - return 1 - else - qdel(S) - return 0 - -/mob/living/simple_animal/construct/cultify() - return - -/mob/living/simple_animal/construct/New() - ..() - name = text("[initial(name)] ([rand(1, 1000)])") - real_name = name - add_language("Cult") - add_language("Occult") - for(var/spell in construct_spells) - src.add_spell(new spell, "const_spell_ready") - updateicon() - -/mob/living/simple_animal/construct/updateicon() - overlays.Cut() - ..() - if(do_glow) - add_glow() - -/mob/living/simple_animal/construct/update_icon() - ..() - if(do_glow) - add_glow() - -/mob/living/simple_animal/construct/death() - new /obj/item/weapon/ectoplasm (src.loc) - ..(null,"collapses in a shattered heap.") - ghostize() - qdel(src) - -/mob/living/simple_animal/construct/attack_generic(var/mob/user) - if(istype(user, /mob/living/simple_animal/construct/builder)) - var/mob/living/simple_animal/construct/builder/B = user - if(health < getMaxHealth()) - var/repair_lower_bound = B.melee_damage_lower * -1 - var/repair_upper_bound = B.melee_damage_upper * -1 - adjustBruteLoss(rand(repair_lower_bound, repair_upper_bound)) - adjustFireLoss(rand(repair_lower_bound, repair_upper_bound)) - user.visible_message("\The [user] mends some of \the [src]'s wounds.") - else - to_chat(user, "\The [src] is undamaged.") - return - return ..() - -/mob/living/simple_animal/construct/examine(mob/user) - ..(user) - var/msg = "*---------*\nThis is \icon[src] \a [src]!\n" - if (src.health < src.getMaxHealth()) - msg += "" - if (src.health >= src.getMaxHealth()/2) - msg += "It looks slightly dented.\n" - else - msg += "It looks severely dented!\n" - msg += "" - msg += "*---------*" - - user << msg - -/mob/living/simple_animal/construct/Process_Spacemove() - return 1 //Constructs levitate, can fall from a shuttle with no harm, and are piloted by either damned spirits or some otherworldly entity. It's not hard to believe. - -/////////////////Juggernaut/////////////// - - - -/mob/living/simple_animal/construct/armoured - name = "Juggernaut" - real_name = "Juggernaut" - construct_type = "juggernaut" - desc = "A possessed suit of armour driven by the will of the restless dead" - icon = 'icons/mob/mob.dmi' - icon_state = "behemoth" - icon_living = "behemoth" - maxHealth = 300 - health = 300 - response_harm = "harmlessly punches" - harm_intent_damage = 0 - melee_damage_lower = 30 - melee_damage_upper = 40 - attack_armor_pen = 60 //Being punched by a living, floating statue. - attacktext = list("smashed their armoured gauntlet into") - friendly = list("pats") - mob_size = MOB_HUGE - speed = 2 //Not super fast, but it might catch up to someone in armor who got punched once or twice. - environment_smash = 2 - attack_sound = 'sound/weapons/heavysmash.ogg' - status_flags = 0 - resistance = 10 - construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser, - /spell/targeted/fortify, - /spell/targeted/construct_advanced/slam - ) - - armor = list( - "melee" = 70, - "bullet" = 30, - "laser" = 30, - "energy" = 30, - "bomb" = 10, - "bio" = 100, - "rad" = 100) - -/mob/living/simple_animal/construct/armoured/Life() - weakened = 0 - ..() - -/mob/living/simple_animal/construct/armoured/bullet_act(var/obj/item/projectile/P) -// if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) //If it's going to be slow, it's probably going to need every reflect it can get. - var/reflectchance = 80 - round(P.damage/3) - if(prob(reflectchance)) - var/damage_mod = rand(2,4) - var/projectile_dam_type = P.damage_type - var/incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3))) - var/armorcheck = run_armor_check(null, P.check_armour) - var/soakedcheck = get_armor_soak(null, P.check_armour) - if(!(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam))) - visible_message("The [P.name] bounces off of [src]'s shell!", \ - "The [P.name] bounces off of [src]'s shell!") - new /obj/item/weapon/material/shard/shrapnel(src.loc) - if(!(P.damage_type == BRUTE || P.damage_type == BURN)) - projectile_dam_type = BRUTE - incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to brute for physical projectiles, though severely decreased. - apply_damage(incoming_damage, projectile_dam_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P) - return -1 //Doesn't reflect non-beams or non-energy projectiles. They just smack and drop with little to no effect. - else - visible_message("The [P.name] gets reflected by [src]'s shell!", \ - "The [P.name] gets reflected by [src]'s shell!") - damage_mod = rand(3,5) - incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3))) - if(!(P.damage_type == BRUTE || P.damage_type == BURN)) - projectile_dam_type = BURN - incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to burn for energy-type projectiles, though severely decreased. - apply_damage(incoming_damage, P.damage_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P) - - // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/turf/curloc = get_turf(src) - - // redirect the projectile - P.redirect(new_x, new_y, curloc, src) - P.reflected = 1 - - return -1 // complete projectile permutation - - return (..(P)) - - - -////////////////////////Wraith///////////////////////////////////////////// - - - -/mob/living/simple_animal/construct/wraith - name = "Wraith" - real_name = "Wraith" - construct_type = "wraith" - desc = "A wicked bladed shell contraption piloted by a bound spirit." - icon = 'icons/mob/mob.dmi' - icon_state = "floating" - icon_living = "floating" - maxHealth = 200 - health = 200 - melee_damage_lower = 25 - melee_damage_upper = 30 - attack_armor_pen = 15 - attack_sharp = 1 - attack_edge = 1 - attacktext = list("slashed") - friendly = list("pinches") - speed = -1 - environment_smash = 1 - attack_sound = 'sound/weapons/rapidslice.ogg' - construct_spells = list(/spell/targeted/ethereal_jaunt/shift, - /spell/targeted/ambush_mode - ) - -/mob/living/simple_animal/construct/wraith/DoPunch(var/atom/A) - . = ..() - if(. && isliving(A)) - var/mob/living/L = A - L.add_modifier(/datum/modifier/deep_wounds, 30 SECONDS) - -/////////////////////////////Artificer///////////////////////// - - - -/mob/living/simple_animal/construct/builder - name = "Artificer" - real_name = "Artificer" - construct_type = "artificer" - desc = "A bulbous construct dedicated to building and maintaining temples to their otherworldly lords." - icon = 'icons/mob/mob.dmi' - icon_state = "artificer" - icon_living = "artificer" - maxHealth = 150 - health = 150 - response_harm = "viciously beaten" - harm_intent_damage = 5 - melee_damage_lower = 15 //It's not the strongest of the bunch, but that doesn't mean it can't hurt you. - melee_damage_upper = 20 - attacktext = list("rammed") - speed = 0 - environment_smash = 2 - attack_sound = 'sound/weapons/rapidslice.ogg' - construct_spells = list(/spell/aoe_turf/conjure/construct/lesser, - /spell/aoe_turf/conjure/wall, - /spell/aoe_turf/conjure/floor, - /spell/aoe_turf/conjure/soulstone, - /spell/aoe_turf/conjure/pylon, - /spell/aoe_turf/conjure/door, - /spell/aoe_turf/conjure/grille, - /spell/targeted/occult_repair_aura, - /spell/targeted/construct_advanced/mend_acolyte - ) - - -/////////////////////////////Behemoth///////////////////////// -/* - * The Behemoth. Admin-allowance only, still try to keep it in some guideline of 'Balanced', even if it means Security has to be fully geared to be so. - */ - -/mob/living/simple_animal/construct/behemoth - name = "Behemoth" - real_name = "Behemoth" - construct_type = "juggernaut" - desc = "The pinnacle of occult technology, Behemoths are nothing shy of both an Immovable Object, and Unstoppable Force." - icon = 'icons/mob/mob.dmi' - icon_state = "behemoth" - icon_living = "behemoth" - maxHealth = 750 - health = 750 - speak_emote = list("rumbles") - response_harm = "harmlessly punched" - harm_intent_damage = 0 - melee_damage_lower = 50 - melee_damage_upper = 50 - attacktext = list("brutally crushed") - friendly = list("pokes") //Anything nice the Behemoth would do would still Kill the Human. Leave it at poke. - speed = 5 - environment_smash = 2 - attack_sound = 'sound/weapons/heavysmash.ogg' - resistance = 10 - icon_scale = 2 - var/energy = 0 - var/max_energy = 1000 - armor = list( - "melee" = 60, - "bullet" = 60, - "laser" = 60, - "energy" = 30, - "bomb" = 10, - "bio" = 100, - "rad" = 100) - construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser, - /spell/targeted/fortify, - /spell/targeted/construct_advanced/slam - ) - -/mob/living/simple_animal/construct/behemoth/bullet_act(var/obj/item/projectile/P) - var/reflectchance = 80 - round(P.damage/3) - if(prob(reflectchance)) - visible_message("The [P.name] gets reflected by [src]'s shell!", \ - "The [P.name] gets reflected by [src]'s shell!") - - // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/turf/curloc = get_turf(src) - - // redirect the projectile - P.redirect(new_x, new_y, curloc, src) - P.reflected = 1 - - return -1 // complete projectile permutation - - return (..(P)) - -////////////////////////Harvester//////////////////////////////// -/* - * Master of Spells and Ranged Abilities. Not as fragile as the Wraith, but nowhere near as maneuverable and deadly in melee. - */ - -/mob/living/simple_animal/construct/harvester - name = "Harvester" - real_name = "Harvester" - construct_type = "harvester" - desc = "A tendril-laden construct piloted by a chained mind." - icon = 'icons/mob/mob.dmi' - icon_state = "harvester" - icon_living = "harvester" - maxHealth = 150 - health = 150 - melee_damage_lower = 20 - melee_damage_upper = 25 - attack_sharp = 1 - attacktext = list("violently stabbed") - friendly = list("caresses") - speed = 0 - environment_smash = 1 - attack_sound = 'sound/weapons/pierce.ogg' - - armor = list( - "melee" = 10, - "bullet" = 20, - "laser" = 20, - "energy" = 20, - "bomb" = 20, - "bio" = 100, - "rad" = 100) - - construct_spells = list( - /spell/aoe_turf/knock/harvester, - /spell/targeted/construct_advanced/inversion_beam, - /spell/targeted/construct_advanced/agonizing_sphere, - /spell/rune_write - ) - -////////////////Glow////////////////// -/mob/living/simple_animal/construct/proc/add_glow() - var/image/eye_glow = image(icon,"glow-[icon_state]") - eye_glow.plane = PLANE_LIGHTING_ABOVE - overlays += eye_glow - set_light(2, -2, l_color = "#FFFFFF") - -/mob/living/simple_animal/construct/proc/remove_glow() - overlays.Cut() - -////////////////HUD////////////////////// - -/mob/living/simple_animal/construct/Life() - . = ..() - if(.) - if(fire) - if(fire_alert) fire.icon_state = "fire1" - else fire.icon_state = "fire0" - if(pullin) - if(pulling) pullin.icon_state = "pull1" - else pullin.icon_state = "pull0" - - if(purged) - if(purge > 0) purged.icon_state = "purge1" - else purged.icon_state = "purge0" - - silence_spells(purge) - -/mob/living/simple_animal/construct/updatehealth() //Special icons. - health = getMaxHealth() - getToxLoss() - getFireLoss() - getBruteLoss() - - //Alive, becoming dead - if((stat < DEAD) && (health <= 0)) - death() - - //Overhealth - if(health > getMaxHealth()) - health = getMaxHealth() - - //Update our hud if we have one - if(healths) - if(stat != DEAD) - var/heal_per = (health / getMaxHealth()) * 100 - switch(heal_per) - if(100 to INFINITY) - healths.icon_state = "[construct_type]_health0" - if(80 to 100) - healths.icon_state = "[construct_type]_health1" - if(60 to 80) - healths.icon_state = "[construct_type]_health2" - if(40 to 60) - healths.icon_state = "[construct_type]_health3" - if(20 to 40) - healths.icon_state = "[construct_type]_health4" - if(0 to 20) - healths.icon_state = "[construct_type]_health5" - else - healths.icon_state = "[construct_type]_health6" - else - healths.icon_state = "[construct_type]_health7" \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/humanoids/head.dm b/code/modules/mob/living/simple_animal/humanoids/head.dm deleted file mode 100644 index 22460f0990..0000000000 --- a/code/modules/mob/living/simple_animal/humanoids/head.dm +++ /dev/null @@ -1,61 +0,0 @@ -//Look Sir, free head! -/mob/living/simple_animal/head - name = "CommandBattle AI" - desc = "A standard borg shell on its chest crude marking saying CommandBattle AI MK4 : Head." - icon_state = "crab" - icon_living = "crab" - icon_dead = "crab_dead" - intelligence_level = SA_ANIMAL - - wander = 0 - stop_automated_movement = 1 - universal_speak = 1 - turns_per_move = 5 - - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "punches" - - speak_chance = 1 - speak_emote = list("clicks") - emote_hear = list("clicks") - emote_see = list("clacks") - - meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat - - var/list/insults = list( - "Man you suck", - "You look like the most retarded douche around", - "What's up?, oh wait nevermind you are a fucking asshat", - "you are just overly retarded", - "Whiteman said what?!",) - var/list/comments = list("Man have you seen those furry cats?,I mean who in the right mind would like something like that?", - "They call me abusive,I just like the truth", - "Beeboop, im a robit", - "Gooogooooll, break ya bones", - "Crab say what?", - "Man they say we have space lizards now, man this shit is getting more wack every minute", - "The so called \"improved\" station AI is just bullshit, that thing aint fun for noone", - "The Colony Director is a traitor, he took my power core.", - "Say \"what\" again. Say \"what\" again. I dare you. I double-dare you, motherfucker. Say \"what\" one more goddamn time.", - "Ezekiel 25:17 ,The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who in the name of charity and good will shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who attempt to poison and destroy my brothers. And you will know my name is the Lord... when I lay my vengeance upon thee.", - "Did you notice a sign out in front of my house that said \"Dead Nigger Storage\"?") - -/mob/living/simple_animal/head/Life() - . = ..() - if(!. || ai_inactive) return - - for(var/mob/A in viewers(world.view,src)) - if(A.ckey) - say_something(A) - -/mob/living/simple_animal/head/proc/say_something(mob/A) - if(prob(85)) - return - if(prob(30)) - var/msg = pick(insults) - msg = "Hey, [A.name].. [msg]" - src.say(msg) - else - var/msg = pick(comments) - src.say(msg) diff --git a/code/modules/mob/living/simple_animal/humanoids/mechamobs.dm b/code/modules/mob/living/simple_animal/humanoids/mechamobs.dm index 9f8c737894..1bc4b985a4 100644 --- a/code/modules/mob/living/simple_animal/humanoids/mechamobs.dm +++ b/code/modules/mob/living/simple_animal/humanoids/mechamobs.dm @@ -96,7 +96,7 @@ ..() playsound(src,'sound/mecha/mechstep.ogg',40,1) - +// This is a PoI mob, not the normal, floaty drones that hang out around windows /mob/living/simple_animal/hostile/mecha/malf_drone name = "autonomous mechanized drone" desc = "It appears to be an exosuit, piloted by a drone intelligence. It looks scary." diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 71a523c7d0..304fd3e7b2 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -591,12 +591,6 @@ if(act) ..(act, type, desc) -/mob/living/simple_animal/proc/visible_emote(var/act_desc) - custom_emote(1, act_desc) - -/mob/living/simple_animal/proc/audible_emote(var/act_desc) - custom_emote(2, act_desc) - /mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) ai_log("bullet_act() I was shot by: [Proj.firer]",2) @@ -710,9 +704,9 @@ //SA vs SA basically /mob/living/simple_animal/attack_generic(var/mob/attacker) - ..() if(attacker) react_to_attack(attacker) + return ..() /mob/living/simple_animal/movement_delay() var/tally = 0 //Incase I need to add stuff other than "speed" later @@ -1280,8 +1274,11 @@ /mob/living/simple_animal/proc/PunchTarget() if(!Adjacent(target_mob)) return - if(!client) - sleep(rand(melee_attack_minDelay, melee_attack_maxDelay)) + if(!canClick()) + return + setClickCooldown(get_attack_speed()) +// if(!client) +// sleep(rand(melee_attack_minDelay, melee_attack_maxDelay)) if(isliving(target_mob)) var/mob/living/L = target_mob @@ -1322,13 +1319,18 @@ //The actual top-level ranged attack proc /mob/living/simple_animal/proc/ShootTarget() + if(!canClick()) + return FALSE + + setClickCooldown(get_attack_speed()) + var/target = target_mob var/tturf = get_turf(target) if((firing_lines && !client) && !CheckFiringLine(tturf)) step_rand(src) face_atom(tturf) - return 0 + return FALSE visible_message("[src] fires at [target]!") if(rapid) @@ -1349,7 +1351,7 @@ if(casingtype) new casingtype - return 1 + return TRUE //Check firing lines for faction_friends (if we're not cooperative, we don't care) /mob/living/simple_animal/proc/CheckFiringLine(var/turf/tturf) @@ -1582,13 +1584,13 @@ return 0 else return armorval - +/* // Force it to target something /mob/living/simple_animal/proc/taunt(var/mob/living/new_target, var/forced = FALSE) if(intelligence_level == SA_HUMANOID && !forced) return set_target(new_target) - +*/ /mob/living/simple_animal/is_sentient() return intelligence_level != SA_PLANT && intelligence_level != SA_ROBOTIC diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index 843990c7f6..2b5d44ded9 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -17,8 +17,8 @@ maxHealth = 150 var/maxHealth_adult = 200 - melee_damage_lower = 5 - melee_damage_upper = 25 + melee_damage_lower = 10 + melee_damage_upper = 15 melee_miss_chance = 0 gender = NEUTER diff --git a/code/modules/mob/living/simple_mob/appearance.dm b/code/modules/mob/living/simple_mob/appearance.dm new file mode 100644 index 0000000000..05f758d7c4 --- /dev/null +++ b/code/modules/mob/living/simple_mob/appearance.dm @@ -0,0 +1,76 @@ +/mob/living/simple_mob/update_icon() + . = ..() + cut_overlays() +// var/mutable_appearance/ma = new(src) +// ma.layer = layer +// ma.plane = plane + + add_overlay(modifier_overlay) + + if(!icon_living) // Prevent the mob from turning invisible if icon_living is null. + icon_living = initial(icon_state) + + //Awake and normal + if((stat == CONSCIOUS) && (!icon_rest || !resting || !incapacitated(INCAPACITATION_DISABLED) )) + icon_state = icon_living + + //Dead + else if(stat >= DEAD) + icon_state = icon_dead + + //Resting or KO'd + else if(((stat == UNCONSCIOUS) || resting || incapacitated(INCAPACITATION_DISABLED) ) && icon_rest) + icon_state = icon_rest + + //Backup + else + icon_state = initial(icon_state) + + if(has_hands) + if(r_hand_sprite) + add_overlay(r_hand_sprite) + if(l_hand_sprite) + add_overlay(l_hand_sprite) + + if(has_eye_glow) + if(icon_state != icon_living) + remove_eyes() + else + add_eyes() + +// appearance = ma + + +// If your simple mob's update_icon() call calls overlays.Cut(), this needs to be called after this, or manually apply modifier_overly to overlays. +/mob/living/simple_mob/update_modifier_visuals() + var/image/effects = null + if(modifier_overlay) + cut_overlay(modifier_overlay) + modifier_overlay.cut_overlays() + effects = modifier_overlay + else + effects = new() + + for(var/datum/modifier/M in modifiers) + if(M.mob_overlay_state) + var/image/I = image("icon" = 'icons/mob/modifier_effects.dmi', "icon_state" = M.mob_overlay_state) + I.appearance_flags = RESET_COLOR // So colored mobs don't affect the overlay. + effects.add_overlay(I) + + modifier_overlay = effects + add_overlay(modifier_overlay) + + +/mob/living/simple_mob/proc/add_eyes() + if(!eye_layer) + eye_layer = image(icon, "[icon_state]-eyes") + eye_layer.plane = PLANE_LIGHTING_ABOVE + + add_overlay(eye_layer) + +/mob/living/simple_mob/proc/remove_eyes() + cut_overlay(eye_layer) + + +/mob/living/simple_mob/gib() + ..(icon_gib,1,icon) // we need to specify where the gib animation is stored \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/combat.dm b/code/modules/mob/living/simple_mob/combat.dm new file mode 100644 index 0000000000..e0cf980cc3 --- /dev/null +++ b/code/modules/mob/living/simple_mob/combat.dm @@ -0,0 +1,236 @@ +// Does a melee attack. +/mob/living/simple_mob/proc/attack_target(atom/A) + set waitfor = FALSE // For attack animations. Don't want the AI processor to get held up. + + if(!A.Adjacent(src)) + return FALSE + var/turf/their_T = get_turf(A) + + face_atom(A) + + if(melee_attack_delay) + // their_T.color = "#FF0000" + melee_pre_animation(A) + handle_attack_delay(A, melee_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false. + + // Cooldown testing is done at click code (for players) and interface code (for AI). + setClickCooldown(get_attack_speed()) + + . = do_attack(A, their_T) + + if(melee_attack_delay) + melee_post_animation(A) + // their_T.color = "#FFFFFF" + + + +// This does the actual attack. +// This is a seperate proc for the purposes of attack animations. +// A is the thing getting attacked, T is the turf A is/was on when attack_target was called. +/mob/living/simple_mob/proc/do_attack(atom/A, turf/T) + face_atom(A) + var/missed = FALSE + if(!isturf(A) && !(A in T) ) // Turfs don't contain themselves so checking contents is pointless if we're targeting a turf. + missed = TRUE + else if(!T.AdjacentQuick(src)) + missed = TRUE + + if(missed) // Most likely we have a slow attack and they dodged it or we somehow got moved. + add_attack_logs(src, A, "Animal-attacked (dodged)", admin_notify = FALSE) + playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1) + visible_message(span("warning", "\The [src] misses their attack.")) + return FALSE + + var/damage_to_do = rand(melee_damage_lower, melee_damage_upper) + + damage_to_do = apply_bonus_melee_damage(A, damage_to_do) + + for(var/datum/modifier/M in modifiers) + if(!isnull(M.outgoing_melee_damage_percent)) + damage_to_do *= M.outgoing_melee_damage_percent + + if(isliving(A)) // Check defenses. + var/mob/living/L = A + + if(prob(melee_miss_chance)) + add_attack_logs(src, L, "Animal-attacked (miss)", admin_notify = FALSE) + do_attack_animation(src) + playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1) + return FALSE // We missed. + + if(ishuman(L)) + var/mob/living/carbon/human/H = L + if(H.check_shields(damage = damage_to_do, damage_source = src, attacker = src, def_zone = null, attack_text = "the attack")) + return FALSE // We were blocked. + + if(apply_attack(A, damage_to_do)) + apply_melee_effects(A) + if(attack_sound) + playsound(src, attack_sound, 75, 1) + + return TRUE + +// Generally used to do the regular attack. +// Override for doing special stuff with the direct result of the attack. +/mob/living/simple_mob/proc/apply_attack(atom/A, damage_to_do) + return A.attack_generic(src, damage_to_do, pick(attacktext)) + +// Override for special effects after a successful attack, like injecting poison or stunning the target. +/mob/living/simple_mob/proc/apply_melee_effects(atom/A) + return + +// Override to modify the amount of damage the mob does conditionally. +// This must return the amount of outgoing damage. +// Note that this is done before mob modifiers scale the damage. +/mob/living/simple_mob/proc/apply_bonus_melee_damage(atom/A, damage_amount) + return damage_amount + +//The actual top-level ranged attack proc +/mob/living/simple_mob/proc/shoot_target(atom/A) + set waitfor = FALSE + setClickCooldown(get_attack_speed()) + + face_atom(A) + + if(ranged_attack_delay) + ranged_pre_animation(A) + handle_attack_delay(A, ranged_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false. + + if(needs_reload) + if(reload_count >= reload_max) + try_reload() + return FALSE + + visible_message("\The [src] fires at \the [A]!") + shoot(A, src.loc, src) + if(casingtype) + new casingtype(loc) + + if(ranged_attack_delay) + ranged_post_animation(A) + + return TRUE + + +//Shoot a bullet at someone (idk why user is an argument when src would fit???) +/mob/living/simple_mob/proc/shoot(atom/A, turf/start, mob/living/user, bullet = 0) + if(A == start) + return + + face_atom(A) + + var/obj/item/projectile/P = new projectiletype(user.loc) + if(!P) + return + + // If the projectile has its own sound, use it. + // Otherwise default to the mob's firing sound. + playsound(user, P.fire_sound ? P.fire_sound : projectilesound, 80, 1) + + P.launch(A) + if(needs_reload) + reload_count++ + +// if(distance >= special_attack_min_range && distance <= special_attack_max_range) +// return TRUE + +/mob/living/simple_mob/proc/try_reload() + set waitfor = FALSE + set_AI_busy(TRUE) + + if(do_after(src, reload_time)) + if(reload_sound) + playsound(src.loc, reload_sound, 50, 1) + reload_count = 0 + . = TRUE + else + . = FALSE + set_AI_busy(FALSE) + +// Can we currently do a special attack? +/mob/living/simple_mob/proc/can_special_attack(atom/A) + // Validity check. + if(!istype(A)) + return FALSE + + // Ability check. + if(isnull(special_attack_min_range) || isnull(special_attack_max_range)) + return FALSE + + // Distance check. + var/distance = get_dist(src, A) + if(distance < special_attack_min_range || distance > special_attack_max_range) + return FALSE + + // Cooldown check. + if(!isnull(special_attack_cooldown) && last_special_attack + special_attack_cooldown > world.time) + return FALSE + + // Charge check. + if(!isnull(special_attack_charges) && special_attack_charges <= 0) + return FALSE + + return TRUE + +// Should we do one? Used to make the AI not waste their special attacks. Only checked for AI. Players are free to screw up on their own. +/mob/living/simple_mob/proc/should_special_attack(atom/A) + return TRUE + +// Special attacks, like grenades or blinding spit or whatever. +// Don't override this, override do_special_attack() for your blinding spit/etc. +/mob/living/simple_mob/proc/special_attack_target(atom/A) + face_atom(A) + + if(special_attack_delay) + special_pre_animation(A) + handle_attack_delay(A, special_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false. + + last_special_attack = world.time + if(do_special_attack(A)) + if(special_attack_charges) + special_attack_charges -= 1 + . = TRUE + else + . = FALSE + + if(special_attack_delay) + special_post_animation(A) + +// Override this for the actual special attack. +/mob/living/simple_mob/proc/do_special_attack(atom/A) + return FALSE + +// Sleeps the proc that called it for the correct amount of time. +// Also makes sure the AI doesn't do anything stupid in the middle of the delay. +/mob/living/simple_mob/proc/handle_attack_delay(atom/A, delay_amount) + set_AI_busy(TRUE) + + // Click delay modifiers also affect telegraphing time. + // This means berserked enemies will leave less time to dodge. + var/true_attack_delay = delay_amount + for(var/datum/modifier/M in modifiers) + if(!isnull(M.attack_speed_percent)) + true_attack_delay *= M.attack_speed_percent + + setClickCooldown(true_attack_delay) // Insurance against a really long attack being longer than default click delay. + + sleep(true_attack_delay) + + set_AI_busy(FALSE) + + +// Override these four for special custom animations (like the GOLEM). +/mob/living/simple_mob/proc/melee_pre_animation(atom/A) + do_windup_animation(A, melee_attack_delay) + +/mob/living/simple_mob/proc/melee_post_animation(atom/A) + +/mob/living/simple_mob/proc/ranged_pre_animation(atom/A) + do_windup_animation(A, ranged_attack_delay) // Semi-placeholder. + +/mob/living/simple_mob/proc/ranged_post_animation(atom/A) + +/mob/living/simple_mob/proc/special_pre_animation(atom/A) + do_windup_animation(A, special_attack_delay) // Semi-placeholder. + +/mob/living/simple_mob/proc/special_post_animation(atom/A) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm new file mode 100644 index 0000000000..af1b2774b3 --- /dev/null +++ b/code/modules/mob/living/simple_mob/defense.dm @@ -0,0 +1,224 @@ +// Hit by a projectile. +/mob/living/simple_mob/bullet_act(var/obj/item/projectile/P) + //Projectiles with bonus SA damage + if(!P.nodamage) + // if(!P.SA_vulnerability || P.SA_vulnerability == intelligence_level) + if(P.SA_vulnerability & mob_class) + P.damage += P.SA_bonus_damage + + . = ..() + + +// When someone clicks us with an empty hand +/mob/living/simple_mob/attack_hand(mob/living/L) + ..() + + switch(L.a_intent) + if(I_HELP) + if(health > 0) + L.visible_message("\The [L] [response_help] \the [src].") + + if(I_DISARM) + L.visible_message("\The [L] [response_disarm] \the [src].") + L.do_attack_animation(src) + //TODO: Push the mob away or something + + if(I_GRAB) + if (L == src) + return + if (!(status_flags & CANPUSH)) + return + if(!incapacitated(INCAPACITATION_ALL) && prob(grab_resist)) + L.visible_message("\The [L] tries to grab \the [src] but fails!") + return + + var/obj/item/weapon/grab/G = new /obj/item/weapon/grab(L, src) + + L.put_in_active_hand(G) + + G.synch() + G.affecting = src + LAssailant = L + + L.visible_message("\The [L] has grabbed [src] passively!") + L.do_attack_animation(src) + + if(I_HURT) + var/armor = run_armor_check(def_zone = null, attack_flag = "melee") + apply_damage(damage = harm_intent_damage, damagetype = BURN, def_zone = null, blocked = armor, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE) + L.visible_message("\The [L] [response_harm] \the [src]!") + L.do_attack_animation(src) + + return + + +// When somoene clicks us with an item in hand +/mob/living/simple_mob/attackby(var/obj/item/O, var/mob/user) + if(istype(O, /obj/item/stack/medical)) + if(stat != DEAD) + // This could be done better. + var/obj/item/stack/medical/MED = O + if(health < getMaxHealth()) + if(MED.amount >= 1) + adjustBruteLoss(-MED.heal_brute) + MED.amount -= 1 + if(MED.amount <= 0) + qdel(MED) + visible_message("\The [user] applies the [MED] on [src].") + else + var/datum/gender/T = gender_datums[src.get_visible_gender()] + to_chat(user, "\The [src] is dead, medical items won't bring [T.him] back to life.") // the gender lookup is somewhat overkill, but it functions identically to the obsolete gender macros and future-proofs this code + if(meat_type && (stat == DEAD)) //if the animal has a meat, and if it is dead. + if(istype(O, /obj/item/weapon/material/knife)) + harvest(user) + + return ..() + + +// Handles the actual harming by a melee weapon. +/mob/living/simple_mob/hit_with_weapon(obj/item/O, mob/living/user, var/effective_force, var/hit_zone) + effective_force = O.force + + //Animals can't be stunned(?) + if(O.damtype == HALLOSS) + effective_force = 0 + if(supernatural && istype(O,/obj/item/weapon/nullrod)) + effective_force *= 2 + purge = 3 + if(O.force <= resistance) + to_chat(user,"This weapon is ineffective, it does no damage.") + return 2 //??? + + . = ..() + + +// Exploding. +/mob/living/simple_mob/ex_act(severity) + if(!blinded) + flash_eyes() + var/armor = run_armor_check(def_zone = null, attack_flag = "bomb") + var/bombdam = 500 + switch (severity) + if (1.0) + bombdam = 500 + if (2.0) + bombdam = 60 + if (3.0) + bombdam = 30 + + apply_damage(damage = bombdam, damagetype = BRUTE, def_zone = null, blocked = armor, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE) + + if(bombdam > maxHealth) + gib() + +// Cold stuff. +/mob/living/simple_mob/get_cold_protection() + return cold_resist + + +// Fire stuff. Not really exciting at the moment. +/mob/living/simple_mob/handle_fire() + return +/mob/living/simple_mob/update_fire() + return +/mob/living/simple_mob/IgniteMob() + return +/mob/living/simple_mob/ExtinguishMob() + return + +/mob/living/simple_mob/get_heat_protection() + return heat_resist + +// Electricity +/mob/living/simple_mob/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null) + shock_damage *= siemens_coeff + if(shock_damage < 1) + return 0 + + apply_damage(damage = shock_damage, damagetype = BURN, def_zone = null, blocked = null, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE) + playsound(loc, "sparks", 50, 1, -1) + + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(5, 1, loc) + s.start() + +/mob/living/simple_mob/get_shock_protection() + return shock_resist + +// Shot with taser/stunvolver +/mob/living/simple_mob/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) + if(taser_kill) + var/stunDam = 0 + var/agonyDam = 0 + var/armor = run_armor_check(def_zone = null, attack_flag = "energy") + + if(stun_amount) + stunDam += stun_amount * 0.5 + apply_damage(damage = stunDam, damagetype = BURN, def_zone = null, blocked = armor, blocked = resistance, used_weapon = used_weapon, sharp = FALSE, edge = FALSE) + + if(agony_amount) + agonyDam += agony_amount * 0.5 + apply_damage(damage = agonyDam, damagetype = BURN, def_zone = null, blocked = armor, blocked = resistance, used_weapon = used_weapon, sharp = FALSE, edge = FALSE) + + +// Electromagnetism +/mob/living/simple_mob/emp_act(severity) + ..() // To emp_act() its contents. + if(!isSynthetic()) + return + switch(severity) + if(1) + // adjustFireLoss(rand(15, 25)) + adjustFireLoss(min(60, getMaxHealth()*0.5)) // Weak mobs will always take two direct EMP hits to kill. Stronger ones might take more. + if(2) + adjustFireLoss(min(30, getMaxHealth()*0.25)) + // adjustFireLoss(rand(10, 18)) + if(3) + adjustFireLoss(min(15, getMaxHealth()*0.125)) + // adjustFireLoss(rand(5, 12)) + if(4) + adjustFireLoss(min(7, getMaxHealth()*0.0625)) + // adjustFireLoss(rand(1, 6)) + +// Water +/mob/living/simple_mob/get_water_protection() + return water_resist + +// "Poison" (aka what reagents would do if we wanted to deal with those). +/mob/living/simple_mob/get_poison_protection() + return poison_resist + +// Armor +/mob/living/simple_mob/getarmor(def_zone, attack_flag) + var/armorval = armor[attack_flag] + if(!armorval) + return 0 + else + return armorval + +/mob/living/simple_mob/getsoak(def_zone, attack_flag) + var/armorval = armor_soak[attack_flag] + if(!armorval) + return 0 + else + return armorval + +// Lightning +/mob/living/simple_mob/lightning_act() + ..() + // If a non-player simple_mob was struck, inflict huge damage. + // If the damage is fatal, it is turned to ash. + if(!client) + inflict_shock_damage(200) // Mobs that are very beefy or resistant to shock may survive getting struck. + updatehealth() + if(health <= 0) + visible_message(span("critical", "\The [src] disintegrates into ash!")) + ash() + return // No point deafening something that wont exist. + +// Injections. +/mob/living/simple_mob/can_inject(mob/user, error_msg, target_zone, ignore_thickness) + if(ignore_thickness) + return TRUE + return !thick_armor + diff --git a/code/modules/mob/living/simple_mob/hands.dm b/code/modules/mob/living/simple_mob/hands.dm new file mode 100644 index 0000000000..0e8820fe2e --- /dev/null +++ b/code/modules/mob/living/simple_mob/hands.dm @@ -0,0 +1,143 @@ +// Hand procs for player-controlled SA's +/mob/living/simple_mob/swap_hand() + src.hand = !( src.hand ) + if(hud_used.l_hand_hud_object && hud_used.r_hand_hud_object) + if(hand) //This being 1 means the left hand is in use + hud_used.l_hand_hud_object.icon_state = "l_hand_active" + hud_used.r_hand_hud_object.icon_state = "r_hand_inactive" + else + hud_used.l_hand_hud_object.icon_state = "l_hand_inactive" + hud_used.r_hand_hud_object.icon_state = "r_hand_active" + return + +/mob/living/simple_mob/put_in_hands(var/obj/item/W) // No hands. + if(has_hands) + put_in_active_hand(W) + return 1 + W.forceMove(get_turf(src)) + return 1 + +//Puts the item into our active hand if possible. returns 1 on success. +/mob/living/simple_mob/put_in_active_hand(var/obj/item/W) + if(!has_hands) + return FALSE + return (hand ? put_in_l_hand(W) : put_in_r_hand(W)) + +/mob/living/simple_mob/put_in_l_hand(var/obj/item/W) + if(!..() || l_hand) + return 0 + W.forceMove(src) + l_hand = W + W.equipped(src,slot_l_hand) + W.add_fingerprint(src) + update_inv_l_hand() + return TRUE + +/mob/living/simple_mob/put_in_r_hand(var/obj/item/W) + if(!..() || r_hand) + return 0 + W.forceMove(src) + r_hand = W + W.equipped(src,slot_r_hand) + W.add_fingerprint(src) + update_inv_r_hand() + return TRUE + +/mob/living/simple_mob/update_inv_r_hand() + if(QDESTROYING(src)) + return + + if(r_hand) + r_hand.screen_loc = ui_rhand //TODO + + //determine icon state to use + var/t_state + if(r_hand.item_state_slots && r_hand.item_state_slots[slot_r_hand_str]) + t_state = r_hand.item_state_slots[slot_r_hand_str] + else if(r_hand.item_state) + t_state = r_hand.item_state + else + t_state = r_hand.icon_state + + //determine icon to use + var/icon/t_icon + if(r_hand.item_icons && (slot_r_hand_str in r_hand.item_icons)) + t_icon = r_hand.item_icons[slot_r_hand_str] + else if(r_hand.icon_override) + t_state += "_r" + t_icon = r_hand.icon_override + else + t_icon = INV_R_HAND_DEF_ICON + + //apply color + var/image/standing = image(icon = t_icon, icon_state = t_state) + standing.color = r_hand.color + + r_hand_sprite = standing + + else + r_hand_sprite = null + + update_icon() + +/mob/living/simple_mob/update_inv_l_hand() + if(QDESTROYING(src)) + return + + if(l_hand) + l_hand.screen_loc = ui_lhand //TODO + + //determine icon state to use + var/t_state + if(l_hand.item_state_slots && l_hand.item_state_slots[slot_l_hand_str]) + t_state = l_hand.item_state_slots[slot_l_hand_str] + else if(l_hand.item_state) + t_state = l_hand.item_state + else + t_state = l_hand.icon_state + + //determine icon to use + var/icon/t_icon + if(l_hand.item_icons && (slot_l_hand_str in l_hand.item_icons)) + t_icon = l_hand.item_icons[slot_l_hand_str] + else if(l_hand.icon_override) + t_state += "_l" + t_icon = l_hand.icon_override + else + t_icon = INV_L_HAND_DEF_ICON + + //apply color + var/image/standing = image(icon = t_icon, icon_state = t_state) + standing.color = l_hand.color + + l_hand_sprite = standing + + else + l_hand_sprite = null + + update_icon() + +//Can insert extra huds into the hud holder here. +/mob/living/simple_mob/proc/extra_huds(var/datum/hud/hud,var/icon/ui_style,var/list/hud_elements) + return + +//If they can or cannot use tools/machines/etc +/mob/living/simple_mob/IsAdvancedToolUser() + return has_hands + +/mob/living/simple_mob/proc/IsHumanoidToolUser(var/atom/tool) + if(!humanoid_hands) + var/display_name = null + if(tool) + display_name = tool + else + display_name = "object" + to_chat(src, "Your [hand_form] are not fit for use of \the [display_name].") + return humanoid_hands + +/mob/living/simple_mob/drop_from_inventory(var/obj/item/W, var/atom/target = null) + . = ..(W, target) + if(!target) + target = src.loc + if(.) + W.forceMove(src.loc) diff --git a/code/modules/mob/living/simple_mob/life.dm b/code/modules/mob/living/simple_mob/life.dm new file mode 100644 index 0000000000..bab2aa4129 --- /dev/null +++ b/code/modules/mob/living/simple_mob/life.dm @@ -0,0 +1,160 @@ +/mob/living/simple_mob/Life() + ..() + + //Health + updatehealth() + if(stat >= DEAD) + return FALSE + + handle_stunned() + handle_weakened() + handle_paralysed() + handle_supernatural() + handle_atmos() + + handle_special() + + return TRUE + + +//Should we be dead? +/mob/living/simple_mob/updatehealth() + health = getMaxHealth() - getFireLoss() - getBruteLoss() - getToxLoss() - getOxyLoss() - getCloneLoss() + + //Alive, becoming dead + if((stat < DEAD) && (health <= 0)) + death() + + //Overhealth + if(health > getMaxHealth()) + health = getMaxHealth() + + //Update our hud if we have one + if(healths) + if(stat != DEAD) + var/heal_per = (health / getMaxHealth()) * 100 + switch(heal_per) + if(100 to INFINITY) + healths.icon_state = "health0" + if(80 to 100) + healths.icon_state = "health1" + if(60 to 80) + healths.icon_state = "health2" + if(40 to 60) + healths.icon_state = "health3" + if(20 to 40) + healths.icon_state = "health4" + if(0 to 20) + healths.icon_state = "health5" + else + healths.icon_state = "health6" + else + healths.icon_state = "health7" + + //Updates the nutrition while we're here + if(nutrition_icon) + var/food_per = (nutrition / initial(nutrition)) * 100 + switch(food_per) + if(90 to INFINITY) + nutrition_icon.icon_state = "nutrition0" + if(75 to 90) + nutrition_icon.icon_state = "nutrition1" + if(50 to 75) + nutrition_icon.icon_state = "nutrition2" + if(25 to 50) + nutrition_icon.icon_state = "nutrition3" + if(0 to 25) + nutrition_icon.icon_state = "nutrition4" + +// Override for special bullshit. +/mob/living/simple_mob/proc/handle_special() + return + + +// Handle interacting with and taking damage from atmos +// TODO - Refactor this to use handle_environment() like a good /mob/living +/mob/living/simple_mob/proc/handle_atmos() + var/atmos_unsuitable = 0 + + var/atom/A = src.loc + + if(istype(A,/turf)) + var/turf/T = A + + var/datum/gas_mixture/Environment = T.return_air() + + if(Environment) + + if( abs(Environment.temperature - bodytemperature) > 40 ) + bodytemperature += ((Environment.temperature - bodytemperature) / 5) + + if(min_oxy) + if(Environment.gas["oxygen"] < min_oxy) + atmos_unsuitable = 1 + if(max_oxy) + if(Environment.gas["oxygen"] > max_oxy) + atmos_unsuitable = 1 + if(min_tox) + if(Environment.gas["phoron"] < min_tox) + atmos_unsuitable = 2 + if(max_tox) + if(Environment.gas["phoron"] > max_tox) + atmos_unsuitable = 2 + if(min_n2) + if(Environment.gas["nitrogen"] < min_n2) + atmos_unsuitable = 1 + if(max_n2) + if(Environment.gas["nitrogen"] > max_n2) + atmos_unsuitable = 1 + if(min_co2) + if(Environment.gas["carbon_dioxide"] < min_co2) + atmos_unsuitable = 1 + if(max_co2) + if(Environment.gas["carbon_dioxide"] > max_co2) + atmos_unsuitable = 1 + + //Atmos effect + if(bodytemperature < minbodytemp) + fire_alert = 2 + adjustFireLoss(cold_damage_per_tick) + if(fire) + fire.icon_state = "fire1" + else if(bodytemperature > maxbodytemp) + fire_alert = 1 + adjustFireLoss(heat_damage_per_tick) + if(fire) + fire.icon_state = "fire2" + else + fire_alert = 0 + if(fire) + fire.icon_state = "fire0" + + if(atmos_unsuitable) + adjustOxyLoss(unsuitable_atoms_damage) + if(oxygen) + oxygen.icon_state = "oxy1" + else if(oxygen) + if(oxygen) + oxygen.icon_state = "oxy0" + adjustOxyLoss(-unsuitable_atoms_damage) + + +/mob/living/simple_mob/proc/handle_supernatural() + if(purge) + purge -= 1 + +/mob/living/simple_mob/death(gibbed, deathmessage = "dies!") + density = 0 //We don't block even if we did before + + if(has_eye_glow) + remove_eyes() + + if(loot_list.len) //Drop any loot + for(var/path in loot_list) + if(prob(loot_list[path])) + new path(get_turf(src)) + + spawn(3) //We'll update our icon in a sec + update_icon() + + return ..(gibbed,deathmessage) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/on_click.dm b/code/modules/mob/living/simple_mob/on_click.dm new file mode 100644 index 0000000000..4b824c9580 --- /dev/null +++ b/code/modules/mob/living/simple_mob/on_click.dm @@ -0,0 +1,48 @@ +/* + Animals +*/ +/mob/living/simple_mob/UnarmedAttack(var/atom/A, var/proximity) + if(!(. = ..())) + return + +// setClickCooldown(get_attack_speed()) + + if(has_hands && istype(A,/obj) && a_intent != I_HURT) + var/obj/O = A + return O.attack_hand(src) + + switch(a_intent) + if(I_HELP) + if(isliving(A)) + custom_emote(1,"[pick(friendly)] \the [A]!") + + if(I_HURT) + if(can_special_attack(A) && special_attack_target(A)) + return + + else if(melee_damage_upper == 0 && istype(A,/mob/living)) + custom_emote(1,"[pick(friendly)] \the [A]!") + + else + attack_target(A) + + if(I_GRAB) + if(has_hands) + A.attack_hand(src) + else + attack_target(A) + + if(I_DISARM) + if(has_hands) + A.attack_hand(src) + else + attack_target(A) + +/mob/living/simple_mob/RangedAttack(var/atom/A) +// setClickCooldown(get_attack_speed()) + + if(can_special_attack(A) && special_attack_target(A)) + return + + if(projectiletype) + shoot_target(A) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/simple_hud.dm b/code/modules/mob/living/simple_mob/simple_hud.dm new file mode 100644 index 0000000000..fe851648b4 --- /dev/null +++ b/code/modules/mob/living/simple_mob/simple_hud.dm @@ -0,0 +1,311 @@ +/mob/living/simple_mob/instantiate_hud(var/datum/hud/hud) + if(!client) + return //Why bother. + + var/ui_style = 'icons/mob/screen1_animal.dmi' + if(ui_icons) + ui_style = ui_icons + + var/ui_color = "#ffffff" + var/ui_alpha = 255 + + var/list/adding = list() + var/list/other = list() + var/list/hotkeybuttons = list() + var/list/slot_info = list() + + hud.adding = adding + hud.other = other + hud.hotkeybuttons = hotkeybuttons + + var/list/hud_elements = list() + var/obj/screen/using + var/obj/screen/inventory/inv_box + + var/has_hidden_gear + if(LAZYLEN(hud_gears)) + for(var/gear_slot in hud_gears) + inv_box = new /obj/screen/inventory() + inv_box.icon = ui_style + inv_box.color = ui_color + inv_box.alpha = ui_alpha + + var/list/slot_data = hud_gears[gear_slot] + inv_box.name = gear_slot + inv_box.screen_loc = slot_data["loc"] + inv_box.slot_id = slot_data["slot"] + inv_box.icon_state = slot_data["state"] + slot_info["[inv_box.slot_id]"] = inv_box.screen_loc + + if(slot_data["dir"]) + inv_box.set_dir(slot_data["dir"]) + + if(slot_data["toggle"]) + other += inv_box + has_hidden_gear = 1 + else + adding += inv_box + + if(has_hidden_gear) + using = new /obj/screen() + using.name = "toggle" + using.icon = ui_style + using.icon_state = "other" + using.screen_loc = ui_inventory + using.hud_layerise() + using.color = ui_color + using.alpha = ui_alpha + adding += using + + //Intent Backdrop + using = new /obj/screen() + using.name = "act_intent" + using.icon = ui_style + using.icon_state = "intent_"+a_intent + using.screen_loc = ui_acti + using.color = ui_color + using.alpha = ui_alpha + hud.adding += using + hud.action_intent = using + + hud_elements |= using + + //Small intent quarters + var/icon/ico + + ico = new(ui_style, "black") + ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1) + ico.DrawBox(rgb(255,255,255,1),1,ico.Height()/2,ico.Width()/2,ico.Height()) + using = new /obj/screen() + using.name = I_HELP + using.icon = ico + using.screen_loc = ui_acti + using.alpha = ui_alpha + using.layer = LAYER_HUD_ITEM //These sit on the intent box + hud.adding += using + hud.help_intent = using + + ico = new(ui_style, "black") + ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1) + ico.DrawBox(rgb(255,255,255,1),ico.Width()/2,ico.Height()/2,ico.Width(),ico.Height()) + using = new /obj/screen() + using.name = I_DISARM + using.icon = ico + using.screen_loc = ui_acti + using.alpha = ui_alpha + using.layer = LAYER_HUD_ITEM + hud.adding += using + hud.disarm_intent = using + + ico = new(ui_style, "black") + ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1) + ico.DrawBox(rgb(255,255,255,1),ico.Width()/2,1,ico.Width(),ico.Height()/2) + using = new /obj/screen() + using.name = I_GRAB + using.icon = ico + using.screen_loc = ui_acti + using.alpha = ui_alpha + using.layer = LAYER_HUD_ITEM + hud.adding += using + hud.grab_intent = using + + ico = new(ui_style, "black") + ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1) + ico.DrawBox(rgb(255,255,255,1),1,1,ico.Width()/2,ico.Height()/2) + using = new /obj/screen() + using.name = I_HURT + using.icon = ico + using.screen_loc = ui_acti + using.alpha = ui_alpha + using.layer = LAYER_HUD_ITEM + hud.adding += using + hud.hurt_intent = using + + //Move intent (walk/run) + using = new /obj/screen() + using.name = "mov_intent" + using.icon = ui_style + using.icon_state = (m_intent == "run" ? "running" : "walking") + using.screen_loc = ui_movi + using.color = ui_color + using.alpha = ui_alpha + hud.adding += using + hud.move_intent = using + + //Resist button + using = new /obj/screen() + using.name = "resist" + using.icon = ui_style + using.icon_state = "act_resist" + using.screen_loc = ui_pull_resist + using.color = ui_color + using.alpha = ui_alpha + hud.hotkeybuttons += using + + //Pull button + pullin = new /obj/screen() + pullin.icon = ui_style + pullin.icon_state = "pull0" + pullin.name = "pull" + pullin.screen_loc = ui_pull_resist + hud.hotkeybuttons += pullin + hud_elements |= pullin + + //Health status + healths = new /obj/screen() + healths.icon = ui_style + healths.icon_state = "health0" + healths.name = "health" + healths.screen_loc = ui_health + hud_elements |= healths + + //Oxygen dep icon + oxygen = new /obj/screen() + oxygen.icon = ui_style + oxygen.icon_state = "oxy0" + oxygen.name = "oxygen" + oxygen.screen_loc = ui_oxygen + hud_elements |= oxygen + + //Toxins present icon + toxin = new /obj/screen() + toxin.icon = ui_style + toxin.icon_state = "tox0" + toxin.name = "toxin" + toxin.screen_loc = ui_toxin + hud_elements |= toxin + + //Fire warning + fire = new /obj/screen() + fire.icon = ui_style + fire.icon_state = "fire0" + fire.name = "fire" + fire.screen_loc = ui_fire + hud_elements |= fire + + //Pressure warning + pressure = new /obj/screen() + pressure.icon = ui_style + pressure.icon_state = "pressure0" + pressure.name = "pressure" + pressure.screen_loc = ui_pressure + hud_elements |= pressure + + //Body temp warning + bodytemp = new /obj/screen() + bodytemp.icon = ui_style + bodytemp.icon_state = "temp0" + bodytemp.name = "body temperature" + bodytemp.screen_loc = ui_temp + hud_elements |= bodytemp + + //Nutrition status + nutrition_icon = new /obj/screen() + nutrition_icon.icon = ui_style + nutrition_icon.icon_state = "nutrition0" + nutrition_icon.name = "nutrition" + nutrition_icon.screen_loc = ui_nutrition + hud_elements |= nutrition_icon + + pain = new /obj/screen( null ) + + zone_sel = new /obj/screen/zone_sel( null ) + zone_sel.icon = ui_style + zone_sel.color = ui_color + zone_sel.alpha = ui_alpha + zone_sel.overlays.Cut() + zone_sel.overlays += image('icons/mob/zone_sel.dmi', "[zone_sel.selecting]") + hud_elements |= zone_sel + + //Hand things + if(has_hands) + //Drop button + using = new /obj/screen() + using.name = "drop" + using.icon = ui_style + using.icon_state = "act_drop" + using.screen_loc = ui_drop_throw + using.color = ui_color + using.alpha = ui_alpha + hud.hotkeybuttons += using + + //Equip detail + using = new /obj/screen() + using.name = "equip" + using.icon = ui_style + using.icon_state = "act_equip" + using.screen_loc = ui_equip + using.color = ui_color + using.alpha = ui_alpha + hud.adding += using + + //Hand slots themselves + inv_box = new /obj/screen/inventory/hand() + inv_box.hud = src + inv_box.name = "r_hand" + inv_box.icon = ui_style + inv_box.icon_state = "r_hand_inactive" + if(!hand) //This being 0 or null means the right hand is in use + inv_box.icon_state = "r_hand_active" + inv_box.screen_loc = ui_rhand + inv_box.slot_id = slot_r_hand + inv_box.color = ui_color + inv_box.alpha = ui_alpha + hud.r_hand_hud_object = inv_box + hud.adding += inv_box + slot_info["[slot_r_hand]"] = inv_box.screen_loc + + inv_box = new /obj/screen/inventory/hand() + inv_box.hud = src + inv_box.name = "l_hand" + inv_box.icon = ui_style + inv_box.icon_state = "l_hand_inactive" + if(hand) //This being 1 means the left hand is in use + inv_box.icon_state = "l_hand_active" + inv_box.screen_loc = ui_lhand + inv_box.slot_id = slot_l_hand + inv_box.color = ui_color + inv_box.alpha = ui_alpha + hud.l_hand_hud_object = inv_box + hud.adding += inv_box + slot_info["[slot_l_hand]"] = inv_box.screen_loc + + //Swaphand titlebar + using = new /obj/screen/inventory() + using.name = "hand" + using.icon = ui_style + using.icon_state = "hand1" + using.screen_loc = ui_swaphand1 + using.color = ui_color + using.alpha = ui_alpha + hud.adding += using + + using = new /obj/screen/inventory() + using.name = "hand" + using.icon = ui_style + using.icon_state = "hand2" + using.screen_loc = ui_swaphand2 + using.color = ui_color + using.alpha = ui_alpha + hud.adding += using + + //Throw button + throw_icon = new /obj/screen() + throw_icon.icon = ui_style + throw_icon.icon_state = "act_throw_off" + throw_icon.name = "throw" + throw_icon.screen_loc = ui_drop_throw + throw_icon.color = ui_color + throw_icon.alpha = ui_alpha + hud.hotkeybuttons += throw_icon + hud_elements |= throw_icon + + extra_huds(hud,ui_style,hud_elements) + + client.screen = list() + + client.screen += hud_elements + client.screen += adding + hotkeybuttons + client.screen += client.void + + return diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm new file mode 100644 index 0000000000..c1e6b91adb --- /dev/null +++ b/code/modules/mob/living/simple_mob/simple_mob.dm @@ -0,0 +1,290 @@ +// Reorganized and somewhat cleaned up. +// AI code has been made into a datum, inside the AI module folder. + +/mob/living/simple_mob + name = "animal" + desc = "" + icon = 'icons/mob/animal.dmi' + health = 20 + maxHealth = 20 + + // Generally we don't want simple_mobs to get displaced when bumped into due to it trivializing combat with windup attacks. + // Some subtypes allow displacement, like passive animals. + mob_bump_flag = HEAVY + mob_swap_flags = ~HEAVY + mob_push_flags = ~HEAVY + + var/tt_desc = "Uncataloged Life Form" //Tooltip description + + //Settings for played mobs + var/show_stat_health = 1 // Does the percentage health show in the stat panel for the mob + var/has_hands = 0 // Set to 1 to enable the use of hands and the hands hud + var/humanoid_hands = 0 // Can a player in this mob use things like guns or AI cards? + var/hand_form = "hands" // Used in IsHumanoidToolUser. 'Your X are not fit-'. + var/list/hud_gears // Slots to show on the hud (typically none) + var/ui_icons // Icon file path to use for the HUD, otherwise generic icons are used + var/r_hand_sprite // If they have hands, + var/l_hand_sprite // they could use some icons. + var/player_msg // Message to print to players about 'how' to play this mob on login. + + //Mob icon/appearance settings + var/icon_living = "" // The iconstate if we're alive, required + var/icon_dead = "" // The iconstate if we're dead, required + var/icon_gib = "generic_gib" // The iconstate for being gibbed, optional. Defaults to a generic gib animation. + var/icon_rest = null // The iconstate for resting, optional + var/image/modifier_overlay = null // Holds overlays from modifiers. + var/image/eye_layer = null // Holds the eye overlay. + var/has_eye_glow = FALSE // If true, adds an overlay over the lighting plane for [icon_state]-eyes. + attack_icon = 'icons/effects/effects.dmi' //Just the default, played like the weapon attack anim + attack_icon_state = "slash" //Just the default + + //Mob talking settings + universal_speak = 0 // Can all mobs in the entire universe understand this one? + var/has_langs = list(LANGUAGE_GALCOM)// Text name of their language if they speak something other than galcom. They speak the first one. + + //Movement things. + var/movement_cooldown = 5 // Lower is faster. + var/movement_sound = null // If set, will play this sound when it moves on its own will. + var/turn_sound = null // If set, plays the sound when the mob's dir changes in most cases. + var/movement_shake_radius = 0 // If set, moving will shake the camera of all living mobs within this radius slightly. + + //Mob interaction + var/response_help = "tries to help" // If clicked on help intent + var/response_disarm = "tries to disarm" // If clicked on disarm intent + var/response_harm = "tries to hurt" // If clicked on harm intent + var/list/friends = list() // Mobs on this list wont get attacked regardless of faction status. + var/harm_intent_damage = 3 // How much an unarmed harm click does to this mob. + var/meat_amount = 0 // How much meat to drop from this mob when butchered + var/obj/meat_type // The meat object to drop + var/list/loot_list = list() // The list of lootable objects to drop, with "/path = prob%" structure + var/obj/item/weapon/card/id/myid// An ID card if they have one to give them access to stuff. + + //Mob environment settings + var/minbodytemp = 250 // Minimum "okay" temperature in kelvin + var/maxbodytemp = 350 // Maximum of above + var/heat_damage_per_tick = 3 // Amount of damage applied if animal's body temperature is higher than maxbodytemp + var/cold_damage_per_tick = 2 // Same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp + var/fire_alert = 0 // 0 = fine, 1 = hot, 2 = cold + + var/min_oxy = 5 // Oxygen in moles, minimum, 0 is 'no minimum' + var/max_oxy = 0 // Oxygen in moles, maximum, 0 is 'no maximum' + var/min_tox = 0 // Phoron min + var/max_tox = 1 // Phoron max + var/min_co2 = 0 // CO2 min + var/max_co2 = 5 // CO2 max + var/min_n2 = 0 // N2 min + var/max_n2 = 0 // N2 max + var/unsuitable_atoms_damage = 2 // This damage is taken when atmos doesn't fit all the requirements above + + //Hostility settings + var/taser_kill = 1 // Is the mob weak to tasers + + //Attack ranged settings + var/projectiletype // The projectiles I shoot + var/projectilesound // The sound I make when I do it + var/casingtype // What to make the hugely laggy casings pile out of + + // Reloading settings, part of ranged code + var/needs_reload = FALSE // If TRUE, mob needs to reload occasionally + var/reload_max = 1 // How many shots the mob gets before it has to reload, will not be used if needs_reload is FALSE + var/reload_count = 0 // A counter to keep track of how many shots the mob has fired so far. Reloads when it hits reload_max. + var/reload_time = 1 SECONDS // How long it takes for a mob to reload. This is to buy a player a bit of time to run or fight. + var/reload_sound = 'sound/weapons/flipblade.ogg' // What sound gets played when the mob successfully reloads. Defaults to the same sound as reloading guns. Can be null. + + //Mob melee settings + var/melee_damage_lower = 2 // Lower bound of randomized melee damage + var/melee_damage_upper = 6 // Upper bound of randomized melee damage + var/list/attacktext = list("attacked") // "You are [attacktext] by the mob!" + var/list/friendly = list("nuzzles") // "The mob [friendly] the person." + var/attack_sound = null // Sound to play when I attack + var/melee_miss_chance = 0 // percent chance to miss a melee attack. + var/attack_armor_type = "melee" // What armor does this check? + var/attack_armor_pen = 0 // How much armor pen this attack has. + var/attack_sharp = FALSE // Is the attack sharp? + var/attack_edge = FALSE // Does the attack have an edge? + + var/melee_attack_delay = null // If set, the mob will do a windup animation and can miss if the target moves out of the way. + var/ranged_attack_delay = null + var/special_attack_delay = null + + //Special attacks +// var/special_attack_prob = 0 // The chance to ATTEMPT a special_attack_target(). If it fails, it will do a regular attack instead. + // This is commented out to ease the AI attack logic by being (a bit more) determanistic. + // You should instead limit special attacks using the below vars instead. + var/special_attack_min_range = null // The minimum distance required for an attempt to be made. + var/special_attack_max_range = null // The maximum for an attempt. + var/special_attack_charges = null // If set, special attacks will work off of a charge system, and won't be usable if all charges are expended. Good for grenades. + var/special_attack_cooldown = null // If set, special attacks will have a cooldown between uses. + var/last_special_attack = null // world.time when a special attack occured last, for cooldown calculations. + + //Damage resistances + var/grab_resist = 0 // Chance for a grab attempt to fail. Note that this is not a true resist and is just a prob() of failure. + var/resistance = 0 // Damage reduction for all types + var/list/armor = list( // Values for normal getarmor() checks + "melee" = 0, + "bullet" = 0, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + var/list/armor_soak = list( // Values for getsoak() checks. + "melee" = 0, + "bullet" = 0, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + // Protection against heat/cold/electric/water effects. + // 0 is no protection, 1 is total protection. Negative numbers increase vulnerability. + var/heat_resist = 0.0 + var/cold_resist = 0.0 + var/shock_resist = 0.0 + var/water_resist = 1.0 + var/poison_resist = 0.0 + var/thick_armor = FALSE // Stops injections and "injections". + var/purge = 0 // Cult stuff. + var/supernatural = FALSE // Ditto. + + +/mob/living/simple_mob/initialize() + verbs -= /mob/verb/observe + health = maxHealth + + for(var/L in has_langs) + languages |= all_languages[L] + if(languages.len) + default_language = languages[1] + + if(has_eye_glow) + add_eyes() + return ..() + + +/mob/living/simple_mob/Destroy() + default_language = null + if(myid) + qdel(myid) + myid = null + + friends.Cut() + languages.Cut() + + if(has_eye_glow) + remove_eyes() + return ..() + +/mob/living/simple_mob/death() + update_icon() + ..() + + +//Client attached +/mob/living/simple_mob/Login() + . = ..() + to_chat(src,"You are \the [src]. [player_msg]") + + +/mob/living/simple_mob/emote(var/act, var/type, var/desc) + if(act) + ..(act, type, desc) + + +/mob/living/simple_mob/SelfMove(turf/n, direct) + var/turf/old_turf = get_turf(src) + var/old_dir = dir + . = ..() + if(. && movement_shake_radius) + for(var/mob/living/L in range(movement_shake_radius, src)) + shake_camera(L, 1, 1) + if(turn_sound && dir != old_dir) + playsound(src, turn_sound, 50, 1) + else if(movement_sound && old_turf != get_turf(src)) // Playing both sounds at the same time generally sounds bad. + playsound(src, movement_sound, 50, 1) +/* +/mob/living/simple_mob/set_dir(new_dir) + if(dir != new_dir) + playsound(src, turn_sound, 50, 1) + return ..() +*/ +/mob/living/simple_mob/movement_delay() + var/tally = 0 //Incase I need to add stuff other than "speed" later + + tally = movement_cooldown + + if(force_max_speed) + return -3 + + for(var/datum/modifier/M in modifiers) + if(!isnull(M.haste) && M.haste == TRUE) + return -3 + if(!isnull(M.slowdown)) + tally += M.slowdown + + // Turf related slowdown + var/turf/T = get_turf(src) + if(T && T.movement_cost && !hovering) // Flying mobs ignore turf-based slowdown. + tally += T.movement_cost + + if(purge)//Purged creatures will move more slowly. The more time before their purge stops, the slower they'll move. + if(tally <= 0) + tally = 1 + tally *= purge + + if(m_intent == "walk") + tally *= 1.5 + + return tally+config.animal_delay + + +/mob/living/simple_mob/Stat() + ..() + if(statpanel("Status") && show_stat_health) + stat(null, "Health: [round((health / getMaxHealth()) * 100)]%") + +/mob/living/simple_mob/lay_down() + ..() + if(resting && icon_rest) + icon_state = icon_rest + else + icon_state = icon_living + update_icon() + + +/mob/living/simple_mob/say(var/message,var/datum/language/language) + var/verb = "says" + if(speak_emote.len) + verb = pick(speak_emote) + + message = sanitize(message) + + ..(message, null, verb) + +/mob/living/simple_mob/get_speech_ending(verb, var/ending) + return verb + + +// Harvest an animal's delicious byproducts +/mob/living/simple_mob/proc/harvest(var/mob/user) + var/actual_meat_amount = max(1,(meat_amount/2)) + if(meat_type && actual_meat_amount>0 && (stat == DEAD)) + for(var/i=0;i[user] chops up \the [src]!") + new/obj/effect/decal/cleanable/blood/splatter(get_turf(src)) + qdel(src) + else + user.visible_message("[user] butchers \the [src] messily!") + gib() + + +/mob/living/simple_mob/is_sentient() + return mob_class & MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL|MOB_CLASS_SLIME // Update this if needed. + +/mob/living/simple_mob/get_nametag_desc(mob/user) + return "[tt_desc]" \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm b/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm new file mode 100644 index 0000000000..e41d5ae66b --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm @@ -0,0 +1,9 @@ +/mob/living/simple_mob/animal + mob_class = MOB_CLASS_ANIMAL + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + response_help = "pets" + response_disarm = "shoos" + response_harm = "hits" + + ai_holder_type = /datum/ai_holder/simple_mob/melee \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm new file mode 100644 index 0000000000..92f4e51c9c --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm @@ -0,0 +1,237 @@ +// Borers are probably still going to be buggy as fuck, this is just bringing their mob defines up to the new system. +// IMO they're a relic of several ages we're long past, their code and their design showing this plainly, but removing them would +// make certain people Unhappy so here we are. They need a complete redesign but thats beyond the scope of the rewrite. + +/mob/living/simple_mob/animal/borer + name = "cortical borer" + desc = "A small, quivering sluglike creature." + icon_state = "brainslug" + item_state = "brainslug" + icon_living = "brainslug" + icon_dead = "brainslug_dead" + + response_help = "pokes" + response_disarm = "prods" + response_harm = "stomps on" + attacktext = list("nipped") + friendly = list("prods") + + status_flags = CANPUSH + pass_flags = PASSTABLE + movement_cooldown = 5 + + universal_understand = TRUE + can_be_antagged = TRUE + + holder_type = /obj/item/weapon/holder/borer + ai_holder_type = null // This is player-controlled, always. + + var/chemicals = 10 // A resource used for reproduction and powers. + var/mob/living/carbon/human/host = null // The humanoid host for the brain worm. + var/true_name = null // String used when speaking among other worms. + var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. + var/controlling = FALSE // Used in human death ceck. + var/docile = FALSE // Sugar can stop borers from acting. + var/has_reproduced = FALSE + var/roundstart = FALSE // If true, spawning won't try to pull a ghost. + var/used_dominate // world.time when the dominate power was last used. + + +/mob/living/simple_mob/animal/borer/roundstart + roundstart = TRUE + +/mob/living/simple_mob/animal/borer/Login() + ..() + if(mind) + borers.add_antagonist(mind) + +/mob/living/simple_mob/animal/borer/initialize() + add_language("Cortical Link") + + verbs += /mob/living/proc/ventcrawl + verbs += /mob/living/proc/hide + + true_name = "[pick("Primary","Secondary","Tertiary","Quaternary")] [rand(1000,9999)]" + + if(!roundstart) + request_player() + + return ..() + +/mob/living/simple_mob/animal/borer/handle_special() + if(host && !stat && !host.stat) + // Handle docility. + if(host.reagents.has_reagent("sugar") && !docile) + var/message = "You feel the soporific flow of sugar in your host's blood, lulling you into docility." + var/target = controlling ? host : src + to_chat(target, span("warning", message)) + docile = TRUE + + else if(docile) + var/message = "You shake off your lethargy as the sugar leaves your host's blood." + var/target = controlling ? host : src + to_chat(target, span("notice", message)) + docile = FALSE + + // Chem regen. + if(chemicals < 250) + chemicals++ + + // Control stuff. + if(controlling) + if(docile) + to_chat(host, span("warning", "You are feeling far too docile to continue controlling your host...")) + host.release_control() + return + + if(prob(5)) + host.adjustBrainLoss(0.1) + + if(prob(host.brainloss/20)) + host.say("*[pick(list("blink","blink_r","choke","aflap","drool","twitch","twitch_v","gasp"))]") + +/mob/living/simple_mob/animal/borer/Stat() + ..() + if(client.statpanel == "Status") + statpanel("Status") + if(emergency_shuttle) + var/eta_status = emergency_shuttle.get_status_panel_eta() + if(eta_status) + stat(null, eta_status) + stat("Chemicals", chemicals) + +/mob/living/simple_mob/animal/borer/proc/detatch() + if(!host || !controlling) + return + + if(istype(host, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = host + var/obj/item/organ/external/head = H.get_organ(BP_HEAD) + if(head) + head.implants -= src + + controlling = FALSE + + host.remove_language("Cortical Link") + host.verbs -= /mob/living/carbon/proc/release_control + host.verbs -= /mob/living/carbon/proc/punish_host + host.verbs -= /mob/living/carbon/proc/spawn_larvae + + if(host_brain) + // these are here so bans and multikey warnings are not triggered on the wrong people when ckey is changed. + // computer_id and IP are not updated magically on their own in offline mobs -walter0o + + // This shit need to die in a phoron fire. + + // host -> self + var/h2s_id = host.computer_id + var/h2s_ip= host.lastKnownIP + host.computer_id = null + host.lastKnownIP = null + + src.ckey = host.ckey + + if(!src.computer_id) + src.computer_id = h2s_id + + if(!host_brain.lastKnownIP) + src.lastKnownIP = h2s_ip + + // brain -> host + var/b2h_id = host_brain.computer_id + var/b2h_ip= host_brain.lastKnownIP + host_brain.computer_id = null + host_brain.lastKnownIP = null + + host.ckey = host_brain.ckey + + if(!host.computer_id) + host.computer_id = b2h_id + + if(!host.lastKnownIP) + host.lastKnownIP = b2h_ip + + qdel(host_brain) + + +/mob/living/simple_mob/animal/borer/proc/leave_host() + if(!host) + return + + if(host.mind) + borers.remove_antagonist(host.mind) + + forceMove(get_turf(host)) + + reset_view(null) + machine = null + + host.reset_view(null) + host.machine = null + host = null + +/mob/living/simple_mob/animal/borer/proc/request_player() + var/datum/ghost_query/Q = new /datum/ghost_query/borer() + var/list/winner = Q.query() // This will sleep the proc for awhile. + if(winner.len) + var/mob/observer/dead/D = winner[1] + transfer_personality(D) + +/mob/living/simple_mob/animal/borer/proc/transfer_personality(mob/candidate) + if(!candidate || !candidate.mind) + return + + src.mind = candidate.mind + candidate.mind.current = src + ckey = candidate.ckey + + if(mind) + mind.assigned_role = "Cortical Borer" + mind.special_role = "Cortical Borer" + + to_chat(src, span("notice", "You are a cortical borer! You are a brain slug that worms its way \ + into the head of its victim. Use stealth, persuasion and your powers of mind control to keep you, \ + your host and your eventual spawn safe and warm.")) + to_chat(src, "You can speak to your victim with say, to other borers with say :x, and use your Abilities tab to access powers.") + +/mob/living/simple_mob/animal/borer/cannot_use_vents() + return + +// This is awful but its literally say code. +/mob/living/simple_mob/animal/borer/say(message) + message = sanitize(message) + message = capitalize(message) + + if(!message) + return + + if(stat >= DEAD) + return say_dead(message) + else if(stat) + return + + if(client && client.prefs.muted & MUTE_IC) + to_chat(src, span("danger", "You cannot speak in IC (muted).")) + return + + if(copytext(message, 1, 2) == "*") + return emote(copytext(message, 2)) + + var/datum/language/L = parse_language(message) + if(L && L.flags & HIVEMIND) + L.broadcast(src,trim(copytext(message,3)), src.true_name) + return + + if(!host) + //TODO: have this pick a random mob within 3 tiles to speak for the borer. + to_chat(src, span("warning", "You have no host to speak to.")) + return //No host, no audible speech. + + to_chat(src, "You drop words into [host]'s mind: \"[message]\"") + to_chat(host, "Your own thoughts speak: \"[message]\"") + + for(var/mob/M in player_list) + if(istype(M, /mob/new_player)) + continue + else if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears)) + to_chat(M, "[src.true_name] whispers to [host], \"[message]\"") diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm new file mode 100644 index 0000000000..f5da079b97 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm @@ -0,0 +1,59 @@ +// Straight move from the old location, with the paths corrected. + +/mob/living/captive_brain + name = "host brain" + real_name = "host brain" + universal_understand = 1 + +/mob/living/captive_brain/say(var/message) + + if (src.client) + if(client.prefs.muted & MUTE_IC) + src << "You cannot speak in IC (muted)." + return + + if(istype(src.loc, /mob/living/simple_mob/animal/borer)) + + message = sanitize(message) + if (!message) + return + log_say(message,src) + if (stat == 2) + return say_dead(message) + + var/mob/living/simple_mob/animal/borer/B = src.loc + src << "You whisper silently, \"[message]\"" + B.host << "The captive mind of [src] whispers, \"[message]\"" + + for (var/mob/M in player_list) + if (istype(M, /mob/new_player)) + continue + else if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears)) + M << "The captive mind of [src] whispers, \"[message]\"" + +/mob/living/captive_brain/emote(var/message) + return + +/mob/living/captive_brain/process_resist() + //Resisting control by an alien mind. + if(istype(src.loc, /mob/living/simple_mob/animal/borer)) + var/mob/living/simple_mob/animal/borer/B = src.loc + var/mob/living/captive_brain/H = src + + H << "You begin doggedly resisting the parasite's control (this will take approximately sixty seconds)." + B.host << "You feel the captive mind of [src] begin to resist your control." + + spawn(rand(200,250)+B.host.brainloss) + if(!B || !B.controlling) return + + B.host.adjustBrainLoss(rand(0.1,0.5)) + H << "With an immense exertion of will, you regain control of your body!" + B.host << "You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you." + B.detatch() + verbs -= /mob/living/carbon/proc/release_control + verbs -= /mob/living/carbon/proc/punish_host + verbs -= /mob/living/carbon/proc/spawn_larvae + + return + + ..() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_powers.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_powers.dm new file mode 100644 index 0000000000..55dac2492a --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_powers.dm @@ -0,0 +1,354 @@ +/mob/living/simple_mob/animal/borer/verb/release_host() + set category = "Abilities" + set name = "Release Host" + set desc = "Slither out of your host." + + if(!host) + src << "You are not inside a host body." + return + + if(stat) + src << "You cannot leave your host in your current state." + + if(docile) + src << "You are feeling far too docile to do that." + return + + if(!host || !src) return + + src << "You begin disconnecting from [host]'s synapses and prodding at their internal ear canal." + + if(!host.stat) + host << "An odd, uncomfortable pressure begins to build inside your skull, behind your ear..." + + spawn(100) + + if(!host || !src) return + + if(src.stat) + src << "You cannot release your host in your current state." + return + + src << "You wiggle out of [host]'s ear and plop to the ground." + if(host.mind) + if(!host.stat) + host << "Something slimy wiggles out of your ear and plops to the ground!" + host << "As though waking from a dream, you shake off the insidious mind control of the brain worm. Your thoughts are your own again." + + detatch() + leave_host() + +/mob/living/simple_mob/animal/borer/verb/infest() + set category = "Abilities" + set name = "Infest" + set desc = "Infest a suitable humanoid host." + + if(host) + src << "You are already within a host." + return + + if(stat) + src << "You cannot infest a target in your current state." + return + + var/list/choices = list() + for(var/mob/living/carbon/C in view(1,src)) + if(src.Adjacent(C)) + choices += C + + if(!choices.len) + src << "There are no viable hosts within range..." + return + + var/mob/living/carbon/M = input(src,"Who do you wish to infest?") in null|choices + + if(!M || !src) return + + if(!(src.Adjacent(M))) return + + if(M.has_brain_worms()) + src << "You cannot infest someone who is already infested!" + return + + if(istype(M,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = M + + var/obj/item/organ/external/E = H.organs_by_name[BP_HEAD] + if(!E || E.is_stump()) + src << "\The [H] does not have a head!" + + if(!H.should_have_organ("brain")) + src << "\The [H] does not seem to have an ear canal to breach." + return + + if(H.check_head_coverage()) + src << "You cannot get through that host's protective gear." + return + + M << "Something slimy begins probing at the opening of your ear canal..." + src << "You slither up [M] and begin probing at their ear canal..." + + if(!do_after(src,30)) + src << "As [M] moves away, you are dislodged and fall to the ground." + return + + if(!M || !src) return + + if(src.stat) + src << "You cannot infest a target in your current state." + return + + if(M in view(1, src)) + src << "You wiggle into [M]'s ear." + if(!M.stat) + M << "Something disgusting and slimy wiggles into your ear!" + + src.host = M + src.forceMove(M) + + //Update their traitor status. + if(host.mind) + borers.add_antagonist_mind(host.mind, 1, borers.faction_role_text, borers.faction_welcome) + + if(istype(M,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/I = H.internal_organs_by_name["brain"] + if(!I) // No brain organ, so the borer moves in and replaces it permanently. + replace_brain() + else + // If they're in normally, implant removal can get them out. + var/obj/item/organ/external/head = H.get_organ(BP_HEAD) + head.implants += src + + return + else + src << "They are no longer in range!" + return + +/* +/mob/living/simple_mob/animal/borer/verb/devour_brain() + set category = "Abilities" + set name = "Devour Brain" + set desc = "Take permanent control of a dead host." + + if(!host) + src << "You are not inside a host body." + return + + if(host.stat != 2) + src << "Your host is still alive." + return + + if(stat) + src << "You cannot do that in your current state." + + if(docile) + src << "You are feeling far too docile to do that." + return + + + src << "It only takes a few moments to render the dead host brain down into a nutrient-rich slurry..." + replace_brain() +*/ + +// BRAIN WORM ZOMBIES AAAAH. +/mob/living/simple_mob/animal/borer/proc/replace_brain() + + var/mob/living/carbon/human/H = host + + if(!istype(host)) + src << "This host does not have a suitable brain." + return + + src << "You settle into the empty brainpan and begin to expand, fusing inextricably with the dead flesh of [H]." + + H.add_language("Cortical Link") + + if(host.stat == 2) + H.verbs |= /mob/living/carbon/human/proc/jumpstart + + H.verbs |= /mob/living/carbon/human/proc/psychic_whisper + H.verbs |= /mob/living/carbon/human/proc/tackle + H.verbs |= /mob/living/carbon/proc/spawn_larvae + + if(H.client) + H.ghostize(0) + + if(src.mind) + src.mind.special_role = "Borer Husk" + src.mind.transfer_to(host) + + H.ChangeToHusk() + + var/obj/item/organ/internal/borer/B = new(H) + H.internal_organs_by_name["brain"] = B + H.internal_organs |= B + + var/obj/item/organ/external/affecting = H.get_organ(BP_HEAD) + affecting.implants -= src + + var/s2h_id = src.computer_id + var/s2h_ip= src.lastKnownIP + src.computer_id = null + src.lastKnownIP = null + + if(!H.computer_id) + H.computer_id = s2h_id + + if(!H.lastKnownIP) + H.lastKnownIP = s2h_ip + +/mob/living/simple_mob/animal/borer/verb/secrete_chemicals() + set category = "Abilities" + set name = "Secrete Chemicals" + set desc = "Push some chemicals into your host's bloodstream." + + if(!host) + src << "You are not inside a host body." + return + + if(stat) + src << "You cannot secrete chemicals in your current state." + + if(docile) + src << "You are feeling far too docile to do that." + return + + if(chemicals < 50) + src << "You don't have enough chemicals!" + + var/chem = input("Select a chemical to secrete.", "Chemicals") as null|anything in list("alkysine","bicaridine","hyperzine","tramadol") + + if(!chem || chemicals < 50 || !host || controlling || !src || stat) //Sanity check. + return + + src << "You squirt a measure of [chem] from your reservoirs into [host]'s bloodstream." + host.reagents.add_reagent(chem, 10) + chemicals -= 50 + +/mob/living/simple_mob/animal/borer/verb/dominate_victim() + set category = "Abilities" + set name = "Paralyze Victim" + set desc = "Freeze the limbs of a potential host with supernatural fear." + + if(world.time - used_dominate < 150) + src << "You cannot use that ability again so soon." + return + + if(host) + src << "You cannot do that from within a host body." + return + + if(src.stat) + src << "You cannot do that in your current state." + return + + var/list/choices = list() + for(var/mob/living/carbon/C in view(3,src)) + if(C.stat != 2) + choices += C + + if(world.time - used_dominate < 150) + src << "You cannot use that ability again so soon." + return + + var/mob/living/carbon/M = input(src,"Who do you wish to dominate?") in null|choices + + if(!M || !src) return + + if(M.has_brain_worms()) + src << "You cannot infest someone who is already infested!" + return + + src << "You focus your psychic lance on [M] and freeze their limbs with a wave of terrible dread." + M << "You feel a creeping, horrible sense of dread come over you, freezing your limbs and setting your heart racing." + M.Weaken(10) + + used_dominate = world.time + +/mob/living/simple_mob/animal/borer/verb/bond_brain() + set category = "Abilities" + set name = "Assume Control" + set desc = "Fully connect to the brain of your host." + + if(!host) + src << "You are not inside a host body." + return + + if(src.stat) + src << "You cannot do that in your current state." + return + + if(docile) + src << "You are feeling far too docile to do that." + return + + src << "You begin delicately adjusting your connection to the host brain..." + + spawn(100+(host.brainloss*5)) + + if(!host || !src || controlling) + return + else + + src << "You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system." + host << "You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours." + host.add_language("Cortical Link") + + // host -> brain + var/h2b_id = host.computer_id + var/h2b_ip= host.lastKnownIP + host.computer_id = null + host.lastKnownIP = null + + qdel(host_brain) + host_brain = new(src) + + host_brain.ckey = host.ckey + + host_brain.name = host.name + + if(!host_brain.computer_id) + host_brain.computer_id = h2b_id + + if(!host_brain.lastKnownIP) + host_brain.lastKnownIP = h2b_ip + + // self -> host + var/s2h_id = src.computer_id + var/s2h_ip= src.lastKnownIP + src.computer_id = null + src.lastKnownIP = null + + host.ckey = src.ckey + + if(!host.computer_id) + host.computer_id = s2h_id + + if(!host.lastKnownIP) + host.lastKnownIP = s2h_ip + + controlling = 1 + + host.verbs += /mob/living/carbon/proc/release_control + host.verbs += /mob/living/carbon/proc/punish_host + host.verbs += /mob/living/carbon/proc/spawn_larvae + + return + +/mob/living/carbon/human/proc/jumpstart() + set category = "Abilities" + set name = "Revive Host" + set desc = "Send a jolt of electricity through your host, reviving them." + + if(stat != 2) + usr << "Your host is already alive." + return + + verbs -= /mob/living/carbon/human/proc/jumpstart + visible_message("With a hideous, rattling moan, [src] shudders back to life!") + + rejuvenate() + restore_blood() + fixblood() + update_canmove() \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/chicken.dm b/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/chicken.dm new file mode 100644 index 0000000000..775f9ae6cb --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/chicken.dm @@ -0,0 +1,156 @@ +GLOBAL_VAR_CONST(MAX_CHICKENS, 50) // How many chickens CAN we have? +GLOBAL_VAR_INIT(chicken_count, 0) // How mant chickens DO we have? + +/mob/living/simple_mob/animal/passive/chicken + name = "chicken" + desc = "Hopefully the eggs are good this season." + tt_desc = "E Gallus gallus" + icon_state = "chicken" + icon_living = "chicken" + icon_dead = "chicken_dead" + + health = 10 + maxHealth = 10 + + pass_flags = PASSTABLE + mob_size = MOB_SMALL + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = list("kicked") + + has_langs = list("Bird") + + say_list_type = /datum/say_list/chicken + + meat_amount = 2 + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + var/eggsleft = 0 + var/body_color + +/mob/living/simple_mob/animal/passive/chicken/New() + ..() + if(!body_color) + body_color = pick( list("brown","black","white") ) + icon_state = "chicken_[body_color]" + icon_living = "chicken_[body_color]" + icon_dead = "chicken_[body_color]_dead" + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + GLOB.chicken_count += 1 + +/mob/living/simple_mob/animal/passive/chicken/Destroy() + ..() + GLOB.chicken_count -= 1 + +/mob/living/simple_mob/animal/passive/chicken/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(istype(O, /obj/item/weapon/reagent_containers/food/snacks/grown)) //feedin' dem chickens + var/obj/item/weapon/reagent_containers/food/snacks/grown/G = O + if(G.seed && G.seed.kitchen_tag == "wheat") + if(!stat && eggsleft < 8) + user.visible_message("[user] feeds [O] to [name]! It clucks happily.","You feed [O] to [name]! It clucks happily.") + user.drop_item() + qdel(O) + eggsleft += rand(1, 4) + else + to_chat(user, "[name] doesn't seem hungry!") + else + to_chat(user, "[name] doesn't seem interested in that.") + else + ..() + +/mob/living/simple_mob/animal/passive/chicken/Life() + . =..() + if(!.) + return + if(!stat && prob(3) && eggsleft > 0) + visible_message("[src] [pick("lays an egg.","squats down and croons.","begins making a huge racket.","begins clucking raucously.")]") + eggsleft-- + var/obj/item/weapon/reagent_containers/food/snacks/egg/E = new(get_turf(src)) + E.pixel_x = rand(-6,6) + E.pixel_y = rand(-6,6) + if(GLOB.chicken_count < GLOB.MAX_CHICKENS && prob(10)) + processing_objects.Add(E) + + + + + + + +/obj/item/weapon/reagent_containers/food/snacks/egg/var/amount_grown = 0 + +// This only starts normally if there are less than MAX_CHICKENS chickens +/obj/item/weapon/reagent_containers/food/snacks/egg/process() + if(isturf(loc)) + amount_grown += rand(1,2) + if(amount_grown >= 100) + visible_message("[src] hatches with a quiet cracking sound.") + new /mob/living/simple_mob/animal/passive/chick(get_turf(src)) + processing_objects.Remove(src) + qdel(src) + else + processing_objects.Remove(src) + + + + + + + +/mob/living/simple_mob/animal/passive/chick + name = "chick" + desc = "Adorable! They make such a racket though." + tt_desc = "E Gallus gallus" + icon_state = "chick" + icon_living = "chick" + icon_dead = "chick_dead" + icon_gib = "chick_gib" + + health = 1 + maxHealth = 1 + + pass_flags = PASSTABLE | PASSGRILLE + mob_size = MOB_MINISCULE + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = list("kicked") + + has_langs = list("Bird") + + say_list_type = /datum/say_list/chick + + meat_amount = 1 + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + var/amount_grown = 0 + +/mob/living/simple_mob/animal/passive/chick/New() + ..() + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + +/mob/living/simple_mob/animal/passive/chick/Life() + . =..() + if(!.) + return + if(!stat) + amount_grown += rand(1,2) + if(amount_grown >= 100) + new /mob/living/simple_mob/animal/passive/chicken(src.loc) + qdel(src) + +// Say Lists +/datum/say_list/chicken + speak = list("Cluck!","BWAAAAARK BWAK BWAK BWAK!","Bwaak bwak.") + emote_hear = list("clucks","croons") + emote_see = list("pecks at the ground","flaps its wings viciously") + +/datum/say_list/chick + speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") + emote_hear = list("cheeps") + emote_see = list("pecks at the ground","flaps its tiny wings") diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/cow.dm b/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/cow.dm new file mode 100644 index 0000000000..b5373c176a --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/cow.dm @@ -0,0 +1,67 @@ +/mob/living/simple_mob/animal/passive/cow + name = "cow" + desc = "Known for their milk, just don't tip them over." + tt_desc = "E Bos taurus" + icon_state = "cow" + icon_living = "cow" + icon_dead = "cow_dead" + icon_gib = "cow_gib" + + health = 50 + maxHealth = 50 + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = list("kicked") + + say_list_type = /datum/say_list/cow + + meat_amount = 6 + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + var/datum/reagents/udder = null + +/mob/living/simple_mob/animal/passive/cow/New() + udder = new(50) + udder.my_atom = src + ..() + +/mob/living/simple_mob/animal/passive/cow/attackby(var/obj/item/O as obj, var/mob/user as mob) + var/obj/item/weapon/reagent_containers/glass/G = O + if(stat == CONSCIOUS && istype(G) && G.is_open_container()) + user.visible_message("[user] milks [src] using \the [O].") + var/transfered = udder.trans_id_to(G, "milk", rand(5,10)) + if(G.reagents.total_volume >= G.volume) + to_chat(user, "The [O] is full.") + if(!transfered) + to_chat(user, "The udder is dry. Wait a bit longer...") + else + ..() + +/mob/living/simple_mob/animal/passive/cow/Life() + . = ..() + if(stat == CONSCIOUS) + if(udder && prob(5)) + udder.add_reagent("milk", rand(5, 10)) + +/mob/living/simple_mob/animal/passive/cow/attack_hand(mob/living/carbon/M as mob) + if(!stat && M.a_intent == I_DISARM && icon_state != icon_dead) + M.visible_message("[M] tips over [src].","You tip over [src].") + Weaken(30) + icon_state = icon_dead + spawn(rand(20,50)) + if(!stat && M) + icon_state = icon_living + var/list/responses = list( "[src] looks at you imploringly.", + "[src] looks at you pleadingly", + "[src] looks at you with a resigned expression.", + "[src] seems resigned to its fate.") + to_chat(M, pick(responses)) + else + ..() + +/datum/say_list/cow + speak = list("moo?","moo","MOOOOOO") + emote_hear = list("brays", "moos","moos hauntingly") + emote_see = list("shakes its head") \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/goat.dm b/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/goat.dm new file mode 100644 index 0000000000..7ff82a8160 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/farm animals/goat.dm @@ -0,0 +1,81 @@ +/mob/living/simple_mob/animal/goat + name = "goat" + desc = "Not known for their pleasant disposition." + tt_desc = "E Oreamnos americanus" + icon_state = "goat" + icon_living = "goat" + icon_dead = "goat_dead" + + faction = "goat" + + health = 40 + maxHealth = 40 + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + + melee_damage_lower = 1 + melee_damage_upper = 5 + attacktext = list("kicked") + + say_list_type = /datum/say_list/goat + ai_holder_type = /datum/ai_holder/simple_mob/retaliate + + meat_amount = 4 + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + var/datum/reagents/udder = null + +/mob/living/simple_mob/animal/goat/New() + udder = new(50) + udder.my_atom = src + ..() + +/mob/living/simple_mob/animal/goat/Life() + . = ..() + if(.) + if(stat == CONSCIOUS) + if(udder && prob(5)) + udder.add_reagent("milk", rand(5, 10)) + + if(locate(/obj/effect/plant) in loc) + var/obj/effect/plant/SV = locate() in loc + SV.die_off(1) + + if(locate(/obj/machinery/portable_atmospherics/hydroponics/soil/invisible) in loc) + var/obj/machinery/portable_atmospherics/hydroponics/soil/invisible/SP = locate() in loc + qdel(SP) + + if(!pulledby) + var/obj/effect/plant/food + food = locate(/obj/effect/plant) in oview(5,loc) + if(food) + var/step = get_step_to(src, food, 0) + Move(step) + +/mob/living/simple_mob/animal/goat/Move() + ..() + if(!stat) + for(var/obj/effect/plant/SV in loc) + SV.die_off(1) + +/mob/living/simple_mob/animal/goat/attackby(var/obj/item/O as obj, var/mob/user as mob) + var/obj/item/weapon/reagent_containers/glass/G = O + if(stat == CONSCIOUS && istype(G) && G.is_open_container()) + user.visible_message("[user] milks [src] using \the [O].") + var/transfered = udder.trans_id_to(G, "milk", rand(5,10)) + if(G.reagents.total_volume >= G.volume) + to_chat(user, "The [O] is full.") + if(!transfered) + to_chat(user, "The udder is dry. Wait a bit longer...") + else + ..() + +/datum/say_list/goat + speak = list("EHEHEHEHEH","eh?") + emote_hear = list("brays") + emote_see = list("shakes its head", "stamps a foot", "glares around") + + // say_got_target doesn't seem to handle emotes, but keeping this here in case someone wants to make it work +// say_got_target = list("[src] gets an evil-looking gleam in their eye.") \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm new file mode 100644 index 0000000000..a85463d723 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm @@ -0,0 +1,64 @@ +/* + Spiders come in various types, and are a fairly common enemy both inside and outside the station. + Their attacks can inject reagents, which can cause harm long after the spider is killed. + Thick material will prevent injections, similar to other means of injections. +*/ + +// The base spider, in the 'walking tank' family. +/mob/living/simple_mob/animal/giant_spider + name = "giant spider" + desc = "Furry and brown, it makes you shudder to look at it. This one has deep red eyes." + tt_desc = "Atrax robustus gigantus" + icon_state = "guard" + icon_living = "guard" + icon_dead = "guard_dead" + has_eye_glow = TRUE + + faction = "spiders" + maxHealth = 200 + health = 200 + pass_flags = PASSTABLE + movement_cooldown = 10 + poison_resist = 0.5 + + see_in_dark = 10 + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "punches" + + melee_damage_lower = 18 + melee_damage_upper = 30 + attack_sharp = 1 + attack_edge = 1 + attack_sound = 'sound/weapons/bite.ogg' + + heat_damage_per_tick = 20 + cold_damage_per_tick = 20 + minbodytemp = 175 // So they can all survive Sif without having to be classed under /sif subtype. + + speak_emote = list("chitters") + + meat_amount = 1 + meat_type = /obj/item/weapon/reagent_containers/food/snacks/xenomeat/spidermeat + + say_list_type = /datum/say_list/spider + + var/poison_type = "spidertoxin" // The reagent that gets injected when it attacks. + var/poison_chance = 10 // Chance for injection to occur. + var/poison_per_bite = 5 // Amount added per injection. + +/mob/living/simple_mob/animal/giant_spider/apply_melee_effects(var/atom/A) + if(isliving(A)) + var/mob/living/L = A + if(L.reagents) + var/target_zone = pick(BP_TORSO,BP_TORSO,BP_TORSO,BP_L_LEG,BP_R_LEG,BP_L_ARM,BP_R_ARM,BP_HEAD) + if(L.can_inject(src, null, target_zone)) + inject_poison(L, target_zone) + +// Does actual poison injection, after all checks passed. +/mob/living/simple_mob/animal/giant_spider/proc/inject_poison(mob/living/L, target_zone) + if(prob(poison_chance)) + to_chat(L, "You feel a tiny prick.") + L.reagents.add_reagent(poison_type, poison_per_bite) + diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/carrier.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/carrier.dm new file mode 100644 index 0000000000..575e3ad204 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/carrier.dm @@ -0,0 +1,67 @@ +// Carriers are not too dangerous on their own, but they create more spiders when dying. + +/mob/living/simple_mob/animal/giant_spider/carrier + desc = "Furry, beige, and red, it makes you shudder to look at it. This one has luminous green eyes." + icon_state = "carrier" + icon_living = "carrier" + icon_dead = "carrier_dead" + + maxHealth = 100 + health = 100 + + melee_damage_lower = 8 + melee_damage_upper = 25 + + poison_per_bite = 3 + poison_type = "chloralhydrate" + + movement_cooldown = 5 + + player_msg = "Upon dying, you will release a swarm of spiderlings or young hunter spiders.
\ + If a spider emerges, you will be placed in control of it." + + var/spiderling_count = 0 + var/spiderling_type = /obj/effect/spider/spiderling + var/swarmling_type = /mob/living/simple_mob/animal/giant_spider/hunter + var/swarmling_faction = "spiders" + var/swarmling_prob = 10 // Odds that a spiderling will be a swarmling instead. + +/mob/living/simple_mob/animal/giant_spider/carrier/initialize() + spiderling_count = rand(5, 10) + adjust_scale(1.2) + return ..() + +/mob/living/simple_mob/animal/giant_spider/carrier/death() + visible_message(span("warning", "\The [src]'s abdomen splits as it rolls over, spiderlings crawling from the wound.") ) + spawn(1) + var/list/new_spiders = list() + for(var/i = 1 to spiderling_count) + if(prob(swarmling_prob) && src) + var/mob/living/simple_mob/animal/giant_spider/swarmling = new swarmling_type(src.loc) + var/swarm_health = Floor(swarmling.maxHealth * 0.4) + var/swarm_dam_lower = Floor(melee_damage_lower * 0.4) + var/swarm_dam_upper = Floor(melee_damage_upper * 0.4) + swarmling.name = "spiderling" + swarmling.maxHealth = swarm_health + swarmling.health = swarm_health + swarmling.melee_damage_lower = swarm_dam_lower + swarmling.melee_damage_upper = swarm_dam_upper + swarmling.faction = swarmling_faction + swarmling.adjust_scale(0.75) + new_spiders += swarmling + else if(src) + var/obj/effect/spider/spiderling/child = new spiderling_type(src.loc) + child.skitter() + else // We might've gibbed or got deleted. + break + // Transfer our player to their new body, if RNG provided one. + if(new_spiders.len && client) + var/mob/living/simple_mob/animal/giant_spider/new_body = pick(new_spiders) + new_body.key = src.key + return ..() + +/mob/living/simple_mob/animal/giant_spider/carrier/recursive + desc = "Furry, beige, and red, it makes you shudder to look at it. This one has luminous green eyes. \ + You have a distinctly bad feeling about this." + + swarmling_type = /mob/living/simple_mob/animal/giant_spider/carrier/recursive \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/electric.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/electric.dm new file mode 100644 index 0000000000..c3d1f60731 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/electric.dm @@ -0,0 +1,45 @@ +// Electric spiders fire taser-like beams at their enemies. +// TODO: AI + +/mob/living/simple_mob/animal/giant_spider/electric + desc = "Spined and yellow, it makes you shudder to look at it. This one has flickering gold eyes." + icon_state = "spark" + icon_living = "spark" + icon_dead = "spark_dead" + + maxHealth = 210 + health = 210 + + taser_kill = 0 //It -is- the taser. + + base_attack_cooldown = 10 + projectilesound = 'sound/weapons/taser2.ogg' + projectiletype = /obj/item/projectile/beam/stun/electric_spider + + melee_damage_lower = 10 + melee_damage_upper = 25 + + poison_chance = 15 + poison_per_bite = 3 + poison_type = "stimm" + + shock_resist = 0.75 + + player_msg = "You can fire a taser-like ranged attack by clicking on an enemy or tile at a distance." + + ai_holder_type = /datum/ai_holder/simple_mob/ranged/electric_spider + + +/obj/item/projectile/beam/stun/electric_spider + name = "stun beam" + agony = 20 + +// The electric spider's AI. +/datum/ai_holder/simple_mob/ranged/electric_spider + +/datum/ai_holder/simple_mob/ranged/electric_spider/max_range(atom/movable/AM) + if(isliving(AM)) + var/mob/living/L = AM + if(L.incapacitated(INCAPACITATION_DISABLED) || L.stat == UNCONSCIOUS) // If our target is stunned, go in for the kill. + return 1 + return ..() // Do ranged if possible otherwise. \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/frost.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/frost.dm new file mode 100644 index 0000000000..19b85cccd4 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/frost.dm @@ -0,0 +1,20 @@ +// Frost spiders inject cryotoxin, slowing people down (which is very bad if trying to run from spiders). + +/mob/living/simple_mob/animal/giant_spider/frost + desc = "Icy and blue, it makes you shudder to look at it. This one has brilliant blue eyes." + icon_state = "frost" + icon_living = "frost" + icon_dead = "frost_dead" + + maxHealth = 175 + health = 175 + + poison_per_bite = 5 + poison_type = "cryotoxin" + heat_resist = -0.50 + cold_resist = 0.75 + +// Sif variant with a somewhat different desc. +/mob/living/simple_mob/animal/giant_spider/frost/sif + desc = "Icy and blue, it makes you shudder to look at it. This one has brilliant blue eyes. \ + It isn't native to Sif." diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm new file mode 100644 index 0000000000..f3eb1cf81e --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm @@ -0,0 +1,165 @@ +// Hunters are fast, fragile, and possess a leaping attack. +// The leaping attack is somewhat telegraphed and can be dodged if one is paying attention. +// The AI would've followed up after a successful leap with dragging the downed victim away, but the dragging code was too janky. + +/mob/living/simple_mob/animal/giant_spider/hunter + desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes." + icon_state = "hunter" + icon_living = "hunter" + icon_dead = "hunter_dead" + + maxHealth = 120 + health = 120 + + poison_per_bite = 5 + melee_damage_lower = 9 + melee_damage_upper = 15 + + movement_cooldown = 0 // Hunters are FAST. + + ai_holder_type = /datum/ai_holder/simple_mob/melee/hunter_spider + + player_msg = "You are very fast, and can perform a leaping attack by clicking on someone from a short distance away.
\ + If the leap succeeds, the target will be knocked down briefly and you will be on top of them.
\ + Note that there is a short delay before you leap!
\ + In addition, you will do more damage to incapacitated opponents." + + // Leaping is a special attack, so these values determine when leap can happen. + // Leaping won't occur if its on cooldown. + special_attack_min_range = 2 + special_attack_max_range = 4 + special_attack_cooldown = 10 SECONDS + + var/leap_warmup = 1 SECOND // How long the leap telegraphing is. + var/leap_sound = 'sound/weapons/spiderlunge.ogg' + +// Multiplies damage if the victim is stunned in some form, including a successful leap. +/mob/living/simple_mob/animal/giant_spider/hunter/apply_bonus_melee_damage(atom/A, damage_amount) + if(isliving(A)) + var/mob/living/L = A + if(L.incapacitated(INCAPACITATION_DISABLED)) + return damage_amount * 1.5 + return ..() + + +// The actual leaping attack. +/mob/living/simple_mob/animal/giant_spider/hunter/do_special_attack(atom/A) + set waitfor = FALSE + set_AI_busy(TRUE) + + // Telegraph, since getting stunned suddenly feels bad. + do_windup_animation(A, leap_warmup) + sleep(leap_warmup) // For the telegraphing. + + // Do the actual leap. + status_flags |= LEAPING // Lets us pass over everything. + visible_message(span("danger","\The [src] leaps at \the [A]!")) + throw_at(get_step(get_turf(A), get_turf(src)), special_attack_max_range+1, 1, src) + playsound(src, leap_sound, 75, 1) + + sleep(5) // For the throw to complete. It won't hold up the AI ticker due to waitfor being false. + + if(status_flags & LEAPING) + status_flags &= ~LEAPING // Revert special passage ability. + + var/turf/T = get_turf(src) // Where we landed. This might be different than A's turf. + + . = FALSE + + // Now for the stun. + var/mob/living/victim = null + for(var/mob/living/L in T) // So player-controlled spiders only need to click the tile to stun them. + if(L == src) + continue + + if(ishuman(L)) + var/mob/living/carbon/human/H = L + if(H.check_shields(damage = 0, damage_source = src, attacker = src, def_zone = null, attack_text = "the leap")) + continue // We were blocked. + + victim = L + break + + if(victim) + victim.Weaken(2) + victim.visible_message(span("danger","\The [src] knocks down \the [victim]!")) + to_chat(victim, span("critical", "\The [src] jumps on you!")) + . = TRUE + + set_AI_busy(FALSE) + +// var/obj/item/weapon/grab/G = new(src, victim) +// put_in_active_hand(G) + +// G.synch() +// G.affecting = victim +// victim.LAssailant = src + +// visible_message("\The [src] seizes \the [victim] aggressively!") +// do_attack_animation(victim) + + +// This AI would've isolated people it stuns with its 'leap' attack, by dragging them away. +/datum/ai_holder/simple_mob/melee/hunter_spider + +/* + +/datum/ai_holder/simple_mob/melee/hunter_spider/post_special_attack(mob/living/L) + drag_away(L) + +// Called after a successful leap. +/datum/ai_holder/simple_mob/melee/hunter_spider/proc/drag_away(mob/living/L) + world << "Doing drag_away attack on [L]" + if(!istype(L)) + world << "Invalid type." + return FALSE + + // If they didn't get stunned, then don't bother. + if(!L.incapacitated(INCAPACITATION_DISABLED)) + world << "Not incapcitated." + return FALSE + + // Grab them. + if(!holder.start_pulling(L)) + world << "Failed to pull." + return FALSE + + holder.visible_message(span("danger","\The [holder] starts to drag \the [L] away!")) + + var/list/allies = list() + var/list/enemies = list() + for(var/mob/living/thing in hearers(vision_range, holder)) + if(thing == holder || thing == L) // Don't count ourselves or the thing we just started pulling. + continue + if(holder.IIsAlly(thing)) + allies += thing + else + enemies += thing + + // First priority: Move our victim to our friends. + if(allies.len) + world << "Going to move to ally" + give_destination(get_turf(pick(allies)), min_distance = 2, combat = TRUE) // This will switch our stance. + + // Second priority: Move our victim away from their friends. + // There's a chance of it derping and pulling towards enemies if there's more than two people. + // Preventing that will likely be both a lot of effort for developers and the CPU. + else if(enemies.len) + world << "Going to move away from enemies" + var/mob/living/hostile = pick(enemies) + var/turf/move_to = get_turf(hostile) + for(var/i = 1 to vision_range) // Move them this many steps away from their friend. + move_to = get_step_away(move_to, L, 7) + if(move_to) + give_destination(move_to, min_distance = 2, combat = TRUE) // This will switch our stance. + + // Third priority: Move our victim SOMEWHERE away from where they were. + else + world << "Going to move away randomly" + var/turf/move_to = get_turf(L) + move_to = get_step(move_to, pick(cardinal)) + for(var/i = 1 to vision_range) // Move them this many steps away from where they were before. + move_to = get_step_away(move_to, L, 7) + if(move_to) + give_destination(move_to, min_distance = 2, combat = TRUE) // This will switch our stance. +*/ diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm new file mode 100644 index 0000000000..1f1ecac678 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm @@ -0,0 +1,103 @@ +// Lurkers are somewhat similar to Hunters, however the big difference is that Lurkers have an imperfect cloak. +// Their AI will try to do hit and run tactics, striking the enemy when its "cloaked", for bonus damage and a stun. +// They keep attacking until the stun ends, then retreat to cloak again and repeat the cycle. +// Hitting the spider before it does its ambush attack will break the cloak and make the spider flee. + +/mob/living/simple_mob/animal/giant_spider/lurker + desc = "Translucent and white, it makes you shudder to look at it. This one has incandescent red eyes." + icon_state = "lurker" + icon_living = "lurker" + icon_dead = "lurker_dead" + + maxHealth = 100 + health = 100 + + poison_per_bite = 5 + + movement_cooldown = 5 + + melee_damage_lower = 10 + melee_damage_upper = 10 + poison_chance = 30 + poison_type = "cryptobiolin" + poison_per_bite = 1 + + player_msg = "You have an imperfect, but automatic stealth. If you attack something while 'hidden', then \ + you will do bonus damage, stun the target, and unstealth for a period of time.
\ + Getting attacked will also break your stealth." + + ai_holder_type = /datum/ai_holder/simple_mob/melee/hit_and_run + + var/cloaked = FALSE + var/cloaked_alpha = 45 // Lower = Harder to see. + var/cloaked_bonus_damage = 30 // This is added on top of the normal melee damage. + var/cloaked_weaken_amount = 3 // How long to stun for. + var/cloak_cooldown = 10 SECONDS // Amount of time needed to re-cloak after losing it. + var/last_uncloak = 0 // world.time + + +/mob/living/simple_mob/animal/giant_spider/lurker/proc/cloak() + if(cloaked) + return + animate(src, alpha = cloaked_alpha, time = 1 SECOND) + cloaked = TRUE + + +/mob/living/simple_mob/animal/giant_spider/lurker/proc/uncloak() + last_uncloak = world.time // This is assigned even if it isn't cloaked already, to 'reset' the timer if the spider is continously getting attacked. + if(!cloaked) + return + animate(src, alpha = initial(alpha), time = 1 SECOND) + cloaked = FALSE + + +// Check if cloaking if possible. +/mob/living/simple_mob/animal/giant_spider/lurker/proc/can_cloak() + if(stat) + return FALSE + if(last_uncloak + cloak_cooldown > world.time) + return FALSE + + return TRUE + + +// Called by things that break cloaks, like Technomancer wards. +/mob/living/simple_mob/animal/giant_spider/lurker/break_cloak() + uncloak() + + +/mob/living/simple_mob/animal/giant_spider/lurker/is_cloaked() + return cloaked + + +// Cloaks the spider automatically, if possible. +/mob/living/simple_mob/animal/giant_spider/lurker/handle_special() + if(!cloaked && can_cloak()) + cloak() + + +// Applies bonus base damage if cloaked. +/mob/living/simple_mob/animal/giant_spider/lurker/apply_bonus_melee_damage(atom/A, damage_amount) + if(cloaked) + return damage_amount + cloaked_bonus_damage + return ..() + +// Applies stun, then uncloaks. +/mob/living/simple_mob/animal/giant_spider/lurker/apply_melee_effects(atom/A) + if(cloaked) + if(isliving(A)) + var/mob/living/L = A + L.Weaken(cloaked_weaken_amount) + to_chat(L, span("danger", "\The [src] ambushes you!")) + playsound(L, 'sound/weapons/spiderlunge.ogg', 75, 1) + uncloak() + ..() // For the poison. + +// Force uncloaking if attacked. +/mob/living/simple_mob/animal/giant_spider/lurker/bullet_act(obj/item/projectile/P) + . = ..() + break_cloak() + +/mob/living/simple_mob/animal/giant_spider/lurker/hit_with_weapon(obj/item/O, mob/living/user, effective_force, hit_zone) + . = ..() + break_cloak() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/nurse.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/nurse.dm new file mode 100644 index 0000000000..62934195dd --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/nurse.dm @@ -0,0 +1,230 @@ +// Nurses, they create webs and eggs. +// They're fragile but their attacks can cause horrifying consequences. +/mob/living/simple_mob/animal/giant_spider/nurse + desc = "Furry and beige, it makes you shudder to look at it. This one has brilliant green eyes." + icon_state = "nurse" + icon_living = "nurse" + icon_dead = "nurse_dead" + + maxHealth = 40 + health = 40 + + movement_cooldown = 5 // A bit faster so that they can inject the eggs easier. + + melee_damage_lower = 5 // Doesn't do a lot of damage, since the goal is to make more spiders with egg attacks. + melee_damage_upper = 10 + poison_per_bite = 5 + poison_type = "stoxin" + + player_msg = "You can spin webs on an adjacent tile, or cocoon an object by clicking on it.
\ + You can also cocoon a dying or dead entity by clicking on them, and you will gain charges for egg-laying.
\ + To lay eggs, click a nearby tile. Laying eggs will deplete a charge." + ai_holder_type = /datum/ai_holder/simple_mob/melee/nurse_spider + + var/fed = 0 // Counter for how many egg laying 'charges' the spider has. + var/egg_inject_chance = 25 // One in four chance to get eggs. + var/egg_type = /obj/effect/spider/eggcluster/small + var/web_type = /obj/effect/spider/stickyweb/dark + + +/mob/living/simple_mob/animal/giant_spider/nurse/inject_poison(mob/living/L, target_zone) + ..() // Inject the stoxin here. + if(ishuman(L) && prob(egg_inject_chance)) + var/mob/living/carbon/human/H = L + var/obj/item/organ/external/O = H.get_organ(target_zone) + if(O) + var/eggcount = 0 + for(var/obj/effect/spider/eggcluster/E in O.implants) + eggcount++ + if(!eggcount) + var/eggs = new egg_type(O, src) + O.implants += eggs + to_chat(H, span("critical", "\The [src] injects something into your [O.name]!") ) // Oh god its laying eggs in me! + +// Webs target in a web if able to. +/mob/living/simple_mob/animal/giant_spider/nurse/attack_target(atom/A) + if(isturf(A)) + if(fed) + return lay_eggs(A) + return web_tile(A) + + if(isliving(A)) + var/mob/living/L = A + if(!L.stat) + return ..() + + if(!istype(A, /atom/movable)) + return + var/atom/movable/AM = A + + if(AM.anchored) + return ..() + + return spin_cocoon(AM) + +/mob/living/simple_mob/animal/giant_spider/nurse/proc/spin_cocoon(atom/movable/AM) + if(!istype(AM)) + return FALSE // We can't cocoon walls sadly. + visible_message(span("notice", "\The [src] begins to secrete a sticky substance around \the [AM].") ) + + // Get our AI to stay still. + set_AI_busy(TRUE) + + if(!do_mob(src, AM, 5 SECONDS)) + set_AI_busy(FALSE) + to_chat(src, span("warning", "You need to stay still to spin a web around \the [AM].")) + return FALSE + + set_AI_busy(FALSE) + + if(!AM) // Make sure it didn't get deleted for whatever reason. + to_chat(src, span("warning", "Whatever you were spinning a web for, its no longer there...")) + return FALSE + + if(!isturf(AM.loc)) + to_chat(src, span("warning", "You can't spin \the [AM] in a web while it is inside \the [AM.loc].")) + return FALSE + + if(!Adjacent(AM)) + to_chat(src, span("warning", "You need to be next to \the [AM] to spin it into a web.")) + return FALSE + + // Finally done with the checks. + var/obj/effect/spider/cocoon/C = new(AM.loc) + var/large_cocoon = FALSE + for(var/mob/living/L in C.loc) + if(istype(L, /mob/living/simple_mob/animal/giant_spider)) // Cannibalism is bad. + continue + fed++ + visible_message(span("warning","\The [src] sticks a proboscis into \the [L], and sucks a viscous substance out.")) + to_chat(src, span("notice", "You've fed upon \the [L], and can now lay [fed] cluster\s of eggs.")) + L.forceMove(C) + large_cocoon = TRUE + break + + // This part's pretty stupid. + for(var/obj/O in C.loc) + if(!O.anchored) + O.forceMove(C) + + // Todo: Put this code on the cocoon object itself? + if(large_cocoon) + C.icon_state = pick("cocoon_large1","cocoon_large2","cocoon_large3") + + return TRUE + +/mob/living/simple_mob/animal/giant_spider/nurse/handle_special() + set waitfor = FALSE + if(get_AI_stance() == STANCE_IDLE && !is_AI_busy() && isturf(loc)) + if(fed) + lay_eggs(loc) + else + web_tile(loc) + +/mob/living/simple_mob/animal/giant_spider/nurse/proc/web_tile(turf/T) + if(!istype(T)) + return FALSE + + var/obj/effect/spider/stickyweb/W = locate() in T + if(W) + return FALSE // Already got webs here. + + visible_message(span("notice", "\The [src] begins to secrete a sticky substance.") ) + // Get our AI to stay still. + set_AI_busy(TRUE) + + if(!do_mob(src, T, 5 SECONDS)) + set_AI_busy(FALSE) + to_chat(src, span("warning", "You need to stay still to spin a web on \the [T].")) + return FALSE + + W = locate() in T + if(W) + return FALSE // Spamclick protection. + + set_AI_busy(FALSE) + new web_type(T) + return TRUE + + +/mob/living/simple_mob/animal/giant_spider/nurse/proc/lay_eggs(turf/T) + if(!istype(T)) + return FALSE + + if(!fed) + return FALSE + + var/obj/effect/spider/eggcluster/E = locate() in T + if(E) + return FALSE // Already got eggs here. + + visible_message(span("notice", "\The [src] begins to lay a cluster of eggs.") ) + // Get our AI to stay still. + set_AI_busy(TRUE) + + if(!do_mob(src, T, 5 SECONDS)) + set_AI_busy(FALSE) + to_chat(src, span("warning", "You need to stay still to lay eggs on \the [T].")) + return FALSE + + E = locate() in T + if(E) + return FALSE // Spamclick protection. + + set_AI_busy(FALSE) + new egg_type(T) + fed-- + return TRUE + + +// Variant that 'blocks' light (by being a negative light source). +// This is done to make webbed rooms scary and allow for spiders on the other side of webs to see prey. +/obj/effect/spider/stickyweb/dark + name = "dense web" + desc = "It's sticky, and blocks a lot of light." + light_color = "#FFFFFF" + light_range = 2 + light_power = -3 + +// This is still stupid, but whatever. +/mob/living/simple_mob/animal/giant_spider/nurse/hat + desc = "Furry and beige, it makes you shudder to look at it. This one has brilliant green eyes and a tiny nurse hat." + icon_state = "nursemed" + icon_living = "nursemed" + icon_dead = "nursemed_dead" + + +// The AI for nurse spiders. Wraps things in webs by 'attacking' them. +/datum/ai_holder/simple_mob/melee/nurse_spider + wander = TRUE + base_wander_delay = 8 + cooperative = FALSE // So we don't ask our spider friends to attack things we're webbing. This might also make them stay at the base if their friends find tasty explorers. + +// Get us unachored objects as an option as well. +/datum/ai_holder/simple_mob/melee/nurse_spider/list_targets() + . = ..() + + var/static/alternative_targets = typecacheof(list(/obj/item, /obj/structure)) + + for(var/AT in typecache_filter_list(range(vision_range, holder), alternative_targets)) + var/obj/O = AT + if(can_see(holder, O, vision_range) && !O.anchored) + . += O + +// Select an obj if no mobs are around. +/datum/ai_holder/melee/nurse_spider/pick_target(list/targets) + var/mobs_only = locate(/mob/living) in targets // If a mob is in the list of targets, then ignore objects. + if(mobs_only) + for(var/A in targets) + if(!isliving(A)) + targets -= A + + return ..(targets) + +/datum/ai_holder/simple_mob/melee/nurse_spider/can_attack(atom/movable/the_target) + . = ..() + if(!.) // Parent returned FALSE. + if(istype(the_target, /obj)) + var/obj/O = the_target + if(!O.anchored) + return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/pepper.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/pepper.dm new file mode 100644 index 0000000000..1c2eaa6dda --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/pepper.dm @@ -0,0 +1,21 @@ +// Pepper spiders inject condensed capsaicin into their victims. + +/mob/living/simple_mob/animal/giant_spider/pepper + desc = "Red and brown, it makes you shudder to look at it. This one has glinting red eyes." + icon_state = "pepper" + icon_living = "pepper" + icon_dead = "pepper_dead" + + maxHealth = 210 + health = 210 + + melee_damage_lower = 8 + melee_damage_upper = 15 + + poison_chance = 20 + poison_per_bite = 5 + poison_type = "condensedcapsaicin_v" + +/mob/living/simple_mob/animal/giant_spider/pepper/initialize() + adjust_scale(1.1) + return ..() \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/phorogenic.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/phorogenic.dm new file mode 100644 index 0000000000..d9508a05d8 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/phorogenic.dm @@ -0,0 +1,56 @@ +// Phorogenic spiders explode when they die. +// You really shouldn't melee them. + +/mob/living/simple_mob/animal/giant_spider/phorogenic + desc = "Crystalline and purple, it makes you shudder to look at it. This one has haunting purple eyes." + icon_state = "phoron" + icon_living = "phoron" + icon_dead = "phoron_dead" + + maxHealth = 225 + health = 225 + taser_kill = FALSE //You will need more than a peashooter to kill the juggernaut. + + melee_damage_lower = 25 + melee_damage_upper = 40 + attack_armor_pen = 15 + + movement_cooldown = 15 + + poison_chance = 30 + poison_per_bite = 0.5 + poison_type = "phoron" + + var/exploded = FALSE + var/explosion_dev_range = 1 + var/explosion_heavy_range = 2 + var/explosion_light_range = 4 + var/explosion_flash_range = 6 // This doesn't do anything iirc. + + var/explosion_delay_lower = 1 SECOND // Lower bound for explosion delay. + var/explosion_delay_upper = 2 SECONDS // Upper bound. + +/mob/living/simple_mob/animal/giant_spider/phorogenic/initialize() + adjust_scale(1.25) + return ..() + +/mob/living/simple_mob/animal/giant_spider/phorogenic/death() + visible_message(span("critical", "\The [src]'s body begins to rupture!")) + var/delay = rand(explosion_delay_lower, explosion_delay_upper) + spawn(0) + // Flash black and red as a warning. + for(var/i = 1 to delay) + if(i % 2 == 0) + color = "#000000" + else + color = "#FF0000" + sleep(1) + + spawn(delay) + // The actual boom. + if(src && !exploded) + visible_message(span("danger", "\The [src]'s body detonates!")) + exploded = TRUE + explosion(src.loc, explosion_dev_range, explosion_heavy_range, explosion_light_range, explosion_flash_range) + return ..() + diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/thermic.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/thermic.dm new file mode 100644 index 0000000000..dc0a345865 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/thermic.dm @@ -0,0 +1,20 @@ +// Thermic spiders inject a special variant of thermite that burns someone from the inside. + +/mob/living/simple_mob/animal/giant_spider/thermic + desc = "Mirage-cloaked and orange, it makes you shudder to look at it. This one has simmering orange eyes." + icon_state = "pit" + icon_living = "pit" + icon_dead = "pit_dead" + + maxHealth = 175 + health = 175 + + melee_damage_lower = 10 + melee_damage_upper = 25 + + heat_resist = 0.75 + cold_resist = -0.50 + + poison_chance = 30 + poison_per_bite = 1 + poison_type = "thermite_v" diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/tunneler.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/tunneler.dm new file mode 100644 index 0000000000..82a68d4c1f --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/tunneler.dm @@ -0,0 +1,185 @@ +// Tunnelers have a special ability that allows them to charge at an enemy by tunneling towards them. +// Any mobs inbetween the tunneler's path and the target will be stunned if the tunneler hits them. +// The target will suffer a stun as well, if the tunneler hits them at the end. A successful hit will stop the tunneler. +// If the target moves fast enough, the tunneler can miss, causing it to overshoot. +// If the tunneler hits a solid wall, the tunneler will suffer a stun. + +/mob/living/simple_mob/animal/giant_spider/tunneler + desc = "Sandy and brown, it makes you shudder to look at it. This one has glittering yellow eyes." + icon_state = "tunneler" + icon_living = "tunneler" + icon_dead = "tunneler_dead" + + maxHealth = 120 + health = 120 + + melee_damage_lower = 10 + melee_damage_upper = 10 + + poison_chance = 15 + poison_per_bite = 3 + poison_type = "serotrotium_v" + +// ai_holder_type = /datum/ai_holder/simple_mob/melee/tunneler + + player_msg = "You can perform a tunneling attack by clicking on someone from a distance.
\ + There is a noticable travel delay as you tunnel towards the tile the target was at when you started the tunneling attack.
\ + Any entities inbetween you and the targeted tile will be stunned for a brief period of time.
\ + Whatever is on the targeted tile when you arrive will suffer a potent stun.
\ + If nothing is on the targeted tile, you will overshoot and keep going for a few more tiles.
\ + If you hit a wall or other solid structure during that time, you will suffer a lengthy stun and be vulnerable to more harm." + + // Tunneling is a special attack, similar to the hunter's Leap. + special_attack_min_range = 2 + special_attack_max_range = 6 + special_attack_cooldown = 10 SECONDS + + var/tunnel_warning = 0.5 SECONDS // How long the dig telegraphing is. + var/tunnel_tile_speed = 2 // How long to wait between each tile. Higher numbers result in an easier to dodge tunnel attack. + +/mob/living/simple_mob/animal/giant_spider/tunneler/frequent + special_attack_cooldown = 5 SECONDS + +/mob/living/simple_mob/animal/giant_spider/tunneler/fast + tunnel_tile_speed = 1 + +/mob/living/simple_mob/animal/giant_spider/tunneler/should_special_attack(atom/A) + // Make sure its possible for the spider to reach the target so it doesn't try to go through a window. + var/turf/destination = get_turf(A) + var/turf/starting_turf = get_turf(src) + var/turf/T = starting_turf + for(var/i = 1 to get_dist(starting_turf, destination)) + if(T == destination) + break + + T = get_step(T, get_dir(T, destination)) + if(T.check_density(ignore_mobs = TRUE)) + return FALSE + return T == destination + + +/mob/living/simple_mob/animal/giant_spider/tunneler/do_special_attack(atom/A) + set waitfor = FALSE + set_AI_busy(TRUE) + + // Save where we're gonna go soon. + var/turf/destination = get_turf(A) + var/turf/starting_turf = get_turf(src) + + // Telegraph to give a small window to dodge if really close. + do_windup_animation(A, tunnel_warning) + sleep(tunnel_warning) // For the telegraphing. + + // Do the dig! + visible_message(span("danger","\The [src] tunnels towards \the [A]!")) + submerge() + + if(handle_tunnel(destination) == FALSE) + set_AI_busy(FALSE) + emerge() + return FALSE + + // Did we make it? + if(!(src in destination)) + set_AI_busy(FALSE) + emerge() + return FALSE + + var/overshoot = TRUE + + // Test if something is at destination. + for(var/mob/living/L in destination) + if(L == src) + continue + + visible_message(span("danger","\The [src] erupts from underneath, and hits \the [L]!")) + playsound(L, 'sound/weapons/heavysmash.ogg', 75, 1) + L.Weaken(3) + overshoot = FALSE + + if(!overshoot) // We hit the target, or something, at destination, so we're done. + set_AI_busy(FALSE) + emerge() + return TRUE + + // Otherwise we need to keep going. + to_chat(src, span("warning", "You overshoot your target!")) + playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1) + var/dir_to_go = get_dir(starting_turf, destination) + for(var/i = 1 to rand(2, 4)) + destination = get_step(destination, dir_to_go) + + if(handle_tunnel(destination) == FALSE) + set_AI_busy(FALSE) + emerge() + return FALSE + + set_AI_busy(FALSE) + emerge() + return FALSE + + + +// Does the tunnel movement, stuns enemies, etc. +/mob/living/simple_mob/animal/giant_spider/tunneler/proc/handle_tunnel(turf/destination) + var/turf/T = get_turf(src) // Hold our current tile. + + // Regular tunnel loop. + for(var/i = 1 to get_dist(src, destination)) + if(stat) + return FALSE // We died or got knocked out on the way. + if(loc == destination) + break // We somehow got there early. + + // Update T. + T = get_step(src, get_dir(src, destination)) + if(T.check_density(ignore_mobs = TRUE)) + to_chat(src, span("critical", "You hit something really solid!")) + playsound(src, "punch", 75, 1) + Weaken(5) + add_modifier(/datum/modifier/tunneler_vulnerable, 10 SECONDS) + return FALSE // Hit a wall. + + // Stun anyone in our way. + for(var/mob/living/L in T) + playsound(L, 'sound/weapons/heavysmash.ogg', 75, 1) + L.Weaken(2) + + // Get into the tile. + forceMove(T) + + // Visuals and sound. + dig_under_floor(get_turf(src)) + playsound(src, 'sound/effects/break_stone.ogg', 75, 1) + sleep(tunnel_tile_speed) + +// For visuals. +/mob/living/simple_mob/animal/giant_spider/tunneler/proc/submerge() + alpha = 0 + dig_under_floor(get_turf(src)) + new /obj/effect/temporary_effect/tunneler_hole(get_turf(src)) + +// Ditto. +/mob/living/simple_mob/animal/giant_spider/tunneler/proc/emerge() + alpha = 255 + dig_under_floor(get_turf(src)) + new /obj/effect/temporary_effect/tunneler_hole(get_turf(src)) + +/mob/living/simple_mob/animal/giant_spider/tunneler/proc/dig_under_floor(turf/T) + new /obj/item/weapon/ore/glass(T) // This will be rather weird when on station but the alternative is too much work. + +/obj/effect/temporary_effect/tunneler_hole + name = "hole" + desc = "A collapsing tunnel hole." + icon_state = "tunnel_hole" + time_to_die = 1 MINUTE + +/datum/modifier/tunneler_vulnerable + name = "Vulnerable" + desc = "You are vulnerable to more harm than usual." + on_created_text = "You feel vulnerable..." + on_expired_text = "You feel better." + stacks = MODIFIER_STACK_EXTEND + + incoming_damage_percent = 2 + evasion = -100 \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/webslinger.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/webslinger.dm new file mode 100644 index 0000000000..0024bc8962 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/webslinger.dm @@ -0,0 +1,37 @@ +/mob/living/simple_mob/animal/giant_spider/webslinger + desc = "Furry and green, it makes you shudder to look at it. This one has brilliant green eyes, and a cloak of web." + tt_desc = "X Brachypelma phorus balisticus" + icon_state = "webslinger" + icon_living = "webslinger" + icon_dead = "webslinger_dead" + maxHealth = 90 + health = 90 + projectilesound = 'sound/weapons/thudswoosh.ogg' + projectiletype = /obj/item/projectile/webball + base_attack_cooldown = 10 + melee_damage_lower = 8 + melee_damage_upper = 15 + poison_per_bite = 2 + poison_type = "psilocybin" + player_msg = "You can fire a ranged attack by clicking on an enemy or tile at a distance." + ai_holder_type = /datum/ai_holder/simple_mob/ranged + +// Check if we should bola, or just shoot the pain ball +/mob/living/simple_mob/animal/giant_spider/webslinger/should_special_attack(atom/A) + if(ismob(A)) + if(ishuman(A)) + var/mob/living/carbon/human/H = A + if(!H.legcuffed) + return TRUE + return FALSE + +// Now we've got a running human in sight, time to throw the bola +/mob/living/simple_mob/animal/giant_spider/webslinger/do_special_attack(atom/A) + set waitfor = FALSE + set_AI_busy(TRUE) + var/obj/item/projectile/bola/B = new /obj/item/projectile/bola(src.loc) + playsound(src, 'sound/weapons/thudswoosh.ogg', 100, 1) + if(!B) + return + B.launch(A) + set_AI_busy(FALSE) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/crab.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/crab.dm new file mode 100644 index 0000000000..3b40147794 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/crab.dm @@ -0,0 +1,25 @@ +//Look Sir, free crabs! +/mob/living/simple_mob/animal/passive/crab + name = "crab" + desc = "A hard-shelled crustacean. Seems quite content to lounge around all the time." + tt_desc = "E Cancer bellianus" + faction = "crabs" + + icon_state = "crab" + icon_living = "crab" + icon_dead = "crab_dead" + + mob_size = MOB_SMALL + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stomps" + friendly = "pinches" + + say_list_type = /datum/say_list/crab + +//COFFEE! SQUEEEEEEEEE! +/mob/living/simple_mob/animal/passive/crab/Coffee + name = "Coffee" + real_name = "Coffee" + desc = "It's Coffee, the other pet!" diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/fish.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/fish.dm new file mode 100644 index 0000000000..bdf8853b4b --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/fish.dm @@ -0,0 +1,77 @@ +// Different types of fish! They are all subtypes of this tho +/mob/living/simple_mob/animal/passive/fish + name = "fish" + desc = "Its a fishy. No touchy fishy." + icon = 'icons/mob/fish.dmi' + + mob_size = MOB_SMALL + // So fish are actually underwater. + plane = TURF_PLANE + layer = UNDERWATER_LAYER + + // By default they can be in any water turf. Subtypes might restrict to deep/shallow etc + var/global/list/suitable_turf_types = list( + /turf/simulated/floor/beach/water, + /turf/simulated/floor/beach/coastline, + /turf/simulated/floor/holofloor/beach/water, + /turf/simulated/floor/holofloor/beach/coastline, + /turf/simulated/floor/water + ) + +// Makes the AI unable to willingly go on land. +/mob/living/simple_mob/animal/passive/fish/IMove(newloc) + if(is_type_in_list(newloc, suitable_turf_types)) + return ..() // Procede as normal. + return MOVEMENT_FAILED // Don't leave the water! + +// Take damage if we are not in water +/mob/living/simple_mob/animal/passive/fish/handle_breathing() + var/turf/T = get_turf(src) + if(T && !is_type_in_list(T, suitable_turf_types)) + if(prob(50)) + say(pick("Blub", "Glub", "Burble")) + adjustBruteLoss(unsuitable_atoms_damage) + +// Subtypes. +/mob/living/simple_mob/animal/passive/fish/bass + name = "bass" + tt_desc = "E Micropterus notius" + icon_state = "bass-swim" + icon_living = "bass-swim" + icon_dead = "bass-dead" + +/mob/living/simple_mob/animal/passive/fish/trout + name = "trout" + tt_desc = "E Salmo trutta" + icon_state = "trout-swim" + icon_living = "trout-swim" + icon_dead = "trout-dead" + +/mob/living/simple_mob/animal/passive/fish/salmon + name = "salmon" + tt_desc = "E Oncorhynchus nerka" + icon_state = "salmon-swim" + icon_living = "salmon-swim" + icon_dead = "salmon-dead" + +/mob/living/simple_mob/animal/passive/fish/perch + name = "perch" + tt_desc = "E Perca flavescens" + icon_state = "perch-swim" + icon_living = "perch-swim" + icon_dead = "perch-dead" + +/mob/living/simple_mob/animal/passive/fish/pike + name = "pike" + tt_desc = "E Esox aquitanicus" + icon_state = "pike-swim" + icon_living = "pike-swim" + icon_dead = "pike-dead" + +/mob/living/simple_mob/animal/passive/fish/koi + name = "koi" + tt_desc = "E Cyprinus rubrofuscus" + icon_state = "koi-swim" + icon_living = "koi-swim" + icon_dead = "koi-dead" + diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/lizard.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/lizard.dm new file mode 100644 index 0000000000..adb2ea83ca --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/lizard.dm @@ -0,0 +1,24 @@ +/mob/living/simple_mob/animal/passive/lizard + name = "lizard" + desc = "A cute, tiny lizard." + tt_desc = "E Anolis cuvieri" + + icon_state = "lizard" + icon_living = "lizard" + icon_dead = "lizard-dead" + + health = 5 + maxHealth = 5 + mob_size = MOB_MINISCULE + + response_help = "pets" + response_disarm = "shoos" + response_harm = "stomps on" + + attacktext = list("bitten") + melee_damage_lower = 1 + melee_damage_upper = 2 + + speak_emote = list("hisses") + + say_list_type = /datum/say_list/lizard diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/misc.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/misc.dm new file mode 100644 index 0000000000..6fc73fc5f9 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/misc.dm @@ -0,0 +1,29 @@ +/mob/living/simple_mob/animal/passive/yithian + name = "yithian" + desc = "A friendly creature vaguely resembling an oversized snail without a shell." + tt_desc = "J Escargot escargot" // a product of Jade, which is a planet that totally exists + + icon_state = "yithian" + icon_living = "yithian" + icon_dead = "yithian_dead" + icon = 'icons/jungle.dmi' + + // Same stats as lizards. + health = 5 + maxHealth = 5 + mob_size = MOB_MINISCULE + +/mob/living/simple_mob/animal/passive/tindalos + name = "tindalos" + desc = "It looks like a large, flightless grasshopper." + tt_desc = "J Locusta bruchus" + + icon_state = "tindalos" + icon_living = "tindalos" + icon_dead = "tindalos_dead" + icon = 'icons/jungle.dmi' + + // Same stats as lizards. + health = 5 + maxHealth = 5 + mob_size = MOB_MINISCULE \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm new file mode 100644 index 0000000000..70afdaf910 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm @@ -0,0 +1,115 @@ +/mob/living/simple_mob/animal/passive/mouse + name = "mouse" + real_name = "mouse" + desc = "It's a small rodent." + tt_desc = "E Mus musculus" + icon_state = "mouse_gray" + item_state = "mouse_gray" + icon_living = "mouse_gray" + icon_dead = "mouse_gray_dead" + + maxHealth = 5 + health = 5 + + mob_size = MOB_MINISCULE + pass_flags = PASSTABLE +// can_pull_size = ITEMSIZE_TINY +// can_pull_mobs = MOB_PULL_NONE + layer = MOB_LAYER + density = 0 + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stamps on" + + min_oxy = 16 //Require atleast 16kPA oxygen + minbodytemp = 223 //Below -50 Degrees Celcius + maxbodytemp = 323 //Above 50 Degrees Celcius + + has_langs = list("Mouse") + + holder_type = /obj/item/weapon/holder/mouse + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + say_list_type = /datum/say_list/mouse + + var/body_color //brown, gray and white, leave blank for random + +/mob/living/simple_mob/animal/passive/mouse/New() + ..() + + verbs += /mob/living/proc/ventcrawl + verbs += /mob/living/proc/hide + + if(name == initial(name)) + name = "[name] ([rand(1, 1000)])" + real_name = name + + if(!body_color) + body_color = pick( list("brown","gray","white") ) + icon_state = "mouse_[body_color]" + item_state = "mouse_[body_color]" + icon_living = "mouse_[body_color]" + icon_dead = "mouse_[body_color]_dead" + icon_rest = "mouse_[body_color]_sleep" + desc = "A small [body_color] rodent, often seen hiding in maintenance areas and making a nuisance of itself." + +/mob/living/simple_mob/animal/passive/mouse/Crossed(AM as mob|obj) + if( ishuman(AM) ) + if(!stat) + var/mob/M = AM + M.visible_message("\icon[src] Squeek!") + playsound(src, 'sound/effects/mouse_squeak.ogg', 35, 1) + ..() + +/mob/living/simple_mob/animal/passive/mouse/death() + layer = MOB_LAYER + playsound(src, 'sound/effects/mouse_squeak_loud.ogg', 35, 1) + if(client) + client.time_died_as_mouse = world.time + ..() + +/mob/living/simple_mob/animal/passive/mouse/cannot_use_vents() + return + +/mob/living/simple_mob/animal/passive/mouse/proc/splat() + src.health = 0 + src.stat = DEAD + src.icon_dead = "mouse_[body_color]_splat" + src.icon_state = "mouse_[body_color]_splat" + layer = MOB_LAYER + if(client) + client.time_died_as_mouse = world.time + +/* + * Mouse types + */ + +/mob/living/simple_mob/animal/passive/mouse/white + body_color = "white" + icon_state = "mouse_white" + +/mob/living/simple_mob/animal/passive/mouse/gray + body_color = "gray" + icon_state = "mouse_gray" + +/mob/living/simple_mob/animal/passive/mouse/brown + body_color = "brown" + icon_state = "mouse_brown" + +//TOM IS ALIVE! SQUEEEEEEEE~K :) +/mob/living/simple_mob/animal/passive/mouse/brown/Tom + name = "Tom" + desc = "Jerry the cat is not amused." + +/mob/living/simple_mob/animal/passive/mouse/brown/Tom/New() + ..() + // Change my name back, don't want to be named Tom (666) + name = initial(name) + + +// Mouse noises +/datum/say_list/mouse + speak = list("Squeek!","SQUEEK!","Squeek?") + emote_hear = list("squeeks","squeaks","squiks") + emote_see = list("runs in a circle", "shakes", "scritches at something") \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/passive.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/passive.dm new file mode 100644 index 0000000000..c488d1ed28 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/passive.dm @@ -0,0 +1,5 @@ +// Passive mobs can't attack things, and will run away instead. +// They can also be displaced by all mobs. +/mob/living/simple_mob/animal/passive + ai_holder_type = /datum/ai_holder/simple_mob/passive + mob_bump_flag = 0 \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/penguin.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/penguin.dm new file mode 100644 index 0000000000..7fcd020f8e --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/penguin.dm @@ -0,0 +1,27 @@ +/mob/living/simple_mob/animal/passive/penguin + name = "penguin" + desc = "An ungainly, waddling, cute, and VERY well-dressed bird." + tt_desc = "Aptenodytes forsteri" + icon_state = "penguin" + icon_living = "penguin" + icon_dead = "penguin_dead" + + maxHealth = 20 + health = 20 + minbodytemp = 175 // Same as Sif mobs. + + response_help = "pets" + response_disarm = "pushes aside" + response_harm = "hits" + + harm_intent_damage = 5 + melee_damage_lower = 10 + melee_damage_upper = 15 + attacktext = list("pecked") + + has_langs = list("Bird") + +/mob/living/simple_mob/animal/passive/penguin/tux + name = "Tux" + desc = "A penguin that has been known to associate with gnus." + speak_emote = list("interjects") diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/bird.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/bird.dm new file mode 100644 index 0000000000..5e77a57a3c --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/bird.dm @@ -0,0 +1,93 @@ +// Base bird type. + +/mob/living/simple_mob/animal/passive/bird + name = "bird" + desc = "A domesticated bird. Tweet tweet!" + player_msg = "You are able to fly." + + icon = 'icons/mob/birds.dmi' + icon_state = "parrot" + item_state = null + icon_rest = "parrot-held" + icon_dead = "parrot-dead" + + pass_flags = PASSTABLE + + health = 30 + maxHealth = 30 + melee_damage_lower = 3 + melee_damage_upper = 3 + + movement_cooldown = 0 + hovering = TRUE // Birds can fly. + softfall = TRUE + parachuting = TRUE + + attacktext = list("claws", "pecks") + speak_emote = list("chirps", "caws") + has_langs = list("Bird") + response_help = "pets" + response_disarm = "gently moves aside" + response_harm = "swats" + + say_list_type = /datum/say_list/bird + holder_type = /obj/item/weapon/holder/bird + +/datum/say_list/bird + speak = list("Chirp!","Caw!","Screech!","Squawk!") + emote_hear = list("chirps","caws") + emote_see = list("shakes their head", "ruffles their feathers") + +/obj/item/weapon/holder/bird + name = "bird" + desc = "It's a bird!" + icon_state = null + item_icons = null + w_class = ITEMSIZE_SMALL + +/obj/item/weapon/holder/bird/sync(var/mob/living/simple_mob/SM) + ..() + icon_state = SM.icon_rest // Looks better if the bird isn't flapping constantly in the UI. + +// Subtypes for birbs. + +/mob/living/simple_mob/animal/passive/bird/black_bird + name = "common blackbird" + desc = "A species of bird, both the males and females are known to be territorial on their breeding grounds." + icon_state = "commonblackbird" + icon_dead = "commonblackbird-dead" + tt_desc = "E Turdus merula" + icon_scale = 0.5 + +/mob/living/simple_mob/animal/passive/bird/azure_tit + name = "azure tit" + desc = "A species of bird, colored blue and white." + icon_state = "azuretit" + icon_dead = "azuretit-dead" + tt_desc = "E Cyanistes cyanus" + icon_scale = 0.5 + +/mob/living/simple_mob/animal/passive/bird/european_robin + name = "european robin" + desc = "A species of bird, they have been studied for their sense of magnetoreception." + icon_state = "europeanrobin" + icon_dead = "europeanrobin-dead" + tt_desc = "E Erithacus rubecula" + icon_scale = 0.5 + +/mob/living/simple_mob/animal/passive/bird/goldcrest + name = "goldcrest" + desc = "A species of bird, they were once called 'king of the birds' in ancient human folklore, for their golden crest. \ + Today, their scientific name still elude towards this, with regulus, meaning petty king." + icon_state = "goldcrest" + icon_dead = "goldcrest-dead" + tt_desc = "E Regulus regulus" + icon_scale = 0.5 + +/mob/living/simple_mob/animal/passive/bird/ringneck_dove + name = "ringneck dove" + desc = "A species of bird. They are also known as the barbary dove, and have a distinct ring-like shape around the back of their neck." + icon_state = "ringneckdove" + icon_dead = "ringneckdove-dead" + tt_desc = "E Streptopelia risoria" // This is actually disputed IRL but since we can't tell the future it'll stay the same for 500+ years. + icon_scale = 0.5 diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat.dm new file mode 100644 index 0000000000..6ded938dff --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat.dm @@ -0,0 +1,136 @@ +/mob/living/simple_mob/animal/passive/cat + name = "cat" + desc = "A domesticated, feline pet. Has a tendency to adopt crewmembers." + tt_desc = "E Felis silvestris catus" + icon_state = "cat2" + item_state = "cat2" + icon_living = "cat2" + icon_dead = "cat2_dead" + icon_rest = "cat2_rest" + + movement_cooldown = 0.5 SECONDS + + see_in_dark = 6 // Not sure if this actually works. + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + + holder_type = /obj/item/weapon/holder/cat + mob_size = MOB_SMALL + + has_langs = list("Cat") + + var/mob/living/friend = null // Our best pal, who we'll follow. Meow. + +/mob/living/simple_mob/animal/passive/cat/handle_special() + if(!stat && prob(2)) // spooky + var/mob/observer/dead/spook = locate() in range(src, 5) + if(spook) + var/turf/T = get_turf(spook) + var/list/visible = list() + for(var/obj/O in T.contents) + if(!O.invisibility && O.name) + visible += O + if(visible.len) + var/atom/A = pick(visible) + visible_emote("suddenly stops and stares at something unseen[istype(A) ? " near [A]":""].") + +// Instakills mice. +/mob/living/simple_mob/animal/passive/cat/apply_melee_effects(var/atom/A) + if(ismouse(A)) + var/mob/living/simple_mob/animal/passive/mouse/mouse = A + if(mouse.getMaxHealth() < 20) // In case a badmin makes giant mice or something. + mouse.splat() + visible_emote(pick("bites \the [mouse]!", "toys with \the [mouse].", "chomps on \the [mouse]!")) + else + ..() + +/mob/living/simple_mob/animal/passive/cat/IIsAlly(mob/living/L) + if(L == friend) // Always be pals with our special friend. + return TRUE + + . = ..() + + if(.) // We're pals, but they might be a dirty mouse... + if(ismouse(L)) + return FALSE // Cats and mice can never get along. + +/mob/living/simple_mob/animal/passive/cat/verb/become_friends() + set name = "Become Friends" + set category = "IC" + set src in view(1) + + var/mob/living/L = usr + if(!istype(L)) + return // Fuck off ghosts. + + if(friend) + if(friend == usr) + to_chat(L, span("notice", "\The [src] is already your friend! Meow!")) + return + else + to_chat(L, span("warning", "\The [src] ignores you.")) + return + + friend = L + face_atom(L) + to_chat(L, span("notice", "\The [src] is now your friend! Meow.")) + visible_emote(pick("nuzzles [friend].", "brushes against [friend].", "rubs against [friend].", "purrs.")) + + if(has_AI()) + var/datum/ai_holder/AI = ai_holder + AI.set_follow(friend) + + +//RUNTIME IS ALIVE! SQUEEEEEEEE~ +/mob/living/simple_mob/animal/passive/cat/runtime + name = "Runtime" + desc = "Her fur has the look and feel of velvet, and her tail quivers occasionally." + tt_desc = "E Felis silvestris medicalis" // a hypoallergenic breed produced by NT for... medical purposes? Sure. + gender = FEMALE + icon_state = "cat" + item_state = "cat" + icon_living = "cat" + icon_dead = "cat_dead" + icon_rest = "cat_rest" + +/mob/living/simple_mob/animal/passive/cat/kitten + name = "kitten" + desc = "D'aaawwww" + icon_state = "kitten" + item_state = "kitten" + icon_living = "kitten" + icon_dead = "kitten_dead" + gender = NEUTER + +/mob/living/simple_mob/animal/passive/cat/kitten/initialize() + if(gender == NEUTER) + gender = pick(MALE, FEMALE) + return ..() + +// Leaving this here for now. +/obj/item/weapon/holder/cat/fluff/bones + name = "Bones" + desc = "It's Bones! Meow." + gender = MALE + icon_state = "cat3" + +/mob/living/simple_mob/animal/passive/cat/bones + name = "Bones" + desc = "That's Bones the cat. He's a laid back, black cat. Meow." + gender = MALE + icon_state = "cat3" + item_state = "cat3" + icon_living = "cat3" + icon_dead = "cat3_dead" + icon_rest = "cat3_rest" + holder_type = /obj/item/weapon/holder/cat/fluff/bones + + +/datum/say_list/cat + speak = list("Meow!","Esp!","Purr!","HSSSSS") + emote_hear = list("meows","mews") + emote_see = list("shakes their head", "shivers") + say_maybe_target = list("Meow?","Mew?","Mao?") + say_got_target = list("MEOW!","HSSSS!","REEER!") + diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm new file mode 100644 index 0000000000..fbfafc426a --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm @@ -0,0 +1,234 @@ +/mob/living/simple_mob/animal/passive/dog + name = "dog" + real_name = "dog" + desc = "It's a dog." + tt_desc = "E Canis lupus familiaris" + icon_state = "corgi" + icon_living = "corgi" + icon_dead = "corgi_dead" + + health = 20 + maxHealth = 20 + + response_help = "pets" + response_disarm = "bops" + response_harm = "kicks" + + mob_size = MOB_SMALL + + has_langs = list("Dog") + + say_list_type = /datum/say_list/dog + + meat_amount = 3 + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat/corgi + + var/obj/item/inventory_head + var/obj/item/inventory_back + + +/mob/living/simple_mob/animal/passive/dog/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(istype(O, /obj/item/weapon/newspaper)) + if(!stat) + for(var/mob/M in viewers(user, null)) + if ((M.client && !( M.blinded ))) + M.show_message("[user] baps [name] on the nose with the rolled up [O]") + spawn(0) + for(var/i in list(1,2,4,8,4,2,1,2)) + set_dir(i) + sleep(1) + else + ..() + +/mob/living/simple_mob/animal/passive/dog/regenerate_icons() + overlays = list() + + if(inventory_head) + var/head_icon_state = inventory_head.icon_state + if(health <= 0) + head_icon_state += "2" + + var/icon/head_icon = image('icons/mob/corgi_head.dmi',head_icon_state) + if(head_icon) + overlays += head_icon + + if(inventory_back) + var/back_icon_state = inventory_back.icon_state + if(health <= 0) + back_icon_state += "2" + + var/icon/back_icon = image('icons/mob/corgi_back.dmi',back_icon_state) + if(back_icon) + overlays += back_icon + + return + + + + +/obj/item/weapon/reagent_containers/food/snacks/meat/corgi + name = "corgi meat" + desc = "Tastes like... well, you know..." + + + + +/datum/say_list/dog + speak = list("YAP", "Woof!", "Bark!", "AUUUUUU") + emote_hear = list("barks", "woofs", "yaps","pants") + emote_see = list("shakes its head", "shivers") + +// This exists so not every type of dog has to be a subtype of corgi, and in case we get more dog sprites +/mob/living/simple_mob/animal/passive/dog/corgi + name = "corgi" + real_name = "corgi" + desc = "It's a corgi." + tt_desc = "E Canis lupus familiaris" + icon_state = "corgi" + icon_living = "corgi" + icon_dead = "corgi_dead" + +/mob/living/simple_mob/animal/passive/dog/corgi/puppy + name = "corgi puppy" + real_name = "corgi" + desc = "It's a corgi puppy." + icon_state = "puppy" + icon_living = "puppy" + icon_dead = "puppy_dead" + +//pupplies cannot wear anything. +/mob/living/simple_mob/animal/passive/dog/corgi/puppy/Topic(href, href_list) + if(href_list["remove_inv"] || href_list["add_inv"]) + usr << "You can't fit this on [src]" + return + ..() + +/mob/living/simple_mob/animal/passive/dog/corgi/puppy/Bockscar + name = "Bockscar" + real_name = "Bockscar" + +//IAN! SQUEEEEEEEEE~ +/mob/living/simple_mob/animal/passive/dog/corgi/Ian + name = "Ian" + real_name = "Ian" //Intended to hold the name without altering it. + gender = MALE + desc = "It's a corgi." + var/turns_since_scan = 0 + var/obj/movement_target + +/mob/living/simple_mob/animal/passive/dog/corgi/Ian/Life() + ..() + + //Not replacing with SA FollowTarget mechanics because Ian behaves... very... specifically. + + //Feeding, chasing food, FOOOOODDDD + if(!stat && !resting && !buckled) + turns_since_scan++ + if(turns_since_scan > 5) + turns_since_scan = 0 + if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) )) + movement_target = null + if( !movement_target || !(movement_target.loc in oview(src, 3)) ) + movement_target = null + for(var/obj/item/weapon/reagent_containers/food/snacks/S in oview(src,3)) + if(isturf(S.loc) || ishuman(S.loc)) + movement_target = S + break + if(movement_target) + step_to(src,movement_target,1) + sleep(3) + step_to(src,movement_target,1) + sleep(3) + step_to(src,movement_target,1) + + if(movement_target) //Not redundant due to sleeps, Item can be gone in 6 decisecomds + if (movement_target.loc.x < src.x) + set_dir(WEST) + else if (movement_target.loc.x > src.x) + set_dir(EAST) + else if (movement_target.loc.y < src.y) + set_dir(SOUTH) + else if (movement_target.loc.y > src.y) + set_dir(NORTH) + else + set_dir(SOUTH) + + if(isturf(movement_target.loc) ) + UnarmedAttack(movement_target) + else if(ishuman(movement_target.loc) && prob(20)) + visible_emote("stares at the [movement_target] that [movement_target.loc] has with sad puppy eyes.") + + if(prob(1)) + visible_emote(pick("dances around","chases their tail")) + spawn(0) + for(var/i in list(1,2,4,8,4,2,1,2,4,8,4,2,1,2,4,8,4,2)) + set_dir(i) + sleep(1) + +//LISA! SQUEEEEEEEEE~ +/mob/living/simple_mob/animal/passive/dog/corgi/Lisa + name = "Lisa" + real_name = "Lisa" + gender = FEMALE + desc = "It's a corgi with a cute pink bow." + icon_state = "lisa" + icon_living = "lisa" + icon_dead = "lisa_dead" + response_help = "pets" + response_disarm = "bops" + response_harm = "kicks" + var/turns_since_scan = 0 + var/puppies = 0 + +//Lisa already has a cute bow! +/mob/living/simple_mob/animal/passive/dog/corgi/Lisa/Topic(href, href_list) + if(href_list["remove_inv"] || href_list["add_inv"]) + to_chat(usr, "[src] already has a cute bow!") + return + ..() + +/mob/living/simple_mob/animal/passive/dog/corgi/Lisa/Life() + ..() + + if(!stat && !resting && !buckled) + turns_since_scan++ + if(turns_since_scan > 15) + turns_since_scan = 0 + var/alone = TRUE + var/ian = FALSE + for(var/mob/M in oviewers(7, src)) + if(istype(M, /mob/living/simple_mob/animal/passive/dog/corgi/Ian)) + if(M.client) + alone = FALSE + break + else + ian = M + else + alone = FALSE + break + if(alone && ian && puppies < 4) + if(near_camera(src) || near_camera(ian)) + return + new /mob/living/simple_mob/animal/passive/dog/corgi/puppy(loc) + + if(prob(1)) + visible_emote(pick("dances around","chases her tail")) + spawn(0) + for(var/i in list(1,2,4,8,4,2,1,2,4,8,4,2,1,2,4,8,4,2)) + set_dir(i) + sleep(1) + +// Tamaskans +/mob/living/simple_mob/animal/passive/dog/tamaskan + name = "tamaskan" + real_name = "tamaskan" + desc = "It's a tamaskan." + icon_state = "tamaskan" + icon_living = "tamaskan" + icon_dead = "tamaskan_dead" + +/mob/living/simple_mob/animal/passive/dog/tamaskan/Spice + name = "Spice" + real_name = "Spice" //Intended to hold the name without altering it. + gender = FEMALE + desc = "It's a tamaskan, the name Spice can be found on its collar." \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/parrot.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/parrot.dm new file mode 100644 index 0000000000..bedd95e3ce --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/parrot.dm @@ -0,0 +1,252 @@ +// Parrots can talk, and may repeat things it hears. +/mob/living/simple_mob/animal/passive/bird/parrot + name = "parrot" + description_info = "You can give it a headset by clicking on it with a headset. \ + To remove it, click the bird while on grab intent." + has_langs = list("Galactic Common", "Bird") + + ai_holder_type = /datum/ai_holder/simple_mob/passive/parrot + + // A headset, so that talking parrots can yell at the crew over comms. + // If set to a type, on initialize it will be instantiated into that type. + var/obj/item/device/radio/headset/my_headset = null + +// Say list +/datum/say_list/bird/poly + speak = list( + "Poly wanna cracker!", + "Check the singulo, you chucklefucks!", + "Wire the solars, you lazy bums!", + "WHO TOOK THE DAMN HARDSUITS?", + "OH GOD ITS FREE CALL THE SHUTTLE", + "Danger! Crystal hyperstructure instability!", + "CRYSTAL DELAMINATION IMMINENT.", + "Tweet tweet, I'm a Teshari.", + "Chitters.", + "Meteors have been detected on a collision course with the station!" + ) + +// Lets the AI use headsets. +// Player-controlled parrots will need to do it manually. +/mob/living/simple_mob/animal/passive/bird/parrot/ISay(message) + if(my_headset && prob(50)) + var/list/keys = list() + for(var/channel in my_headset.channels) + var/key = get_radio_key_from_channel(channel) + if(key) + keys += key + if(keys.len) + var/key_used = pick(keys) + return say("[key_used] [message]") + return say(message) + +// Ugly saycode so parrots can use their headsets. +/mob/living/simple_mob/animal/passive/bird/parrot/handle_message_mode(message_mode, message, verb, speaking, used_radios, alt_name) + ..() + if(message_mode) + if(my_headset && istype(my_headset, /obj/item/device/radio)) + my_headset.talk_into(src, message, message_mode, verb, speaking) + used_radios += my_headset + +// Clicked on while holding an object. +/mob/living/simple_mob/animal/passive/bird/parrot/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/device/radio/headset)) + give_headset(I, user) + return + return ..() + +// Clicked on by empty hand. +/mob/living/simple_mob/animal/passive/bird/parrot/attack_hand(mob/living/L) + if(L.a_intent == I_GRAB && my_headset) + remove_headset(L) + else + ..() + + +/mob/living/simple_mob/animal/passive/bird/parrot/proc/give_headset(obj/item/device/radio/headset/new_headset, mob/living/user) + if(!istype(new_headset)) + to_chat(user, span("warning", "\The [new_headset] isn't a headset.")) + return + if(my_headset) + to_chat(user, span("warning", "\The [src] is already wearing \a [my_headset].")) + return + else + user.drop_item(new_headset) + my_headset = new_headset + new_headset.forceMove(src) + to_chat(user, span("warning", "You place \a [new_headset] on \the [src]. You monster.")) + to_chat(src, span("notice", "\The [user] gives you \a [new_headset]. You should put it to good use immediately.")) + return + +/mob/living/simple_mob/animal/passive/bird/parrot/proc/remove_headset(mob/living/user) + if(!my_headset) + to_chat(user, "\The [src] doesn't have a headset to remove, thankfully.") + else + ISay("BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") + my_headset.forceMove(get_turf(src)) + user.put_in_hands(my_headset) + to_chat(user, span("notice", "You take away \the [src]'s [my_headset.name]. Finally.")) + to_chat(src, span("warning", "\The [user] takes your [my_headset.name] away! How cruel!")) + my_headset = null + +/mob/living/simple_mob/animal/passive/bird/parrot/examine(mob/user) + ..() + if(my_headset) + to_chat(user, "It is wearing \a [my_headset].") + +/mob/living/simple_mob/animal/passive/bird/parrot/initialize() + if(my_headset) + my_headset = new my_headset(src) + return ..() + +// Subtypes. + +// Best Bird +/mob/living/simple_mob/animal/passive/bird/parrot/poly + name = "Poly" + desc = "It's a parrot. An expert on quantum cracker theory." + icon_state = "poly" + icon_rest = "poly-held" + icon_dead = "poly-dead" + tt_desc = "E Ara macao" + my_headset = /obj/item/device/radio/headset/headset_eng + say_list_type = /datum/say_list/bird/poly + +// Best Bird with best headset. +/mob/living/simple_mob/animal/passive/bird/parrot/poly/ultimate + my_headset = /obj/item/device/radio/headset/omni + +/mob/living/simple_mob/animal/passive/bird/parrot/kea + name = "kea" + desc = "A species of parrot. On Earth, they are unique among other parrots for residing in alpine climates. \ + They are known to be intelligent and curious, which has made some consider them a pest." + icon_state = "kea" + icon_rest = "kea-held" + icon_dead = "kea-dead" + tt_desc = "E Nestor notabilis" + +/mob/living/simple_mob/animal/passive/bird/parrot/eclectus + name = "eclectus" + desc = "A species of parrot, this species features extreme sexual dimorphism in their plumage's colors. \ + A male eclectus has emerald green plumage, where as a female eclectus has red and purple plumage." + icon_state = "eclectus" + icon_rest = "eclectus-held" + icon_dead = "eclectus-dead" + tt_desc = "E Eclectus roratus" + +/mob/living/simple_mob/animal/passive/bird/parrot/eclectus/initialize() + gender = pick(MALE, FEMALE) + if(gender == FEMALE) + icon_state = "eclectusf" + icon_rest = "eclectusf-held" + icon_dead = "eclectusf-dead" + return ..() + +/mob/living/simple_mob/animal/passive/bird/parrot/grey_parrot + name = "grey parrot" + desc = "A species of parrot. This one is predominantly grey, but has red tail feathers." + icon_state = "agrey" + icon_rest = "agrey-held" + icon_dead = "agrey-dead" + tt_desc = "E Psittacus erithacus" + +/mob/living/simple_mob/animal/passive/bird/parrot/black_headed_caique + name = "black-headed caique" + desc = "A species of parrot, these birds have a distinct black color on their heads, distinguishing them from their relative Caiques." + icon_state = "bcaique" + icon_rest = "bcaique-held" + icon_dead = "bcaique-dead" + tt_desc = "E Pionites melanocephalus" + +/mob/living/simple_mob/animal/passive/bird/parrot/white_caique + name = "white-bellied caique" + desc = "A species of parrot, they are also known as the Green-Thighed Parrot." + icon_state = "wcaique" + icon_rest = "wcaique-held" + icon_dead = "wcaique-dead" + tt_desc = "E Pionites leucogaster" + +/mob/living/simple_mob/animal/passive/bird/parrot/budgerigar + name = "budgerigar" + desc = "A species of parrot, they are also known as the common parakeet, or in some circles, the budgie. \ + This one is has its natural colors of green and yellow." + icon_state = "gbudge" + icon_rest = "gbudge-held" + icon_dead = "gbudge-dead" + tt_desc = "E Melopsittacus undulatus" + +/mob/living/simple_mob/animal/passive/bird/parrot/budgerigar/blue + icon_state = "bbudge" + icon_rest = "bbudge-held" + icon_dead = "bbudge-dead" + desc = "A species of parrot, they are also known as the common parakeet, or in some circles, the budgie. \ + This one has a mutation which altered its color to be blue instead of green and yellow." + +/mob/living/simple_mob/animal/passive/bird/parrot/budgerigar/bluegreen + icon_state = "bgbudge" + icon_rest = "bgbudge-held" + icon_dead = "bgbudge-dead" + desc = "A species of parrot, they are also known as the common parakeet, or in some circles, the budgie. \ + This one has a mutation which altered its color to be a mix of blue and green." + +/mob/living/simple_mob/animal/passive/bird/parrot/cockatiel + name = "cockatiel" + desc = "A species of parrot. This one has a highly visible crest." + icon_state = "tiel" + icon_rest = "tiel-held" + icon_dead = "tiel-dead" + tt_desc = "E Nymphicus hollandicus" + +/mob/living/simple_mob/animal/passive/bird/parrot/cockatiel/white + icon_state = "wtiel" + icon_rest = "wtiel-held" + icon_dead = "wtiel-dead" + +/mob/living/simple_mob/animal/passive/bird/parrot/cockatiel/yellowish + icon_state = "luttiel" + icon_rest = "luttiel-held" + icon_dead = "luttiel-dead" + +/mob/living/simple_mob/animal/passive/bird/parrot/cockatiel/grey + icon_state = "blutiel" // idk why this is blu. + icon_rest = "blutiel-held" + icon_dead = "blutiel-dead" + +// This actually might be the yellow-crested cockatoo but idk. +/mob/living/simple_mob/animal/passive/bird/parrot/sulphur_cockatoo + name = "sulphur-crested cockatoo" + desc = "A species of parrot. This one has an expressive yellow crest. Their underwing and tail feathers are also yellow." + icon_state = "too" + icon_rest = "too-held" + icon_dead = "too-dead" + tt_desc = "E Cacatua galerita" + +// This was originally called 'hooded_too', which might not mean the unbrella cockatoo but idk. +/mob/living/simple_mob/animal/passive/bird/parrot/white_cockatoo + name = "white cockatoo" + desc = "A species of parrot. This one is also known as the Umbrella Cockatoo, due to the semicircular shape of its crest." + icon_state = "utoo" + icon_rest = "utoo-held" + icon_dead = "utoo-dead" + tt_desc = "E Cacatua alba" + +/mob/living/simple_mob/animal/passive/bird/parrot/pink_cockatoo + name = "pink cockatoo" + desc = "A species of parrot. This one is also known as Major Mitchell's cockatoo, \ + in honor of a human surveyor and explorer who existed before humans fully explored their home planet." + icon_state = "mtoo" + icon_rest = "mtoo-held" + icon_dead = "mtoo-dead" + tt_desc = "E Lophochroa leadbeateri" + + +// AI +/datum/ai_holder/simple_mob/passive/parrot + speak_chance = 2 + base_wander_delay = 8 + +/datum/ai_holder/simple_mob/passive/parrot/on_hear_say(mob/living/speaker, message) + if(holder.stat || !holder.say_list || !message || speaker == holder) + return + var/datum/say_list/S = holder.say_list + S.speak |= message \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/diyaab.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/diyaab.dm new file mode 100644 index 0000000000..fdd9f66ae5 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/diyaab.dm @@ -0,0 +1,35 @@ +// Diyaabs are rather weak, but tend to exist in large numbers. +// They cooperate with other diyaabs, in order to swarm whoever decides to pick on the little fluffy critter. +// A cleaving weapon like an axe will make short work of the pack. + +/mob/living/simple_mob/animal/sif/diyaab + name = "diyaab" + desc = "A small pack animal. Although omnivorous, it will hunt meat on occasion." + tt_desc = "S Choeros hirtus" //diyaab and shantak are technically reletives! + + faction = "diyaab" + + icon_state = "diyaab" + icon_living = "diyaab" + icon_dead = "diyaab_dead" + icon = 'icons/jungle.dmi' + + maxHealth = 25 + health = 25 + + movement_cooldown = 0 + + melee_damage_lower = 2 + melee_damage_upper = 6 + base_attack_cooldown = 1 SECOND + attack_sharp = 1 //Bleeds, but it shouldn't rip off a limb? + attacktext = list("gouged") + + say_list_type = /datum/say_list/diyaab + ai_holder_type = /datum/ai_holder/simple_mob/retaliate/cooperative + +/datum/say_list/diyaab + speak = list("Awrr?", "Aowrl!", "Worrl.") + emote_see = list("sniffs the air cautiously","looks around") + emote_hear = list("snuffles") + diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/hooligan_crab.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/hooligan_crab.dm new file mode 100644 index 0000000000..3b7f279653 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/hooligan_crab.dm @@ -0,0 +1,102 @@ +/* + Hooligan Crabs are called so because they are rather curious and tend to follow people, + whether the people want them to or not, and sometimes causing vandalism by accident. + They're pretty strong and have strong melee armor, but won't attack first. + They unknowingly play a role in keeping the shoreline fairly safe, by killing whatever would attack other people. + + They also have a slow, but very strong attack that is telegraphed. If it hits, it will briefly stun whatever got hit + and inflict a very large chunk of damage. If the thing was already stunned, the crab will 'throw' them away, to + hopefully prevent chainstuns forever. +*/ + +/mob/living/simple_mob/animal/sif/hooligan_crab + name = "hooligan crab" + desc = "A large, hard-shelled crustacean. This one is mostly grey. \ + You probably shouldn't mess with it." + icon_state = "sif_crab" + icon_living = "sif_crab" + icon_dead = "sif_crab_dead" + icon_scale = 1.5 + + faction = "crabs" + + maxHealth = 200 + health = 200 + movement_cooldown = 10 + movement_sound = 'sound/weapons/heavysmash.ogg' + movement_shake_radius = 5 + + taser_kill = FALSE + armor = list( + "melee" = 40, + "bullet" = 20, + "laser" = 10, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + armor_soak = list( + "melee" = 10, + "bullet" = 5, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + + mob_size = MOB_LARGE + + melee_damage_lower = 22 + melee_damage_upper = 35 + attack_armor_pen = 35 + attack_sharp = TRUE + attack_edge = TRUE + melee_attack_delay = 1 SECOND + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + friendly = "pinches" + attacktext = list("clawed", "pinched", "crushed") + speak_emote = list("clicks") + + ai_holder_type = /datum/ai_holder/simple_mob/melee/hooligan + say_list_type = /datum/say_list/crab + + var/weaken_amount = 2 // Be careful with this number. High values will equal a permastun. + +// Stuns the thing that got hit briefly. +/mob/living/simple_mob/animal/sif/hooligan_crab/apply_melee_effects(atom/A) + if(isliving(A)) + var/mob/living/L = A + var/was_stunned = L.incapacitated(INCAPACITATION_DISABLED) + L.Weaken(weaken_amount) + + playsound(L, 'sound/effects/break_stone.ogg', 75, 1) + if(was_stunned) // Try to prevent chain-stuns by having them thrown. + var/throwdir = get_dir(src, L) + L.throw_at(get_edge_target_turf(L, throwdir), 5, 1, src) + visible_message(span("danger", "\The [src] hurls \the [L] away!")) + else + visible_message(span("danger", "\The [src] crushes \the [L]!")) + +// The AI for hooligan crabs. Follows people for awhile. +/datum/ai_holder/simple_mob/melee/hooligan + hostile = FALSE + retaliate = TRUE + returns_home = TRUE + max_home_distance = 12 + var/random_follow = TRUE // Turn off if you want to bus with crabs. + +/datum/ai_holder/simple_mob/melee/hooligan/handle_stance_strategical() + ..() + if(random_follow && stance == STANCE_IDLE && !leader) + if(prob(10)) + for(var/mob/living/L in hearers(holder)) + if(!istype(L, holder)) // Don't follow other hooligan crabs. + holder.visible_message("\The [holder] starts to follow \the [L].") + set_follow(L, rand(20 SECONDS, 40 SECONDS)) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/savik.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/savik.dm new file mode 100644 index 0000000000..e8d56d6bb4 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/savik.dm @@ -0,0 +1,48 @@ +// Saviks are dangerous, angry creatures that hit hard, and will berserk if losing a fight. + +/mob/living/simple_mob/animal/sif/savik + name = "savik" + tt_desc = "S Pistris tellus" //landshark + player_msg = "You have the ability to berserk at will, which will grant strong physical bonuses for \ + a short period of time, however it will tire you and you will be much weaker for awhile after it expires." + + faction = "savik" + + icon_state = "savik" + icon_living = "savik" + icon_dead = "savik_dead" + icon = 'icons/jungle.dmi' + + maxHealth = 125 + health = 125 + + movement_cooldown = 0.5 SECONDS + + melee_damage_lower = 15 + melee_damage_upper = 35 + attack_armor_pen = 15 + attack_sharp = TRUE + attack_edge = TRUE + melee_attack_delay = 1 SECOND + attacktext = list("mauled") + + say_list_type = /datum/say_list/savik + +/datum/say_list/savik + speak = list("Hruuugh!","Hrunnph") + emote_see = list("paws the ground","shakes its mane","stomps") + emote_hear = list("snuffles") + +/mob/living/simple_mob/animal/sif/savik/handle_special() + if((get_AI_stance() in list(STANCE_APPROACH, STANCE_FIGHT)) && !is_AI_busy() && isturf(loc)) + if(health <= (maxHealth * 0.5)) // At half health, and fighting someone currently. + berserk() + + +// So players can use it too. +/mob/living/simple_mob/animal/sif/savik/verb/berserk() + set name = "Berserk" + set desc = "Enrage and become vastly stronger for a period of time, however you will be weaker afterwards." + set category = "Abilities" + + add_modifier(/datum/modifier/berserk, 30 SECONDS) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/shantak.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/shantak.dm new file mode 100644 index 0000000000..0148a39954 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/shantak.dm @@ -0,0 +1,70 @@ +// Shantaks are essentially sif wolves. + +/mob/living/simple_mob/animal/sif/shantak + name = "shantak" + desc = "A piglike creature with a bright iridiscent mane that sparkles as though lit by an inner light. \ + Don't be fooled by its beauty though." + tt_desc = "S Choeros shantak" + + faction = "shantak" + + icon_state = "shantak" + icon_living = "shantak" + icon_dead = "shantak_dead" + icon = 'icons/jungle.dmi' + + maxHealth = 75 + + movement_cooldown = 5 + + melee_damage_lower = 6 + melee_damage_upper = 14 + base_attack_cooldown = 1 SECOND + melee_attack_delay = 0.5 SECONDS + attack_armor_pen = 5 + attack_sharp = TRUE + attack_edge = TRUE + attacktext = list("gouged") + + say_list_type = /datum/say_list/shantak + +/datum/say_list/shantak + speak = list("Shuhn.","Shrunnph?","Shunpf.") + emote_see = list("scratches the ground", "shakes out its mane", "clinks gently as it moves") + + +// The pack leader. +// Will command other shantaks to follow it. +/mob/living/simple_mob/animal/sif/shantak/leader + name = "big shantak" + desc = "A piglike creature with a bright iridiscent mane that sparkles as though lit by an inner light. \ + This one seems bigger than the others, and has a commanding presence." + icon_scale = 1.5 + maxHealth = 125 + player_msg = "You have the ability to command other shantaks to follow you." + +/mob/living/simple_mob/animal/sif/shantak/leader/verb/rally_pack() + set name = "Rally Pack" + set desc = "Commands your fellow packmembers to follow you, the leader." + set category = "Abilities" + + for(var/mob/living/simple_mob/animal/sif/shantak/S in hearers(7, src)) + if(istype(S, /mob/living/simple_mob/animal/sif/shantak/leader)) // Leaders won't follow other leaders. Also avoids trying to follow ourselves. + continue + if(!S.ai_holder) + continue + if(S.faction != src.faction) + continue + var/datum/ai_holder/AI = S.ai_holder + AI.set_follow(src) + +// Variant that automatically commands nearby allies to follow it when created. +// Suggested to spawn last so it can rally up all the shantaks easily before hunting for tasty explorers. +/mob/living/simple_mob/animal/sif/shantak/leader/autofollow/initialize() + rally_pack() + return ..() + + +// These ones only retaliate. Used for a PoI. +/mob/living/simple_mob/animal/sif/shantak/retaliate + ai_holder_type = /datum/ai_holder/simple_mob/retaliate \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/sif.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/sif.dm new file mode 100644 index 0000000000..8d19324cf2 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/sif.dm @@ -0,0 +1,5 @@ +// Mobs intended to be on Sif. As such, they won't die to the cold. +/mob/living/simple_mob/animal/sif + minbodytemp = 175 + cold_resist = 0.75 + heat_resist = -0.5 \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/alien.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/alien.dm new file mode 100644 index 0000000000..7de14dc6e7 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/alien.dm @@ -0,0 +1,118 @@ +/mob/living/simple_mob/animal/space/alien + name = "alien hunter" + desc = "Hiss!" + icon = 'icons/mob/alien.dmi' + icon_state = "alienh_running" + icon_living = "alienh_running" + icon_dead = "alien_l" + icon_gib = "syndicate_gib" + icon_rest = "alienh_sleep" + + faction = "xeno" + + mob_class = MOB_CLASS_ABERRATION + + response_help = "pokes" + response_disarm = "shoves" + response_harm = "hits" + + maxHealth = 100 + health = 100 + + harm_intent_damage = 5 + melee_damage_lower = 25 + melee_damage_upper = 25 + attack_sharp = TRUE + attack_edge = TRUE + + attacktext = list("slashed") + attack_sound = 'sound/weapons/bladeslice.ogg' + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/xenomeat + +/mob/living/simple_mob/animal/space/alien/drone + name = "alien drone" + icon_state = "aliend_running" + icon_living = "aliend_running" + icon_dead = "aliend_l" + icon_rest = "aliend_sleep" + health = 60 + melee_damage_lower = 15 + melee_damage_upper = 15 + +/mob/living/simple_mob/animal/space/alien/sentinel + name = "alien sentinel" + icon_state = "aliens_running" + icon_living = "aliens_running" + icon_dead = "aliens_l" + icon_rest = "aliens_sleep" + health = 120 + melee_damage_lower = 15 + melee_damage_upper = 15 + projectiletype = /obj/item/projectile/energy/neurotoxin/toxic + projectilesound = 'sound/weapons/pierce.ogg' + +/mob/living/simple_mob/animal/space/alien/sentinel/praetorian + name = "alien praetorian" + icon = 'icons/mob/64x64.dmi' + icon_state = "prat_s" + icon_living = "prat_s" + icon_dead = "prat_dead" + icon_rest = "prat_sleep" + maxHealth = 200 + health = 200 + + pixel_x = -16 + old_x = -16 + meat_amount = 5 + +/mob/living/simple_mob/animal/space/alien/queen + name = "alien queen" + icon_state = "alienq_running" + icon_living = "alienq_running" + icon_dead = "alienq_l" + icon_rest = "alienq_sleep" + health = 250 + maxHealth = 250 + melee_damage_lower = 15 + melee_damage_upper = 15 + projectiletype = /obj/item/projectile/energy/neurotoxin/toxic + projectilesound = 'sound/weapons/pierce.ogg' + + + movement_cooldown = 8 + +/mob/living/simple_mob/animal/space/alien/queen/empress + name = "alien empress" + icon = 'icons/mob/64x64.dmi' + icon_state = "queen_s" + icon_living = "queen_s" + icon_dead = "queen_dead" + icon_rest = "queen_sleep" + maxHealth = 400 + health = 400 + meat_amount = 5 + + pixel_x = -16 + old_x = -16 + +/mob/living/simple_mob/animal/space/alien/queen/empress/mother + name = "alien mother" + icon = 'icons/mob/96x96.dmi' + icon_state = "empress_s" + icon_living = "empress_s" + icon_dead = "empress_dead" + icon_rest = "empress_rest" + maxHealth = 600 + health = 600 + meat_amount = 10 + melee_damage_lower = 15 + melee_damage_upper = 25 + + pixel_x = -32 + old_x = -32 + +/mob/living/simple_mob/animal/space/alien/death() + ..() + visible_message("[src] lets out a waning guttural screech, green blood bubbling from its maw...") + playsound(src, 'sound/voice/hiss6.ogg', 100, 1) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/bats.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/bats.dm new file mode 100644 index 0000000000..5244fd9b98 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/bats.dm @@ -0,0 +1,52 @@ +/mob/living/simple_mob/animal/space/bats + name = "space bat swarm" + desc = "A swarm of cute little blood sucking bats that looks pretty upset." + tt_desc = "N Bestia gregaria" //Nispean swarm bats, because of course Nisp has swarm bats + icon = 'icons/mob/bats.dmi' + icon_state = "bat" + icon_living = "bat" + icon_dead = "bat_dead" + icon_gib = "bat_dead" + + faction = "scarybat" + + maxHealth = 20 + health = 20 + + attacktext = list("bites") + attack_sound = 'sound/weapons/bite.ogg' + + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "hits the" + + harm_intent_damage = 10 + + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_sharp = TRUE + + ai_holder_type = /datum/ai_holder/simple_mob/melee/evasive + + has_langs = list("Mouse") + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + say_list_type = /datum/say_list/mouse // Close enough + + var/scare_chance = 15 + +/mob/living/simple_mob/animal/space/bats/apply_melee_effects(var/atom/A) + if(isliving(A)) + var/mob/living/L = A + if(prob(scare_chance)) + L.Stun(1) + L.visible_message("\the [src] scares \the [L]!") + +// Spookiest of bats +/mob/living/simple_mob/animal/space/bats/cult + faction = "cult" + supernatural = TRUE + +/mob/living/simple_mob/animal/space/bats/cult/cultify() + return \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/bear.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/bear.dm new file mode 100644 index 0000000000..e4199373bd --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/bear.dm @@ -0,0 +1,46 @@ +/mob/living/simple_mob/animal/space/bear + name = "space bear" + desc = "A product of Space Russia?" + tt_desc = "U Ursinae aetherius" //...bearspace? Maybe. + icon_state = "bear" + icon_living = "bear" + icon_dead = "bear_dead" + icon_gib = "bear_gib" + + faction = "russian" + + maxHealth = 125 + health = 125 + + movement_cooldown = 0.5 SECONDS + + melee_damage_lower = 15 + melee_damage_upper = 35 + attack_armor_pen = 15 + attack_sharp = TRUE + attack_edge = TRUE + melee_attack_delay = 1 SECOND + attacktext = list("mauled") + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/bearmeat + + say_list_type = /datum/say_list/bear + +/datum/say_list/bear + speak = list("RAWR!","Rawr!","GRR!","Growl!") + emote_see = list("stares ferociously", "stomps") + emote_hear = list("rawrs","grumbles","grawls", "growls", "roars") + +// Is it time to be mad? +/mob/living/simple_mob/animal/space/bear/handle_special() + if((get_AI_stance() in list(STANCE_APPROACH, STANCE_FIGHT)) && !is_AI_busy() && isturf(loc)) + if(health <= (maxHealth * 0.5)) // At half health, and fighting someone currently. + berserk() + +// So players can use it too. +/mob/living/simple_mob/animal/space/bear/verb/berserk() + set name = "Berserk" + set desc = "Enrage and become vastly stronger for a period of time, however you will be weaker afterwards." + set category = "Abilities" + + add_modifier(/datum/modifier/berserk, 30 SECONDS) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm new file mode 100644 index 0000000000..e1327f033b --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm @@ -0,0 +1,128 @@ +// Space carp show up as a random event to wreck hapless people in space or near windows. +// They generally fit the archetype of 'fast but fragile'. +// This is compensated by being in groups (usually). +/mob/living/simple_mob/animal/space/carp + name = "space carp" + desc = "A ferocious, fang-bearing creature that resembles a fish." + icon_state = "carp" + icon_living = "carp" + icon_dead = "carp_dead" + icon_gib = "carp_gib" + + faction = "carp" + maxHealth = 25 + health = 25 + movement_cooldown = 0 // Carp go fast + hovering = TRUE + + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "hits the" + + melee_damage_lower = 7 // About 14 DPS. + melee_damage_upper = 7 + base_attack_cooldown = 10 // One attack a second. + attack_sharp = TRUE + attack_sound = 'sound/weapons/bite.ogg' + attacktext = list("bitten") + + meat_amount = 1 + meat_type = /obj/item/weapon/reagent_containers/food/snacks/carpmeat + + ai_holder_type = /datum/ai_holder/simple_mob/melee + + var/knockdown_chance = 15 + +/mob/living/simple_mob/animal/space/carp/apply_melee_effects(var/atom/A) + if(isliving(A)) + var/mob/living/L = A + if(prob(knockdown_chance)) + L.Weaken(3) + L.visible_message(span("danger", "\The [src] knocks down \the [L]!")) + +// Subtypes. + +// Won't wander away. +/mob/living/simple_mob/animal/space/carp/event + ai_holder_type = /datum/ai_holder/simple_mob/event + + +/mob/living/simple_mob/animal/space/carp/large + name = "elder carp" + desc = "An older, more matured carp. Few survive to this age due to their aggressiveness." + icon = 'icons/mob/64x32.dmi' + icon_state = "shark" + icon_living = "shark" + icon_dead = "shark_dead" + + maxHealth = 50 + health = 50 + movement_cooldown = 5 // Slower than the younger carp. + mob_size = MOB_LARGE + + pixel_x = -16 + default_pixel_x = -16 + + meat_amount = 3 + + +/mob/living/simple_mob/animal/space/carp/large/huge + name = "great white carp" + desc = "A very rare breed of carp- and a very aggressive one." + icon = 'icons/mob/64x64.dmi' + icon_dead = "megacarp_dead" + icon_living = "megacarp" + icon_state = "megacarp" + + maxHealth = 230 + health = 230 + movement_cooldown = 10 + + melee_damage_lower = 15 // About 20 DPS. + melee_damage_upper = 25 + + pixel_y = -16 + default_pixel_y = -16 + + meat_amount = 10 + + +/mob/living/simple_mob/animal/space/carp/holographic + name = "holographic carp" + desc = "An obviously holographic, but still ferocious looking carp." + // Might be worth using a filter similar to AI holograms in the future. + icon = 'icons/mob/AI.dmi' + icon_state = "holo4" + icon_living = "holo4" + icon_dead = "holo4" + alpha = 127 + icon_gib = null + meat_amount = 0 + meat_type = null + + mob_class = MOB_CLASS_PHOTONIC // Xeno-taser won't work on this as its not a 'real' carp. + +/mob/living/simple_mob/animal/space/carp/holographic/initialize() + set_light(2) // Hologram lighting. + return ..() + +// Presumably the holodeck emag code requires this. +// Pass TRUE to make safe. Pass FALSE to make unsafe. +/mob/living/simple_mob/animal/space/carp/holographic/proc/set_safety(safe) + if(!isnull(get_AI_stance())) // Will return null if lacking an AI holder or a player is controlling it w/o autopilot var. + ai_holder.hostile = !safe // Inverted so safe = TRUE means hostility = FALSE. + ai_holder.forget_everything() // Reset state so it'll stop chewing on its target. + +// Called on death. +/mob/living/simple_mob/animal/space/carp/holographic/proc/derez() + visible_message(span("notice", "\The [src] fades away!")) + qdel(src) + +/mob/living/simple_mob/animal/space/carp/holographic/gib() + derez() // Holograms can't gib. + +/mob/living/simple_mob/animal/space/carp/holographic/death() + ..() + derez() + + diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/goose.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/goose.dm new file mode 100644 index 0000000000..43d159a9f0 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/goose.dm @@ -0,0 +1,44 @@ +/mob/living/simple_mob/animal/space/goose + name = "goose" + desc = "It looks pretty angry!" + tt_desc = "E Branta canadensis" //that iconstate is just a regular goose + icon_state = "goose" + icon_living = "goose" + icon_dead = "goose_dead" + + faction = "geese" + + maxHealth = 30 + health = 30 + + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "hits the" + + harm_intent_damage = 5 + melee_damage_lower = 5 //they're meant to be annoying, not threatening. + melee_damage_upper = 5 //unless there's like a dozen of them, then you're screwed. + attacktext = list("pecked") + attack_sound = 'sound/weapons/bite.ogg' + + has_langs = list("Bird") + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + +/datum/say_list/goose + speak = list("HONK!") + emote_hear = list("honks loudly!") + say_maybe_target = list("Honk?") + say_got_target = list("HONK!!!") + +/mob/living/simple_mob/animal/space/goose/handle_special() + if((get_AI_stance() in list(STANCE_APPROACH, STANCE_FIGHT)) && !is_AI_busy() && isturf(loc)) + if(health <= (maxHealth * 0.5)) // At half health, and fighting someone currently. + berserk() + +/mob/living/simple_mob/animal/space/goose/verb/berserk() + set name = "Berserk" + set desc = "Enrage and become vastly stronger for a period of time, however you will be weaker afterwards." + set category = "Abilities" + + add_modifier(/datum/modifier/berserk, 30 SECONDS) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/space.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/space.dm new file mode 100644 index 0000000000..b7f77e6410 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/space.dm @@ -0,0 +1,15 @@ +// 'Space' mobs don't care about atmos (like carp) +/mob/living/simple_mob/animal/space + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + +// They can also, you know, move around, in space +/mob/living/simple_mob/animal/space/Process_Spacemove(var/check_drift = 0) + return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/blob/blob.dm b/code/modules/mob/living/simple_mob/subtypes/blob/blob.dm new file mode 100644 index 0000000000..2d700646ae --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/blob/blob.dm @@ -0,0 +1,62 @@ +// Blob simple_mobs generally get made from the blob random event. +// They're considered slimes for the purposes of attack bonuses from certain weapons. + +// Do not spawn, this is a base type. +/mob/living/simple_mob/blob + icon = 'icons/mob/blob.dmi' + pass_flags = PASSBLOB | PASSTABLE + faction = "blob" + + heat_damage_per_tick = 0 + cold_damage_per_tick = 0 + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + + taser_kill = FALSE + + var/mob/observer/blob/overmind = null + var/obj/structure/blob/factory/factory = null + + mob_class = MOB_CLASS_SLIME + ai_holder_type = /datum/ai_holder/simple_mob/melee + +/mob/living/simple_mob/blob/speech_bubble_appearance() + return "slime" + +/mob/living/simple_mob/blob/update_icons() + if(overmind) + color = overmind.blob_type.complementary_color + else + color = null + ..() + +/mob/living/simple_mob/blob/Destroy() + if(overmind) + overmind.blob_mobs -= src + return ..() + +/mob/living/simple_mob/blob/blob_act(obj/structure/blob/B) + if(!overmind && B.overmind) + overmind = B.overmind + update_icon() + + if(stat != DEAD && health < maxHealth) + adjustBruteLoss(-maxHealth*0.0125) + adjustFireLoss(-maxHealth*0.0125) + +/mob/living/simple_mob/blob/CanPass(atom/movable/mover, turf/target) + if(istype(mover, /obj/structure/blob)) // Don't block blobs from expanding onto a tile occupied by a blob mob. + return TRUE + return ..() + +/mob/living/simple_mob/blob/Process_Spacemove() + for(var/obj/structure/blob/B in range(1, src)) + return TRUE + return ..() \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/blob/spore.dm b/code/modules/mob/living/simple_mob/subtypes/blob/spore.dm new file mode 100644 index 0000000000..fd91b00b33 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/blob/spore.dm @@ -0,0 +1,148 @@ +// Spores are made from blob factories. +// They are very weak and expendable, but can overwhelm when a lot of them are together. +// When attacking, spores will hit harder if near other friendly spores. +// Some blobs can infest dead non-robotic mobs, making them into Not Zombies. + +/mob/living/simple_mob/blob/spore + name = "blob spore" + desc = "A floating, fragile spore." + + icon_state = "blobpod" + icon_living = "blobpod" + glow_range = 3 + glow_intensity = 5 + layer = ABOVE_MOB_LAYER // Over the blob. + + health = 30 + maxHealth = 30 + melee_damage_lower = 2 + melee_damage_upper = 4 + movement_cooldown = 0 + hovering = TRUE + + attacktext = list("slams into") + attack_sound = 'sound/effects/slime_squish.ogg' + say_list_type = /datum/say_list/spore + + var/mob/living/carbon/human/infested = null // The human this thing is totally not making into a zombie. + var/can_infest = FALSE + var/is_infesting = FALSE + +/datum/say_list/spore + emote_see = list("sways", "inflates briefly") + +/datum/say_list/infested + emote_see = list("shambles around", "twitches", "stares") + + +/mob/living/simple_mob/blob/spore/infesting + name = "infesting blob spore" + can_infest = TRUE + +/mob/living/simple_mob/blob/spore/weak + name = "fragile blob spore" + health = 15 + maxHealth = 15 + melee_damage_lower = 1 + melee_damage_upper = 2 + +/mob/living/simple_mob/blob/spore/initialize(mapload, var/obj/structure/blob/factory/my_factory) + if(istype(my_factory)) + factory = my_factory + factory.spores += src + return ..() + +/mob/living/simple_mob/blob/spore/Destroy() + if(factory) + factory.spores -= src + factory = null + if(infested) + infested.forceMove(get_turf(src)) + visible_message(span("warning", "\The [infested] falls to the ground as the blob spore bursts.")) + infested = null + return ..() + +/mob/living/simple_mob/blob/spore/death(gibbed, deathmessage = "bursts!") + if(overmind) + overmind.blob_type.on_spore_death(src) + ..(gibbed, deathmessage) + qdel(src) + +/mob/living/simple_mob/blob/spore/update_icons() + ..() // This will cut our overlays. + + if(overmind) + color = overmind.blob_type.complementary_color + glow_color = color + glow_toggle = TRUE + else + color = null + glow_color = null + glow_toggle = FALSE + + if(is_infesting) + icon = infested.icon + copy_overlays(infested) + // overlays = infested.overlays + var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/blob.dmi', "blob_head") + if(overmind) + blob_head_overlay.color = overmind.blob_type.complementary_color + color = initial(color)//looks better. + // overlays += blob_head_overlay + add_overlay(blob_head_overlay, TRUE) + +/mob/living/simple_mob/blob/spore/handle_special() + ..() + if(can_infest && !is_infesting && isturf(loc)) + for(var/mob/living/carbon/human/H in view(src,1)) + if(H.stat != DEAD) // We want zombies. + continue + if(H.isSynthetic()) // Not philosophical zombies. + continue + infest(H) + break + + if(factory && z != factory.z) // This is to prevent spores getting lost in space and making the factory useless. + qdel(src) + +/mob/living/simple_mob/blob/spore/proc/infest(mob/living/carbon/human/H) + is_infesting = TRUE + if(H.wear_suit) + var/obj/item/clothing/suit/A = H.wear_suit + if(A.armor && A.armor["melee"]) + maxHealth += A.armor["melee"] //That zombie's got armor, I want armor! + + maxHealth += 40 + health = maxHealth + name = "Infested [H.real_name]" // Not using the Z word. + desc = "A parasitic organism attached to a deceased body, controlling it directly as if it were a puppet." + melee_damage_lower += 8 // 10 total. + melee_damage_upper += 11 // 15 total. + attacktext = list("claws") + + H.forceMove(src) + infested = H + + say_list = new /datum/say_list/infested() + + update_icons() + visible_message(span("warning", "The corpse of [H.name] suddenly rises!")) + +/mob/living/simple_mob/blob/spore/GetIdCard() + if(infested) // If we've infested someone, use their ID. + return infested.GetIdCard() + +/mob/living/simple_mob/blob/spore/apply_bonus_melee_damage(A, damage_to_do) + var/helpers = 0 + for(var/mob/living/simple_mob/blob/spore/S in view(1, src)) + if(S == src) // Don't count ourselves. + continue + if(!IIsAlly(S)) // Only friendly spores make us stronger. + continue + // Friendly spores contribute 1/4th of their averaged attack power to our attack. + damage_to_do += ((S.melee_damage_lower + S.melee_damage_upper) / 2) / 4 + helpers++ + + if(helpers) + to_chat(src, span("notice", "Your attack is assisted by [helpers] other spore\s.")) + return damage_to_do \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/clown.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/clown.dm new file mode 100644 index 0000000000..b6f9520698 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/clown.dm @@ -0,0 +1,29 @@ +/mob/living/simple_mob/humanoid/clown + clown + name = "clown" + desc = "A denizen of clown planet" + tt_desc = "E Homo sapiens corydon" //this is an actual clown, as opposed to someone dressed up as one + icon_state = "clown" + icon_living = "clown" + icon_dead = "clown_dead" + icon_gib = "clown_gib" + + faction = "clown" + + loot_list = list(/obj/item/weapon/bikehorn = 100) + + response_help = "pokes" + response_disarm = "gently pushes aside" + response_harm = "hits" + + harm_intent_damage = 8 + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = list("attacked") + attack_sound = 'sound/items/bikehorn.ogg' + + say_list_type = /datum/say_list/clown + +/datum/say_list/clown + speak = list("HONK", "Honk!", "Welcome to clown planet!") + emote_see = list("honks") \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/humanoid.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/humanoid.dm new file mode 100644 index 0000000000..6e17e2e1a0 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/humanoid.dm @@ -0,0 +1,26 @@ +/mob/living/simple_mob/humanoid + mob_class = MOB_CLASS_HUMANOID + + // Generic humanoid mob tolerances + min_oxy = 5 + max_oxy = 0 + min_tox = 0 + max_tox = 1 + min_co2 = 0 + max_co2 = 5 + min_n2 = 0 + max_n2 = 0 + unsuitable_atoms_damage = 15 + + health = 150 // Point of human crit, as of commenting + maxHealth = 150 + + // Most humans leave a corpse + var/corpse = null + +/mob/living/simple_mob/humanoid/death() + ..() + if(corpse) + new corpse (src.loc) + qdel(src) + return \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm new file mode 100644 index 0000000000..57d5986e80 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm @@ -0,0 +1,278 @@ +/////////////////////////////// +// Merc Mobs Go Here +/////////////////////////////// + +// Probably shouldn't use this directly, there are a bunch of sub-classes that are more complete. +/mob/living/simple_mob/humanoid/merc + name = "mercenary" + desc = "A tough looking heavily-armed individual." + tt_desc = "E Homo sapiens" + icon_state = "syndicate" + icon_living = "syndicate" + icon_dead = "syndicate_dead" + icon_gib = "syndicate_gib" + + faction = "syndicate" + movement_cooldown = 4 + + status_flags = 0 + + response_help = "pokes" + response_disarm = "shoves" + response_harm = "hits" + + harm_intent_damage = 5 + melee_damage_lower = 15 //Tac Knife damage + melee_damage_upper = 15 + attack_sharp = 1 + attack_edge = 1 + attacktext = list("slashed", "stabbed") + armor = list(melee = 40, bullet = 30, laser = 30, energy = 10, bomb = 10, bio = 100, rad = 100) // Same armor values as the vest they drop, plus simple mob immunities + + corpse = /obj/effect/landmark/mobcorpse/syndicatesoldier + loot_list = list(/obj/item/weapon/material/knife/tacknife = 100) // Might as well give it the knife + + ai_holder_type = /datum/ai_holder/simple_mob/merc + say_list_type = /datum/say_list/merc + + // Grenade special attack vars + var/grenade_type = /obj/item/weapon/grenade/concussion + special_attack_cooldown = 45 SECONDS + special_attack_min_range = 2 + special_attack_max_range = 7 + +//////////////////////////////// +// Grenade Attack +//////////////////////////////// + +// Any merc can use this, just set special_attack_charges to a positive value + +// Check if we should bother with the grenade +/mob/living/simple_mob/humanoid/merc/should_special_attack(atom/A) + var/mob_count = 0 // Are there enough mobs to consider grenading? + var/turf/T = get_turf(A) + for(var/mob/M in range(T, 2)) + if(M.faction == faction) // Don't grenade our friends + return FALSE + if(M in oview(src, special_attack_max_range)) // And lets check if we can actually see at least two people before we throw a grenade + if(!M.stat) // Dead things don't warrant a grenade + mob_count ++ + if(mob_count < 2) + return FALSE + else + return TRUE + +// Yes? Throw the grenade +/mob/living/simple_mob/humanoid/merc/do_special_attack(atom/A) + set waitfor = FALSE + set_AI_busy(TRUE) + + var/obj/item/weapon/grenade/G = new grenade_type(get_turf(src)) + if(istype(G)) + G.throw_at(A, G.throw_range, G.throw_speed, src) + G.attack_self(src) + special_attack_charges = max(special_attack_charges-1, 0) + + set_AI_busy(FALSE) + + +//////////////////////////////// +// Merc AI Types +//////////////////////////////// +/datum/ai_holder/simple_mob/merc + threaten = TRUE + returns_home = TRUE // Stay close to the base... + wander = TRUE // ... but "patrol" a little. + +/datum/ai_holder/simple_mob/merc/ranged + pointblank = TRUE // They get close? Just shoot 'em! + firing_lanes = TRUE // But not your buddies! + conserve_ammo = TRUE // And don't go wasting bullets! + + +//////////////////////////////// +// Melee +//////////////////////////////// +/mob/living/simple_mob/humanoid/merc/melee // Defined in case we add non-sword-and-board mercs + loot_list = list(/obj/item/weapon/material/knife/tacknife = 100) + +// Sword and Shield Merc +/mob/living/simple_mob/humanoid/merc/melee/sword + icon_state = "syndicatemelee" + icon_living = "syndicatemelee" + + melee_damage_lower = 30 + melee_damage_upper = 30 + attack_armor_pen = 50 + attack_sharp = 1 + attack_edge = 1 + attacktext = list("slashed") + + loot_list = list(/obj/item/weapon/melee/energy/sword/red = 100, /obj/item/weapon/shield/energy = 100) + +// They have a shield, so they try to block +/mob/living/simple_mob/humanoid/merc/melee/sword/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(O.force) + if(prob(20)) + visible_message("\The [src] blocks \the [O] with its shield!") + if(user) + ai_holder.react_to_attack(user) + return + else + ..() + else + to_chat(user, "This weapon is ineffective, it does no damage.") + visible_message("\The [user] gently taps [src] with \the [O].") + +/mob/living/simple_mob/humanoid/merc/melee/sword/bullet_act(var/obj/item/projectile/Proj) + if(!Proj) return + if(prob(35)) + visible_message("[src] blocks [Proj] with its shield!") + if(Proj.firer) + ai_holder.react_to_attack(Proj.firer) + return + else + ..() + + +//////////////////////////////// +// Ranged +//////////////////////////////// + +// Base Ranged Merc, so we don't have to redefine a million vars for every subtype. Uses a pistol. +/mob/living/simple_mob/humanoid/merc/ranged + icon_state = "syndicateranged" + icon_living = "syndicateranged" + projectiletype = /obj/item/projectile/bullet/pistol/medium +// casingtype = /obj/item/ammo_casing/spent //Makes infinite stacks of bullets when put in PoIs. + projectilesound = 'sound/weapons/Gunshot_light.ogg' + loot_list = list(/obj/item/weapon/gun/projectile/colt = 100) + + needs_reload = TRUE + reload_max = 7 // Not the best default, but it fits the pistol + ai_holder_type = /datum/ai_holder/simple_mob/merc/ranged + +// C20r SMG +/mob/living/simple_mob/humanoid/merc/ranged/smg + icon_state = "syndicateranged_smg" + icon_living = "syndicateranged_smg" + + loot_list = list(/obj/item/weapon/gun/projectile/automatic/c20r = 100) + + base_attack_cooldown = 5 // Two attacks a second or so. + reload_max = 20 + +// Laser Rifle +/mob/living/simple_mob/humanoid/merc/ranged/laser + icon_state = "syndicateranged_laser" + icon_living = "syndicateranged_laser" + projectiletype = /obj/item/projectile/beam/midlaser + projectilesound = 'sound/weapons/Laser.ogg' + + loot_list = list(/obj/item/weapon/gun/energy/laser = 100) + + reload_max = 10 + +// Ion Rifle +/mob/living/simple_mob/humanoid/merc/ranged/ionrifle + icon_state = "syndicateranged_ionrifle" + icon_living = "syndicateranged_ionrifle" + projectiletype = /obj/item/projectile/ion + projectilesound = 'sound/weapons/Laser.ogg' + + loot_list = list(/obj/item/weapon/gun/energy/ionrifle = 100) + + reload_max = 10 + +// Grenadier, Basically a miniboss +/mob/living/simple_mob/humanoid/merc/ranged/grenadier + icon_state = "syndicateranged_shotgun" + icon_living = "syndicateranged_shotgun" + projectiletype = /obj/item/projectile/bullet/pellet/shotgun // Buckshot + projectilesound = 'sound/weapons/gunshot/shotgun.ogg' + + loot_list = list(/obj/item/weapon/gun/projectile/shotgun/pump = 100) + + reload_max = 4 + reload_time = 1.5 SECONDS // It's a shotgun, it takes a moment + + special_attack_charges = 5 + + +//////////////////////////////// +// Space Mercs +//////////////////////////////// + +// Sword Space Merc +/mob/living/simple_mob/humanoid/merc/melee/sword/space + name = "syndicate commando" + icon_state = "syndicatemeleespace" + icon_living = "syndicatemeleespace" + + movement_cooldown = 0 + + armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 35, bio = 100, rad = 100) // Same armor as their voidsuit + + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + + corpse = /obj/effect/landmark/mobcorpse/syndicatecommando + +/mob/living/simple_mob/humanoid/merc/melee/sword/space/Process_Spacemove(var/check_drift = 0) + return + +// Ranged Space Merc +/mob/living/simple_mob/humanoid/merc/ranged/space + name = "syndicate sommando" + icon_state = "syndicaterangedpsace" + icon_living = "syndicaterangedpsace" + + movement_cooldown = 0 + + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + + corpse = /obj/effect/landmark/mobcorpse/syndicatecommando + +/mob/living/simple_mob/humanoid/merc/ranged/space/Process_Spacemove(var/check_drift = 0) + return + +//////////////////////////////// +// PoI Mercs +//////////////////////////////// + +// None of these drop weapons, until we have a better way to balance them +/mob/living/simple_mob/humanoid/merc/melee/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/melee/sword/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/smg/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/laser/poi + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/ionrifle + loot_list = list() + +/mob/living/simple_mob/humanoid/merc/ranged/grenadier/poi + loot_list = list() \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm new file mode 100644 index 0000000000..cc16cebedc --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm @@ -0,0 +1,40 @@ +/mob/living/simple_mob/humanoid/pirate + name = "Pirate" + desc = "Does what he wants cause a pirate is free." + tt_desc = "E Homo sapiens" + icon_state = "piratemelee" + icon_living = "piratemelee" + icon_dead = "piratemelee_dead" + + faction = "pirate" + + response_help = "pushes" + response_disarm = "shoves" + response_harm = "hits" + + harm_intent_damage = 5 + melee_damage_lower = 30 + melee_damage_upper = 30 + attack_armor_pen = 50 + attack_sharp = 1 + attack_edge = 1 + + attacktext = list("slashed") + attack_sound = 'sound/weapons/bladeslice.ogg' + + loot_list = list(/obj/item/weapon/melee/energy/sword/pirate = 100) + + corpse = /obj/effect/landmark/mobcorpse/pirate + +/mob/living/simple_mob/humanoid/pirate/ranged + name = "Pirate Gunner" + icon_state = "pirateranged" + icon_living = "pirateranged" + icon_dead = "piratemelee_dead" + + projectiletype = /obj/item/projectile/beam + projectilesound = 'sound/weapons/laser.ogg' + + loot_list = list(/obj/item/weapon/gun/energy/laser = 100) + + corpse = /obj/effect/landmark/mobcorpse/pirate/ranged \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/russian.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/russian.dm new file mode 100644 index 0000000000..4a0bd00261 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/russian.dm @@ -0,0 +1,35 @@ +/mob/living/simple_mob/humanoid/russian + name = "russian" + desc = "For the Motherland!" + tt_desc = "E Homo sapiens" + icon_state = "russianmelee" + icon_living = "russianmelee" + icon_dead = "russianmelee_dead" + icon_gib = "syndicate_gib" + + faction = "russian" + + response_help = "pokes" + response_disarm = "shoves" + response_harm = "hits" + + harm_intent_damage = 5 + melee_damage_lower = 15 + melee_damage_upper = 15 + attacktext = list("punched") + + loot_list = list(/obj/item/weapon/material/knife = 100) + + corpse = /obj/effect/landmark/mobcorpse/russian + +/mob/living/simple_mob/humanoid/russian/ranged + icon_state = "russianranged" + icon_living = "russianranged" + + projectiletype = /obj/item/projectile/bullet + casingtype = /obj/item/ammo_casing/spent + projectilesound = 'sound/weapons/Gunshot.ogg' + + loot_list = list(/obj/item/weapon/gun/projectile/revolver/mateba = 100) + + corpse = /obj/effect/landmark/mobcorpse/russian/ranged \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm b/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm new file mode 100644 index 0000000000..60cc13fb2b --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm @@ -0,0 +1,101 @@ +// Illusion type mobs pretend to be other things visually, and generally cannot be harmed as they're not 'real'. + +/mob/living/simple_mob/illusion + name = "illusion" + desc = "If you can read me, the game broke. Please report this to a coder." + + resistance = 1000 // Holograms are tough. + heat_resist = 1 + cold_resist = 1 + shock_resist = 1 + poison_resist = 1 + + movement_cooldown = 0 + mob_bump_flag = 0 // If the illusion can't be swapped it will be obvious. + + response_help = "pushes a hand through" + response_disarm = "tried to disarm" + response_harm = "tried to punch" + + mob_class = MOB_CLASS_ILLUSION + + ai_holder_type = /datum/ai_holder/simple_mob/inert/astar // Gets controlled manually by technomancers/admins, with AI pathfinding assistance. + + var/atom/movable/copying = null // The thing we're trying to look like. + var/realistic = FALSE // If true, things like bullets and weapons will hit it, to be a bit more convincing from a distance. + +/mob/living/simple_mob/illusion/update_icon() // We don't want the appearance changing AT ALL unless by copy_appearance(). + return + +/mob/living/simple_mob/illusion/proc/copy_appearance(atom/movable/thing_to_copy) + if(!thing_to_copy) + return FALSE + appearance = thing_to_copy.appearance + copying = thing_to_copy + density = thing_to_copy.density // So you can't bump into objects that aren't supposed to be dense. + return TRUE + +// Because we can't perfectly duplicate some examine() output, we directly examine the AM it is copying. It's messy but +// this is to prevent easy checks from the opposing force. +/mob/living/simple_mob/illusion/examine(mob/user) + if(copying) + copying.examine(user) + return + ..() + +/mob/living/simple_mob/illusion/bullet_act(obj/item/projectile/P) + if(!P) + return + + if(realistic) + return ..() + + return PROJECTILE_FORCE_MISS + +/mob/living/simple_mob/illusion/attack_hand(mob/living/carbon/human/M) + if(!realistic) + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) + visible_message(span("warning", "\The [M]'s hand goes through \the [src]!")) + return + else + switch(M.a_intent) + if(I_HELP) + var/datum/gender/T = gender_datums[src.get_visible_gender()] + M.visible_message( + span("notice", "\The [M] hugs [src] to make [T.him] feel better!"), \ + span("notice", "You hug [src] to make [T.him] feel better!") + ) // slightly redundant as at the moment most mobs still use the normal gender var, but it works and future-proofs it + playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + + if(I_DISARM) + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) + visible_message(span("danger", "\The [M] attempted to disarm [src]!")) + M.do_attack_animation(src) + + if(I_GRAB) + ..() + + if(I_HURT) + adjustBruteLoss(harm_intent_damage) + M.visible_message(span("danger", "\The [M] [response_harm] \the [src]")) + M.do_attack_animation(src) + +/mob/living/simple_mob/illusion/hit_with_weapon(obj/item/I, mob/living/user, effective_force, hit_zone) + if(realistic) + return ..() + + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) + visible_message(span("warning", "\The [user]'s [I] goes through \the [src]!")) + return FALSE + +/mob/living/simple_mob/illusion/ex_act() + return + +// Try to have the same tooltip, or else it becomes really obvious which one is fake. +/mob/living/simple_mob/illusion/get_nametag_name(mob/user) + if(copying) + return copying.get_nametag_name(user) + +/mob/living/simple_mob/illusion/get_nametag_desc(mob/user) + if(copying) + return copying.get_nametag_desc(user) diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/combat_drone.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/combat_drone.dm new file mode 100644 index 0000000000..8731a06764 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/combat_drone.dm @@ -0,0 +1,77 @@ +/* + Combat drones have a rapid ranged attack, and have a projectile shield. + They are rather slow, but attempt to 'kite' its target. + A solid hit with an EMP grenade will kill the shield instantly. +*/ + +/mob/living/simple_mob/mechanical/combat_drone + name = "combat drone" + desc = "An automated combat drone armed with state of the art weaponry and shielding." + icon_state = "drone" + icon_living = "drone" + icon_dead = "drone_dead" + has_eye_glow = TRUE + + faction = "malf_drone" + + maxHealth = 50 // Shield has 150 for total of 200. + health = 50 + movement_cooldown = 5 + hovering = TRUE + + base_attack_cooldown = 5 + projectiletype = /obj/item/projectile/beam/drone + projectilesound = 'sound/weapons/laser3.ogg' + + response_help = "pokes" + response_disarm = "gently pushes aside" + response_harm = "hits" + + ai_holder_type = /datum/ai_holder/simple_mob/ranged/kiting/threatening + say_list_type = /datum/say_list/malf_drone + + var/datum/effect/effect/system/ion_trail_follow/ion_trail = null + var/obj/item/shield_projector/shields = null + +/mob/living/simple_mob/mechanical/combat_drone/initialize() + ion_trail = new + ion_trail.set_up(src) + ion_trail.start() + + shields = new /obj/item/shield_projector/rectangle/automatic/drone(src) + return ..() + +/mob/living/simple_mob/mechanical/combat_drone/Destroy() + QDEL_NULL(ion_trail) + QDEL_NULL(shields) + return ..() + +/mob/living/simple_mob/mechanical/combat_drone/death() + ..(null,"suddenly breaks apart.") + qdel(src) + +/mob/living/simple_mob/mechanical/combat_drone/Process_Spacemove(var/check_drift = 0) + return TRUE + +/obj/item/projectile/beam/drone + damage = 10 + +/obj/item/shield_projector/rectangle/automatic/drone + shield_health = 150 + max_shield_health = 150 + shield_regen_delay = 10 SECONDS + shield_regen_amount = 10 + size_x = 1 + size_y = 1 + +// A slightly easier drone, for POIs. +// Difference is that it should not be faster than you. +/mob/living/simple_mob/mechanical/combat_drone/lesser + desc = "An automated combat drone with an aged apperance." + movement_cooldown = 10 + + +// This one is the type spawned by the random event. +// It won't wander away from its spawn point +/mob/living/simple_mob/mechanical/combat_drone/event + ai_holder_type = /datum/ai_holder/simple_mob/ranged/kiting/threatening/event diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/golem.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/golem.dm new file mode 100644 index 0000000000..cffa9cb134 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/golem.dm @@ -0,0 +1,152 @@ +// The GOLEM is a spell-flinging synthetic. + +/mob/living/simple_mob/mechanical/technomancer_golem + name = "unknown synthetic" + desc = "A rather unusual looking synthetic." + icon = 'icons/mob/mob.dmi' + icon_state = "golem" + health = 300 + maxHealth = 300 + + faction = "golem" + + response_help = "pets" + response_disarm = "pushes away" + response_harm = "punches" + harm_intent_damage = 3 + friendly = "hugs" + + melee_damage_lower = 30 // It has a built in esword. + melee_damage_upper = 30 + attack_sound = 'sound/weapons/blade1.ogg' + attacktext = list("slashed") + melee_attack_delay = 0.5 SECONDS // Even has custom attack animations. + ranged_attack_delay = 0.5 SECONDS + special_attack_delay = 1 SECOND + + special_attack_min_range = 0 + special_attack_max_range = 7 + + ai_holder_type = /datum/ai_holder/simple_mob/melee + + var/obj/item/weapon/technomancer_core/golem/core = null + var/obj/item/weapon/spell/active_spell = null // Shield and ranged spells + var/mob/living/master = null + var/casting = FALSE // Used to ensure the correct animation is played. Testing if a spell exists won't always work as some spells delete themselves upon use. + + var/list/known_spells = list( + "beam" = /obj/item/weapon/spell/projectile/beam, + "chain lightning" = /obj/item/weapon/spell/projectile/chain_lightning, + "force missile" = /obj/item/weapon/spell/projectile/force_missile, + "ionic bolt" = /obj/item/weapon/spell/projectile/ionic_bolt, + "lightning" = /obj/item/weapon/spell/projectile/lightning, + "blink" = /obj/item/weapon/spell/blink, + "dispel" = /obj/item/weapon/spell/dispel, + "oxygenate" = /obj/item/weapon/spell/oxygenate, + "mend life" = /obj/item/weapon/spell/modifier/mend_life, + "mend synthetic" = /obj/item/weapon/spell/modifier/mend_synthetic, + "mend organs" = /obj/item/weapon/spell/mend_organs, + "purify" = /obj/item/weapon/spell/modifier/purify, + "resurrect" = /obj/item/weapon/spell/resurrect, + "passwall" = /obj/item/weapon/spell/passwall, + "repel missiles" = /obj/item/weapon/spell/modifier/repel_missiles, + "corona" = /obj/item/weapon/spell/modifier/corona, + "haste" = /obj/item/weapon/spell/modifier/haste + ) + +/mob/living/simple_mob/mechanical/technomancer_golem/initialize() + core = new(src) + return ..() + +/mob/living/simple_mob/mechanical/technomancer_golem/Destroy() + qdel(core) + return ..() + +/mob/living/simple_mob/mechanical/technomancer_golem/unref_spell() + active_spell = null + return ..() + +/mob/living/simple_mob/mechanical/technomancer_golem/death() + ..() + visible_message("\The [src] disintegrates!") + new /obj/effect/decal/cleanable/blood/gibs/robot(src.loc) + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + qdel(src) + +/mob/living/simple_mob/mechanical/technomancer_golem/place_spell_in_hand(var/path) + if(!path || !ispath(path)) + return FALSE + if(active_spell) + qdel(active_spell) + + active_spell = new path(src) + +/mob/living/simple_mob/mechanical/technomancer_golem/verb/test_giving_spells() + var/choice = input(usr, "What spell?", "Give spell") as null|anything in known_spells + if(choice) + place_spell_in_hand(known_spells[choice]) + else + qdel(active_spell) + +/mob/living/simple_mob/mechanical/technomancer_golem/get_technomancer_core() + return core + +/mob/living/simple_mob/mechanical/technomancer_golem/can_special_attack(atom/A) + if(active_spell) // Don't bother checking everything else if no spell is ready. + return ..() + return FALSE + +/mob/living/simple_mob/mechanical/technomancer_golem/should_special_attack(atom/A) + return instability < 50 // Don't kill ourselves by casting everything. + + +/mob/living/simple_mob/mechanical/technomancer_golem/do_special_attack(atom/A) + var/proximity = Adjacent(A) + if(active_spell) + if(proximity && active_spell.cast_methods & CAST_MELEE) // Use melee method if available and close enough. + return active_spell.on_melee_cast(A, src) + else if(active_spell.cast_methods & CAST_RANGED) // Otherwise use ranged if possible. Will also work for point-blank range. + return active_spell.on_ranged_cast(A, src) + return ..() + +/mob/living/simple_mob/mechanical/technomancer_golem/melee_pre_animation(atom/A) + if(active_spell && active_spell.cast_methods & CAST_MELEE|CAST_RANGED) // If they're trying to melee-cast a spell, use the special animation instead. + special_pre_animation(A) + return + + flick("golem_pre_melee", src) // To force the animation to restart. + icon_living = "golem_pre_melee" // The animation will hold after this point until melee_post_animation() gets called. + icon_state = "golem_pre_melee" + setClickCooldown(2) + +/mob/living/simple_mob/mechanical/technomancer_golem/melee_post_animation(atom/A) + if(casting) // Some spells delete themselves when used, so we use a different variable set earlier instead. + special_post_animation(A) + return + + flick("golem_post_melee", src) + icon_living = "golem" + icon_state = "golem" + setClickCooldown(6) + +/mob/living/simple_mob/mechanical/technomancer_golem/ranged_pre_animation(atom/A) + flick("golem_pre_ranged", src) + icon_living = "golem_pre_ranged" + icon_state = "golem_pre_ranged" + setClickCooldown(5) + +/mob/living/simple_mob/mechanical/technomancer_golem/ranged_post_animation(atom/A) + flick("golem_post_ranged", src) + icon_living = "golem" + icon_state = "golem" + setClickCooldown(5) + +/mob/living/simple_mob/mechanical/technomancer_golem/special_pre_animation(atom/A) + casting = TRUE + ranged_pre_animation(A) // Both have the same animation. + +/mob/living/simple_mob/mechanical/technomancer_golem/special_post_animation(atom/A) + casting = FALSE + ranged_post_animation(A) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm new file mode 100644 index 0000000000..fe43c856f9 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm @@ -0,0 +1,54 @@ +// Hivebots are tuned towards how many default lasers are needed to kill them. +// As such, if laser damage is ever changed, you should change this define. +#define LASERS_TO_KILL * 40 + +/mob/living/simple_mob/mechanical/hivebot + name = "hivebot" + desc = "A robot. It appears to be somewhat resilient, but lacks a true weapon." + icon = 'icons/mob/hivebot.dmi' + icon_state = "basic" + icon_living = "basic" + + faction = "hivebot" + + maxHealth = 3 LASERS_TO_KILL + health = 3 LASERS_TO_KILL + water_resist = 0.5 + movement_sound = 'sound/effects/servostep.ogg' + + attacktext = list("clawed") + projectilesound = 'sound/weapons/Gunshot.ogg' + + ai_holder_type = /datum/ai_holder/simple_mob/hivebot + say_list_type = /datum/say_list/hivebot + + +/mob/living/simple_mob/mechanical/hivebot/death() + ..() + visible_message(span("warning","\The [src] blows apart!")) + new /obj/effect/decal/cleanable/blood/gibs/robot(src.loc) + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + qdel(src) + +// The hivebot's default projectile. +/obj/item/projectile/bullet/hivebot + damage = 10 + damage_type = BRUTE + sharp = FALSE + edge = FALSE + +/mob/living/simple_mob/mechanical/hivebot/swarm + name = "swarm hivebot" + desc = "A robot. It looks fragile and weak." + maxHealth = 1 LASERS_TO_KILL + health = 1 LASERS_TO_KILL + melee_damage_lower = 8 + melee_damage_upper = 8 + +/datum/ai_holder/simple_mob/hivebot + pointblank = TRUE + conserve_ammo = TRUE + firing_lanes = TRUE + can_flee = FALSE // Fearless dumb machines. \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm new file mode 100644 index 0000000000..5e4d877751 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm @@ -0,0 +1,147 @@ +// These hivebots are intended for general damage causing, at range. + +/mob/living/simple_mob/mechanical/hivebot/ranged_damage + maxHealth = 2 LASERS_TO_KILL // 60 health + health = 2 LASERS_TO_KILL + projectiletype = /obj/item/projectile/bullet/hivebot + +// The regular ranged hivebot, that fires somewhat weak projectiles. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/basic + name = "ranged hivebot" + desc = "A robot with a makeshift integrated ballistic weapon." + + +// This one shoots quickly, and is considerably more dangerous. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/rapid + name = "rapid hivebot" + desc = "A robot with a crude but deadly integrated rifle." + base_attack_cooldown = 5 // Two attacks a second or so. + player_msg = "You have a rapid fire attack." + + +// Shoots deadly lasers. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/laser + name = "laser hivebot" + desc = "A robot with a photonic weapon integrated into itself." + projectiletype = /obj/item/projectile/beam/blue + projectilesound = 'sound/weapons/Laser.ogg' + player_msg = "You have a laser attack." + + +// Shoots EMPs, to screw over other robots. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/ion + name = "ionic hivebot" + desc = "A robot with an electromagnetic pulse projector." + icon_state = "yellow" + icon_living = "yellow" + + projectiletype = /obj/item/projectile/ion + projectilesound = 'sound/weapons/Laser.ogg' + player_msg = "You have a ranged ion attack, which is very strong against other synthetics.
\ + Be careful to not hit yourself or your team, as it will affect you as well." + +// Beefy and ranged. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong + name = "strong hivebot" + desc = "A robot with a crude ballistic weapon and strong armor." + maxHealth = 4 LASERS_TO_KILL // 120 health. + health = 4 LASERS_TO_KILL + melee_damage_lower = 15 + melee_damage_upper = 15 + +// Also beefy, but tries to stay at their 'home', ideal for base defense. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong/guard + name = "guard hivebot" + desc = "A robot that seems to be guarding something." +// ai_holder_type = todo + + +// Inflicts a damage-over-time modifier on things it hits. +// It is able to stack with repeated attacks. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/dot + name = "ember hivebot" + desc = "A robot that appears to utilize fire to cook their enemies." + icon_state = "red" + icon_living = "red" + + projectiletype = /obj/item/projectile/fire + heat_resist = 1 + player_msg = "Your attacks inflict a damage over time effect, that will \ + harm your target slowly. The effect stacks with further attacks.
\ + You are also immune to fire." + +/obj/item/projectile/fire + name = "ember" + icon = 'icons/effects/effects.dmi' + icon_state = "explosion_particle" + modifier_type_to_apply = /datum/modifier/fire + modifier_duration = 6 SECONDS // About 15 damage per stack, as Life() ticks every two seconds. + damage = 0 + nodamage = TRUE + + +// Close to mid-ranged shooter that arcs over other things, ideal if allies are in front of it. +// Difference from siege hivebots is that siege hivebots have limited charges for their attacks, are very long range, and \ +// the projectiles have an AoE component, where as backline hivebots do not. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/backline + name = "backline hivebot" + desc = "A robot that can fire short-ranged projectiles over their allies." + projectiletype = /obj/item/projectile/arc/blue_energy + projectilesound = 'sound/weapons/Laser.ogg' + player_msg = "Your attacks are short-ranged, but can arc over obstructions such as allies \ + or barriers." + +/obj/item/projectile/arc/blue_energy + name = "energy missile" + icon_state = "force_missile" + damage = 15 // A bit stronger since arcing projectiles are much easier to avoid than traditional ones. + damage_type = BURN + +// Very long ranged hivebot that rains down hell. +// Their projectiles arc, meaning they go over everything until it hits the ground. +// This means they're somewhat easier to avoid, but go over most defenses (like allies, or barriers), +// and tend to do more harm than a regular projectile, due to being AoE. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege + name = "siege engine hivebot" + desc = "A large robot capable of delivering long range bombardment." + projectiletype = /obj/item/projectile/arc/test + icon_scale = 2 + icon_state = "red" + icon_living = "red" + + player_msg = "You are capable of firing very long range bombardment attacks.
\ + To use, click on a tile or enemy at a long range. Note that the projectile arcs in the air, \ + so it will fly over everything inbetween you and the target.
\ + The bombardment is most effective when attacking a static structure, as it cannot avoid your fire." + +// Fires EMP blasts. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/emp + name = "ionic artillery hivebot" + desc = "A large robot capable of annihilating electronics from a long distance." + projectiletype = /obj/item/projectile/arc/emp_blast + +/obj/item/projectile/arc/emp_blast + name = "emp blast" + icon_state = "bluespace" + +/obj/item/projectile/arc/emp_blast/on_impact(turf/T) + empulse(T, 2, 4, 7, 10) // Normal EMP grenade. + return ..() + +/obj/item/projectile/arc/emp_blast/weak/on_impact(turf/T) + empulse(T, 1, 2, 3, 4) // Sec EMP grenade. + return ..() + + +// Fires shots that irradiate the tile hit. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/radiation + name = "desolator hivebot" + desc = "A large robot capable of irradiating a large area from afar." + projectiletype = /obj/item/projectile/arc/radioactive + + +// Essentially a long ranged frag grenade. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/fragmentation + name = "anti-personnel artillery hivebot" + desc = "A large robot capable of delivering fragmentation shells to rip apart their fleshy enemies." + projectiletype = /obj/item/projectile/arc/fragmentation \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm new file mode 100644 index 0000000000..360686d591 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm @@ -0,0 +1,87 @@ +// These hivebots help their team in various ways, and can be very powerful with allies, but are otherwise very weak when alone. + +/mob/living/simple_mob/mechanical/hivebot/support + icon_state = "white" + icon_living = "white" + attacktext = list("prodded") + movement_cooldown = 5 + melee_damage_lower = 2 + melee_damage_upper = 2 + + +// This hivebot supplies a general buff to nearby hivebots that improve their performance. +// Note that the commander itself does not receive the buff. +/mob/living/simple_mob/mechanical/hivebot/support/commander + name = "commander hivebot" + desc = "A robot that appears to be directing the others." + maxHealth = 5 LASERS_TO_KILL // 150 health + health = 5 LASERS_TO_KILL + player_msg = "You increase the performance of other hivebots near you passively.
\ + You are otherwise very weak offensively." + +/mob/living/simple_mob/mechanical/hivebot/support/commander/handle_special() + for(var/mob/living/L in range(4, src)) + if(L == src) + continue // Don't buff ourselves. + if(IIsAlly(L) && L.isSynthetic()) // Don't buff enemies. + L.add_modifier(/datum/modifier/aura/hivebot_commander_buff, null, src) + +// Modifier added to friendly hivebots nearby. +// Boosts most stats by 30%. +// The boost is lost if the commander is too far away or dies. +/datum/modifier/aura/hivebot_commander_buff + name = "Strategicals" + on_created_text = "Signal established with commander. Optimizating combat performance..." + on_expired_text = "Lost signal to commander. Optimization halting." + stacks = MODIFIER_STACK_FORBID + aura_max_distance = 4 + mob_overlay_state = "signal_blue" + + disable_duration_percent = 0.7 + outgoing_melee_damage_percent = 1.3 + attack_speed_percent = 1.3 + accuracy = 30 + slowdown = -1 + evasion = 30 + +// Variant that automatically commands nearby allies to follow it when created. +// Useful to avoid having to manually set follow to a lot of hivebots that are gonna die in the next minute anyways. +/mob/living/simple_mob/mechanical/hivebot/support/commander/autofollow/initialize() + for(var/mob/living/L in hearers(7, src)) + if(!L.ai_holder) + continue + if(L.faction != src.faction) + continue + var/datum/ai_holder/AI = L.ai_holder + AI.set_follow(src) + return ..() + + +// This hivebot adds charges to nearby allied hivebots that use the charge system for their special attacks. +// A charge is given to a nearby ally every so often. +// Charges cannot exceed the initial starting amount. +/mob/living/simple_mob/mechanical/hivebot/support/logistics + name = "logistics hivebot" + desc = "A robot that resupplies their allies." + maxHealth = 3 LASERS_TO_KILL // 90 health + health = 3 LASERS_TO_KILL + player_msg = "You passively restore 'charges' to allies with special abilities who are \ + limited to using them a specific number of times." + var/resupply_range = 5 + var/resupply_cooldown = 4 SECONDS + var/last_resupply = null + +/mob/living/simple_mob/mechanical/hivebot/support/logistics/handle_special() + if(last_resupply + resupply_cooldown > world.time) + return // On cooldown. + + for(var/mob/living/simple_mob/SM in hearers(resupply_range, src)) + if(SM == src) + continue // We don't use charges buuuuut in case that changes in the future... + if(IIsAlly(SM)) // Don't resupply enemies. + if(!isnull(SM.special_attack_charges) && SM.special_attack_charges < initial(SM.special_attack_charges)) + SM.special_attack_charges += 1 + to_chat(SM, span("notice", "\The [src] has resupplied you, and you can use your special ability one additional time.")) + to_chat(src, span("notice", "You have resupplied \the [SM].")) + last_resupply = world.time + break // Only one resupply per pulse. diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/tank.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/tank.dm new file mode 100644 index 0000000000..007f190c04 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/tank.dm @@ -0,0 +1,166 @@ +// These hivebots are harder to kill than normal, and are meant to protect their squad by +// distracting their enemies. This is done by being seen as very threatening. +// Their melee attacks weaken whatever they hit. + +/mob/living/simple_mob/mechanical/hivebot/tank + attacktext = list("prodded") + projectiletype = null // To force the AI to melee. + movement_cooldown = 10 + melee_damage_lower = 3 + melee_damage_upper = 3 + attack_sound = 'sound/weapons/Egloves.ogg' + +// All tank hivebots apply a modifier to their target, and force them to attack them if they're AI controlled. +/mob/living/simple_mob/mechanical/hivebot/tank/apply_melee_effects(atom/A) + if(isliving(A)) + var/mob/living/L = A + L.taunt(src, TRUE) + L.add_modifier(/datum/modifier/hivebot_weaken, 3 SECONDS) + +// Modifier applied to whatever a tank hivebot hits, intended to make the target do even less damage. +/datum/modifier/hivebot_weaken + name = "Shocked" + desc = "You feel less able to exert yourself after being prodded." + on_created_text = "You feel weak..." + on_expired_text = "You feel better." + stacks = MODIFIER_STACK_EXTEND + mob_overlay_state = "electricity" + + attack_speed_percent = 0.6 + outgoing_melee_damage_percent = 0.7 + accuracy = -40 + accuracy_dispersion = 1 + slowdown = 1 + evasion = -20 + +// This one is tanky by having a massive amount of health. +/mob/living/simple_mob/mechanical/hivebot/tank/meatshield + name = "bulky hivebot" + desc = "A large robot." + maxHealth = 10 LASERS_TO_KILL // 300 health + health = 10 LASERS_TO_KILL + icon_scale = 2 + player_msg = "You have a very large amount of health." + + +// This one is tanky by having armor. +/mob/living/simple_mob/mechanical/hivebot/tank/armored + name = "armored hivebot" + desc = "A robot clad in heavy armor." + maxHealth = 5 LASERS_TO_KILL // 150 health. + health = 5 LASERS_TO_KILL + icon_scale = 1.5 + player_msg = "You are heavily armored." + // Note that armor effectively makes lasers do about 9 damage instead of 30, + // so it has an effective health of ~16.6 LASERS_TO_KILL if regular lasers are used. + // Xrays will do much better against this. + armor = list( + "melee" = 40, + "bullet" = 40, + "laser" = 40, + "energy" = 30, + "bomb" = 30, + "bio" = 100, + "rad" = 100 + ) + armor_soak = list( + "melee" = 15, + "bullet" = 10, + "laser" = 15, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + +/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_melee + name = "riot hivebot" + desc = "A robot specialized in close quarters combat." + player_msg = "You are heavily armored against close quarters combat." + armor = list( + "melee" = 70, + "bullet" = 0, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + armor_soak = list( + "melee" = 20, + "bullet" = 0, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + +/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_bullet + name = "bulletproof hivebot" + desc = "A robot specialized in ballistic defense." + player_msg = "You are heavily armored against ballistic weapons." + armor = list( + "melee" = 0, + "bullet" = 70, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + armor_soak = list( + "melee" = 0, + "bullet" = 20, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + +/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_laser + name = "ablative hivebot" + desc = "A robot specialized in photonic defense." + player_msg = "You are heavily armored against laser weapons." + armor = list( + "melee" = 0, + "bullet" = 0, + "laser" = 70, + "energy" = 0, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + armor_soak = list( + "melee" = 0, + "bullet" = 0, + "laser" = 20, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + var/reflect_chance = 40 // Same as regular ablative. + +// Ablative Hivebots can reflect lasers just like humans. +/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_laser/bullet_act(obj/item/projectile/P) + if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) + var/reflect_prob = reflect_chance - round(P.damage/3) + if(prob(reflect_prob)) + visible_message(span("danger", "The [P.name] gets reflected by [src]'s armor!"), \ + span("userdanger", "The [P.name] gets reflected by [src]'s armor!")) + + // Find a turf near or on the original location to bounce to + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/turf/curloc = get_turf(src) + + // redirect the projectile + P.redirect(new_x, new_y, curloc, src) + P.reflected = 1 + + return -1 // complete projectile permutation + + return (..(P)) diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm new file mode 100644 index 0000000000..246b0fa1c6 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm @@ -0,0 +1,261 @@ +// Stronger than a regular Dark Gygax, this one has three special attacks, based on intents. +// First special attack launches three arcing rockets at the current target. +// Second special attack fires a projectile that creates a short-lived microsingularity that pulls in everything nearby. Magboots can protect from this. +// Third special attack creates a dangerous electric field that causes escalating electric damage, before emitting a tesla shock and blinding anyone looking at the mecha. +// The AI will choose one every ten seconds. +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced + name = "advanced dark gygax" + desc = "An experimental exosuit that utilizes advanced materials to allow for greater protection while still being lightweight and fast. \ + It also is armed with an array of next-generation weaponry." + icon_state = "darkgygax_adv" + wreckage = /obj/structure/loot_pile/mecha/gygax/dark/adv + icon_scale = 1.5 + movement_shake_radius = 14 + + maxHealth = 450 + deflect_chance = 25 + has_repair_droid = TRUE + armor = list( + "melee" = 50, + "bullet" = 50, + "laser" = 50, + "energy" = 30, + "bomb" = 30, + "bio" = 100, + "rad" = 100 + ) + + special_attack_min_range = 1 + special_attack_max_range = 7 + special_attack_cooldown = 10 SECONDS + projectiletype = /obj/item/projectile/force_missile + projectilesound = 'sound/weapons/wave.ogg' + var/obj/effect/overlay/energy_ball/energy_ball = null + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/Destroy() + if(energy_ball) + energy_ball.stop_orbit() + qdel(energy_ball) + return ..() + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/do_special_attack(atom/A) + . = TRUE // So we don't fire a laser as well. + switch(a_intent) + if(I_DISARM) // Side gun + electric_defense(A) + if(I_HURT) // Rockets + launch_rockets(A) + if(I_GRAB) // Micro-singulo + launch_microsingularity(A) + +#define ELECTRIC_ZAP_POWER 20000 + +// Charges a tesla shot, while emitting a dangerous electric field. The exosuit is immune to electric damage while this is ongoing. +// It also briefly blinds anyone looking directly at the mech without flash protection. +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/electric_defense(atom/target) + set waitfor = FALSE + + // Temporary immunity to shock to avoid killing themselves with their own attack. + var/old_shock_resist = shock_resist + shock_resist = 1 + + // Make the energy ball. This is purely visual since the tesla ball is hyper-deadly. + energy_ball = new(loc) + energy_ball.adjust_scale(0.5) + energy_ball.orbit(src, 32, TRUE, 1 SECOND) + + visible_message(span("warning", "\The [src] creates \an [energy_ball] around itself!")) + + playsound(src.loc, 'sound/effects/lightning_chargeup.ogg', 100, 1, extrarange = 30) + + // Shock nearby things that aren't ourselves. + for(var/i = 1 to 10) + energy_ball.adjust_scale(0.5 + (i/10)) + energy_ball.set_light(i/2, i/2, "#0000FF") + for(var/thing in range(3, src)) + // This is stupid because mechs are stupid and not mobs. + if(isliving(thing)) + var/mob/living/L = thing + + if(L == src) + continue + if(L.stat) + continue // Otherwise it can get pretty laggy if there's loads of corpses around. + L.inflict_shock_damage(i * 2) + if(L && L.has_AI()) // Some mobs delete themselves when dying. + L.ai_holder.react_to_attack(src) + + else if(istype(thing, /obj/mecha)) + var/obj/mecha/M = thing + M.take_damage(i * 2, "energy") // Mechs don't have a concept for siemens so energy armor check is the best alternative. + + sleep(1 SECOND) + + // Shoot a tesla bolt, and flashes people who are looking at the mecha without sufficent eye protection. + visible_message(span("warning", "\The [energy_ball] explodes in a flash of light, sending a shock everywhere!")) + playsound(src.loc, 'sound/effects/lightningbolt.ogg', 100, 1, extrarange = 30) + tesla_zap(src.loc, 5, ELECTRIC_ZAP_POWER, FALSE) + for(var/mob/living/L in viewers(src)) + if(L == src) + continue + var/dir_towards_us = get_dir(L, src) + if(L.dir && L.dir & dir_towards_us) + to_chat(L, span("danger", "The flash of light blinds you briefly.")) + L.flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = TRUE) + + // Get rid of our energy ball. + energy_ball.stop_orbit() + qdel(energy_ball) + + sleep(1 SECOND) + // Resist resistance to old value. + shock_resist = old_shock_resist // Not using initial() in case the value gets modified by an admin or something. + +#undef ELECTRIC_ZAP_POWER + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/launch_rockets(atom/target) + set waitfor = FALSE + + // Telegraph our next move. + Beam(target, icon_state = "sat_beam", time = 3.5 SECONDS, maxdistance = INFINITY) + visible_message(span("warning", "\The [src] deploys a missile rack!")) + playsound(src, 'sound/effects/turret/move1.wav', 50, 1) + sleep(0.5 SECONDS) + + for(var/i = 1 to 3) + if(target) // Might get deleted in the meantime. + var/turf/T = get_turf(target) + if(T) + visible_message(span("warning", "\The [src] fires a rocket into the air!")) + playsound(src, 'sound/weapons/rpg.ogg', 70, 1) + face_atom(T) + var/obj/item/projectile/arc/explosive_rocket/rocket = new(loc) + rocket.launch(T) + sleep(1 SECOND) + + visible_message(span("warning", "\The [src] retracts the missile rack.")) + playsound(src, 'sound/effects/turret/move2.wav', 50, 1) + +// Arcing rocket projectile that produces a weak explosion when it lands. +// Shouldn't punch holes in the floor, but will still hurt. +/obj/item/projectile/arc/explosive_rocket + name = "rocket" + icon_state = "mortar" + +/obj/item/projectile/arc/explosive_rocket/on_impact(turf/T) + new /obj/effect/explosion(T) // Weak explosions don't produce this on their own, apparently. + explosion(T, 0, 0, 2, adminlog = FALSE) + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/launch_microsingularity(atom/target) + var/turf/T = get_turf(target) + visible_message(span("warning", "\The [src] fires an energetic sphere into the air!")) + playsound(src, 'sound/weapons/Laser.ogg', 50, 1) + face_atom(T) + var/obj/item/projectile/arc/microsingulo/sphere = new(loc) + sphere.launch(T) + +/obj/item/projectile/arc/microsingulo + name = "micro singularity" + icon_state = "bluespace" + +/obj/item/projectile/arc/microsingulo/on_impact(turf/T) + new /obj/effect/temporary_effect/pulse/microsingulo(T) + + +/obj/effect/temporary_effect/pulse/microsingulo + name = "micro singularity" + desc = "It's sucking everything in!" + icon = 'icons/obj/objects.dmi' + icon_state = "bhole3" + light_range = 4 + light_power = 5 + light_color = "#2ECCFA" + pulses_remaining = 10 + pulse_delay = 0.5 SECONDS + var/pull_radius = 3 + var/pull_strength = STAGE_THREE + +/obj/effect/temporary_effect/pulse/microsingulo/on_pulse() + for(var/atom/A in range(pull_radius, src)) + A.singularity_pull(src, pull_strength) + + +// The Advanced Dark Gygax's AI. +// The mob has three special attacks, based on the current intent. +// This AI choose the appropiate intent for the situation, and tries to ensure it doesn't kill itself by firing missiles at its feet. +/datum/ai_holder/simple_mob/intentional/adv_dark_gygax + conserve_ammo = TRUE // Might help avoid 'I shoot the wall forever' cheese. + var/closest_desired_distance = 1 // Otherwise run up to them to be able to potentially shock or punch them. + + var/electric_defense_radius = 3 // How big to assume electric defense's area is. + var/microsingulo_radius = 3 // Same but for microsingulo pull. + var/rocket_explosive_radius = 2 // Explosion radius for the rockets. + + var/electric_defense_threshold = 2 // How many non-targeted people are needed in close proximity before electric defense is viable. + var/microsingulo_threshold = 2 // Similar to above, but uses an area around the target. + +// Used to control the mob's positioning based on which special attack it has done. +// Note that the intent will not change again until the next special attack is about to happen. +/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/on_engagement(atom/A) + // Make the AI backpeddle if using an AoE special attack. + var/list/risky_intents = list(I_GRAB, I_HURT) // Mini-singulo and missiles. + if(holder.a_intent in risky_intents) + var/closest_distance = 1 + switch(holder.a_intent) // Plus one just in case. + if(I_HURT) + closest_distance = rocket_explosive_radius + 1 + if(I_GRAB) + closest_distance = microsingulo_radius + 1 + + if(get_dist(holder, A) <= closest_distance) + holder.IMove(get_step_away(holder, A, closest_distance)) + + // Otherwise get up close and personal. + else if(get_dist(holder, A) > closest_desired_distance) + holder.IMove(get_step_towards(holder, A)) + +// Changes the mob's intent, which controls which special attack is used. +// I_DISARM causes Electric Defense, I_GRAB causes Micro-Singularity, and I_HURT causes Missile Barrage. +/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/pre_special_attack(atom/A) + if(isliving(A)) + var/mob/living/target = A + + // If we're surrounded, Electric Defense will quickly fix that. + var/tally = 0 + var/list/potential_targets = list_targets() // Returns list of mobs and certain objects like mechs and turrets. + for(var/atom/movable/AM in potential_targets) + if(get_dist(holder, AM) > electric_defense_radius) + continue + if(!can_attack(AM)) + continue + tally++ + + // Should we shock them? + if(tally >= electric_defense_threshold || get_dist(target, holder) <= electric_defense_radius) + holder.a_intent = I_DISARM + return + + // Otherwise they're a fair distance away and we're not getting mobbed up close. + // See if we should use missiles or microsingulo. + tally = 0 // Let's recycle the var. + for(var/atom/movable/AM in potential_targets) + if(get_dist(target, AM) > microsingulo_radius) // Deliberately tests distance between target and nearby targets and not the holder. + continue + if(!can_attack(AM)) + continue + if(AM.anchored) // Microsingulo doesn't do anything to anchored things. + tally-- + else + tally++ + + // Lots of people means minisingulo would be more useful. + if(tally >= microsingulo_threshold) + holder.a_intent = I_GRAB + else // Otherwise use rockets. + holder.a_intent = I_HURT + + else + if(get_dist(holder, A) >= rocket_explosive_radius + 1) + holder.a_intent = I_HURT // Fire rockets if it's an obj/turf. + else + holder.a_intent = I_DISARM // Electricity might not work but it's safe up close. diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/combat_mecha.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/combat_mecha.dm new file mode 100644 index 0000000000..7a2d8abb19 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/combat_mecha.dm @@ -0,0 +1,38 @@ +// Base type for the 'combat' mechas like gygax/durand/maulers/etc. +// They generally are walking tanks, and their melee attack knocks back and stuns, like the real deal. + +/mob/living/simple_mob/mechanical/mecha/combat + name = "combat mecha" + desc = "An even bigger stompy mech!!" + + movement_cooldown = 10 + melee_damage_lower = 30 + melee_damage_upper = 30 + melee_attack_delay = 1 SECOND + attacktext = list("punched", "slammed", "uppercutted", "pummeled") + + armor = list( + "melee" = 30, + "bullet" = 30, + "laser" = 15, + "energy" = 0, + "bomb" = 20, + "bio" = 100, + "rad" = 100 + ) + + var/weaken_amount = 2 // Be careful with this number. High values can equal a permastun. + +// Melee hits knock back by one tile (or more if already stunned to help prevent permastuns). +/mob/living/simple_mob/mechanical/mecha/combat/apply_melee_effects(atom/A) + if(isliving(A)) + var/mob/living/L = A + if(L.mob_size <= MOB_MEDIUM) + visible_message(span("danger", "\The [src] sends \the [L] flying with their mechanized fist!")) + playsound(src, "punch", 50, 1) + L.Weaken(weaken_amount) + var/throw_dir = get_dir(src, L) + var/throw_dist = L.incapacitated(INCAPACITATION_DISABLED) ? 4 : 1 + L.throw_at(get_edge_target_turf(L, throw_dir), throw_dist, 1, src) + else + to_chat(L, span("warning", "\The [src] punches you with incredible force, but you remain in place.")) diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/durand.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/durand.dm new file mode 100644 index 0000000000..c20d623c53 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/durand.dm @@ -0,0 +1,53 @@ +// Durands are slow, tanky, beefy, and hit really hard. +// They can also root themselves to become even tankier. +// The AI doesn't do this currently. + +/mob/living/simple_mob/mechanical/mecha/combat/durand + name = "durand" + desc = "An aging combat exosuit utilized by many corporations. It was originally developed to fight in the First Contact War." + icon_state = "durand" + movement_cooldown = 10 + wreckage = /obj/structure/loot_pile/mecha/durand + + maxHealth = 400 + deflect_chance = 20 + armor = list( + "melee" = 50, + "bullet" = 35, + "laser" = 15, + "energy" = 10, + "bomb" = 20, + "bio" = 100, + "rad" = 100 + ) + melee_damage_lower = 40 + melee_damage_upper = 40 + base_attack_cooldown = 2 SECONDS + projectiletype = /obj/item/projectile/beam/heavylaser + + var/defense_mode = FALSE + var/defense_deflect = 35 + +/mob/living/simple_mob/mechanical/mecha/combat/durand/proc/set_defense_mode(new_mode) + defense_mode = new_mode + deflect_chance = defense_mode ? defense_deflect : initial(deflect_chance) + to_chat(src, span("notice", "You [defense_mode ? "en" : "dis"]able defense mode.")) + +/mob/living/simple_mob/mechanical/mecha/combat/durand/SelfMove(turf/n, direct) + if(defense_mode) + to_chat(src, span("warning", "You are in defense mode, you cannot move.")) + return FALSE + return ..() + +// So players can toggle it too. +/mob/living/simple_mob/mechanical/mecha/combat/durand/verb/toggle_defense_mode() + set name = "Toggle Defense Mode" + set desc = "Toggles a special mode which makes you immobile and much more resilient." + set category = "Abilities" + + set_defense_mode(!defense_mode) + +// Variant that starts in defense mode, perhaps for PoIs. +/mob/living/simple_mob/mechanical/mecha/combat/durand/defensive/initialize() + set_defense_mode(TRUE) + return ..() diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/gygax.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/gygax.dm new file mode 100644 index 0000000000..4e0cc0cb3d --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/gygax.dm @@ -0,0 +1,57 @@ +// Gygaxes are tough but also fast. +// Their AI, unlike most, will advance towards their target instead of remaining in place. + +/mob/living/simple_mob/mechanical/mecha/combat/gygax + name = "gygax" + desc = "A lightweight, security exosuit. Popular among private and corporate security." + icon_state = "gygax" + movement_cooldown = 0 + wreckage = /obj/structure/loot_pile/mecha/gygax + + maxHealth = 300 + armor = list( + "melee" = 25, + "bullet" = 20, + "laser" = 30, + "energy" = 15, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + + projectiletype = /obj/item/projectile/beam/midlaser + + ai_holder_type = /datum/ai_holder/simple_mob/intentional/adv_dark_gygax + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/manned + pilot_type = /mob/living/simple_mob/humanoid/merc/ranged // Carries a pistol. + + +// A stronger variant. +/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark + name = "dark gygax" + desc = "A significantly upgraded Gygax security mech, often utilized by corporate asset protection teams and \ + PMCs." + icon_state = "darkgygax" + wreckage = /obj/structure/loot_pile/mecha/gygax/dark + + maxHealth = 400 + deflect_chance = 25 + has_repair_droid = TRUE + armor = list( + "melee" = 40, + "bullet" = 40, + "laser" = 50, + "energy" = 35, + "bomb" = 20, + "bio" = 100, + "rad" = 100 + ) + +/mob/living/simple_mob/mechanical/mecha/combat/gygax/medgax + name = "medgax" + desc = "An unorthodox fusion of the Gygax and Odysseus exosuits, this one is fast, sturdy, and carries a wide array of \ + potent chemicals and delivery mechanisms. The doctor is in!" + icon_state = "medgax" + wreckage = /obj/structure/loot_pile/mecha/gygax/medgax + diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/hoverpod.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/hoverpod.dm new file mode 100644 index 0000000000..29f102b9b9 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/hoverpod.dm @@ -0,0 +1,27 @@ +// Ranged, and capable of flight. +/mob/living/simple_mob/mechanical/mecha/hoverpod + name = "hover pod" + desc = "Stubby and round, this space-capable craft is an ancient favorite. It has a jury-rigged welder-laser." + icon_state = "engineering_pod" + movement_sound = 'sound/machines/hiss.ogg' + wreckage = /obj/structure/loot_pile/mecha/hoverpod + + maxHealth = 150 + hovering = TRUE // Can fly. + + projectiletype = /obj/item/projectile/beam + base_attack_cooldown = 2 SECONDS + + var/datum/effect/effect/system/ion_trail_follow/ion_trail + +/mob/living/simple_mob/mechanical/mecha/hoverpod/manned + pilot_type = /mob/living/simple_mob/humanoid/merc/ranged + +/mob/living/simple_mob/mechanical/mecha/hoverpod/initialize() + ion_trail = new /datum/effect/effect/system/ion_trail_follow() + ion_trail.set_up(src) + ion_trail.start() + return ..() + +/mob/living/simple_mob/mechanical/mecha/hoverpod/Process_Spacemove(var/check_drift = 0) + return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/marauder.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/marauder.dm new file mode 100644 index 0000000000..b4fa89c005 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/marauder.dm @@ -0,0 +1,44 @@ +// Marauders are even tougher than Durands. + +/mob/living/simple_mob/mechanical/mecha/combat/marauder + name = "marauder" + desc = "A heavy-duty, combat exosuit, developed after the Durand model. This is rarely found among civilian populations." + icon_state = "marauder" + movement_cooldown = 5 + wreckage = /obj/structure/loot_pile/mecha/marauder + + maxHealth = 500 + deflect_chance = 25 + sight = SEE_SELF | SEE_MOBS + armor = list( + "melee" = 50, + "bullet" = 55, + "laser" = 40, + "energy" = 30, + "bomb" = 30, + "bio" = 100, + "rad" = 100 + ) + melee_damage_lower = 45 + melee_damage_upper = 45 + base_attack_cooldown = 2 SECONDS + projectiletype = /obj/item/projectile/beam/heavylaser + + +// Slightly stronger, used to allow comdoms to frontline without dying instantly, I guess. +/mob/living/simple_mob/mechanical/mecha/combat/marauder/seraph + name = "seraph" + desc = "A heavy-duty, combat/command exosuit. This one is specialized towards housing important commanders such as high-ranking \ + military personnel. It's stronger than the regular Marauder model, but not by much." + icon_state = "seraph" + wreckage = /obj/structure/loot_pile/mecha/marauder/seraph + health = 550 + melee_damage_lower = 55 // The real version hits this hard apparently. Ouch. + melee_damage_upper = 55 + + +/mob/living/simple_mob/mechanical/mecha/combat/marauder/mauler + name = "mauler" + desc = "A heavy duty, combat exosuit that is based off of the Marauder model." + icon_state = "mauler" + wreckage = /obj/structure/loot_pile/mecha/marauder/mauler diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm new file mode 100644 index 0000000000..7aa24bdf74 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm @@ -0,0 +1,141 @@ +// Mecha simple_mobs are essentially fake mechs. Generally tough and scary to fight. +// By default, they're automatically piloted by some kind of drone AI. They can be set to be "piloted" instead with a var. +// Tries to be as similar to the real deal as possible. + +/mob/living/simple_mob/mechanical/mecha + name = "mecha" + desc = "A big stompy mech!" + icon = 'icons/mecha/mecha.dmi' + + faction = "syndicate" + movement_cooldown = 5 + movement_sound = "mechstep" // This gets fed into playsound(), which can also take strings as a 'group' of sound files. + turn_sound = 'sound/mecha/mechturn.ogg' + maxHealth = 300 + mob_size = MOB_LARGE + + // Very close to the base 'damage_absorption' var on the base mecha class. + armor = list( + "melee" = 20, + "bullet" = 10, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + + response_help = "taps on" + response_disarm = "knocks on" + response_harm = "uselessly hits" + harm_intent_damage = 0 + + ai_holder_type = /datum/ai_holder/simple_mob/melee + say_list_type = /datum/say_list/malf_drone + + var/datum/effect/effect/system/spark_spread/sparks + var/wreckage = /obj/effect/decal/mecha_wreckage/gygax/dark + var/pilot_type = null // Set to spawn a pilot when destroyed. Setting this also makes the mecha vulnerable to things that affect sentient minds. + var/deflect_chance = 10 // Chance to outright stop an attack, just like a normal exosuit. + var/has_repair_droid = FALSE // If true, heals 2 damage every tick and gets a repair droid overlay. + + +/mob/living/simple_mob/mechanical/mecha/initialize() + sparks = new (src) + sparks.set_up(3, 1, src) + sparks.attach(src) + + if(!pilot_type) + name = "autonomous [initial(name)]" + desc = "[initial(desc)] It appears to be piloted by a drone intelligence." + else + say_list_type = /datum/say_list/merc + + if(has_repair_droid) + update_icon() + + return ..() + +/mob/living/simple_mob/mechanical/mecha/Destroy() + qdel(sparks) + return ..() + +/mob/living/simple_mob/mechanical/mecha/death() + ..(0,"explodes!") // Do everything else first. + + // Make the exploding more convincing with an actual explosion and some sparks. + sparks.start() + explosion(get_turf(src), 0, 0, 1, 3) + + // 'Eject' our pilot, if one exists. + if(pilot_type) + var/mob/living/L = new pilot_type(loc) + L.faction = src.faction + + new wreckage(loc) // Leave some wreckage. + + qdel(src) // Then delete us since we don't actually have a body. + +/mob/living/simple_mob/mechanical/mecha/handle_special() + if(has_repair_droid) + adjustBruteLoss(-2) + adjustFireLoss(-2) + adjustToxLoss(-2) + adjustOxyLoss(-2) + adjustCloneLoss(-2) + ..() + +/mob/living/simple_mob/mechanical/mecha/update_icon() + ..() // Cuts everything else, so do that first. + if(has_repair_droid) + add_overlay(image(icon = 'icons/mecha/mecha_equipment.dmi', icon_state = "repair_droid")) + +/mob/living/simple_mob/mechanical/mecha/bullet_act() + . = ..() + sparks.start() + +/mob/living/simple_mob/mechanical/mecha/speech_bubble_appearance() + return pilot_type ? "" : ..() + +// Piloted mechs are controlled by (presumably) something humanoid so they are vulnerable to certain things. +/mob/living/simple_mob/mechanical/mecha/is_sentient() + return pilot_type ? TRUE : FALSE + +/* +// Real mechs can't turn and run at the same time. This tries to simulate that. +// Commented out because the AI can't handle it sadly. +/mob/living/simple_mob/mechanical/mecha/SelfMove(turf/n, direct) + if(direct != dir) + set_dir(direct) + return FALSE // We didn't actually move, and returning FALSE means the mob can try to actually move almost immediately and not have to wait the full movement cooldown. + return ..() +*/ + +/mob/living/simple_mob/mechanical/mecha/bullet_act(obj/item/projectile/P) + if(prob(deflect_chance)) + visible_message(span("warning", "\The [P] is deflected by \the [src]'s armor!")) + deflect_sprite() + return 0 + return ..() + +/mob/living/simple_mob/mechanical/mecha/proc/deflect_sprite() + var/image/deflect_image = image('icons/effects/effects.dmi', "deflect_static") + add_overlay(deflect_image) + sleep(1 SECOND) + cut_overlay(deflect_image) + qdel(deflect_image) +// flick_overlay_view(deflect_image, src, duration = 1 SECOND, gc_after = TRUE) + +/mob/living/simple_mob/mechanical/mecha/attackby(obj/item/I, mob/user) + if(prob(deflect_chance)) + visible_message(span("warning", "\The [user]'s [I] bounces off \the [src]'s armor!")) + deflect_sprite() + user.setClickCooldown(user.get_attack_speed(I)) + return + ..() + +/mob/living/simple_mob/mechanical/mecha/ex_act(severity) + if(prob(deflect_chance)) + severity++ // This somewhat misleadingly makes it less severe. + deflect_sprite() + ..(severity) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm new file mode 100644 index 0000000000..245bd54193 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm @@ -0,0 +1,71 @@ +// Shoots syringe-darts at enemies, which applies a stacking poison modifier that hurts over time. +// They also do this in melee. +// Fortunately they're quite fragile and don't fire that fast. + +/mob/living/simple_mob/mechanical/mecha/odysseus + name = "odysseus" + desc = "These exosuits are developed and produced by Vey-Med. This one has a syringe gun." + icon_state = "odysseus" + wreckage = /obj/structure/loot_pile/mecha/odysseus + + maxHealth = 120 + movement_cooldown = 0 + turn_sound = 'sound/mecha/mechmove01.ogg' + + melee_damage_lower = 5 + melee_damage_upper = 5 + base_attack_cooldown = 2 SECONDS + attacktext = list("injected") + projectiletype = /obj/item/projectile/fake_syringe/poison + projectilesound = 'sound/weapons/empty.ogg' // Just like the syringe gun. + + ai_holder_type = /datum/ai_holder/simple_mob/ranged/kiting/no_moonwalk + +/mob/living/simple_mob/mechanical/mecha/odysseus/manned + pilot_type = /mob/living/simple_mob/humanoid/merc/ranged // Carries a pistol. + + +// Resprite of the regular one, perhaps for merc PoIs. +/mob/living/simple_mob/mechanical/mecha/odysseus/murdysseus + icon_state = "murdysseus" + wreckage = /obj/structure/loot_pile/mecha/odysseus/murdysseus + +/mob/living/simple_mob/mechanical/mecha/odysseus/murdysseus/manned + pilot_type = /mob/living/simple_mob/humanoid/merc/ranged + + +/mob/living/simple_mob/mechanical/mecha/odysseus/apply_melee_effects(atom/A) + if(isliving(A)) + var/mob/living/L = A + + var/target_zone = pick(BP_TORSO,BP_TORSO,BP_TORSO,BP_L_LEG,BP_R_LEG,BP_L_ARM,BP_R_ARM,BP_HEAD) + if(L.can_inject(src, null, target_zone)) + to_chat(L, span("warning", "You feel a tiny prick.")) + if(L.get_poison_protection() < 1) + L.add_modifier(/datum/modifier/poisoned, 30 SECONDS) + L.inflict_poison_damage(5) + + +// Fake syringe that tests if target can be injected before applying damage/modifiers/etc. +/obj/item/projectile/fake_syringe + name = "syringe" + icon_state = "syringe" + damage = 5 // Getting hit with a launched syringe probably hurts, and makes it at least slightly relevant against synthetics. + var/piercing = FALSE // If true, ignores thick material. + +/obj/item/projectile/fake_syringe/on_hit(atom/target, blocked = 0, def_zone = null) + if(isliving(target)) + var/mob/living/L = target + if(!L.can_inject(null, null, def_zone, piercing)) + return FALSE + to_chat(L, span("warning", "You feel a tiny prick.")) + return ..() // This will add the modifier and return the correct value. + + +// Fake syringe, which inflicts a long lasting modifier that slowly kills them. +/obj/item/projectile/fake_syringe/poison + modifier_type_to_apply = /datum/modifier/poisoned + modifier_duration = 1 MINUTE // About 30 damage per stack over a minute. + + + diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/phazon.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/phazon.dm new file mode 100644 index 0000000000..ebbe80ecf4 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/phazon.dm @@ -0,0 +1,22 @@ +// Phazons are weird. + +/mob/living/simple_mob/mechanical/mecha/combat/phazon + name = "phazon" + desc = "An extremly enigmatic exosuit." + icon_state = "phazon" + movement_cooldown = 5 + wreckage = /obj/structure/loot_pile/mecha/phazon + + maxHealth = 200 + deflect_chance = 30 + armor = list( + "melee" = 30, + "bullet" = 30, + "laser" = 30, + "energy" = 30, + "bomb" = 30, + "bio" = 100, + "rad" = 100 + ) + projectiletype = /obj/item/projectile/energy/declone + diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/ripley.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/ripley.dm new file mode 100644 index 0000000000..8372363ea0 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/ripley.dm @@ -0,0 +1,64 @@ +// Beefy, but somewhat slow. +// Melee attack is to bore you with its big drill, which has a lot of armor penetration and strikes rapidly. + +/mob/living/simple_mob/mechanical/mecha/ripley + name = "\improper APLU ripley" + desc = "Autonomous Power Loader Unit. The workhorse of the exosuit world. This one has big drill." + icon_state = "ripley" + wreckage = /obj/structure/loot_pile/mecha/ripley + + maxHealth = 200 + + melee_damage_lower = 10 + melee_damage_upper = 10 + base_attack_cooldown = 5 // About 20 DPS. + attack_armor_pen = 50 + attack_sharp = TRUE + attack_sound = 'sound/mecha/mechdrill.ogg' + attacktext = list("drilled", "bored", "pierced") + +/mob/living/simple_mob/mechanical/mecha/ripley/manned + pilot_type = /mob/living/simple_mob/humanoid/merc/ranged // Carries a pistol. + +/mob/living/simple_mob/mechanical/mecha/ripley/red_flames + icon_state = "ripley_flames_red" + +/mob/living/simple_mob/mechanical/mecha/ripley/blue_flames + icon_state = "ripley_flames_blue" + + +// Immune to heat damage, resistant to lasers, and somewhat beefier. Still tries to melee you. +/mob/living/simple_mob/mechanical/mecha/ripley/firefighter + name = "\improper APLU firefighter" + desc = "A standard APLU chassis, refitted with additional thermal protection and cistern. This one has a big drill." + icon_state = "firefighter" + wreckage = /obj/structure/loot_pile/mecha/ripley/firefighter + + maxHealth = 250 + heat_resist = 1 + armor = list( + "melee" = 0, + "bullet" = 20, + "laser" = 50, + "energy" = 0, + "bomb" = 50, + "bio" = 100, + "rad" = 100 + ) + +/mob/living/simple_mob/mechanical/mecha/ripley/firefighter/manned + pilot_type = /mob/living/simple_mob/humanoid/merc/ranged + +// Mostly a joke mob, like the real DEATH-RIPLEY. +/mob/living/simple_mob/mechanical/mecha/ripley/deathripley + name = "\improper DEATH-RIPLEY" + desc = "OH SHIT RUN!!! IT HAS A KILL CLAMP!" + icon_state = "deathripley" + wreckage = /obj/structure/loot_pile/mecha/deathripley + + melee_damage_lower = 0 + melee_damage_upper = 0 + friendly = list("utterly obliterates", "furiously destroys", "permanently removes", "unflichingly decimates", "brutally murders", "absolutely demolishes", "completely annihilates") + +/mob/living/simple_mob/mechanical/mecha/ripley/deathripley/manned + pilot_type = /mob/living/simple_mob/humanoid/merc/ranged diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mechanical.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mechanical.dm new file mode 100644 index 0000000000..f61212ba75 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mechanical.dm @@ -0,0 +1,25 @@ +// Mechanical mobs don't care about the atmosphere and cannot be hurt by tasers. +// They're also immune to poisons as they're entirely metal, however this also makes most of them vulnerable to shocks. +// They can also be hurt by EMP. + +/mob/living/simple_mob/mechanical + mob_class = MOB_CLASS_SYNTHETIC + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + + taser_kill = FALSE + poison_resist = 1.0 + shock_resist = -0.5 + +/mob/living/simple_mob/mechanical/isSynthetic() + return TRUE + +/mob/living/simple_mob/mechanical/speech_bubble_appearance() + return faction != "neutral" ? "synthetic_evil" : "machine" \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/viscerator.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/viscerator.dm new file mode 100644 index 0000000000..09d778547b --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/viscerator.dm @@ -0,0 +1,51 @@ +/* + Viscerators are fragile and don't hit very hard, but fast, evasive, and rarely come alone. + They also tend to dodge while in melee range. + A weapon that can cleave is very effective against them. +*/ + +/mob/living/simple_mob/mechanical/viscerator + name = "viscerator" + desc = "A small, twin-bladed machine capable of inflicting very deadly lacerations." + icon = 'icons/mob/critter.dmi' + icon_state = "viscerator_attack" + icon_living = "viscerator_attack" + hovering = TRUE // Won't trigger landmines. + + faction = "syndicate" + maxHealth = 15 + health = 15 + movement_cooldown = 0 + + pass_flags = PASSTABLE + mob_swap_flags = 0 + mob_push_flags = 0 + + melee_damage_lower = 4 // Approx 8 DPS. + melee_damage_upper = 4 + base_attack_cooldown = 5 // Two attacks a second or so. + attack_sharp = 1 + attack_edge = 1 + attack_sound = 'sound/weapons/bladeslice.ogg' + attacktext = list("cut", "sliced") + + ai_holder_type = /datum/ai_holder/simple_mob/melee/evasive + +/mob/living/simple_mob/mechanical/viscerator/death() + ..(null,"is smashed into pieces!") + qdel(src) + +// Variant that is always loyal to mercenary antagonists. +// Used for a special grenade, to ensure they don't attack the wrong thing. +/mob/living/simple_mob/mechanical/viscerator/mercenary/IIsAlly(mob/living/L) + . = ..() + if(!.) // Not friendly, see if they're a baddie first. + if(L.mind && mercs.is_antagonist(L.mind)) + return TRUE + +// Similar to above but for raiders. +/mob/living/simple_mob/mechanical/viscerator/raider/IIsAlly(mob/living/L) + . = ..() + if(!.) // Not friendly, see if they're a baddie first. + if(L.mind && raiders.is_antagonist(L.mind)) + return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/monitor_ward.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/monitor_ward.dm new file mode 100644 index 0000000000..153bc9c478 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/monitor_ward.dm @@ -0,0 +1,106 @@ +/* + 'Monitor' wards are drones that yell at their creator if they see someone besides them that they are hostile to. + They can also force an invisible entity to uncloak if the invisible mob is hostile to the ward. + If AI controlled, they will also warn their faction if they see a hostile entity, acting as floating cameras. +*/ + +/mob/living/simple_mob/mechanical/ward/monitor + desc = "It's a little flying drone. This one seems to be watching you..." + icon_state = "ward" + glow_color = "#00FF00" + see_invisible = SEE_INVISIBLE_LEVEL_TWO + + has_eye_glow = TRUE + glow_range = 3 + glow_intensity = 3 + glow_toggle = TRUE + + player_msg = "You will automatically alert your owner (if one exists) of enemies you see nearby.
\ + You can also see invisible entities, and will automatically uncloak nearby invisible or hidden enemies." + + ai_holder_type = /datum/ai_holder/simple_mob/monitor + + var/list/seen_mobs = list() + var/view_range = 5 + +// For PoIs. +/mob/living/simple_mob/mechanical/ward/monitor/syndicate + faction = "syndicate" + +/mob/living/simple_mob/mechanical/ward/monitor/crew + faction = "neutral" + +/mob/living/simple_mob/mechanical/ward/monitor/death() + if(owner) + to_chat(owner, span("warning", "Your [src.name] inside [get_area(src)] was destroyed!")) + ..() + +/mob/living/simple_mob/mechanical/ward/monitor/handle_special() + detect_mobs() + +/mob/living/simple_mob/mechanical/ward/monitor/update_icon() + if(seen_mobs.len) + icon_living = "ward_spotted" + glow_color = "#FF0000" + else + icon_living = "ward" + glow_color = "#00FF00" + handle_light() // Update the light immediately. + ..() + +/mob/living/simple_mob/mechanical/ward/monitor/proc/detect_mobs() + var/last_seen_mobs_len = seen_mobs.len + var/list/mobs_nearby = hearers(view_range, src) + var/list/newly_seen_mobs = list() + for(var/mob/living/L in mobs_nearby) + if(L == src) // Don't detect ourselves. + continue + + if(L.stat) // Dead mobs aren't concerning. + continue + + if(src.IIsAlly(L)) + continue + + // Decloak them . + if(L.is_cloaked()) + Beam(L, icon_state = "solar_beam", time = 5) + playsound(L, 'sound/effects/EMPulse.ogg', 75, 1) + L.break_cloak() + + to_chat(L, span("danger", "\The [src] disrupts your cloak!")) + if(owner) + to_chat(owner, span("notice", "Your [src.name] at [get_area(src)] uncloaked \the [L].")) + + // Warn the owner when it sees a new mob. + if(!(L in seen_mobs)) + seen_mobs += L + newly_seen_mobs += L + + if(newly_seen_mobs.len && owner) // Yell at our owner if someone new shows up. + to_chat(owner, span("notice", "Your [src.name] at [get_area(src)] detected [english_list(newly_seen_mobs)].")) + + // Now get rid of old mobs that left vision. + for(var/thing in seen_mobs) + if(!(thing in mobs_nearby)) + seen_mobs -= thing + + // Check if we need to update icon. + if(seen_mobs.len != last_seen_mobs_len) + update_icon() + + +// Can't attack but calls for help. Used by the monitor and spotter wards. +// Special attacks are not blocked since they might be used for things besides attacking, and can be conditional. +/datum/ai_holder/simple_mob/monitor + hostile = TRUE // Required to call for help. + cooperative = TRUE + stand_ground = TRUE // So it doesn't run up to the thing it sees. + wander = FALSE + can_flee = FALSE + +/datum/ai_holder/simple_mob/monitor/melee_attack(atom/A) + return FALSE + +/datum/ai_holder/simple_mob/monitor/ranged_attack(atom/A) + return FALSE \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/ward.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/ward.dm new file mode 100644 index 0000000000..0e897599ac --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/ward/ward.dm @@ -0,0 +1,42 @@ +/* + Wards are a specific type of mechanical simplemob that generally fill a support role for their faction or for a specific mob. + Generally they are helpless by themselves and are fragile, but can do very useful things if protected. This makes them a high priority target. +*/ + +/mob/living/simple_mob/mechanical/ward + name = "ward" + desc = "A small floating machine. This one seems rather useless..." + icon = 'icons/mob/critter.dmi' + icon_state = "ward" + icon_living = "ward" + hovering = TRUE // Won't trigger landmines. + response_help = "pets" + response_disarm = "swats away" + response_harm = "punches" + faction = "wards" // Needed as most human mobs are in neutral faction. The owner is generally except from any ward hostility regardless. + + maxHealth = 15 + health = 15 + movement_cooldown = 0 + hovering = TRUE + + mob_bump_flag = 0 + + melee_damage_lower = 0 + melee_damage_upper = 0 + + ai_holder_type = null + var/mob/living/owner = null // The mob that made the ward, if any. Used to ensure the ward does not interfere with its creator. + +/mob/living/simple_mob/mechanical/ward/death() + ..(null,"is smashed into pieces!") + qdel(src) + +/mob/living/simple_mob/mechanical/ward/Destroy() + owner = null + return ..() + +/mob/living/simple_mob/mechanical/ward/IIsAlly(mob/living/L) + if(owner == L) + return TRUE + return ..() \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/_construct.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/_construct.dm new file mode 100644 index 0000000000..cd3b074a40 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/_construct.dm @@ -0,0 +1,163 @@ +//////////////////////////// +// Base Construct +//////////////////////////// + +/mob/living/simple_mob/construct + name = "Construct" + real_name = "Construct" + desc = "" + tt_desc = "Error" + + icon_living = "shade" + icon_dead = "shade_dead" + + mob_class = MOB_CLASS_DEMONIC + + ui_icons = 'icons/mob/screen1_construct.dmi' + has_hands = 1 + hand_form = "stone manipulators" + + response_help = "thinks better of touching" + response_disarm = "flailed at" + response_harm = "punched" + + hovering = TRUE + softfall = TRUE //Beings made of Hellmarble and powered by the tears of the damned are not concerned with mortal things such as 'gravity'. + parachuting = TRUE + + has_langs = list(LANGUAGE_GALCOM, LANGUAGE_CULT, LANGUAGE_OCCULT) + + has_eye_glow = TRUE + + taser_kill = FALSE + + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + + shock_resist = 0.1 //Electricity isn't very effective on stone, especially that from hell. + poison_resist = 1.0 + + armor = list( + "melee" = 10, + "bullet" = 10, + "laser" = 10, + "energy" = 10, + "bomb" = 10, + "bio" = 100, + "rad" = 100) + + can_be_antagged = TRUE + faction = "cult" + + supernatural = TRUE + + var/construct_type = "shade" + var/list/construct_spells = list() +// var/do_glow = TRUE + +/mob/living/simple_mob/construct/place_spell_in_hand(var/path) + if(!path || !ispath(path)) + return 0 + + //var/obj/item/weapon/spell/S = new path(src) + var/obj/item/weapon/spell/construct/S = new path(src) + + //No hands needed for innate casts. + if(S.cast_methods & CAST_INNATE) + if(S.run_checks()) + S.on_innate_cast(src) + + if(l_hand && r_hand) //Make sure our hands aren't full. + if(istype(r_hand, /obj/item/weapon/spell)) //If they are full, perhaps we can still be useful. + var/obj/item/weapon/spell/r_spell = r_hand + if(r_spell.aspect == ASPECT_CHROMATIC) //Check if we can combine the new spell with one in our hands. + r_spell.on_combine_cast(S, src) + else if(istype(l_hand, /obj/item/weapon/spell)) + var/obj/item/weapon/spell/l_spell = l_hand + if(l_spell.aspect == ASPECT_CHROMATIC) //Check the other hand too. + l_spell.on_combine_cast(S, src) + else //Welp + to_chat(src, "You require a free manipulator to use this power.") + return 0 + + if(S.run_checks()) + put_in_hands(S) + return 1 + else + qdel(S) + return 0 + +/mob/living/simple_mob/construct/cultify() + return + +/mob/living/simple_mob/construct/New() + ..() + name = text("[initial(name)] ([rand(1, 1000)])") + real_name = name + for(var/spell in construct_spells) + src.add_spell(new spell, "const_spell_ready") + updateicon() + +/* +/mob/living/simple_mob/construct/update_icon() + ..() + if(do_glow) + add_glow() +*/ + +/mob/living/simple_mob/construct/death() + new /obj/item/weapon/ectoplasm (src.loc) + ..(null,"collapses in a shattered heap.") + ghostize() + qdel(src) + +/mob/living/simple_mob/construct/attack_generic(var/mob/user) + if(istype(user, /mob/living/simple_mob/construct/artificer)) + var/mob/living/simple_mob/construct/artificer/A = user + if(health < getMaxHealth()) + var/repair_lower_bound = A.melee_damage_lower * -1 + var/repair_upper_bound = A.melee_damage_upper * -1 + adjustBruteLoss(rand(repair_lower_bound, repair_upper_bound)) + adjustFireLoss(rand(repair_lower_bound, repair_upper_bound)) + user.visible_message("\The [user] mends some of \the [src]'s wounds.") + else + to_chat(user, "\The [src] is undamaged.") + return + return ..() + +/mob/living/simple_mob/construct/examine(mob/user) + ..(user) + var/msg = "*---------*\nThis is \icon[src] \a [src]!\n" + if (src.health < src.getMaxHealth()) + msg += "" + if (src.health >= src.getMaxHealth()/2) + msg += "It looks slightly dented.\n" + else + msg += "It looks severely dented!\n" + msg += "" + msg += "*---------*" + + user << msg + +//Constructs levitate, can fall from a shuttle with no harm, and are piloted by either damned spirits or some otherworldly entity. Let 'em float in space. +/mob/living/simple_mob/construct/Process_Spacemove() + return 1 + +/* +// Glowing Procs +/mob/living/simple_mob/construct/proc/add_glow() + var/image/eye_glow = image(icon,"glow-[icon_state]") + eye_glow.plane = PLANE_LIGHTING_ABOVE + overlays += eye_glow + set_light(2, -2, l_color = "#FFFFFF") + +/mob/living/simple_mob/construct/proc/remove_glow() + overlays.Cut() +*/ \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/artificer.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/artificer.dm new file mode 100644 index 0000000000..69d0da0251 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/artificer.dm @@ -0,0 +1,30 @@ +//////////////////////////// +// Artificer +//////////////////////////// + +/mob/living/simple_mob/construct/artificer + name = "Artificer" + real_name = "Artificer" + construct_type = "artificer" + desc = "A bulbous construct dedicated to building and maintaining temples to their otherworldly lords." + icon = 'icons/mob/mob.dmi' + icon_state = "artificer" + icon_living = "artificer" + maxHealth = 150 + health = 150 + response_harm = "viciously beaten" + harm_intent_damage = 5 + melee_damage_lower = 15 //It's not the strongest of the bunch, but that doesn't mean it can't hurt you. + melee_damage_upper = 20 + attacktext = list("rammed") + attack_sound = 'sound/weapons/rapidslice.ogg' + construct_spells = list(/spell/aoe_turf/conjure/construct/lesser, + /spell/aoe_turf/conjure/wall, + /spell/aoe_turf/conjure/floor, + /spell/aoe_turf/conjure/soulstone, + /spell/aoe_turf/conjure/pylon, + /spell/aoe_turf/conjure/door, + /spell/aoe_turf/conjure/grille, + /spell/targeted/occult_repair_aura, + /spell/targeted/construct_advanced/mend_acolyte + ) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/harvester.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/harvester.dm new file mode 100644 index 0000000000..9328d9cffd --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/harvester.dm @@ -0,0 +1,40 @@ +//////////////////////////// +// Harvester +//////////////////////////// + +/mob/living/simple_mob/construct/harvester + name = "Harvester" + real_name = "Harvester" + construct_type = "harvester" + desc = "A tendril-laden construct piloted by a chained mind." + icon = 'icons/mob/mob.dmi' + icon_state = "harvester" + icon_living = "harvester" + maxHealth = 150 + health = 150 + melee_damage_lower = 20 + melee_damage_upper = 25 + attack_sharp = 1 + attacktext = list("violently stabbed") + friendly = list("caresses") + movement_cooldown = 0 + + // environment_smash = 1 // Whatever this gets renamed to, Harvesters need to break things + + attack_sound = 'sound/weapons/pierce.ogg' + + armor = list( + "melee" = 10, + "bullet" = 20, + "laser" = 20, + "energy" = 20, + "bomb" = 20, + "bio" = 100, + "rad" = 100) + + construct_spells = list( + /spell/aoe_turf/knock/harvester, + /spell/targeted/construct_advanced/inversion_beam, + /spell/targeted/construct_advanced/agonizing_sphere, + /spell/rune_write + ) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm new file mode 100644 index 0000000000..31dfa4d34a --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm @@ -0,0 +1,143 @@ +//////////////////////////// +// Juggernaut +//////////////////////////// + +/mob/living/simple_mob/construct/juggernaut + name = "Juggernaut" + real_name = "Juggernaut" + construct_type = "juggernaut" + desc = "A possessed suit of armour driven by the will of the restless dead" + icon = 'icons/mob/mob.dmi' + icon_state = "behemoth" + icon_living = "behemoth" + maxHealth = 300 + health = 300 + response_harm = "harmlessly punches" + harm_intent_damage = 0 + melee_damage_lower = 30 + melee_damage_upper = 40 + attack_armor_pen = 60 //Being punched by a living, floating statue. + attacktext = list("smashed their armoured gauntlet into") + friendly = list("pats") + mob_size = MOB_HUGE + + + movement_cooldown = 6 //Not super fast, but it might catch up to someone in armor who got punched once or twice. + +// environment_smash = 2 // Whatever this gets renamed to, Juggernauts need to break things + + + attack_sound = 'sound/weapons/heavysmash.ogg' + status_flags = 0 + resistance = 10 + construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser, + /spell/targeted/fortify, + /spell/targeted/construct_advanced/slam + ) + + armor = list( + "melee" = 70, + "bullet" = 30, + "laser" = 30, + "energy" = 30, + "bomb" = 10, + "bio" = 100, + "rad" = 100) + +/mob/living/simple_mob/construct/juggernaut/Life() + weakened = 0 + ..() + +/mob/living/simple_mob/construct/juggernaut/bullet_act(var/obj/item/projectile/P) + var/reflectchance = 80 - round(P.damage/3) + if(prob(reflectchance)) + var/damage_mod = rand(2,4) + var/projectile_dam_type = P.damage_type + var/incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3))) + var/armorcheck = run_armor_check(null, P.check_armour) + var/soakedcheck = get_armor_soak(null, P.check_armour) + if(!(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam))) + visible_message("The [P.name] bounces off of [src]'s shell!", \ + "The [P.name] bounces off of [src]'s shell!") + new /obj/item/weapon/material/shard/shrapnel(src.loc) + if(!(P.damage_type == BRUTE || P.damage_type == BURN)) + projectile_dam_type = BRUTE + incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to brute for physical projectiles, though severely decreased. + apply_damage(incoming_damage, projectile_dam_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P) + return -1 //Doesn't reflect non-beams or non-energy projectiles. They just smack and drop with little to no effect. + else + visible_message("The [P.name] gets reflected by [src]'s shell!", \ + "The [P.name] gets reflected by [src]'s shell!") + damage_mod = rand(3,5) + incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3))) + if(!(P.damage_type == BRUTE || P.damage_type == BURN)) + projectile_dam_type = BURN + incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to burn for energy-type projectiles, though severely decreased. + apply_damage(incoming_damage, P.damage_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P) + + // Find a turf near or on the original location to bounce to + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/turf/curloc = get_turf(src) + + // redirect the projectile + P.redirect(new_x, new_y, curloc, src) + P.reflected = 1 + + return -1 // complete projectile permutation + + return (..(P)) + +/* + * The Behemoth. Admin-allowance only, still try to keep it in some guideline of 'Balanced', even if it means Security has to be fully geared to be so. + */ + +/mob/living/simple_mob/construct/juggernaut/behemoth + name = "Behemoth" + real_name = "Behemoth" + desc = "The pinnacle of occult technology, Behemoths are nothing shy of both an Immovable Object, and Unstoppable Force." + maxHealth = 750 + health = 750 + speak_emote = list("rumbles") + melee_damage_lower = 50 + melee_damage_upper = 50 + attacktext = list("brutally crushed") + friendly = list("pokes") //Anything nice the Behemoth would do would still Kill the Human. Leave it at poke. + attack_sound = 'sound/weapons/heavysmash.ogg' + resistance = 10 + icon_scale = 2 + var/energy = 0 + var/max_energy = 1000 + armor = list( + "melee" = 60, + "bullet" = 60, + "laser" = 60, + "energy" = 30, + "bomb" = 10, + "bio" = 100, + "rad" = 100) + construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser, + /spell/targeted/fortify, + /spell/targeted/construct_advanced/slam + ) + +/mob/living/simple_mob/construct/juggernaut/behemoth/bullet_act(var/obj/item/projectile/P) + var/reflectchance = 80 - round(P.damage/3) + if(prob(reflectchance)) + visible_message("The [P.name] gets reflected by [src]'s shell!", \ + "The [P.name] gets reflected by [src]'s shell!") + + // Find a turf near or on the original location to bounce to + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/turf/curloc = get_turf(src) + + // redirect the projectile + P.redirect(new_x, new_y, curloc, src) + P.reflected = 1 + + return -1 // complete projectile permutation + + return (..(P)) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/shade.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/shade.dm new file mode 100644 index 0000000000..1a56abcdcd --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/shade.dm @@ -0,0 +1,48 @@ +//////////////////////////// +// Shade +//////////////////////////// + +/mob/living/simple_mob/construct/shade + name = "Shade" + real_name = "Shade" + desc = "A bound spirit" + icon = 'icons/mob/mob.dmi' + icon_state = "shade" + icon_living = "shade" + icon_dead = "shade_dead" + + response_help = "puts their hand through" + response_disarm = "flails at" + response_harm = "punches" + + melee_damage_lower = 5 + melee_damage_upper = 15 + attack_armor_pen = 100 //It's a ghost/horror from beyond, I ain't gotta explain 100 AP + attacktext = list("drained the life from") + + minbodytemp = 0 + maxbodytemp = 4000 + min_oxy = 0 + max_co2 = 0 + max_tox = 0 + + universal_speak = 1 + + loot_list = list(/obj/item/weapon/ectoplasm = 100) + +/mob/living/simple_mob/construct/shade/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(istype(O, /obj/item/device/soulstone)) + var/obj/item/device/soulstone/S = O; + S.transfer_soul("SHADE", src, user) + return + ..() + +/mob/living/simple_mob/construct/shade/death() + ..() + for(var/mob/M in viewers(src, null)) + if((M.client && !( M.blinded ))) + M.show_message("[src] lets out a contented sigh as their form unwinds.") + + ghostize() + qdel(src) + return \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/wraith.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/wraith.dm new file mode 100644 index 0000000000..13580bc845 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/wraith.dm @@ -0,0 +1,33 @@ +//////////////////////////// +// Wraith +//////////////////////////// + +/mob/living/simple_mob/construct/wraith + name = "Wraith" + real_name = "Wraith" + construct_type = "wraith" + desc = "A wicked bladed shell contraption piloted by a bound spirit." + icon = 'icons/mob/mob.dmi' + icon_state = "floating" + icon_living = "floating" + maxHealth = 200 + health = 200 + melee_damage_lower = 25 + melee_damage_upper = 30 + attack_armor_pen = 15 + attack_sharp = 1 + attack_edge = 1 + attacktext = list("slashed") + friendly = list("pinches") + movement_cooldown = 0 + attack_sound = 'sound/weapons/rapidslice.ogg' + construct_spells = list(/spell/targeted/ethereal_jaunt/shift, + /spell/targeted/ambush_mode + ) + +// environment_smash = 1 // Whatever this gets renamed to, Wraiths need to break things + +/mob/living/simple_mob/construct/wraith/apply_melee_effects(var/atom/A) + if(isliving(A)) + var/mob/living/L = A + L.add_modifier(/datum/modifier/deep_wounds, 30 SECONDS) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/creature.dm b/code/modules/mob/living/simple_mob/subtypes/occult/creature.dm new file mode 100644 index 0000000000..3cc9df4fa3 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/occult/creature.dm @@ -0,0 +1,68 @@ +/mob/living/simple_mob/creature + name = "creature" + desc = "A sanity-destroying otherthing." + icon = 'icons/mob/critter.dmi' + icon_state = "otherthing" + icon_living = "otherthing" + icon_dead = "otherthing-dead" + + mob_class = MOB_CLASS_ABERRATION + + faction = "creature" + + maxHealth = 40 + health = 40 + + harm_intent_damage = 8 + + melee_damage_lower = 8 + melee_damage_upper = 15 + attack_armor_pen = 5 //It's a horror from beyond, I ain't gotta explain 5 AP + attack_sharp = 1 + attack_edge = 1 + + attacktext = list("chomped") + attack_sound = 'sound/weapons/bite.ogg' + + speak_emote = list("gibbers") + + ai_holder_type = /datum/ai_holder/simple_mob/melee + +// Strong Variant +/mob/living/simple_mob/creature/strong + maxHealth = 160 + health = 160 + + harm_intent_damage = 5 + melee_damage_lower = 13 + melee_damage_upper = 25 + +// Cult Variant +/mob/living/simple_mob/creature/cult + mob_class = MOB_CLASS_DEMONIC + + faction = "cult" + + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + + supernatural = TRUE + +/mob/living/simple_mob/creature/cult/cultify() + return + +// Strong Cult Variant +/mob/living/simple_mob/creature/cult/strong + maxHealth = 160 + health = 160 + + harm_intent_damage = 5 + melee_damage_lower = 13 + melee_damage_upper = 25 \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/faithless.dm b/code/modules/mob/living/simple_mob/subtypes/occult/faithless.dm new file mode 100644 index 0000000000..1c4ac8876c --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/occult/faithless.dm @@ -0,0 +1,77 @@ +/mob/living/simple_mob/faithless + name = "Faithless" + desc = "The Wish Granter's faith in humanity, incarnate" + icon_state = "faithless" + icon_living = "faithless" + icon_dead = "faithless_dead" + + faction = "faithless" + + mob_class = MOB_CLASS_DEMONIC + + maxHealth = 50 + health = 50 + + response_help = "passes through" + response_disarm = "shoves" + response_harm = "hits" + + harm_intent_damage = 10 + + melee_damage_lower = 10 + melee_damage_upper = 18 + attack_armor_pen = 5 //It's a horror from beyond, I ain't gotta explain 5 AP + + attacktext = list("gripped") + attack_sound = 'sound/hallucinations/growl1.ogg' + + ai_holder_type = /datum/ai_holder/simple_mob/melee + + + taser_kill = FALSE + + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + minbodytemp = 0 + +/mob/living/simple_mob/faithless/Process_Spacemove(var/check_drift = 0) + return 1 + +/mob/living/simple_mob/faithless/apply_melee_effects(var/atom/A) + if(isliving(A)) + var/mob/living/L = A + if(prob(12)) + L.Weaken(3) + L.visible_message("\the [src] knocks down \the [L]!") + +// Strong Variant +/mob/living/simple_mob/faithless/strong + maxHealth = 100 + health = 100 + + harm_intent_damage = 5 + melee_damage_lower = 13 + melee_damage_upper = 28 + +// Cult Variant +/mob/living/simple_mob/faithless/cult + faction = "cult" + supernatural = TRUE + +/mob/living/simple_mob/faithless/cult/cultify() + return + +// Strong Cult Variant +/mob/living/simple_mob/faithless/cult/strong + maxHealth = 100 + health = 100 + + harm_intent_damage = 5 + melee_damage_lower = 13 + melee_damage_upper = 28 \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/plant/tomato.dm b/code/modules/mob/living/simple_mob/subtypes/plant/tomato.dm new file mode 100644 index 0000000000..79e5c4d349 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/plant/tomato.dm @@ -0,0 +1,27 @@ +/mob/living/simple_mob/tomato + name = "tomato" + desc = "It's a horrifyingly enormous beef tomato, and it's packing extra beef!" + tt_desc = "X Solanum abominable" + icon_state = "tomato" + icon_living = "tomato" + icon_dead = "tomato_dead" + + mob_class = MOB_CLASS_PLANT + + faction = "plants" + maxHealth = 15 + health = 15 + poison_resist = 1.0 + + response_help = "prods" + response_disarm = "pushes aside" + response_harm = "smacks" + + harm_intent_damage = 5 + melee_damage_upper = 15 + melee_damage_lower = 10 + attacktext = list("mauled") + + ai_holder_type = /datum/ai_holder/simple_mob/melee + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/tomatomeat diff --git a/code/modules/mob/living/simple_mob/subtypes/plant/tree.dm b/code/modules/mob/living/simple_mob/subtypes/plant/tree.dm new file mode 100644 index 0000000000..30c891088c --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/plant/tree.dm @@ -0,0 +1,43 @@ +/mob/living/simple_mob/animal/space/tree + name = "pine tree" + desc = "A pissed off tree-like alien. It seems annoyed with the festivities..." + tt_desc = "X Festivus tyrannus" + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "pine_1" + icon_living = "pine_1" + icon_dead = "pine_1" + icon_gib = "pine_1" + + mob_class = MOB_CLASS_PLANT + + faction = "plants" + maxHealth = 250 + health = 250 + poison_resist = 1.0 + + response_help = "brushes" + response_disarm = "pushes" + response_harm = "hits" + + harm_intent_damage = 5 + melee_damage_lower = 8 + melee_damage_upper = 12 + attacktext = list("bitten") + attack_sound = 'sound/weapons/bite.ogg' + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/xenomeat + + pixel_x = -16 + +/mob/living/simple_mob/animal/space/tree/apply_melee_effects(var/atom/A) + if(isliving(A)) + var/mob/living/L = A + if(prob(15)) + L.Weaken(3) + L.visible_message(span("danger", "\The [src] knocks down \the [L]!")) + +/mob/living/simple_mob/animal/space/tree/death() + ..(null,"is hacked into pieces!") + playsound(loc, 'sound/effects/woodcutting.ogg', 100, 1) + new /obj/item/stack/material/wood(loc) + qdel(src) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm new file mode 100644 index 0000000000..8986cbcbe3 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm @@ -0,0 +1,94 @@ +// These slimes lack certain xenobio features but get more combat-oriented goodies. Generally these are more oriented towards Explorers than Xenobiologists. + +/mob/living/simple_mob/slime/feral + name = "feral slime" + desc = "The result of slimes escaping containment from some xenobiology lab. \ + Having the means to successfully escape their lab, as well as having to survive on a harsh, cold world has made these \ + creatures rival the ferocity of other apex predators in this region of Sif. It is considered to be a very invasive species." + description_info = "Note that processing this large slime will give six cores." + + cores = 6 // Xenobio will love getting their hands on these. + + icon_state = "slime adult" + icon_living = "slime adult" + icon_dead = "slime adult dead" + glow_range = 5 + glow_intensity = 4 + icon_scale = 2 // Twice as big as the xenobio variant. + pixel_y = -10 // Since the base sprite isn't centered properly, the pixel auto-adjustment needs some help. + default_pixel_y = -10 // To prevent resetting above var. + + maxHealth = 300 + movement_cooldown = 10 + melee_attack_delay = 0.5 SECONDS + + ai_holder_type = /datum/ai_holder/simple_mob/ranged/pointblank + + +// Slimebatoning/xenotasing it just makes it mad at you (which can be good if you're heavily armored and your friends aren't). +/mob/living/simple_mob/slime/feral/slimebatoned(mob/living/user, amount) + taunt(user, TRUE) + + +// *********** +// *Dark Blue* +// *********** + +// Dark Blue feral slimes can fire a strong icicle projectile every few seconds. The icicle hits hard and has some armor penetration. +// They also have a similar aura as their xenobio counterparts, which inflicts cold damage. It also chills non-resistant mobs. + +/mob/living/simple_mob/slime/feral/dark_blue + name = "dark blue feral slime" + color = "#2398FF" + glow_toggle = TRUE + slime_color = "dark blue" + coretype = /obj/item/slime_extract/dark_blue + cold_resist = 1 // Complete immunity. + minbodytemp = 0 + cold_damage_per_tick = 0 + + projectiletype = /obj/item/projectile/icicle + base_attack_cooldown = 2 SECONDS + ranged_attack_delay = 1 SECOND + + player_msg = "You can fire an icicle projectile every two seconds. It hits hard, and armor has a hard time resisting it.
\ + You are also immune to the cold, and you cause enemies around you to suffer periodic harm from the cold, if unprotected.
\ + Unprotected enemies are also Chilled, making them slower and less evasive, and disabling effects last longer." + +/obj/item/projectile/icicle + name = "icicle" + icon_state = "ice_2" + damage = 40 + damage_type = BRUTE + check_armour = "melee" + armor_penetration = 30 + step_delay = 2 // Make it a bit easier to dodge since its not a bullet. + icon_scale = 2 // It hits like a truck. + sharp = TRUE + +/obj/item/projectile/icicle/on_impact(atom/A) + playsound(get_turf(A), "shatter", 70, 1) + return ..() + +/obj/item/projectile/icicle/get_structure_damage() + return damage / 2 // They're really deadly against mobs, but less effective against solid things. + +/mob/living/simple_mob/slime/feral/dark_blue/handle_special() + if(stat != DEAD) + cold_aura() + ..() + +/mob/living/simple_mob/slime/feral/dark_blue/proc/cold_aura() + for(var/mob/living/L in view(3, src)) + if(L == src) + continue + chill(L) + +/mob/living/simple_mob/slime/feral/dark_blue/proc/chill(mob/living/L) + L.inflict_cold_damage(10) + if(L.get_cold_protection() < 1) + L.add_modifier(/datum/modifier/chilled, 5 SECONDS, src) + + if(L.has_AI()) // Other AIs should react to hostile auras. + L.ai_holder.react_to_attack(src) + diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/slime.dm b/code/modules/mob/living/simple_mob/subtypes/slime/slime.dm new file mode 100644 index 0000000000..dd756d267f --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/slime/slime.dm @@ -0,0 +1,216 @@ +// The top-level slime defines. Xenobio slimes and feral slimes will inherit from this. + +/mob/living/simple_mob/slime + name = "slime" + desc = "It's a slime." + tt_desc = "A Macrolimbus vulgaris" + icon = 'icons/mob/slime2.dmi' + icon_state = "slime baby" + icon_living = "slime baby" + icon_dead = "slime baby dead" + var/shiny = FALSE // If true, will add a 'shiny' overlay. + var/icon_state_override = null // Used for special slime appearances like the rainbow slime. + color = "#CACACA" + glow_range = 3 + glow_intensity = 2 + gender = NEUTER + + faction = "slime" // Note that slimes are hostile to other slimes of different color regardless of faction (unless Unified). + maxHealth = 150 + movement_cooldown = 0 + pass_flags = PASSTABLE + makes_dirt = FALSE // Goop + mob_class = MOB_CLASS_SLIME + + response_help = "pets" + + // Atmos stuff. + minbodytemp = T0C-30 + heat_damage_per_tick = 0 + cold_damage_per_tick = 40 + + min_oxy = 0 + max_oxy = 0 + min_tox = 0 + max_tox = 0 + min_co2 = 0 + max_co2 = 0 + min_n2 = 0 + max_n2 = 0 + unsuitable_atoms_damage = 0 + shock_resist = 0.5 // Slimes are resistant to electricity, and it actually charges them. + taser_kill = FALSE + water_resist = 0 // Slimes are very weak to water. + + melee_damage_lower = 10 + melee_damage_upper = 15 + base_attack_cooldown = 10 // One attack a second. + attack_sound = 'sound/weapons/bite.ogg' + attacktext = list("glomped") + speak_emote = list("chirps") + friendly = list("pokes") + + ai_holder_type = /datum/ai_holder/simple_mob/melee + say_list_type = /datum/say_list/slime + + var/cores = 1 // How many cores you get when placed in a Processor. + var/obj/item/clothing/head/hat = null // The hat the slime may be wearing. + var/slime_color = "grey" // Used for updating the name and for slime color-ism. + var/unity = FALSE // If true, slimes will consider other colors as their own. Other slimes will see this slime as the same color as well. + var/coretype = /obj/item/slime_extract/grey // What core is inside the slime, and what you get from the processor. + var/reagent_injected = null // Some slimes inject reagents on attack. This tells the game what reagent to use. + var/injection_amount = 5 // This determines how much. + var/mood = ":3" // Icon to use to display 'mood', as an overlay. + + can_enter_vent_with = list(/obj/item/clothing/head) + +/datum/say_list/slime + speak = list("Blorp...", "Blop...") + emote_see = list("bounces", "jiggles", "sways") + emote_hear = list("squishes") + +/mob/living/simple_mob/slime/initialize() + verbs += /mob/living/proc/ventcrawl + update_mood() + glow_color = color + handle_light() + update_icon() + return ..() + +/mob/living/simple_mob/slime/Destroy() + if(hat) + drop_hat() + return ..() + +/mob/living/simple_mob/slime/death() + // Make dead slimes stop glowing. + glow_toggle = FALSE + handle_light() + ..() + +/mob/living/simple_mob/slime/revive() + // Make revived slimes resume glowing. + glow_toggle = initial(glow_toggle) + handle_light() + ..() + +/mob/living/simple_mob/slime/update_icon() + ..() // Do the regular stuff first. + + if(stat != DEAD) + // General slime shine. + var/image/I = image(icon, src, "slime light") + I.appearance_flags = RESET_COLOR + add_overlay(I) + + // 'Shiny' overlay, for gemstone-slimes. + if(shiny) + I = image(icon, src, "slime shiny") + I.appearance_flags = RESET_COLOR + add_overlay(I) + + // Mood overlay. + I = image(icon, src, "aslime-[mood]") + I.appearance_flags = RESET_COLOR + add_overlay(I) + + // Hat simulator. + if(hat) + var/hat_state = hat.item_state ? hat.item_state : hat.icon_state + var/image/I = image('icons/mob/head.dmi', src, hat_state) + I.pixel_y = -7 // Slimes are small. + I.appearance_flags = RESET_COLOR + add_overlay(I) + +// Controls the 'mood' overlay. Overrided in subtypes for specific behaviour. +/mob/living/simple_mob/slime/proc/update_mood() + mood = "feral" // This is to avoid another override in the /feral subtype. + +/mob/living/simple_mob/slime/proc/unify() + unity = TRUE + +// Interface override, because slimes are supposed to attack other slimes of different color regardless of faction +// (unless Unified, of course). +/mob/living/simple_mob/slime/IIsAlly(mob/living/L) + . = ..() + if(.) // Need to do an additional check if its another slime. + if(istype(L, /mob/living/simple_mob/slime)) + var/mob/living/simple_mob/slime/S = L + if(S.slime_color != src.slime_color) + if(S.unity || src.unity) + return TRUE + return FALSE + // The other stuff was already checked in parent proc, and the . variable will implicitly return the correct value. + +// Slimes regenerate passively. +/mob/living/simple_mob/slime/handle_special() + adjustOxyLoss(-1) + adjustToxLoss(-1) + adjustFireLoss(-1) + adjustCloneLoss(-1) + adjustBruteLoss(-1) + +// Clicked on by empty hand. +/mob/living/simple_mob/slime/attack_hand(mob/living/L) + if(L.a_intent == I_GRAB && hat) + remove_hat(L) + else + ..() + +// Clicked on while holding an object. +/mob/living/simple_mob/slime/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/clothing/head)) // Handle hat simulator. + give_hat(I, user) + return + + // Otherwise they're probably fighting the slime. + if(prob(25)) + visible_message(span("warning", "\The [user]'s [I] passes right through \the [src]!")) + user.setClickCooldown(user.get_attack_speed(I)) + return + ..() + +// Called when hit with an active slimebaton (or xeno taser). +// Subtypes react differently. +/mob/living/simple_mob/slime/proc/slimebatoned(mob/living/user, amount) + return + +// Hat simulator +/mob/living/simple_mob/slime/proc/give_hat(var/obj/item/clothing/head/new_hat, var/mob/living/user) + if(!istype(new_hat)) + to_chat(user, span("warning", "\The [new_hat] isn't a hat.")) + return + if(hat) + to_chat(user, span("warning", "\The [src] is already wearing \a [hat].")) + return + else + user.drop_item(new_hat) + hat = new_hat + new_hat.forceMove(src) + to_chat(user, span("notice", "You place \a [new_hat] on \the [src]. How adorable!")) + update_icon() + return + +/mob/living/simple_mob/slime/proc/remove_hat(var/mob/living/user) + if(!hat) + to_chat(user, "\The [src] doesn't have a hat to remove.") + else + hat.forceMove(get_turf(src)) + user.put_in_hands(hat) + to_chat(user, "You take away \the [src]'s [hat.name]. How mean.") + hat = null + update_icon() + +/mob/living/simple_mob/slime/proc/drop_hat() + if(!hat) + return + hat.forceMove(get_turf(src)) + hat = null + update_icon() + +/mob/living/simple_mob/slime/speech_bubble_appearance() + return "slime" + +/mob/living/simple_mob/slime/proc/squish() + playsound(src.loc, 'sound/effects/slime_squish.ogg', 50, 0) + visible_message("\The [src] squishes!") \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/combat.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/combat.dm new file mode 100644 index 0000000000..2cda9bfc7e --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/combat.dm @@ -0,0 +1,76 @@ +// Code for slimes attacking other things. + +// Slime attacks change based on intent. +/mob/living/simple_mob/slime/xenobio/apply_attack(mob/living/L, damage_to_do) + if(istype(L)) + switch(a_intent) + if(I_HELP) // This shouldn't happen but just in case. + return FALSE + + if(I_DISARM) + var/stun_power = between(0, power_charge + rand(0, 3), 10) + + if(ishuman(L)) + var/mob/living/carbon/human/H = L + stun_power *= max(H.species.siemens_coefficient, 0) + + if(prob(stun_power * 10)) // Try an electric shock. + power_charge = max(0, power_charge - 3) + L.visible_message( + span("danger", "\The [src] has shocked \the [L]!"), + span("danger", "\The [src] has shocked you!") + ) + playsound(src, 'sound/weapons/Egloves.ogg', 75, 1) + L.Weaken(4) + L.Stun(4) + do_attack_animation(L) + if(L.buckled) + L.buckled.unbuckle_mob() // To prevent an exploit where being buckled prevents slimes from jumping on you. + L.stuttering = max(L.stuttering, stun_power) + + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(5, 1, L) + s.start() + + if(prob(stun_power * 10) && stun_power >= 8) + L.adjustFireLoss(power_charge * rand(1, 2)) + return FALSE + + else if(prob(20)) // Try to do a regular disarm attack. + L.visible_message( + span("danger", "\The [src] has pounced at \the [L]!"), + span("danger", "\The [src] has pounced at you!") + ) + playsound(src, 'sound/weapons/thudswoosh.ogg', 75, 1) + L.Weaken(2) + do_attack_animation(L) + if(L.buckled) + L.buckled.unbuckle_mob() // To prevent an exploit where being buckled prevents slimes from jumping on you. + return FALSE + + else // Failed to do anything this time. + L.visible_message( + span("warning", "\The [src] has tried to pounce at \the [L]!"), + span("warning", "\The [src] has tried to pounce at you!") + ) + playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1) + do_attack_animation(L) + return FALSE + + if(I_GRAB) + start_consuming(L) + return FALSE + + if(I_HURT) + return ..() // Regular stuff. + else + return ..() // Do the regular stuff if we're hitting a window/mech/etc. + +/mob/living/simple_mob/slime/xenobio/apply_melee_effects(mob/living/L) + if(istype(L) && a_intent == I_HURT) + // Pump them full of toxins, if able. + if(L.reagents && L.can_inject() && reagent_injected) + L.reagents.add_reagent(reagent_injected, injection_amount) + + // Feed off of their flesh, if able. + consume(L, 5) diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/consumption.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/consumption.dm new file mode 100644 index 0000000000..b997dde35d --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/consumption.dm @@ -0,0 +1,169 @@ +// Handles hunger, starvation, growth, and eatting humans. + +// Might be best to make this a /mob/living proc and override. +/mob/living/simple_mob/slime/xenobio/proc/adjust_nutrition(input) + nutrition = between(0, nutrition + input, get_max_nutrition()) + + if(input > 0) + // Gain around one level per 50 nutrition. + if(prob(input * 2)) + power_charge = min(power_charge++, 10) + if(power_charge == 10) + adjustToxLoss(-10) + + // Heal 1 point of damage per 5 nutrition coming in. + adjustBruteLoss(-input * 0.2) + adjustFireLoss(-input * 0.2) + adjustToxLoss(-input * 0.2) + adjustOxyLoss(-input * 0.2) + adjustCloneLoss(-input * 0.2) + + +/mob/living/simple_mob/slime/xenobio/proc/get_max_nutrition() // Can't go above it + return is_adult ? 1200 : 1000 + +/mob/living/simple_mob/slime/xenobio/proc/get_grow_nutrition() // Above it we grow, below it we can eat + return is_adult ? 1000 : 800 + +/mob/living/simple_mob/slime/xenobio/proc/get_hunger_nutrition() // Below it we will always eat + return is_adult ? 600 : 500 + +/mob/living/simple_mob/slime/xenobio/proc/get_starve_nutrition() // Below it we will eat before everything else + return is_adult ? 300 : 200 + +// Called by Life(). +/mob/living/simple_mob/slime/xenobio/proc/handle_nutrition() + if(harmless) + return + + if(prob(15)) + adjust_nutrition(is_adult ? -2 : -1) // Adult slimes get hungry faster. + + if(nutrition <= get_starve_nutrition()) + handle_starvation() + + else if(nutrition >= get_grow_nutrition() && amount_grown < 10) + adjust_nutrition(-20) + amount_grown = between(0, amount_grown + 1, 10) + +// Called if above proc happens while below a nutrition threshold. +/mob/living/simple_mob/slime/xenobio/proc/handle_starvation() + if(nutrition < get_starve_nutrition() && !client) // if a slime is starving, it starts losing its friends + if(friends.len && prob(1)) + var/mob/nofriend = pick(friends) + if(nofriend) + friends -= nofriend + say("[nofriend]... food now...") + + if(nutrition <= 0) + adjustToxLoss(rand(1,3)) + if(client && prob(5)) + to_chat(src, span("danger", "You are starving!")) + + +/mob/living/simple_mob/slime/xenobio/proc/handle_consumption() + if(victim && !stat) + if(istype(victim) && consume(victim, 20)) + if(prob(25)) + to_chat(src, span("notice", "You continue absorbing \the [victim].")) + + else + var/list/feedback = list( + "This subject is incompatable", + "This subject does not have a life energy", + "This subject is empty", + "I am not satisfied", + "I can not feed from this subject", + "I do not feel nourished", + "This subject is not food" + ) + to_chat(src, span("warning", "[pick(feedback)]...")) + stop_consumption() + + if(victim) + victim.updatehealth() + + else + stop_consumption() + +/mob/living/simple_mob/slime/xenobio/proc/start_consuming(mob/living/L) + if(!can_consume(L)) + return + if(!Adjacent(L)) + return + + step_towards(src, L) // Get on top of them to feed. + if(loc != L.loc) + return + + if(L.buckle_mob(src, forced = TRUE)) + victim = L + update_icon() + set_AI_busy(TRUE) // Don't want the AI to interfere with eatting. + victim.visible_message( + span("danger", "\The [src] latches onto \the [victim]!"), + span("critical", "\The [src] latches onto you!") + ) + +/mob/living/simple_mob/slime/xenobio/proc/stop_consumption(mob/living/L) + if(!victim) + return + victim.unbuckle_mob() + victim.visible_message( + span("notice", "\The [src] slides off of [victim]!"), + span("notice", "\The [src] slides off of you!") + ) + victim = null + update_icon() + set_AI_busy(FALSE) // Resume normal operations. + +/mob/living/simple_mob/slime/xenobio/proc/can_consume(mob/living/L) + if(!L || !istype(L)) + to_chat(src, "This subject is incomparable...") + return FALSE + if(harmless) + to_chat(src, "I am pacified... I cannot eat...") + return FALSE + if(L.mob_class & MOB_CLASS_SLIME) + to_chat(src, "I cannot feed on other slimes...") + return FALSE + if(L.isSynthetic()) + to_chat(src, "This subject is not biological...") + return FALSE + if(L.getarmor(null, "bio") >= 75) + to_chat(src, "I cannot reach this subject's biological matter...") + return FALSE + if(!Adjacent(L)) + to_chat(src, "This subject is too far away...") + return FALSE + if(L.getCloneLoss() >= L.getMaxHealth() * 1.5) + to_chat(src, "This subject does not have an edible life energy...") + return FALSE + if(L.has_buckled_mobs()) + for(var/A in L.buckled_mobs) + if(istype(A, /mob/living/simple_mob/slime/xenobio)) + if(A != src) + to_chat(src, "\The [A] is already feeding on this subject...") + return FALSE + return TRUE + +// This does the actual damage, as well as give nutrition and heals. +// Assuming no bio armor, calling consume(10) will result in; +// 6 clone damage to victim +// 4 tox damage to victim. +// 25 nutrition for the slime. +// 2 points of damage healed on the slime (as a result of the nutrition). +// 50% of giving +1 charge to the slime (same as above). +/mob/living/simple_mob/slime/xenobio/proc/consume(mob/living/victim, amount) + if(can_consume(victim)) + var/armor_modifier = abs((victim.getarmor(null, "bio") / 100) - 1) + var/damage_done = amount * armor_modifier + if(damage_done > 0) + victim.adjustCloneLoss(damage_done * 0.6) + victim.adjustToxLoss(damage_done * 0.4) + adjust_nutrition(damage_done * 5) + Beam(victim, icon_state = "slime_consume", time = 8) + to_chat(src, span("notice", "You absorb some biomaterial from \the [victim].")) + to_chat(victim, span("danger", "\The [src] consumes some of your flesh!")) + return TRUE + return FALSE diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/defense.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/defense.dm new file mode 100644 index 0000000000..084f4633b2 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/defense.dm @@ -0,0 +1,54 @@ +// Contains code for slimes getting attacked, beat, touched, etc, and reacting to that. + +// Clicked on by empty hand. +// Handles trying to wrestle a slime off of someone being eatten. +/mob/living/simple_mob/slime/xenobio/attack_hand(mob/living/L) + if(victim) // Are we eating someone? + var/fail_odds = 30 + if(victim == L) // Harder to get the slime off if it's you that is being eatten. + fail_odds = 60 + + if(prob(fail_odds)) + visible_message(span("warning", "\The [L] attempts to wrestle \the [name] off!")) + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) + + else + visible_message(span("warning", "\The [L] manages to wrestle \the [name] off!")) + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + + if(prob(40)) + adjust_discipline(1) // Do this here so that it will be justified discipline. + stop_consumption() + step_away(src, L) + + else + ..() + +// Handles the actual harming by a melee weapon. +/mob/living/simple_mob/slime/xenobio/hit_with_weapon(obj/item/I, mob/living/user, effective_force, hit_zone) + ..() // Apply damage and etc. + if(!stat && effective_force > 0) + if(!is_justified_to_discipline()) // Wow, buddy, why am I getting attacked?? + adjust_discipline(1) // This builds resentment due to being unjustified. + + if(user in friends) // Friend attacking us for no reason. + if(prob(25)) + friends -= user + say("[user]... not friend...") + + else // We're actually being bad. + var/prob_to_back_down = round(effective_force) + if(is_adult) + prob_to_back_down /= 2 + if(prob(prob_to_back_down)) + adjust_discipline(2) // Justified. + +// Shocked grilles don't hurt slimes, and in fact give them charge. +/mob/living/simple_mob/slime/xenobio/electrocute_act(shock_damage, obj/source, siemens_coeff = 1.0, def_zone = null) + power_charge = between(0, power_charge + round(shock_damage / 10), 10) + to_chat(src, span("notice", "\The [source] shocks you, and it charges you.")) + +// Getting slimebatoned/xenotased. +/mob/living/simple_mob/slime/xenobio/slimebatoned(mob/living/user, amount) + Weaken(amount) + adjust_discipline(round(amount/2)) diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/discipline.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/discipline.dm new file mode 100644 index 0000000000..5360dab7b3 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/discipline.dm @@ -0,0 +1,23 @@ +// Handles the subjugation of slimes by force. +// Mostly a way for things to talk to the AI indirectly. + +/mob/living/simple_mob/slime/xenobio/proc/adjust_discipline(amount, silent) + if(amount > 0) + to_chat(src, span("warning", "You've been disciplined!")) + if(has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder + AI.adjust_discipline(amount, silent) + + +/mob/living/simple_mob/slime/xenobio/proc/is_justified_to_discipline() + if(victim) // Punish if eating someone that isn't a monkey. + if(ishuman(victim)) + var/mob/living/carbon/human/H = victim + if(istype(H.species, /datum/species/monkey)) + return FALSE + return TRUE + + else if(has_AI()) // Now for thoughtcrimes. + var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder + return AI.is_justified_to_discipline() // Will return true if targeting a non-monkey. + return FALSE diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm new file mode 100644 index 0000000000..03cd14eb3a --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm @@ -0,0 +1,784 @@ +// Here are where all the other colors of slime live. +// They will generally fight each other if not Unified, meaning the xenobiologist has to seperate them. + +// Tier 1. + +/mob/living/simple_mob/slime/xenobio/purple + desc = "This slime is rather toxic to handle, as it is poisonous." + color = "#CC23FF" + slime_color = "purple" + coretype = /obj/item/slime_extract/purple + reagent_injected = "toxin" + + description_info = "This slime spreads a toxin when it attacks. A biosuit or other thick armor can protect from the toxic attack." + player_msg = "You inject a harmful toxin when attacking." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/dark_purple, + /mob/living/simple_mob/slime/xenobio/dark_blue, + /mob/living/simple_mob/slime/xenobio/green, + /mob/living/simple_mob/slime/xenobio + ) + +/mob/living/simple_mob/slime/xenobio/orange + desc = "This slime is known to be flammable and can ignite enemies." + color = "#FFA723" + slime_color = "orange" + coretype = /obj/item/slime_extract/orange + melee_damage_lower = 5 + melee_damage_upper = 5 + heat_resist = 1 + + description_info = "The slime is immune to burning attacks, and attacks from this slime will burn you, and can ignite you. \ + A firesuit can protect from the burning attacks of this slime." + player_msg = "You inflict burning attacks, which causes additional damage, makes the target more flammable, and has a chance to ignite them.
\ + You are also immune to burning attacks." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/dark_purple, + /mob/living/simple_mob/slime/xenobio/yellow, + /mob/living/simple_mob/slime/xenobio/red, + /mob/living/simple_mob/slime/xenobio + ) + +/mob/living/simple_mob/slime/xenobio/orange/apply_melee_effects(atom/A) + ..() + if(isliving(A)) + var/mob/living/L = A + L.inflict_heat_damage(is_adult ? 10 : 5) + to_chat(src, span("span", "You burn \the [L].")) + to_chat(L, span("danger", "You've been burned by \the [src]!")) + L.adjust_fire_stacks(1) + if(prob(12)) + L.IgniteMob() + +/mob/living/simple_mob/slime/xenobio/blue + desc = "This slime produces 'cryotoxin' and uses it against their foes. Very deadly to other slimes." + color = "#19FFFF" + slime_color = "blue" + coretype = /obj/item/slime_extract/blue + reagent_injected = "cryotoxin" + cold_resist = 0.50 // Not as strong as dark blue, which has immunity. + + description_info = "The slime is resistant to the cold, and attacks from this slime can inject cryotoxin into you. \ + A biosuit or other thick armor can protect from the injection." + player_msg = "You inject cryotoxin on attack, which causes them to get very cold, slowing them down and harming them over time.
\ + You are also resistant to cold attacks." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/dark_blue, + /mob/living/simple_mob/slime/xenobio/silver, + /mob/living/simple_mob/slime/xenobio/pink, + /mob/living/simple_mob/slime/xenobio + ) + + +/mob/living/simple_mob/slime/xenobio/metal + desc = "This slime is a lot more resilient than the others, due to having a metamorphic metallic and sloped surface." + color = "#5F5F5F" + slime_color = "metal" + shiny = TRUE + coretype = /obj/item/slime_extract/metal + + description_info = "This slime is a lot more durable and tough to damage than the others. It also seems to provoke others to attack it over others." + player_msg = "You are more resilient and armored than more slimes. Your attacks will also encourage less intelligent enemies to focus on you." + + maxHealth = 250 + maxHealth_adult = 350 + + // The sloped armor. + // It's resistant to most weapons (but a spraybottle still kills it rather fast). + armor = list( + "melee" = 25, + "bullet" = 25, + "laser" = 25, + "energy" = 50, + "bomb" = 80, + "bio" = 100, + "rad" = 100 + ) + + armor_soak = list( + "melee" = 5, + "bullet" = 5, + "laser" = 5, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/silver, + /mob/living/simple_mob/slime/xenobio/yellow, + /mob/living/simple_mob/slime/xenobio/gold, + /mob/living/simple_mob/slime/xenobio + ) + +/mob/living/simple_mob/slime/xenobio/metal/apply_melee_effects(atom/A) + ..() + if(isliving(A)) + var/mob/living/L = A + L.taunt(src, TRUE) // We're the party tank now. + +// Tier 2 + +/mob/living/simple_mob/slime/xenobio/yellow + desc = "This slime is very conductive, and is known to use electricity as a means of defense moreso than usual for slimes." + color = "#FFF423" + slime_color = "yellow" + coretype = /obj/item/slime_extract/yellow + melee_damage_lower = 5 + melee_damage_upper = 5 + shock_resist = 1 + + projectiletype = /obj/item/projectile/beam/lightning/slime + projectilesound = 'sound/effects/lightningbolt.ogg' + glow_toggle = TRUE + + description_info = "In addition to being immune to electrical shocks, this slime will fire ranged lightning attacks at \ + enemies if they are at range, inflict shocks upon entities they attack, and generate electricity for their stun \ + attack faster than usual. Insulative or reflective armor can protect from these attacks." + player_msg = "You have a ranged electric attack. You also shock enemies you attack, and your electric stun attack charges passively.
\ + You are also immune to shocking attacks." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/bluespace, + /mob/living/simple_mob/slime/xenobio/bluespace, + /mob/living/simple_mob/slime/xenobio/metal, + /mob/living/simple_mob/slime/xenobio/orange + ) + +/mob/living/simple_mob/slime/xenobio/yellow/apply_melee_effects(atom/A) + ..() + if(isliving(A)) + var/mob/living/L = A + L.inflict_shock_damage(is_adult ? 10 : 5) + to_chat(src, span("span", "You shock \the [L].")) + to_chat(L, span("danger", "You've been shocked by \the [src]!")) + +/mob/living/simple_mob/slime/xenobio/yellow/handle_special() + if(stat == CONSCIOUS) + if(prob(25)) + power_charge = between(0, power_charge + 1, 10) + ..() + +/obj/item/projectile/beam/lightning/slime + power = 10 + fire_sound = 'sound/effects/lightningbolt.ogg' + + +/mob/living/simple_mob/slime/xenobio/dark_purple + desc = "This slime produces ever-coveted phoron. Risky to handle but very much worth it." + color = "#660088" + slime_color = "dark purple" + coretype = /obj/item/slime_extract/dark_purple + reagent_injected = "phoron" + + description_info = "This slime applies phoron to enemies it attacks. A biosuit or other thick armor can protect from the toxic attack. \ + If hit with a burning attack, it will erupt in flames." + player_msg = "You inject phoron into enemies you attack.
\ + You will erupt into flames if harmed by fire!" + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/purple, + /mob/living/simple_mob/slime/xenobio/orange, + /mob/living/simple_mob/slime/xenobio/ruby, + /mob/living/simple_mob/slime/xenobio/ruby + ) + +/mob/living/simple_mob/slime/xenobio/dark_purple/proc/ignite() + visible_message(span("critical", "\The [src] erupts in an inferno!")) + for(var/turf/simulated/target_turf in view(2, src)) + target_turf.assume_gas("phoron", 30, 1500+T0C) + spawn(0) + target_turf.hotspot_expose(1500+T0C, 400) + qdel(src) + +/mob/living/simple_mob/slime/xenobio/dark_purple/ex_act(severity) + log_and_message_admins("[src] ignited due to a chain reaction with an explosion.") + ignite() + +/mob/living/simple_mob/slime/xenobio/dark_purple/fire_act(datum/gas_mixture/air, temperature, volume) + log_and_message_admins("[src] ignited due to exposure to fire.") + ignite() + +/mob/living/simple_mob/slime/xenobio/dark_purple/bullet_act(var/obj/item/projectile/P, var/def_zone) + if(P.damage_type && P.damage_type == BURN && P.damage) // Most bullets won't trigger the explosion, as a mercy towards Security. + log_and_message_admins("[src] ignited due to bring hit by a burning projectile[P.firer ? " by [key_name(P.firer)]" : ""].") + ignite() + else + ..() + +/mob/living/simple_mob/slime/xenobio/dark_purple/attackby(var/obj/item/weapon/W, var/mob/user) + if(istype(W) && W.force && W.damtype == BURN) + log_and_message_admins("[src] ignited due to being hit with a burning weapon ([W]) by [key_name(user)].") + ignite() + else + ..() + + + +/mob/living/simple_mob/slime/xenobio/dark_blue + desc = "This slime makes other entities near it feel much colder, and is more resilient to the cold. It tends to kill other slimes rather quickly." + color = "#2398FF" + glow_toggle = TRUE + slime_color = "dark blue" + coretype = /obj/item/slime_extract/dark_blue + melee_damage_lower = 5 + melee_damage_upper = 5 + cold_resist = 1 + + description_info = "This slime is immune to the cold, however water will still kill it. Its presense, as well as its attacks, will \ + also cause you additional harm from the cold. A winter coat or other cold-resistant clothing can protect from this." + player_msg = "You are immune to the cold, inflict additional cold damage on attack, and cause nearby entities to suffer from coldness." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/purple, + /mob/living/simple_mob/slime/xenobio/blue, + /mob/living/simple_mob/slime/xenobio/cerulean, + /mob/living/simple_mob/slime/xenobio/cerulean + ) + + minbodytemp = 0 + cold_damage_per_tick = 0 + +/mob/living/simple_mob/slime/xenobio/dark_blue/handle_special() + if(stat != DEAD) + cold_aura() + ..() + +/mob/living/simple_mob/slime/xenobio/dark_blue/proc/cold_aura() + for(var/mob/living/L in view(2, src)) + if(L == src) + continue + chill(L) + + var/turf/T = get_turf(src) + var/datum/gas_mixture/env = T.return_air() + if(env) + env.add_thermal_energy(-10 * 1000) + +/mob/living/simple_mob/slime/xenobio/dark_blue/apply_melee_effects(atom/A) + ..() + if(isliving(A)) + var/mob/living/L = A + chill(L) + to_chat(src, span("span", "You chill \the [L].")) + to_chat(L, span("danger", "You've been chilled by \the [src]!")) + + +/mob/living/simple_mob/slime/xenobio/dark_blue/proc/chill(mob/living/L) + L.inflict_cold_damage(is_adult ? 10 : 5) + if(L.get_cold_protection() < 1 && L.has_AI()) // Harmful auras will make the AI react to its bearer. + L.ai_holder.react_to_attack(src) + + +/mob/living/simple_mob/slime/xenobio/silver + desc = "This slime is shiny, and can deflect lasers or other energy weapons directed at it." + color = "#AAAAAA" + slime_color = "silver" + coretype = /obj/item/slime_extract/silver + shiny = TRUE + + description_info = "Tasers, including the slime version, are ineffective against this slime. The slimebation still works." + player_msg = "You automatically reflect lasers, beams, and tasers that hit you." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/metal, + /mob/living/simple_mob/slime/xenobio/blue, + /mob/living/simple_mob/slime/xenobio/amber, + /mob/living/simple_mob/slime/xenobio/amber + ) + +/mob/living/simple_mob/slime/xenobio/silver/bullet_act(var/obj/item/projectile/P, var/def_zone) + if(istype(P,/obj/item/projectile/beam) || istype(P, /obj/item/projectile/energy)) + visible_message(span("danger", "\The [src] reflects \the [P]!")) + + // Find a turf near or on the original location to bounce to + var/new_x = P.starting.x + pick(0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, -1, 1, -2, 2) + var/turf/curloc = get_turf(src) + + // redirect the projectile + P.redirect(new_x, new_y, curloc, src) + P.reflected = TRUE + return PROJECTILE_CONTINUE // complete projectile permutation + else + ..() + + +// Tier 3 + +/mob/living/simple_mob/slime/xenobio/bluespace + desc = "Trapping this slime in a cell is generally futile, as it can teleport at will." + color = null + slime_color = "bluespace" + icon_state_override = "bluespace" + coretype = /obj/item/slime_extract/bluespace + + description_info = "This slime will teleport to attack something if it is within a range of seven tiles. The teleport has a cooldown of five seconds." + player_msg = "You can teleport at will to a specific tile by clicking on it at range. This has a five second cooldown." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/bluespace, + /mob/living/simple_mob/slime/xenobio/bluespace, + /mob/living/simple_mob/slime/xenobio/yellow, + /mob/living/simple_mob/slime/xenobio/yellow + ) + + special_attack_min_range = 3 + special_attack_max_range = 7 + special_attack_cooldown = 5 SECONDS + +/mob/living/simple_mob/slime/xenobio/bluespace/do_special_attack(atom/A) + // Teleport attack. + if(!A) + to_chat(src, span("warning", "There's nothing to teleport to.")) + return FALSE + + var/list/nearby_things = range(1, A) + var/list/valid_turfs = list() + + // All this work to just go to a non-dense tile. + for(var/turf/potential_turf in nearby_things) + var/valid_turf = TRUE + if(potential_turf.density) + continue + for(var/atom/movable/AM in potential_turf) + if(AM.density) + valid_turf = FALSE + if(valid_turf) + valid_turfs.Add(potential_turf) + + var/turf/T = get_turf(src) + var/turf/target_turf = pick(valid_turfs) + + if(!target_turf) + to_chat(src, span("warning", "There wasn't an unoccupied spot to teleport to.")) + return FALSE + + var/datum/effect/effect/system/spark_spread/s1 = new /datum/effect/effect/system/spark_spread + s1.set_up(5, 1, T) + var/datum/effect/effect/system/spark_spread/s2 = new /datum/effect/effect/system/spark_spread + s2.set_up(5, 1, target_turf) + + + T.visible_message(span("notice", "\The [src] vanishes!")) + s1.start() + + forceMove(target_turf) + playsound(target_turf, 'sound/effects/phasein.ogg', 50, 1) + to_chat(src, span("notice", "You teleport to \the [target_turf].")) + + target_turf.visible_message(span("warning", "\The [src] appears!")) + s2.start() + + if(Adjacent(A)) + attack_target(A) + + +/mob/living/simple_mob/slime/xenobio/ruby + desc = "This slime has great physical strength." + color = "#FF3333" + slime_color = "ruby" + shiny = TRUE + glow_toggle = TRUE + coretype = /obj/item/slime_extract/ruby + + description_info = "This slime is unnaturally stronger, allowing it to hit much harder, take less damage, and be stunned for less time. \ + Their glomp attacks also send the victim flying." + player_msg = "Your attacks knock back the target a fair distance.
\ + You also hit harder, take less damage, and stuns affect you for less time." + + melee_attack_delay = 1 SECOND + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/dark_purple, + /mob/living/simple_mob/slime/xenobio/dark_purple, + /mob/living/simple_mob/slime/xenobio/ruby, + /mob/living/simple_mob/slime/xenobio/ruby + ) + +/mob/living/simple_mob/slime/xenobio/ruby/initialize() + add_modifier(/datum/modifier/slime_strength, null, src) // Slime is always swole. + return ..() + +/mob/living/simple_mob/slime/xenobio/ruby/apply_melee_effects(atom/A) + ..() + + if(isliving(A) && a_intent == I_HURT) + var/mob/living/L = A + if(L.mob_size <= MOB_MEDIUM) + visible_message(span("danger", "\The [src] sends \the [L] flying with the impact!")) + playsound(src, "punch", 50, 1) + L.Weaken(1) + var/throwdir = get_dir(src, L) + L.throw_at(get_edge_target_turf(L, throwdir), 3, 1, src) + else + to_chat(L, span("warning", "\The [src] hits you with incredible force, but you remain in place.")) + + +/mob/living/simple_mob/slime/xenobio/amber + desc = "This slime seems to be an expert in the culinary arts, as they create their own food to share with others. \ + They would probably be very important to other slimes, if the other colors didn't try to kill them." + color = "#FFBB00" + slime_color = "amber" + shiny = TRUE + glow_toggle = TRUE + coretype = /obj/item/slime_extract/amber + + description_info = "This slime feeds nearby entities passively while it is alive. This can cause uncontrollable \ + slime growth and reproduction if not kept in check. The amber slime cannot feed itself, but can be fed by other amber slimes." + player_msg = "You passively provide nutrition to nearby entities." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/silver, + /mob/living/simple_mob/slime/xenobio/silver, + /mob/living/simple_mob/slime/xenobio/amber, + /mob/living/simple_mob/slime/xenobio/amber + ) + +/mob/living/simple_mob/slime/xenobio/amber/handle_special() + if(stat != DEAD) + feed_aura() + ..() + +/mob/living/simple_mob/slime/xenobio/amber/proc/feed_aura() + for(var/mob/living/L in view(2, src)) + if(L == src) // Don't feed themselves, or it is impossible to stop infinite slimes without killing all of the ambers. + continue + if(istype(L, /mob/living/simple_mob/slime/xenobio)) + var/mob/living/simple_mob/slime/xenobio/X = L + X.adjust_nutrition(rand(15, 25)) + if(ishuman(L)) + var/mob/living/carbon/human/H = L + if(H.isSynthetic()) + continue + H.nutrition = between(0, H.nutrition + rand(15, 25), 800) + +/mob/living/simple_mob/slime/xenobio/cerulean + desc = "This slime is generally superior in a wide range of attributes, compared to the common slime. The jack of all trades, but master of none." + color = "#4F7EAA" + slime_color = "cerulean" + coretype = /obj/item/slime_extract/cerulean + + // Less than the specialized slimes, but higher than the rest. + maxHealth = 200 + maxHealth_adult = 250 + + melee_damage_lower = 10 + melee_damage_upper = 30 + + movement_cooldown = 0 // This actually isn't any faster due to AI limitations that hopefully the timer subsystem can fix in the future. + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/dark_blue, + /mob/living/simple_mob/slime/xenobio/dark_blue, + /mob/living/simple_mob/slime/xenobio/cerulean, + /mob/living/simple_mob/slime/xenobio/cerulean + ) + +// Tier 4 + +/mob/living/simple_mob/slime/xenobio/red + desc = "This slime is full of energy, and very aggressive. 'The red ones go faster.' seems to apply here." + color = "#FF3333" + slime_color = "red" + coretype = /obj/item/slime_extract/red + movement_cooldown = 0 // See above. + + description_info = "This slime is faster than the others. Attempting to discipline this slime will always cause it to go rabid and berserk." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/red, + /mob/living/simple_mob/slime/xenobio/oil, + /mob/living/simple_mob/slime/xenobio/oil, + /mob/living/simple_mob/slime/xenobio/orange + ) + + ai_holder_type = /datum/ai_holder/simple_mob/xenobio_slime/red // Will enrage if disciplined. + + +/mob/living/simple_mob/slime/xenobio/green + desc = "This slime is radioactive." + color = "#14FF20" + slime_color = "green" + coretype = /obj/item/slime_extract/green + glow_toggle = TRUE + reagent_injected = "radium" + var/rads = 25 + + description_info = "This slime will irradiate anything nearby passively, and will inject radium on attack. \ + A radsuit or other thick and radiation-hardened armor can protect from this. It will only radiate while alive." + player_msg = "You passively irradiate your surroundings.
\ + You also inject radium on attack." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/purple, + /mob/living/simple_mob/slime/xenobio/green, + /mob/living/simple_mob/slime/xenobio/emerald, + /mob/living/simple_mob/slime/xenobio/emerald + ) + +/mob/living/simple_mob/slime/xenobio/green/handle_special() + if(stat != DEAD) + irradiate() + ..() + +/mob/living/simple_mob/slime/xenobio/green/proc/irradiate() + radiation_repository.radiate(src, rads) + + + +/mob/living/simple_mob/slime/xenobio/pink + desc = "This slime has regenerative properties." + color = "#FF0080" + slime_color = "pink" + coretype = /obj/item/slime_extract/pink + glow_toggle = TRUE + + description_info = "This slime will passively heal nearby entities within two tiles, including itself. It will only do this while alive." + player_msg = "You passively heal yourself and nearby allies." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/blue, + /mob/living/simple_mob/slime/xenobio/light_pink, + /mob/living/simple_mob/slime/xenobio/light_pink, + /mob/living/simple_mob/slime/xenobio/pink + ) + +/mob/living/simple_mob/slime/xenobio/pink/handle_special() + if(stat != DEAD) + heal_aura() + ..() + +/mob/living/simple_mob/slime/xenobio/pink/proc/heal_aura() + for(var/mob/living/L in view(src, 2)) + if(L.stat == DEAD || !IIsAlly(L)) + continue + L.add_modifier(/datum/modifier/aura/slime_heal, null, src) + +/datum/modifier/aura/slime_heal + name = "slime mending" + desc = "You feel somewhat gooy." + mob_overlay_state = "pink_sparkles" + stacks = MODIFIER_STACK_FORBID + aura_max_distance = 2 + + on_created_text = "Twinkling spores of goo surround you. It makes you feel healthier." + on_expired_text = "The spores of goo have faded, although you feel much healthier than before." + +/datum/modifier/aura/slime_heal/tick() + if(holder.stat == DEAD) + expire() + + if(ishuman(holder)) // Robolimbs need this code sadly. + var/mob/living/carbon/human/H = holder + for(var/obj/item/organ/external/E in H.organs) + var/obj/item/organ/external/O = E + O.heal_damage(2, 2, 0, 1) + else + holder.adjustBruteLoss(-2) + holder.adjustFireLoss(-2) + + holder.adjustToxLoss(-2) + holder.adjustOxyLoss(-2) + holder.adjustCloneLoss(-1) + + +/mob/living/simple_mob/slime/xenobio/gold + desc = "This slime absorbs energy, and cannot be stunned by normal means." + color = "#EEAA00" + shiny = TRUE + slime_color = "gold" + coretype = /obj/item/slime_extract/gold + description_info = "This slime is immune to the slimebaton and taser, and will actually charge the slime, however it will still discipline the slime." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/metal, + /mob/living/simple_mob/slime/xenobio/gold, + /mob/living/simple_mob/slime/xenobio/sapphire, + /mob/living/simple_mob/slime/xenobio/sapphire + ) + +/mob/living/simple_mob/slime/xenobio/gold/slimebatoned(mob/living/user, amount) + power_charge = between(0, power_charge + amount, 10) + +/mob/living/simple_mob/slime/xenobio/gold/get_description_interaction() // So it doesn't say to use a baton on them. + return list() + + +// Tier 5 + +/mob/living/simple_mob/slime/xenobio/oil + desc = "This slime is explosive and volatile. Smoking near it is probably a bad idea." + color = "#333333" + slime_color = "oil" + shiny = TRUE + coretype = /obj/item/slime_extract/oil + + description_info = "If this slime suffers damage from a fire or heat based source, or if it is caught inside \ + an explosion, it will explode. Oil slimes will also suicide-bomb themselves when fighting something that is not a monkey or slime." + player_msg = "You will explode if struck by a burning attack, or if you hit an enemy with a melee attack that is not a monkey or another slime." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/oil, + /mob/living/simple_mob/slime/xenobio/oil, + /mob/living/simple_mob/slime/xenobio/red, + /mob/living/simple_mob/slime/xenobio/red + ) + +/mob/living/simple_mob/slime/xenobio/oil/proc/explode() + if(stat != DEAD) + explosion(src.loc, 0, 2, 4) // A bit weaker since the suicide charger tended to gib the poor sod being targeted. + if(src) // Delete ourselves if the explosion didn't do it. + qdel(src) + +/mob/living/simple_mob/slime/xenobio/oil/apply_melee_effects(atom/A) + if(isliving(A)) + var/mob/living/L = A + if(ishuman(L)) + var/mob/living/carbon/human/H = A + if(istype(H.species, /datum/species/monkey)) + return ..()// Don't blow up when just eatting monkeys. + + else if(isslime(L)) + return ..() + + // Otherwise blow ourselves up. + say(pick("Sacrifice...!", "Sssss...", "Boom...!")) + set_AI_busy(TRUE) + sleep(2 SECONDS) + log_and_message_admins("[src] has suicide-bombed themselves while trying to kill \the [L].") + explode() + + return ..() + +/mob/living/simple_mob/slime/xenobio/oil/ex_act(severity) + log_and_message_admins("[src] exploded due to a chain reaction with another explosion.") + explode() + +/mob/living/simple_mob/slime/xenobio/oil/fire_act(datum/gas_mixture/air, temperature, volume) + log_and_message_admins("[src] exploded due to exposure to fire.") + explode() + +/mob/living/simple_mob/slime/xenobio/oil/bullet_act(obj/item/projectile/P, def_zone) + if(P.damage_type && P.damage_type == BURN && P.damage) // Most bullets won't trigger the explosion, as a mercy towards Security. + log_and_message_admins("[src] exploded due to bring hit by a burning projectile[P.firer ? " by [key_name(P.firer)]" : ""].") + explode() + else + ..() + +/mob/living/simple_mob/slime/xenobio/oil/attackby(obj/item/weapon/W, mob/living/user) + if(istype(W) && W.force && W.damtype == BURN) + log_and_message_admins("[src] exploded due to being hit with a burning weapon ([W]) by [key_name(user)].") + explode() + else + ..() + + +/mob/living/simple_mob/slime/xenobio/sapphire + desc = "This slime seems a bit brighter than the rest, both figuratively and literally." + color = "#2398FF" + slime_color = "sapphire" + shiny = TRUE + glow_toggle = TRUE + coretype = /obj/item/slime_extract/sapphire + ai_holder_type = /datum/ai_holder/simple_mob/xenobio_slime/sapphire + + description_info = "This slime uses more robust tactics when fighting and won't hold back, so it is dangerous to be alone \ + with one if hostile, and especially dangerous if they outnumber you." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/sapphire, + /mob/living/simple_mob/slime/xenobio/sapphire, + /mob/living/simple_mob/slime/xenobio/gold, + /mob/living/simple_mob/slime/xenobio/gold + ) + + +/mob/living/simple_mob/slime/xenobio/emerald + desc = "This slime is faster than usual, even more so than the red slimes." + color = "#22FF22" + shiny = TRUE + glow_toggle = TRUE + slime_color = "emerald" + coretype = /obj/item/slime_extract/emerald + + description_info = "This slime will make everything around it, and itself, faster for a few seconds, if close by." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/green, + /mob/living/simple_mob/slime/xenobio/green, + /mob/living/simple_mob/slime/xenobio/emerald, + /mob/living/simple_mob/slime/xenobio/emerald + ) + +/mob/living/simple_mob/slime/xenobio/emerald/handle_special() + if(stat != DEAD) + zoom_aura() + ..() + +/mob/living/simple_mob/slime/xenobio/emerald/proc/zoom_aura() + for(var/mob/living/L in view(src, 2)) + if(L.stat == DEAD || !IIsAlly(L)) + continue + L.add_modifier(/datum/modifier/technomancer/haste, 5 SECONDS, src) + + +/mob/living/simple_mob/slime/xenobio/light_pink + desc = "This slime seems a lot more peaceful than the others." + color = "#FF8888" + slime_color = "light pink" + coretype = /obj/item/slime_extract/light_pink + + description_info = "This slime is effectively always disciplined initially." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/pink, + /mob/living/simple_mob/slime/xenobio/pink, + /mob/living/simple_mob/slime/xenobio/light_pink, + /mob/living/simple_mob/slime/xenobio/light_pink + ) + + ai_holder_type = /datum/ai_holder/simple_mob/xenobio_slime/light_pink + +// Special +/mob/living/simple_mob/slime/xenobio/rainbow + desc = "This slime changes colors constantly." + color = null // Uses a special icon_state. + slime_color = "rainbow" + coretype = /obj/item/slime_extract/rainbow + icon_state_override = "rainbow" + unity = TRUE + + description_info = "This slime is considered to be the same color as all other slime colors at the same time for the purposes of \ + other slimes being friendly to them, and therefore will never be harmed by another slime. \ + Attacking this slime will provoke the wrath of all slimes within range." + player_msg = "You are considered to be the same color as every slime, \ + meaning that you are considered an ally to all slimes." + + slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/rainbow, + /mob/living/simple_mob/slime/xenobio/rainbow, + /mob/living/simple_mob/slime/xenobio/rainbow, + /mob/living/simple_mob/slime/xenobio/rainbow + ) + +/mob/living/simple_mob/slime/xenobio/rainbow/initialize() + unify() + return ..() + +// The RD's pet slime. +/mob/living/simple_mob/slime/xenobio/rainbow/kendrick + name = "Kendrick" + desc = "The Research Director's pet slime. It shifts colors constantly." + rainbow_core_candidate = FALSE + // Doing pacify() in initialize() won't actually pacify the AI due to the ai_holder not existing due to parent initialize() not being called yet. + // Instead lets just give them an ai_holder that does that for us. + ai_holder_type = /datum/ai_holder/simple_mob/xenobio_slime/passive + +/mob/living/simple_mob/slime/xenobio/rainbow/kendrick/initialize() + pacify() // So the physical mob also gets made harmless. + return ..() \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/xenobio.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/xenobio.dm new file mode 100644 index 0000000000..f7f594f139 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/xenobio.dm @@ -0,0 +1,284 @@ +// These slimes have the mechanics xenobiologists care about, such as reproduction, mutating into new colors, and being able to submit through fear. + +/mob/living/simple_mob/slime/xenobio + desc = "The most basic of slimes. The grey slime has no remarkable qualities, however it remains one of the most useful colors for scientists." + layer = MOB_LAYER + 1 // Need them on top of other mobs or it looks weird when consuming something. + ai_holder_type = /datum/ai_holder/simple_mob/xenobio_slime // This should never be changed for xenobio slimes. + var/is_adult = FALSE // Slimes turn into adults when fed enough. Adult slimes are somewhat stronger, and can reproduce if fed enough. + var/maxHealth_adult = 200 + var/power_charge = 0 // Disarm attacks can shock someone if high/lucky enough. + var/mob/living/victim = null // the person the slime is currently feeding on + var/rainbow_core_candidate = TRUE // If false, rainbow cores cannot make this type randomly. + var/mutation_chance = 25 // Odds of spawning as a new color when reproducing. Can be modified by certain xenobio products. Carried across generations of slimes. + var/list/slime_mutation = list( + /mob/living/simple_mob/slime/xenobio/orange, + /mob/living/simple_mob/slime/xenobio/metal, + /mob/living/simple_mob/slime/xenobio/blue, + /mob/living/simple_mob/slime/xenobio/purple + ) + var/amount_grown = 0 // controls how long the slime has been overfed, if 10, grows or reproduces + var/number = 0 // This is used to make the slime semi-unique for indentification. + var/harmless = FALSE // Set to true when pacified. Makes the slime harmless, not get hungry, and not be able to grow/reproduce. + +/mob/living/simple_mob/slime/xenobio/initialize() + ASSERT(ispath(ai_holder_type, /datum/ai_holder/simple_mob/xenobio_slime)) + number = rand(1, 1000) + update_name() + return ..() + +/mob/living/simple_mob/slime/xenobio/Destroy() + if(victim) + stop_consumption() // Unbuckle us from our victim. + return ..() + +/mob/living/simple_mob/slime/xenobio/update_icon() + icon_living = "[icon_state_override ? "[icon_state_override] slime" : "slime"] [is_adult ? "adult" : "baby"][victim ? " eating" : ""]" + icon_dead = "[icon_state_override ? "[icon_state_override] slime" : "slime"] [is_adult ? "adult" : "baby"] dead" + icon_rest = icon_dead + ..() // This will apply the correct icon_state and do the other overlay-related things. + + +/mob/living/simple_mob/slime/xenobio/handle_special() + if(stat != DEAD) + handle_nutrition() + + if(victim) + handle_consumption() + + handle_stuttering() // ?? + + ..() + +/mob/living/simple_mob/slime/xenobio/examine(mob/user) + ..() + if(hat) + to_chat(user, "It is wearing \a [hat].") + + if(stat == DEAD) + to_chat(user, "It appears to be dead.") + else if(incapacitated(INCAPACITATION_DISABLED)) + to_chat(user, "It appears to be incapacitated.") + else if(harmless) + to_chat(user, "It appears to have been pacified.") + else + if(has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder + if(AI.rabid) + to_chat(user, "It seems very, very angry and upset.") + else if(AI.obedience >= 5) + to_chat(user, "It looks rather obedient.") + else if(AI.discipline) + to_chat(user, "It has been subjugated by force, at least for now.") + +/mob/living/simple_mob/slime/xenobio/proc/make_adult() + if(is_adult) + return + + is_adult = TRUE + melee_damage_lower = round(melee_damage_lower * 2) // 20 + melee_damage_upper = round(melee_damage_upper * 2) // 30 + maxHealth = maxHealth_adult + amount_grown = 0 + update_icon() + update_name() + +/mob/living/simple_mob/slime/xenobio/proc/update_name() + if(harmless) // Docile slimes are generally named, so we shouldn't mess with it. + return + name = "[slime_color] [is_adult ? "adult" : "baby"] [initial(name)] ([number])" + real_name = name + +/mob/living/simple_mob/slime/xenobio/update_mood() + var/old_mood = mood + if(incapacitated(INCAPACITATION_DISABLED)) + mood = "sad" + else if(harmless) + mood = ":33" + else if(has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder + if(AI.rabid) + mood = "angry" + else if(AI.target) + mood = "mischevous" + else if(AI.discipline) + mood = "pout" + else + mood = ":3" + else + mood = ":3" + + if(old_mood != mood) + update_icon() + +/mob/living/simple_mob/slime/xenobio/proc/enrage() + if(harmless) + return + if(has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder + AI.enrage() + +/mob/living/simple_mob/slime/xenobio/proc/pacify() + harmless = TRUE + if(has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder + AI.pacify() + + faction = "neutral" + + // If for whatever reason the mob AI (or player) decides to try to attack something anyways. + melee_damage_upper = 0 + melee_damage_lower = 0 + + update_mood() + + +// These are verbs so that player slimes can evolve/split. +/mob/living/simple_mob/slime/xenobio/verb/evolve() + set category = "Slime" + set desc = "This will let you evolve from baby to adult slime." + + if(stat) + to_chat(src, span("warning", "I must be conscious to do this...")) + return + + if(harmless) + to_chat(src, span("warning", "I have been pacified. I cannot evolve...")) + return + + if(!is_adult) + if(amount_grown >= 10) + make_adult() + else + to_chat(src, span("warning", "I am not ready to evolve yet...")) + else + to_chat(src, span("warning", "I have already evolved...")) + + +/mob/living/simple_mob/slime/xenobio/verb/reproduce() + set category = "Slime" + set desc = "This will make you split into four new slimes." + + if(stat) + to_chat(src, span("warning", "I must be conscious to do this...")) + return + + if(harmless) + to_chat(src, span("warning", "I have been pacified. I cannot reproduce...")) + return + + if(is_adult) + if(amount_grown >= 10) + // Check if there's enough 'room' to split. + var/list/nearby_things = orange(1, src) + var/free_tiles = 0 + for(var/turf/T in nearby_things) + var/free = TRUE + if(T.density) // No walls. + continue + for(var/atom/movable/AM in T) + if(AM.density) + free = FALSE + break + + if(free) + free_tiles++ + + if(free_tiles < 3) // Three free tiles are needed, as four slimes are made and the 4th tile is from the center tile that the current slime occupies. + to_chat(src, span("warning", "It is too cramped here to reproduce...")) + return + + var/list/babies = list() + for(var/i = 1 to 4) + babies.Add(make_new_slime()) + + var/mob/living/simple_mob/slime/new_slime = pick(babies) + new_slime.universal_speak = universal_speak + if(src.mind) + src.mind.transfer_to(new_slime) + else + new_slime.key = src.key + qdel(src) + else + to_chat(src, span("warning", "I am not ready to reproduce yet...")) + else + to_chat(src, span("warning", "I have not evolved enough to reproduce yet...")) + +// Used when reproducing or dying. +/mob/living/simple_mob/slime/xenobio/proc/make_new_slime(var/desired_type) + var/t = src.type + if(desired_type) + t = desired_type + if(prob(mutation_chance / 10)) + t = /mob/living/simple_mob/slime/xenobio/rainbow + else if(prob(mutation_chance) && slime_mutation.len) + t = slime_mutation[rand(1, slime_mutation.len)] + var/mob/living/simple_mob/slime/xenobio/baby = new t(loc) + + // Handle 'inheriting' from parent slime. + baby.mutation_chance = mutation_chance + baby.power_charge = round(power_charge / 4) + + pass_on_data(baby) // Transfer the AI stuff slowly, sadly. + + if(!istype(baby, /mob/living/simple_mob/slime/xenobio/rainbow)) + baby.unity = unity + baby.faction = faction + baby.friends = friends.Copy() + + step_away(baby, src) + return baby + +/mob/living/simple_mob/slime/xenobio/proc/pass_on_data(mob/living/simple_mob/slime/xenobio/baby) + // This is superdumb but the AI datum won't exist until the new slime's initialize() finishes. + var/new_discipline = 0 + var/new_obedience = 0 + var/new_resentment = 0 + var/new_rabid = FALSE + + // First, get this slime's AI values since they are likely to be deleted in a moment. + if(src && src.has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/our_AI = ai_holder + new_discipline = max(our_AI.discipline - 1, 0) + new_obedience = max(our_AI.obedience - 1, 0) + new_resentment = max(our_AI.resentment - 1, 0) + new_rabid = our_AI.rabid + + spawn(2) // Race conditions are fun, but with the first two letters capitalized. + if(istype(baby) && baby.has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/their_AI = baby.ai_holder + + if(!istype(baby, /mob/living/simple_mob/slime/xenobio/light_pink)) + their_AI.discipline = new_discipline + their_AI.obedience = new_obedience + + their_AI.resentment = new_resentment + + their_AI.rabid = new_rabid + +/mob/living/simple_mob/slime/xenobio/get_description_interaction() + var/list/results = list() + + if(!stat) + results += "[desc_panel_image("slimebaton")]to stun the slime, if it's being bad." + + results += ..() + + return results + +/mob/living/simple_mob/slime/xenobio/get_description_info() + var/list/lines = list() + var/intro_line = "Slimes are generally the test subjects of Xenobiology, with different colors having different properties. \ + They can be extremely dangerous if not handled properly." + lines.Add(intro_line) + lines.Add(null) // To pad the line breaks. + + var/list/rewards = list() + for(var/potential_color in slime_mutation) + var/mob/living/simple_mob/slime/S = potential_color + rewards.Add(initial(S.slime_color)) + var/reward_line = "This color of slime can mutate into [english_list(rewards)] colors, when it reproduces. It will do so when it has eatten enough." + lines.Add(reward_line) + lines.Add(null) + + lines.Add(description_info) + return lines.Join("\n") \ No newline at end of file diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index c7c0c9a253..d41314a69b 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -743,13 +743,12 @@ /mob/proc/facedir(var/ndir) - if(!canface() || (client && (client.moving || (world.time < client.move_delay)))) + if(!canface() || (client && (client.moving || (world.time < move_delay)))) return 0 set_dir(ndir) if(buckled && buckled.buckle_movable) buckled.set_dir(ndir) - if(client) - client.move_delay += movement_delay() + move_delay += movement_delay() return 1 @@ -1002,7 +1001,7 @@ mob/proc/yank_out_object() /mob/proc/has_brain_worms() for(var/I in contents) - if(istype(I,/mob/living/simple_animal/borer)) + if(istype(I,/mob/living/simple_mob/animal/borer)) return I return 0 diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 32902d7d48..9852c3f723 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -7,6 +7,8 @@ var/datum/mind/mind var/stat = 0 //Whether a mob is alive or dead. TODO: Move this to living - Nodrak + var/move_delay = null // For movement speed delays. + var/next_move = null // For click delay, despite the misleading name. //Not in use yet var/obj/effect/organstructure/organStructure = null @@ -62,7 +64,6 @@ var/sdisabilities = 0 //Carbon var/disabilities = 0 //Carbon var/atom/movable/pulling = null - var/next_move = null var/transforming = null //Carbon var/other = 0.0 var/eye_blind = null //Carbon diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 186487d0b9..6c31f54f8e 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -54,6 +54,9 @@ proc/isdeaf(A) /mob/proc/break_cloak() return +/mob/proc/is_cloaked() + return FALSE + proc/hasorgans(A) // Fucking really?? return ishuman(A) @@ -534,19 +537,17 @@ proc/is_blind(A) return threatcount -/mob/living/simple_animal/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest) +/mob/living/simple_mob/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest) var/threatcount = ..() if(. == SAFE_PERP) return SAFE_PERP - if(!istype(src, /mob/living/simple_animal/retaliate/goat)) - if(hostile) - if(faction != "neutral") // Otherwise Runtime gets killed. - threatcount += 4 + if(has_AI() && ai_holder.hostile && faction != "neutral") // Otherwise Runtime gets killed. + threatcount += 4 return threatcount // Beepsky will (try to) only beat 'bad' slimes. -/mob/living/simple_animal/slime/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest) +/mob/living/simple_mob/slime/xenobio/assess_perp(var/obj/access_obj, var/check_access, var/auth_weapons, var/check_records, var/check_arrest) var/threatcount = 0 if(stat == DEAD) @@ -565,8 +566,10 @@ proc/is_blind(A) if(victim) threatcount += 4 */ - if(rabid) - threatcount = 10 + if(has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder + if(AI.rabid) + threatcount = 10 return threatcount diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 6b65c81c84..8848ed4aec 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -11,8 +11,12 @@ return /mob/proc/setMoveCooldown(var/timeout) - if(client) - client.move_delay = max(world.time + timeout, client.move_delay) + move_delay = max(world.time + timeout, move_delay) + +/mob/proc/check_move_cooldown() + if(world.time < src.move_delay) + return FALSE // Need to wait more. + return TRUE /client/North() ..() @@ -199,7 +203,8 @@ if(moving) return 0 - if(world.time < move_delay) return + if(!mob.check_move_cooldown()) + return if(locate(/obj/effect/stop/, mob.loc)) for(var/obj/effect/stop/S in mob.loc) @@ -271,21 +276,21 @@ src << "You're pinned to a wall by [mob.pinned[1]]!" return 0 - move_delay = world.time//set move delay + mob.move_delay = world.time//set move delay switch(mob.m_intent) if("run") if(mob.drowsyness > 0) - move_delay += 6 - move_delay += config.run_speed + mob.move_delay += 6 + mob.move_delay += config.run_speed if("walk") - move_delay += config.walk_speed - move_delay += mob.movement_delay() + mob.move_delay += config.walk_speed + mob.move_delay += mob.movement_delay() if(istype(mob.buckled))// VOREStation Removal - , /obj/vehicle)) //manually set move_delay for vehicles so we don't inherit any mob movement penalties //specific vehicle move delays are set in code\modules\vehicles\vehicle.dm - move_delay = world.time + mob.move_delay = world.time //drunk driving if(mob.confused && prob(20)) //vehicles tend to keep moving in the same direction direct = turn(direct, pick(90, -90)) @@ -314,14 +319,14 @@ if(prob(50)) direct = turn(direct, pick(90, -90)) if("walk") if(prob(25)) direct = turn(direct, pick(90, -90)) - move_delay += 2 + mob.move_delay += 2 return mob.buckled.relaymove(mob,direct) //We are now going to move moving = 1 //Something with pulling things if(locate(/obj/item/weapon/grab, mob)) - move_delay = max(move_delay, world.time + 7) + mob.move_delay = max(mob.move_delay, world.time + 7) var/list/L = mob.ret_grab() if(istype(L, /list)) if(L.len == 2) @@ -572,4 +577,4 @@ /client/verb/moveleft() set name = ".moveleft" set instant = 1 - Move(get_step(mob, WEST), WEST) \ No newline at end of file + Move(get_step(mob, WEST), WEST) diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index dcd95c20de..3b3477b833 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -227,7 +227,7 @@ for(var/t in organs) //this really should not be necessary qdel(t) - var/mob/living/simple_animal/corgi/new_corgi = new /mob/living/simple_animal/corgi (loc) + var/mob/living/simple_mob/animal/passive/dog/corgi/new_corgi = new /mob/living/simple_mob/animal/passive/dog/corgi (loc) new_corgi.a_intent = I_HURT new_corgi.key = key @@ -237,7 +237,7 @@ /mob/living/carbon/human/Animalize() - var/list/mobtypes = typesof(/mob/living/simple_animal) + var/list/mobtypes = typesof(/mob/living/simple_mob) var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes if(!safe_animal(mobpath)) @@ -271,7 +271,7 @@ /mob/proc/Animalize() - var/list/mobtypes = typesof(/mob/living/simple_animal) + var/list/mobtypes = typesof(/mob/living/simple_mob) var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes if(!safe_animal(mobpath)) @@ -297,39 +297,29 @@ if(!MP) return 0 //Sanity, this should never happen. +/* if(ispath(MP, /mob/living/simple_animal/space_worm)) return 0 //Unfinished. Very buggy, they seem to just spawn additional space worms everywhere and eating your own tail results in new worms spawning. - - if(ispath(MP, /mob/living/simple_animal/construct/behemoth)) - return 0 //I think this may have been an unfinished WiP or something. These constructs should really have their own class simple_animal/construct/subtype - - if(ispath(MP, /mob/living/simple_animal/construct/armoured)) - return 0 //Verbs do not appear for players. These constructs should really have their own class simple_animal/construct/subtype - - if(ispath(MP, /mob/living/simple_animal/construct/wraith)) - return 0 //Verbs do not appear for players. These constructs should really have their own class simple_animal/construct/subtype - - if(ispath(MP, /mob/living/simple_animal/construct/builder)) - return 0 //Verbs do not appear for players. These constructs should really have their own class simple_animal/construct/subtype +*/ //Good mobs! - if(ispath(MP, /mob/living/simple_animal/cat)) + if(ispath(MP, /mob/living/simple_mob/animal/passive/cat)) return 1 - if(ispath(MP, /mob/living/simple_animal/corgi)) + if(ispath(MP, /mob/living/simple_mob/animal/passive/dog)) return 1 - if(ispath(MP, /mob/living/simple_animal/crab)) + if(ispath(MP, /mob/living/simple_mob/animal/passive/crab)) return 1 - if(ispath(MP, /mob/living/simple_animal/hostile/carp)) + if(ispath(MP, /mob/living/simple_mob/animal/space/carp)) return 1 - if(ispath(MP, /mob/living/simple_animal/shade)) + if(ispath(MP, /mob/living/simple_mob/construct)) return 1 - if(ispath(MP, /mob/living/simple_animal/hostile/tomato)) + if(ispath(MP, /mob/living/simple_mob/tomato)) return 1 - if(ispath(MP, /mob/living/simple_animal/mouse)) + if(ispath(MP, /mob/living/simple_mob/animal/passive/mouse)) return 1 //It is impossible to pull up the player panel for mice (Fixed! - Nodrak) - if(ispath(MP, /mob/living/simple_animal/hostile/bear)) + if(ispath(MP, /mob/living/simple_mob/animal/space/bear)) return 1 //Bears will auto-attack mobs, even if they're player controlled (Fixed! - Nodrak) - if(ispath(MP, /mob/living/simple_animal/parrot)) + if(ispath(MP, /mob/living/simple_mob/animal/passive/bird/parrot)) return 1 //Parrots are no longer unfinished! -Nodrak //Not in here? Must be untested! diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index 06840f65fe..6f3cbb9bc8 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -83,11 +83,13 @@ /mob/proc/can_overcome_gravity() return FALSE -/mob/living/carbon/human/can_overcome_gravity() - return species && species.can_overcome_gravity(src) +/mob/living/can_overcome_gravity() + return hovering -/mob/living/simple_animal/construct/can_overcome_gravity() - return 1 //They care not for standard physics. +/mob/living/carbon/human/can_overcome_gravity() + . = ..() + if(!.) + return species && species.can_overcome_gravity(src) /mob/observer/zMove(direction) var/turf/destination = (direction == UP) ? GetAbove(src) : GetBelow(src) @@ -117,36 +119,44 @@ return ..() /mob/observer/can_ztravel() - return 1 + return TRUE -/mob/living/simple_animal/construct/can_ztravel() - return 1 +/mob/living/can_ztravel() + if(incapacitated()) + return FALSE + return hovering /mob/living/carbon/human/can_ztravel() if(incapacitated()) - return 0 + return FALSE + + if(hovering) + return TRUE if(flying) //VOREStation Edit. Allows movement up/down with wings. return 1 //VOREStation Edit if(Process_Spacemove()) - return 1 + return TRUE if(Check_Shoegrip()) //scaling hull with magboots for(var/turf/simulated/T in trange(1,src)) if(T.density) - return 1 + return TRUE /mob/living/silicon/robot/can_ztravel() if(incapacitated() || is_dead()) - return 0 + return FALSE + + if(hovering) + return TRUE if(Process_Spacemove()) //Checks for active jetpack - return 1 + return FALSE for(var/turf/simulated/T in trange(1,src)) //Robots get "magboots" if(T.density) - return 1 + return TRUE // TODO - Leshana Experimental @@ -262,19 +272,15 @@ if((locate(/obj/structure/disposalpipe/up) in below) || locate(/obj/machinery/atmospherics/pipe/zpipe/up in below)) return FALSE +/mob/living/can_fall() + if(hovering) + return FALSE + return ..() + /mob/living/carbon/human/can_fall() if(..()) return species.can_fall(src) -/mob/living/simple_animal/parrot/can_fall() // Poly can fly. - return FALSE - -/mob/living/simple_animal/hostile/carp/can_fall() // So can carp apparently. - return FALSE - -/mob/living/simple_animal/construct/can_fall() //As do Constructs. - return FALSE - // Check if this atom prevents things standing on it from falling. Return TRUE to allow the fall. /obj/proc/CanFallThru(atom/movable/mover as mob|obj, turf/target as turf) if(!isturf(mover.loc)) // VORESTATION EDIT. We clearly didn't have enough backup checks. diff --git a/code/modules/multiz/turf.dm b/code/modules/multiz/turf.dm index cbf7623267..18f8523907 100644 --- a/code/modules/multiz/turf.dm +++ b/code/modules/multiz/turf.dm @@ -149,3 +149,12 @@ /turf/simulated/open/is_space() var/turf/below = GetBelow(src) return !below || below.is_space() +<<<<<<< HEAD +======= + +/turf/simulated/open/is_safe_to_enter(mob/living/L) + if(L.can_fall()) + if(!locate(/obj/structure/stairs) in GetBelow(src)) + return FALSE + return ..() +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync diff --git a/code/modules/organs/internal/brain.dm b/code/modules/organs/internal/brain.dm index 5f8a9dd0d7..2003abe04f 100644 --- a/code/modules/organs/internal/brain.dm +++ b/code/modules/organs/internal/brain.dm @@ -107,7 +107,7 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) if(name == initial(name)) name = "\the [owner.real_name]'s [initial(name)]" - var/mob/living/simple_animal/borer/borer = owner.has_brain_worms() + var/mob/living/simple_mob/animal/borer/borer = owner.has_brain_worms() if(borer) borer.detatch() //Should remove borer if the brain is removed - RR diff --git a/code/modules/organs/misc.dm b/code/modules/organs/misc.dm index 862c96a96f..30515e746b 100644 --- a/code/modules/organs/misc.dm +++ b/code/modules/organs/misc.dm @@ -34,7 +34,7 @@ ..() - var/mob/living/simple_animal/borer/B = owner.has_brain_worms() + var/mob/living/simple_mob/animal/borer/B = owner.has_brain_worms() if(B) B.leave_host() B.ckey = owner.ckey diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm index 4a81179c72..4a24500fd3 100644 --- a/code/modules/paperwork/paperbin.dm +++ b/code/modules/paperwork/paperbin.dm @@ -19,7 +19,7 @@ /obj/item/weapon/paper_bin/MouseDrop(mob/user as mob) if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr)))))) - if(!istype(usr, /mob/living/simple_animal)) + if(!istype(usr, /mob/living/simple_mob)) if( !usr.get_active_hand() ) //if active hand is empty var/mob/living/carbon/human/H = user var/obj/item/organ/external/temp = H.organs_by_name["r_hand"] diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 535debad21..9e95e67a6d 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -33,7 +33,6 @@ var/global/photo_count = 0 var/icon/img //Big photo image var/scribble //Scribble on the back. var/icon/tiny - var/cursed = 0 var/photo_size = 3 /obj/item/weapon/photo/New() @@ -237,10 +236,6 @@ var/global/photo_count = 0 else mob_detail += "You can also see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]." - for(var/mob/living/simple_animal/hostile/statue/S in the_turf) - if(S) - mob_detail += "You can see \a [S] on the photo. Its stare makes you feel uneasy." //"That which holds the image of an angel, becomes itself an angel." - return mob_detail /obj/item/device/camera/afterattack(atom/target as mob|obj|turf|area, mob/user as mob, flag) @@ -282,20 +277,7 @@ var/global/photo_count = 0 y_c-- x_c = x_c - size - - - var/obj/item/weapon/photo/p = createpicture(target, user, turfs, mobs, flag) - if(findtext(mobs, "Its stare makes you feel uneasy")) - p.cursed = 1 - user.visible_message("Something starts to slowly manifest from the picture!") - spawn(150) - var/turf/T = get_turf(p) - var/mob/living/simple_animal/hostile/statue/S = new /mob/living/simple_animal/hostile/statue/(T) - S.banishable = 1//At least you can get rid of those bastards - T.visible_message("The photo turns into \a [S]!") - qdel(p) - printpicture(user, p) @@ -339,16 +321,6 @@ var/global/photo_count = 0 p.pixel_y = pixel_y p.photo_size = photo_size p.scribble = scribble - p.cursed = cursed - if(p.cursed) - var/turf/T = get_turf(p) - T.visible_message("Something starts to slowly manifest from the picture!") - spawn(150) - T = get_turf(p) //second time, because the photo could've moved - var/mob/living/simple_animal/hostile/statue/S = new /mob/living/simple_animal/hostile/statue/(T) - S.banishable = 1//At least you can get rid of those bastards - T.visible_message("The photo turns into \a [S]!") - qdel(p) if(copy_id) p.id = id diff --git a/code/modules/planet/sif.dm b/code/modules/planet/sif.dm index b8c99636f6..371130511a 100644 --- a/code/modules/planet/sif.dm +++ b/code/modules/planet/sif.dm @@ -446,4 +446,4 @@ var/datum/planet/sif/planet_sif = null observed_message = "Everything is red. Something really wrong is going on." transition_messages = list( "The sky turns blood red!" - ) \ No newline at end of file + ) diff --git a/code/modules/power/singularity/act.dm b/code/modules/power/singularity/act.dm index 5998906c66..a5d7dc3651 100644 --- a/code/modules/power/singularity/act.dm +++ b/code/modules/power/singularity/act.dm @@ -115,6 +115,15 @@ ChangeTurf(get_base_turf_by_area(src)) return 2 +/turf/simulated/floor/singularity_pull(S, current_size) + if(flooring && current_size >= STAGE_THREE) + if(prob(current_size / 2)) + var/leave_tile = TRUE + if(broken || burnt || flooring.flags & TURF_IS_FRAGILE) + leave_tile = FALSE + playsound(src, 'sound/items/crowbar.ogg', 50, 1) + make_plating(leave_tile) + /turf/simulated/wall/singularity_pull(S, current_size) if(!reinf_material) diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm index 9274bb85a6..fd548bdb81 100644 --- a/code/modules/power/tesla/energy_ball.dm +++ b/code/modules/power/tesla/energy_ball.dm @@ -185,7 +185,6 @@ /obj/machinery/atmospherics, /obj/machinery/power/emitter, /obj/machinery/field_generator, - /mob/living/simple_animal, /obj/machinery/door/blast, /obj/machinery/particle_accelerator/control_box, /obj/structure/particle_accelerator/fuel_chamber, @@ -290,7 +289,7 @@ else if(closest_mob) var/shock_damage = Clamp(round(power/400), 10, 90) + rand(-5, 5) - closest_mob.electrocute_act(shock_damage, source, 1, ran_zone()) + closest_mob.electrocute_act(shock_damage, source, 1 - closest_mob.get_shock_protection(), ran_zone()) log_game("TESLA([source.x],[source.y],[source.z]) Shocked [key_name(closest_mob)] for [shock_damage]dmg.") message_admins("Tesla zapped [key_name_admin(closest_mob)]!") if(issilicon(closest_mob)) diff --git a/code/modules/power/tesla/tesla_act.dm b/code/modules/power/tesla/tesla_act.dm index 8e617fca86..01b58f8a80 100644 --- a/code/modules/power/tesla/tesla_act.dm +++ b/code/modules/power/tesla/tesla_act.dm @@ -60,6 +60,9 @@ ..() //extend the zap explode() +/obj/mecha/tesla_act(power) + ..() + take_damage(power / 200, "energy") // A surface lightning strike will do 100 damage. diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 40dbaf0650..cb8ca6cdf2 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -173,7 +173,7 @@ if(!user.IsAdvancedToolUser()) return 0 if(isanimal(user)) - var/mob/living/simple_animal/S = user + var/mob/living/simple_mob/S = user if(!S.IsHumanoidToolUser(src)) return 0 @@ -526,7 +526,8 @@ return 2 //just assume we can shoot through glass and stuff. No big deal, the player can just choose to not target someone //on the other side of a window if it makes a difference. Or if they run behind a window, too bad. - return check_trajectory(target, user) + if(check_trajectory(target, user)) + return 1 // Magic numbers are fun. //called if there was no projectile to shoot /obj/item/weapon/gun/proc/handle_click_empty(mob/user) diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index f1e20b169b..7bd20cdcef 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -155,13 +155,13 @@ else src.visible_message("*fizzle*") playsound(src.loc, 'sound/effects/sparks1.ogg', 100, 1) - +/* /obj/item/weapon/gun/energy/staff/animate name = "staff of animation" desc = "An artifact that spits bolts of life force, which causes objects which are hit by it to animate and come to life! This magic doesn't affect machines." projectile_type = /obj/item/projectile/animate charge_cost = 240 - +*/ obj/item/weapon/gun/energy/staff/focus name = "mental focus" desc = "An artifact that channels the will of the user into destructive bolts of force. If you aren't careful with it, you might poke someone's brain out." diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 4566577a94..8b113299b3 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -59,6 +59,8 @@ var/drowsy = 0 var/agony = 0 var/reflected = 0 // This should be set to 1 if reflected by any means, to prevent infinite reflections. + var/modifier_type_to_apply = null // If set, will apply a modifier to mobs that are hit by this projectile. + var/modifier_duration = null // How long the above modifier should last for. Leave null to be permanent. embed_chance = 0 //Base chance for a projectile to embed @@ -86,6 +88,8 @@ // if(isanimal(target)) return 0 var/mob/living/L = target L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) // add in AGONY! + if(modifier_type_to_apply) + L.add_modifier(modifier_type_to_apply, modifier_duration) return 1 //called when the projectile stops flying because it collided with something @@ -421,6 +425,7 @@ yo = null xo = null var/result = 0 //To pass the message back to the gun. + var/atom/movable/result_ref = null // The thing that got hit that made the check return true. /obj/item/projectile/test/Bump(atom/A as mob|obj|turf|area) if(A == firer) @@ -439,6 +444,7 @@ if(!S.anchored) return if(istype(A, /mob/living) || istype(A, /obj/mecha) || istype(A, /obj/vehicle)) + result_ref = A result = 2 //We hit someone, return 1! return result = 1 @@ -459,7 +465,8 @@ /obj/item/projectile/test/process(var/turf/targloc) while(src) //Loop on through! if(result) - return (result - 1) + return result_ref + // return (result - 1) if((!( targloc ) || loc == targloc)) targloc = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) //Finding the target turf at map edge @@ -470,11 +477,13 @@ var/mob/living/M = locate() in get_turf(src) if(istype(M)) //If there is someting living... - return 1 //Return 1 + result_ref = M + return result_ref //Return 1 else M = locate() in get_step(src,targloc) if(istype(M)) - return 1 + result_ref = M + return result_ref //Helper proc to check if you can hit them or not. /proc/check_trajectory(atom/target as mob|obj, atom/firer as mob|obj, var/pass_flags=PASSTABLE|PASSGLASS|PASSGRILLE, flags=null) diff --git a/code/modules/projectiles/projectile/change.dm b/code/modules/projectiles/projectile/change.dm index 0beffe46a3..df802f4bb5 100644 --- a/code/modules/projectiles/projectile/change.dm +++ b/code/modules/projectiles/projectile/change.dm @@ -54,7 +54,7 @@ Robot.mmi = new /obj/item/device/mmi(new_mob) Robot.mmi.transfer_identity(M) //Does not transfer key/client. if("slime") - new_mob = new /mob/living/simple_animal/slime(M.loc) + new_mob = new /mob/living/simple_mob/slime/xenobio(M.loc) new_mob.universal_speak = 1 else var/mob/living/carbon/human/H diff --git a/code/modules/random_map/drop/droppod.dm b/code/modules/random_map/drop/droppod.dm index da346d2eb7..c7afde1d7f 100644 --- a/code/modules/random_map/drop/droppod.dm +++ b/code/modules/random_map/drop/droppod.dm @@ -15,7 +15,7 @@ floor_type = /turf/simulated/floor/reinforced var/list/supplied_drop_types = list() var/door_type = /obj/structure/droppod_door - var/drop_type = /mob/living/simple_animal/parrot + var/drop_type = /mob/living/simple_mob/animal/passive/bird/parrot var/auto_open_doors var/placement_explosion_dev = 1 diff --git a/code/modules/random_map/noise/desert.dm b/code/modules/random_map/noise/desert.dm index 1c7ca229a4..0264d2a781 100644 --- a/code/modules/random_map/noise/desert.dm +++ b/code/modules/random_map/noise/desert.dm @@ -27,7 +27,7 @@ var/grass_path = pick(typesof(/obj/structure/flora/grass)-/obj/structure/flora/grass) new grass_path(T) if(prob(5)) - var/mob_type = pick(list(/mob/living/simple_animal/lizard, /mob/living/simple_animal/mouse)) + var/mob_type = pick(list(/mob/living/simple_mob/animal/passive/lizard, /mob/living/simple_mob/animal/passive/mouse)) new mob_type(T) if(5 to 6) if(prob(20)) diff --git a/code/modules/random_map/noise/tundra.dm b/code/modules/random_map/noise/tundra.dm index 83b7435e40..6cb8114298 100644 --- a/code/modules/random_map/noise/tundra.dm +++ b/code/modules/random_map/noise/tundra.dm @@ -46,13 +46,13 @@ switch(val) if(2) if(prob(5)) - new /mob/living/simple_animal/crab(T) + new /mob/living/simple_mob/animal/passive/crab(T) if(6) if(prob(60)) var/grass_path = pick(typesof(/obj/structure/flora/grass)-/obj/structure/flora/grass) new grass_path(T) if(prob(5)) - var/mob_type = pick(list(/mob/living/simple_animal/lizard, /mob/living/simple_animal/mouse)) + var/mob_type = pick(list(/mob/living/simple_mob/animal/passive/lizard, /mob/living/simple_mob/animal/passive/mouse)) new mob_type(T) if(7) if(prob(60)) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm index a32f83f794..cbe7dd9c5b 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm @@ -169,8 +169,8 @@ /datum/reagent/water/touch_mob(var/mob/living/L, var/amount) if(istype(L)) // First, kill slimes. - if(istype(L, /mob/living/simple_animal/slime)) - var/mob/living/simple_animal/slime/S = L + if(istype(L, /mob/living/simple_mob/slime)) + var/mob/living/simple_mob/slime/S = L S.adjustToxLoss(15 * amount) S.visible_message("[S]'s flesh sizzles where the water touches it!", "Your flesh burns in the water!") diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm index a7bcd87892..f66d91dd42 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm @@ -305,8 +305,6 @@ M.bodytemperature = max(M.bodytemperature - 10 * TEMPERATURE_DAMAGE_COEFFICIENT, 215) if(prob(1)) M.emote("shiver") - if(istype(M, /mob/living/simple_animal/slime)) - M.bodytemperature = max(M.bodytemperature - rand(10,20), 0) holder.remove_reagent("capsaicin", 5) /datum/reagent/frostoil/cryotoxin //A longer lasting version of frost oil. @@ -346,8 +344,6 @@ M.apply_effect(2, AGONY, 0) if(prob(5)) M.visible_message("[M] [pick("dry heaves!","coughs!","splutters!")]", "You feel like your insides are burning!") - if(istype(M, /mob/living/simple_animal/slime)) - M.bodytemperature += rand(10, 25) holder.remove_reagent("frostoil", 5) /datum/reagent/condensedcapsaicin @@ -492,8 +488,6 @@ M.apply_effect(4, AGONY, 0) if(prob(5)) M.visible_message("[M] [pick("dry heaves!","coughs!","splutters!")]", "You feel like your insides are burning!") - if(istype(M, /mob/living/simple_animal/slime)) - M.bodytemperature += rand(15, 30) holder.remove_reagent("frostoil", 5) /* Drinks */ diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm index 6b705f4f54..9cf58bbfb8 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm @@ -106,6 +106,8 @@ M.drowsyness = max(0, M.drowsyness - 6 * removed * chem_effective) M.hallucination = max(0, M.hallucination - 9 * removed * chem_effective) M.adjustToxLoss(-4 * removed * chem_effective) + if(prob(10)) + M.remove_a_modifier_of_type(/datum/modifier/poisoned) /datum/reagent/carthatoline name = "Carthatoline" @@ -121,6 +123,8 @@ if(M.getToxLoss() && prob(10)) M.vomit(1) M.adjustToxLoss(-8 * removed) + if(prob(30)) + M.remove_a_modifier_of_type(/datum/modifier/poisoned) if(ishuman(M)) var/mob/living/carbon/human/H = M var/obj/item/organ/internal/liver/L = H.internal_organs_by_name[O_LIVER] diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm index 53ce37b3ef..ff753a5b37 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm @@ -360,7 +360,7 @@ S.dirt = 0 T.clean_blood() - for(var/mob/living/simple_animal/slime/M in T) + for(var/mob/living/simple_mob/slime/M in T) M.adjustToxLoss(rand(5, 10)) /datum/reagent/space_cleaner/affect_touch(var/mob/living/carbon/M, var/alien, var/removed) diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm index 737dbdcdad..1b70001c28 100644 --- a/code/modules/reagents/reagent_containers/glass.dm +++ b/code/modules/reagents/reagent_containers/glass.dm @@ -37,8 +37,8 @@ /obj/machinery/iv_drip, /obj/machinery/disease2/incubator, /obj/machinery/disposal, - /mob/living/simple_animal/cow, - /mob/living/simple_animal/retaliate/goat, + /mob/living/simple_mob/animal/passive/cow, + /mob/living/simple_mob/animal/goat, /obj/machinery/computer/centrifuge, /obj/machinery/sleeper, /obj/machinery/smartfridge/, diff --git a/code/modules/shieldgen/directional_shield.dm b/code/modules/shieldgen/directional_shield.dm index 519edcea07..6a0aae0005 100644 --- a/code/modules/shieldgen/directional_shield.dm +++ b/code/modules/shieldgen/directional_shield.dm @@ -88,6 +88,8 @@ but allow those projectiles to leave the shield from the inside. Blocking too many damaging projectiles will cause the shield to fail." icon = 'icons/obj/device.dmi' icon_state = "signmaker_sec" + light_range = 4 + light_power = 4 var/active = FALSE // If it's on. var/shield_health = 400 // How much damage the shield blocks before breaking. This is a shared health pool for all shields attached to this projector. var/max_shield_health = 400 // Ditto. This is fairly high, but shields are really big, you can't miss them, and laser carbines pump out so much hurt. @@ -170,6 +172,8 @@ var/new_color = rgb(new_r, new_g, new_b) + set_light(light_range, light_power, new_color) + // Now deploy the new color to all the shields. for(var/obj/effect/directional_shield/S in active_shields) S.update_color(new_color) diff --git a/code/modules/shieldgen/energy_field.dm b/code/modules/shieldgen/energy_field.dm index 023d5fee95..1c5b8d2965 100644 --- a/code/modules/shieldgen/energy_field.dm +++ b/code/modules/shieldgen/energy_field.dm @@ -53,6 +53,12 @@ user.setClickCooldown(user.get_attack_speed(W)) ..() +/obj/effect/energy_field/attack_generic(mob/user, damage) + if(damage) + adjust_strength(-damage / 20) + user.do_attack_animation(src) + user.setClickCooldown(user.get_attack_speed()) + /obj/effect/energy_field/attack_hand(var/mob/living/user) impact_effect(3) // Harmless, but still produces the 'impact' effect. ..() diff --git a/code/modules/shuttles/shuttle.dm b/code/modules/shuttles/shuttle.dm index 080f21a524..c8f74aff81 100644 --- a/code/modules/shuttles/shuttle.dm +++ b/code/modules/shuttles/shuttle.dm @@ -208,6 +208,7 @@ for(var/turf/T in dstturfs) var/turf/D = locate(T.x, throwy - 1, T.z) +<<<<<<< HEAD for(var/I in T) if(istype(I,/mob/living)) var/mob/living/L = I @@ -215,6 +216,16 @@ else if(istype(I,/obj)) var/obj/O = I O.forceMove(D) +======= + for(var/atom/movable/AM as mob|obj in T) + AM.Move(D) + + for(var/mob/living/carbon/bug in destination) + bug.gib() + + for(var/mob/living/simple_mob/pest in destination) + pest.gib() +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync origin.move_contents_to(destination, direction=direction) diff --git a/code/modules/spells/aoe_turf/summons.dm b/code/modules/spells/aoe_turf/summons.dm index e8769d8537..94548a362f 100644 --- a/code/modules/spells/aoe_turf/summons.dm +++ b/code/modules/spells/aoe_turf/summons.dm @@ -20,7 +20,7 @@ invocation_type = SpI_SHOUT range = 1 - summon_type = list(/mob/living/simple_animal/hostile/carp) + summon_type = list(/mob/living/simple_mob/animal/space/carp) hud_state = "wiz_carp" @@ -36,6 +36,10 @@ summon_amt = 10 range = 3 +<<<<<<< HEAD summon_type = list(/mob/living/simple_animal/hostile/creature/vore) // Vorestation Edit +======= + summon_type = list(/mob/living/simple_mob/creature) +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync hud_state = "wiz_creature" \ No newline at end of file diff --git a/code/modules/spells/spell_code.dm b/code/modules/spells/spell_code.dm index afd9bfdfa3..c1af923f72 100644 --- a/code/modules/spells/spell_code.dm +++ b/code/modules/spells/spell_code.dm @@ -198,10 +198,10 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now if(findNullRod(T)) return 0 - if(istype(user, /mob/living/simple_animal) && holder == user) - var/mob/living/simple_animal/SA = user - if(SA.purge) - SA << "The nullrod's power interferes with your own!" + if(istype(user, /mob/living/simple_mob) && holder == user) + var/mob/living/simple_mob/SM = user + if(SM.purge) + SM << "The nullrod's power interferes with your own!" return 0 if(!src.check_charge(skipcharge, user)) //sees if we can cast based on charges alone diff --git a/code/modules/spells/spellbook.dm b/code/modules/spells/spellbook.dm index 515453f6a3..1f41ce9e3d 100644 --- a/code/modules/spells/spellbook.dm +++ b/code/modules/spells/spellbook.dm @@ -221,11 +221,6 @@ new /obj/item/clothing/head/helmet/space/void/wizard(get_turf(H)) temp = "You have purchased a suit of wizard armor." max_uses-- - if("staffanimation") - feedback_add_details("wizard_spell_learned","SA") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - new /obj/item/weapon/gun/energy/staff/animate(get_turf(H)) - temp = "You have purchased a staff of animation." - max_uses-- if("scrying") feedback_add_details("wizard_spell_learned","SO") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells new /obj/item/weapon/scrying(get_turf(H)) diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm index ae0e7610a7..58a322febd 100644 --- a/code/modules/surgery/implant.dm +++ b/code/modules/surgery/implant.dm @@ -207,8 +207,8 @@ BITSET(target.hud_updateflag, IMPLOYAL_HUD) //Handle possessive brain borers. - if(istype(obj,/mob/living/simple_animal/borer)) - var/mob/living/simple_animal/borer/worm = obj + if(istype(obj,/mob/living/simple_mob/animal/borer)) + var/mob/living/simple_mob/animal/borer/worm = obj if(worm.controlling) target.release_control() worm.detatch() diff --git a/code/modules/tension/tension.dm b/code/modules/tension/tension.dm index 913f1d2dff..82151a7e78 100644 --- a/code/modules/tension/tension.dm +++ b/code/modules/tension/tension.dm @@ -9,35 +9,42 @@ /atom/movable/proc/guess_threat_level(var/mob/living/threatened) return 0 -/mob/living/simple_animal +/mob/living/simple_mob var/threat_level = null // Set this if you want an explicit danger rating. -/mob/living/simple_animal/guess_threat_level(var/mob/living/threatened) +/mob/living/simple_mob/guess_threat_level(var/mob/living/threatened) if(threat_level) // If they have a predefined number, use it. return threat_level // Otherwise we need to guess how scary this thing is. var/threat_guess = 0 // First lets consider their attack ability. + var/will_point_blank = FALSE + if(has_AI()) + will_point_blank = ai_holder.pointblank + var/potential_damage = 0 - if(!ranged) //Melee damage. + if(!projectiletype || ( get_dist(src, threatened >= 1) && !will_point_blank ) ) // Melee damage. potential_damage = (melee_damage_lower + melee_damage_upper) / 2 + + // Treat potential_damage as estimated DPS. If the enemy attacks twice as fast as usual, it will double the number. + potential_damage *= 1 SECOND / (base_attack_cooldown + melee_attack_delay) else - if(projectiletype) - var/obj/item/projectile/P = new projectiletype(src) - if(P.nodamage || P.taser_effect) // Tasers are somewhat less scary. - potential_damage = P.agony / 2 - else - potential_damage = P.damage - if(P.damage_type == HALLOSS) // Not sure if any projectiles do this, but can't be too safe. - potential_damage /= 2 - // Rubber bullets, I guess. - potential_damage += P.agony / 2 + var/obj/item/projectile/P = new projectiletype(src) + if(P.nodamage || P.taser_effect) // Tasers are somewhat less scary. + potential_damage = P.agony / 2 + else + potential_damage = P.damage + if(P.damage_type == HALLOSS) // Not sure if any projectiles do this, but can't be too safe. + potential_damage /= 2 + // Rubber bullets, I guess. + potential_damage += P.agony / 2 + qdel(P) - if(rapid) // This makes them shoot three times per cycle. - potential_damage *= 3 + potential_damage *= 1 SECOND / (base_attack_cooldown + ranged_attack_delay) + + // Special attacks are ultra-specific to the mob so a generic threat assessment isn't really possible. - qdel(P) threat_guess += potential_damage // Then consider their defense. @@ -50,11 +57,12 @@ return 0 -/mob/living/simple_animal/get_threat(var/mob/living/threatened) +/mob/living/simple_mob/get_threat(var/mob/living/threatened) . = ..() - if(!hostile) - return 0 // Can't hurt anyone. + if(has_AI()) + if(!ai_holder.hostile) + return 0 // Can't hurt anyone. if(incapacitated(INCAPACITATION_DISABLED)) return 0 // Can't currently hurt you if it's stunned. @@ -86,7 +94,7 @@ // Handle ability to harm. // Being five tiles away from some spiders is a lot less scary than being in melee range of five spiders at once. - if(!ranged) + if(!projectiletype) threat /= max(get_dist(src, threatened), 1) return threat diff --git a/code/modules/ventcrawl/ventcrawl.dm b/code/modules/ventcrawl/ventcrawl.dm index 1a4271050d..14d9a21dd7 100644 --- a/code/modules/ventcrawl/ventcrawl.dm +++ b/code/modules/ventcrawl/ventcrawl.dm @@ -9,8 +9,12 @@ var/list/ventcrawl_machinery = list( /obj/item/device/radio/borg, /obj/item/weapon/holder, /obj/machinery/camera, +<<<<<<< HEAD /mob/living/simple_animal/borer, /obj/belly, //VOREStation Edit, +======= + /mob/living/simple_mob/animal/borer, +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync /obj/screen ) @@ -38,7 +42,7 @@ var/list/ventcrawl_machinery = list( add_ventcrawl(loc) client.screen += global_hud.centermarker -/mob/living/simple_animal/slime/can_ventcrawl() +/mob/living/simple_mob/slime/xenobio/can_ventcrawl() if(victim) to_chat(src, "You cannot ventcrawl while feeding.") return FALSE @@ -70,11 +74,6 @@ var/list/ventcrawl_machinery = list( return 1 return ..() -/mob/living/simple_animal/spiderbot/is_allowed_vent_crawl_item(var/obj/item/carried_item) - if(carried_item == held_item) - return 1 - return ..() - /mob/living/proc/ventcrawl_carry() for(var/atom/A in contents) if(!is_allowed_vent_crawl_item(A)) diff --git a/code/modules/xenoarcheaology/artifacts/autocloner.dm b/code/modules/xenoarcheaology/artifacts/autocloner.dm index 535eee1559..d0568b2080 100644 --- a/code/modules/xenoarcheaology/artifacts/autocloner.dm +++ b/code/modules/xenoarcheaology/artifacts/autocloner.dm @@ -21,6 +21,7 @@ //33% chance to spawn nasties if(prob(33)) +<<<<<<< HEAD spawn_type = pick(\ /mob/living/simple_animal/hostile/giant_spider/nurse,\ /mob/living/simple_animal/hostile/alien,\ @@ -28,18 +29,25 @@ /mob/living/simple_animal/hostile/carp,\ /mob/living/simple_animal/hostile/creature\ ) // Vorestation Edits +======= + spawn_type = pick( + /mob/living/simple_mob/animal/giant_spider/nurse, + /mob/living/simple_mob/animal/space/alien, + /mob/living/simple_mob/animal/space/bear, + /mob/living/simple_mob/creature, + /mob/living/simple_mob/slime/xenobio) +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync else spawn_type = pick(\ - /mob/living/simple_animal/cat, - /mob/living/simple_animal/corgi, - /mob/living/simple_animal/corgi/puppy, - /mob/living/simple_animal/chicken, - /mob/living/simple_animal/cow, - /mob/living/simple_animal/parrot, - /mob/living/simple_animal/slime, - /mob/living/simple_animal/crab, - /mob/living/simple_animal/mouse, - /mob/living/simple_animal/retaliate/goat) + /mob/living/simple_mob/animal/passive/cat, + /mob/living/simple_mob/animal/passive/dog/corgi, + /mob/living/simple_mob/animal/passive/dog/corgi/puppy, + /mob/living/simple_mob/animal/passive/chicken, + /mob/living/simple_mob/animal/passive/cow, + /mob/living/simple_mob/animal/passive/bird/parrot, + /mob/living/simple_mob/animal/passive/crab, + /mob/living/simple_mob/animal/passive/mouse, + /mob/living/simple_mob/animal/goat) //todo: how the hell is the asteroid permanently powered? /obj/machinery/auto_cloner/process() diff --git a/code/modules/xenoarcheaology/artifacts/replicator.dm b/code/modules/xenoarcheaology/artifacts/replicator.dm index f5575721e5..7e27528aeb 100644 --- a/code/modules/xenoarcheaology/artifacts/replicator.dm +++ b/code/modules/xenoarcheaology/artifacts/replicator.dm @@ -26,9 +26,14 @@ /obj/item/roller, /obj/structure/closet/crate, /obj/structure/closet/acloset, +<<<<<<< HEAD /mob/living/simple_animal/hostile/mimic/crate, /mob/living/simple_animal/hostile/viscerator, /mob/living/simple_animal/hostile/hivebot, +======= + /mob/living/simple_mob/mechanical/viscerator, + /mob/living/simple_mob/mechanical/hivebot, +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync /obj/item/device/analyzer, /obj/item/device/camera, /obj/item/device/flash, diff --git a/code/modules/xenoarcheaology/finds/special.dm b/code/modules/xenoarcheaology/finds/special.dm index 8f3d7e4a79..d467af7a4b 100644 --- a/code/modules/xenoarcheaology/finds/special.dm +++ b/code/modules/xenoarcheaology/finds/special.dm @@ -87,7 +87,11 @@ if(charges >= 3) if(prob(5)) charges -= 1 +<<<<<<< HEAD var/spawn_type = pick(/mob/living/simple_animal/hostile/creature/vore) // Vorestation Edit +======= + var/spawn_type = pick(/mob/living/simple_mob/creature) +>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync new spawn_type(pick(view(1,src))) playsound(src.loc, pick('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg'), 50, 1, -3) diff --git a/code/modules/xenobio/items/extracts.dm b/code/modules/xenobio/items/extracts.dm index 6d472b5a09..397ca52c33 100644 --- a/code/modules/xenobio/items/extracts.dm +++ b/code/modules/xenobio/items/extracts.dm @@ -77,7 +77,7 @@ /datum/chemical_reaction/slime/grey_new_slime/on_reaction(var/datum/reagents/holder) holder.my_atom.visible_message("Infused with phoron, the core begins to quiver and grow, and soon a new baby slime emerges from it!") - new /mob/living/simple_animal/slime(get_turf(holder.my_atom)) + new /mob/living/simple_mob/slime/xenobio(get_turf(holder.my_atom)) ..() /datum/chemical_reaction/slime/grey_monkey @@ -495,16 +495,16 @@ if(!(their_turf in Z.contents)) // Not in the same zone. continue - if(istype(L, /mob/living/simple_animal/slime)) - var/mob/living/simple_animal/slime/S = L - if(S.cold_damage_per_tick <= 0) // Immune to cold. + if(istype(L, /mob/living/simple_mob/slime)) + var/mob/living/simple_mob/slime/S = L + if(S.cold_resist >= 1) // Immune to cold. to_chat(S, "A chill is felt around you, however it cannot harm you.") continue if(S.client) // Don't instantly kill player slimes. to_chat(S, "You feel your body crystalize as an intense chill overwhelms you!") - S.adjustToxLoss(S.cold_damage_per_tick * 2) + S.inflict_cold_damage(100) else - S.adjustToxLoss(S.cold_damage_per_tick * 5) // Metal slimes can survive this 'slime nuke'. + S.inflict_cold_damage(200) // Metal slimes can survive this 'slime nuke'. continue if(ishuman(L)) @@ -552,17 +552,21 @@ required = /obj/item/slime_extract/red /datum/chemical_reaction/slime/red_enrage/on_reaction(var/datum/reagents/holder) - for(var/mob/living/simple_animal/slime/S in view(get_turf(holder.my_atom))) - if(S.stat || S.docile || S.rabid) + for(var/mob/living/simple_mob/slime/S in view(get_turf(holder.my_atom))) + if(S.stat) continue + if(istype(S, /mob/living/simple_mob/slime/xenobio)) + var/mob/living/simple_mob/slime/xenobio/X = S + if(X.harmless) + continue + if(!X.client) + X.enrage() + S.add_modifier(/datum/modifier/berserk, 30 SECONDS) if(S.client) // Player slimes always have free will. to_chat(S, "An intense wave of rage is felt from inside, but you remain in control of yourself.") - continue - - S.enrage() for(var/mob/living/carbon/human/H in view(get_turf(holder.my_atom))) if(H.species.name == SPECIES_PROMETHEAN) @@ -957,8 +961,8 @@ /datum/chemical_reaction/slime/rainbow_random_slime/on_reaction(var/datum/reagents/holder) - var/mob/living/simple_animal/slime/S - var/list/slime_types = typesof(/mob/living/simple_animal/slime) + var/mob/living/simple_mob/slime/xenobio/S + var/list/slime_types = typesof(/mob/living/simple_mob/slime/xenobio) while(slime_types.len) S = pick(slime_types) diff --git a/code/modules/xenobio/items/slimepotions.dm b/code/modules/xenobio/items/slimepotions.dm index df03fdab6f..944853bec7 100644 --- a/code/modules/xenobio/items/slimepotions.dm +++ b/code/modules/xenobio/items/slimepotions.dm @@ -21,7 +21,7 @@ icon_state = "potcyan" description_info = "The slime needs to be alive for this to work. It will reduce the chances of mutation by 15%." -/obj/item/slimepotion/stabilizer/attack(mob/living/simple_animal/slime/M, mob/user) +/obj/item/slimepotion/stabilizer/attack(mob/living/simple_mob/slime/xenobio/M, mob/user) if(!istype(M)) to_chat(user, "The stabilizer only works on slimes!") return ..() @@ -45,7 +45,7 @@ description_info = "The slime needs to be alive for this to work. It will increase the chances of mutation by 12%." icon_state = "potred" -/obj/item/slimepotion/mutator/attack(mob/living/simple_animal/slime/M, mob/user) +/obj/item/slimepotion/mutator/attack(mob/living/simple_mob/slime/xenobio/M, mob/user) if(!istype(M)) to_chat(user, "The mutator only works on slimes!") return ..() @@ -67,20 +67,25 @@ name = "docility agent" desc = "A potent chemical mix that nullifies a slime's hunger, causing it to become docile and tame. It might also work on other creatures?" icon_state = "potlightpink" - description_info = "The target needs to be alive, not already passive, and have animal-like intelligence." + description_info = "The target needs to be alive, not already passive, and be an animal or slime type entity." -/obj/item/slimepotion/docility/attack(mob/living/simple_animal/M, mob/user) +/obj/item/slimepotion/docility/attack(mob/living/simple_mob/M, mob/user) if(!istype(M)) to_chat(user, "The agent only works on creatures!") return ..() if(M.stat == DEAD) to_chat(user, "\The [M] is dead!") return ..() + if(!M.has_AI()) + to_chat(user, span("warning", "\The [M] is too strongly willed for this to affect them.")) // Most likely player controlled. + return + + var/datum/ai_holder/AI = M.ai_holder // Slimes. - if(istype(M, /mob/living/simple_animal/slime)) - var/mob/living/simple_animal/slime/S = M - if(S.docile) + if(istype(M, /mob/living/simple_mob/slime/xenobio)) + var/mob/living/simple_mob/slime/xenobio/S = M + if(S.harmless) to_chat(user, "The slime is already docile!") return ..() @@ -89,22 +94,22 @@ to_chat(M, "You absorb the agent and feel your intense desire to feed melt away.") to_chat(user, "You feed the slime the agent, removing its hunger and calming it.") - // Simple Animals. - else if(istype(M, /mob/living/simple_animal)) - var/mob/living/simple_animal/SA = M - if(SA.intelligence_level > SA_ANIMAL) // So you can't use this on Russians/syndies/hivebots/etc. - to_chat(user, "\The [SA] is too intellient for this to affect them.") + // Simple Mobs. + else if(istype(M, /mob/living/simple_mob)) + var/mob/living/simple_mob/SM = M + if(!(SM.mob_class & MOB_CLASS_SLIME|MOB_CLASS_ANIMAL)) // So you can't use this on Russians/syndies/hivebots/etc. + to_chat(user, "\The [SM] only works on slimes and animals.") return ..() - if(!SA.hostile) - to_chat(user, "\The [SA] is already passive!") + if(!AI.hostile) + to_chat(user, "\The [SM] is already passive!") return ..() - SA.hostile = FALSE + AI.hostile = FALSE to_chat(M, "You consume the agent and feel a serene sense of peace.") - to_chat(user, "You feed \the [SA] the agent, calming it.") + to_chat(user, "You feed \the [SM] the agent, calming it.") playsound(src, 'sound/effects/bubbles.ogg', 50, 1) - M.LoseTarget() // So hostile things stop attacking people even if not hostile anymore. + AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. var/newname = copytext(sanitize(input(user, "Would you like to give \the [M] a name?", "Name your new pet", M.name) as null|text),1,MAX_NAME_LEN) if(newname) @@ -121,7 +126,7 @@ Extra extracts are not passed down to offspring when reproducing." icon_state = "potpurple" -/obj/item/slimepotion/steroid/attack(mob/living/simple_animal/slime/M, mob/user) +/obj/item/slimepotion/steroid/attack(mob/living/simple_mob/slime/xenobio/M, mob/user) if(!istype(M)) to_chat(user, "The steroid only works on slimes!") return ..() @@ -149,7 +154,7 @@ carry over to offspring when reproducing." icon_state = "potpink" -/obj/item/slimepotion/unity/attack(mob/living/simple_animal/slime/M, mob/user) +/obj/item/slimepotion/unity/attack(mob/living/simple_mob/slime/M, mob/user) if(!istype(M)) to_chat(user, "The agent only works on slimes!") return ..() @@ -175,12 +180,12 @@ the user's faction, which means the slime will attack things that are hostile to the user's faction, such as carp, spiders, and other slimes." icon_state = "potred" -/obj/item/slimepotion/loyalty/attack(mob/living/simple_animal/M, mob/user) +/obj/item/slimepotion/loyalty/attack(mob/living/simple_mob/M, mob/user) if(!istype(M)) - to_chat(user, "The agent only works on animals!") + to_chat(user, "The agent only works on creatures!") return ..() - if(M.intelligence_level > SA_ANIMAL) // So you can't use this on Russians/syndies/hivebots/etc. - to_chat(user, "\The [M] is too intellient for this to affect them.") + if(!(M.mob_class & MOB_CLASS_SLIME|MOB_CLASS_ANIMAL)) // So you can't use this on Russians/syndies/hivebots/etc. + to_chat(user, "\The [M] only works on slimes and animals.") return ..() if(M.stat == DEAD) to_chat(user, "The animal is dead!") @@ -188,12 +193,16 @@ if(M.faction == user.faction) to_chat(user, "\The [M] is already loyal to your species!") return ..() + if(!M.has_AI()) + to_chat(user, span("warning", "\The [M] is too strong-willed for this to affect them.")) + return ..() + + var/datum/ai_holder/AI = M.ai_holder to_chat(user, "You feed \the [M] the agent. It will now try to murder things that want to murder you instead.") to_chat(M, "\The [user] feeds you \the [src], and feel that the others will regard you as an outsider now.") M.faction = user.faction - M.attack_same = FALSE - M.LoseTarget() // So hostile things stop attacking people even if not hostile anymore. + AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. playsound(src, 'sound/effects/bubbles.ogg', 50, 1) qdel(src) @@ -206,28 +215,29 @@ their 'friend', and will never attack them. This might also work on other things besides slimes." icon_state = "potlightpink" -/obj/item/slimepotion/friendship/attack(mob/living/simple_animal/M, mob/user) +/obj/item/slimepotion/friendship/attack(mob/living/simple_mob/M, mob/user) if(!istype(M)) - to_chat(user, "The agent only works on animals!") + to_chat(user, "The agent only works on creatures!") return ..() - if(M.intelligence_level > SA_ANIMAL) // So you can't use this on Russians/syndies/hivebots/etc. - to_chat(user, "\The [M] is too intellient for this to affect them.") + if(!(M.mob_class & MOB_CLASS_SLIME|MOB_CLASS_ANIMAL)) // So you can't use this on Russians/syndies/hivebots/etc. + to_chat(user, "\The [M] only works on slimes and animals.") return ..() if(M.stat == DEAD) - to_chat(user, "The animal is dead!") + to_chat(user, "\The [M] is dead!") return ..() if(user in M.friends) to_chat(user, "\The [M] is already loyal to you!") return ..() + if(!M.has_AI()) + to_chat(user, span("warning", "\The [M] is too strong-willed for this to affect them.")) + return ..() + + var/datum/ai_holder/AI = M.ai_holder to_chat(user, "You feed \the [M] the agent. It will now be your best friend.") to_chat(M, "\The [user] feeds you \the [src], and feel that \the [user] wants to be best friends with you.") - if(isslime(M)) - var/mob/living/simple_animal/slime/S = M - S.befriend(user) - else - M.friends.Add(user) - M.LoseTarget() // So hostile things stop attacking people even if not hostile anymore. + M.friends.Add(user) + AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. playsound(src, 'sound/effects/bubbles.ogg', 50, 1) qdel(src) @@ -239,15 +249,16 @@ description_info = "The slime needs to be alive for this to work. It will instantly grow the slime enough to reproduce." icon_state = "potyellow" -/obj/item/slimepotion/feeding/attack(mob/living/simple_animal/slime/M, mob/user) +/obj/item/slimepotion/feeding/attack(mob/living/simple_mob/slime/xenobio/M, mob/user) if(!istype(M)) - to_chat(user, "The mutator only works on slimes!") + to_chat(user, "The feeding agent only works on slimes!") return ..() if(M.stat == DEAD) to_chat(user, "The slime is dead!") return ..() to_chat(user, "You feed the slime the feeding agent. It will now instantly reproduce.") + M.amount_grown = 10 M.make_adult() M.amount_grown = 10 M.reproduce() diff --git a/code/modules/xenobio/items/weapons.dm b/code/modules/xenobio/items/weapons.dm index 8fb5f9eea6..bb735c35f2 100644 --- a/code/modules/xenobio/items/weapons.dm +++ b/code/modules/xenobio/items/weapons.dm @@ -1,6 +1,6 @@ /obj/item/weapon/melee/baton/slime name = "slimebaton" - desc = "A modified stun baton designed to stun slimes and other lesser xeno lifeforms for handling." + desc = "A modified stun baton designed to stun slimes and other lesser slimy xeno lifeforms for handling." icon_state = "slimebaton" item_state = "slimebaton" slot_flags = SLOT_BELT @@ -9,31 +9,30 @@ origin_tech = list(TECH_COMBAT = 2, TECH_BIO = 2) agonyforce = 10 //It's not supposed to be great at stunning human beings. hitcost = 48 //Less zap for less cost - description_info = "This baton will stun a slime or other lesser lifeform for about five seconds, if hit with it while on." + description_info = "This baton will stun a slime or other slime-based lifeform for about five seconds, if hit with it while on." -/obj/item/weapon/melee/baton/slime/attack(mob/M, mob/user, hit_zone) - // Simple Animals. - if(istype(M, /mob/living/simple_animal/slime) && status) - var/mob/living/simple_animal/SA = M - if(SA.intelligence_level <= SA_ANIMAL) // So it doesn't stun hivebots or syndies. - SA.Weaken(5) - if(isslime(SA)) - var/mob/living/simple_animal/slime/S = SA - S.adjust_discipline(3) +/obj/item/weapon/melee/baton/slime/attack(mob/living/L, mob/user, hit_zone) + if(istype(L) && status) // Is it on? + if(L.mob_class & MOB_CLASS_SLIME) // Are they some kind of slime? (Prommies might pass this check someday). + if(isslime(L)) + var/mob/living/simple_mob/slime/S = L + S.slimebatoned(user, 5) // Feral and xenobio slimes will react differently to this. + else + L.Weaken(5) + + // Now for prommies. + if(ishuman(L)) + var/mob/living/carbon/human/H = L + if(H.species && H.species.name == SPECIES_PROMETHEAN) + var/agony_to_apply = 60 - agonyforce + H.apply_damage(agony_to_apply, HALLOSS) - // Prometheans. - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.species && H.species.name == SPECIES_PROMETHEAN && status) - var/agony_to_apply = 60 - agonyforce - H.apply_damage(agony_to_apply, HALLOSS) ..() -/obj/item/weapon/melee/baton/slime/loaded/New() - ..() +/obj/item/weapon/melee/baton/slime/loaded/initialize() bcell = new/obj/item/weapon/cell/device(src) update_icon() - return + return ..() // Research borg's version @@ -61,9 +60,9 @@ charge_cost = 120 // Twice as many shots. projectile_type = /obj/item/projectile/beam/stun/xeno accuracy = 30 // Make it a bit easier to hit the slimes. - description_info = "This gun will stun a slime or other lesser lifeform for about two seconds, if hit with the projectile it fires." + description_info = "This gun will stun a slime or other lesser slimy lifeform for about two seconds, if hit with the projectile it fires." description_fluff = "An easy to use weapon designed by NanoTrasen, for NanoTrasen. This weapon is designed to subdue lesser \ - xeno lifeforms at a distance. It is ineffective at stunning larger lifeforms such as humanoids." + slime-based xeno lifeforms at a distance. It is ineffective at stunning non-slimy lifeforms such as humanoids." /obj/item/weapon/gun/energy/taser/xeno/robot // Borg version self_recharge = 1 @@ -71,7 +70,7 @@ recharge_time = 3 /obj/item/weapon/gun/energy/taser/xeno/sec //NT's corner-cutting option for their on-station security. - desc = "An NT Mk30 NL retrofitted to fire beams for subduing non-humanoid xeno life forms." + desc = "An NT Mk30 NL retrofitted to fire beams for subduing non-humanoid slimy xeno life forms." icon_state = "taserblue" item_state = "taser" projectile_type = /obj/item/projectile/beam/stun/xeno/weak @@ -102,20 +101,17 @@ /obj/item/projectile/beam/stun/xeno/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) if(istype(target, /mob/living)) var/mob/living/L = target + if(L.mob_class & MOB_CLASS_SLIME) + if(isslime(L)) + var/mob/living/simple_mob/slime/S = L + S.slimebatoned(firer, round(agony/2)) + else + L.Weaken(round(agony/2)) - // Simple Animals. - if(istype(L, /mob/living/simple_animal/slime)) - var/mob/living/simple_animal/SA = L - if(SA.intelligence_level <= SA_ANIMAL) // So it doesn't stun hivebots or syndies. - SA.Weaken(round(agony/2)) // Less powerful since its ranged, and therefore safer. - if(isslime(SA)) - var/mob/living/simple_animal/slime/S = SA - S.adjust_discipline(round(agony/2)) - - // Prometheans. if(ishuman(L)) var/mob/living/carbon/human/H = L if(H.species && H.species.name == SPECIES_PROMETHEAN) - if(agony == initial(agony)) + if(agony == initial(agony)) // ?????? agony = round((14 * agony) - agony) //60-4 = 56, 56 / 4 = 14. Prior was flat 60 - agony of the beam to equate to 60. + ..() diff --git a/code/modules/xenobio/machinery/processor.dm b/code/modules/xenobio/machinery/processor.dm index 56b7cee3bf..6d888adb26 100644 --- a/code/modules/xenobio/machinery/processor.dm +++ b/code/modules/xenobio/machinery/processor.dm @@ -79,8 +79,8 @@ playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) /obj/machinery/processor/proc/extract(var/atom/movable/AM) - if(istype(AM, /mob/living/simple_animal/slime)) - var/mob/living/simple_animal/slime/S = AM + if(istype(AM, /mob/living/simple_mob/slime)) + var/mob/living/simple_mob/slime/S = AM while(S.cores) new S.coretype(get_turf(src)) playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) @@ -98,8 +98,8 @@ sleep(1 SECOND) /obj/machinery/processor/proc/can_insert(var/atom/movable/AM) - if(istype(AM, /mob/living/simple_animal/slime)) - var/mob/living/simple_animal/slime/S = AM + if(istype(AM, /mob/living/simple_mob/slime)) + var/mob/living/simple_mob/slime/S = AM if(S.stat != DEAD) return FALSE return TRUE diff --git a/code/stylesheet.dm b/code/stylesheet.dm index 7c595b923f..dd992a62bf 100644 --- a/code/stylesheet.dm +++ b/code/stylesheet.dm @@ -110,4 +110,11 @@ h1.alert, h2.alert {color: #000000;} BIG IMG.icon {width: 32px; height: 32px;} +/* Debug Logs */ +.debug_error {color:#FF0000; font-weight:bold} +.debug_warning {color:#FF0000;} +.debug_info {} +.debug_debug {color:#0000FF;} +.debug_trace {color:#888888;} + "} diff --git a/html/changelog.html b/html/changelog.html index 91fdd23b30..6c8289bb04 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -85,7 +85,7 @@

Mechoid updated:

    -
  • Hallucinations are no longer only Pun Pun.
  • +
  • Hallucinations are no longer only Pun Pun.

Neerti updated:

    @@ -187,8 +187,8 @@

Mechoid updated:

    -
  • Promethean limbs store more damage.
  • -
  • Promethean limbs, in addition to normal severing rules, have a higher chance to be splattered or ashed once they reach maximum damage.
  • +
  • Promethean limbs store more damage.
  • +
  • Promethean limbs, in addition to normal severing rules, have a higher chance to be splattered or ashed once they reach maximum damage.
  • Limbs can now spread their damage to neighbors with the spread_dam var, when reaching max damage.

PrismaticGynoid updated:

@@ -322,7 +322,7 @@