diff --git a/code/__defines/hud.dm b/code/__defines/hud.dm index 84f45706ab..286d97f49a 100644 --- a/code/__defines/hud.dm +++ b/code/__defines/hud.dm @@ -164,7 +164,8 @@ #define ui_genetic_master "EAST-1:16,NORTH-3:16" // Ghost ones -#define ui_ghost_returntomenu "SOUTH:6,CENTER-3:24" +#define ui_ghost_returntomenu "SOUTH:6,CENTER-4:24" +#define ui_ghost_vr "SOUTH: 6,CENTER-3:24" #define ui_ghost_jumptomob "SOUTH:6,CENTER-2:24" #define ui_ghost_orbit "SOUTH:6,CENTER-1:24" #define ui_ghost_reenter_corpse "SOUTH:6,CENTER:24" diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 8a90b93686..c755d22603 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -117,6 +117,10 @@ #define AREA_BLOCK_GHOST_SIGHT 0x2000 // If an area blocks sight for ghosts // The 0x800000 is blocked by INITIALIZED, do NOT use it! +#define PHASE_SHIELDED 0x100000 // A less rough way to prevent phase shifting without blocking access //VOREStation Note: Not implemented on VS. Used downstream. +#define AREA_LIMIT_DARK_RESPITE 0x200000 // Shadekin will die normally in those areas //VOREStation Note: Not implemented on VS. Used downstream. +#define AREA_ALLOW_CLOCKOUT 0x400000 // The PDA timeclock app can only be used in these areas //VOREStation Note: Not implemented on VS. Used downstream. + // OnTopic return values #define TOPIC_NOACTION 0 #define TOPIC_HANDLED 1 @@ -209,9 +213,10 @@ #define DEPARTMENT_RESEARCH "Research" #define DEPARTMENT_CARGO "Cargo" #define DEPARTMENT_CIVILIAN "Civilian" -#define DEPARTMENT_PLANET "Exploration" //VOREStation Edit // I hate having this be here and not in a SC file. Hopefully someday the manifest can be rewritten to be map-agnostic. +#define DEPARTMENT_PLANET "Exploration" // I hate having this be here and not in a SC file. Hopefully someday the manifest can be rewritten to be map-agnostic. #define DEPARTMENT_SYNTHETIC "Synthetic" +#define DEPARTMENT_NONCREW "Non crew" // These are mostly for the department guessing code and event system. #define DEPARTMENT_UNKNOWN "Unknown" #define DEPARTMENT_EVERYONE "Everyone" @@ -335,6 +340,11 @@ var/global/list/##LIST_NAME = list();\ #define JOB_SILICON_AI 0x4 #define JOB_SILICON 0x6 // 2|4, probably don't set jobs to this, but good for checking +//Job defines +#define JOB_OUTSIDER "Outsider" //VOREStation Note: Not implemented on VS. Used downstream. +#define JOB_ANOMALY "Anomaly" //VOREStation Note: Not implemented on VS. Used downstream. +#define JOB_VR "VR Avatar" + #define DEFAULT_OVERMAP_RANGE 0 // Makes general computers and devices be able to connect to other overmap z-levels on the same tile. /* diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm index 4bd3ecf777..0233206b05 100644 --- a/code/_helpers/global_lists.dm +++ b/code/_helpers/global_lists.dm @@ -400,3 +400,164 @@ var/global/list/item_digestion_blacklist = list( /obj/item/rig/protean) var/global/list/item_tf_spawnpoints = list() // Global variable tracking which items are item tf spawnpoints + +// Options for transforming into a different mob in virtual reality. +var/global/list/vr_mob_tf_options = list( + "Borg" = /mob/living/silicon/robot, + "Cortical borer" = /mob/living/simple_mob/animal/borer/non_antag, + //"Hyena" = /mob/living/simple_mob/animal/hyena, //TODO: Port from Downstream + "Giant spider" = /mob/living/simple_mob/animal/giant_spider/thermic, + //"Armadillo" = /mob/living/simple_mob/animal/passive/armadillo, //TODO: Port from Downstream + "Parrot" = /mob/living/simple_mob/animal/passive/bird/parrot, + "Cat" = /mob/living/simple_mob/animal/passive/cat, + "Corgi" = /mob/living/simple_mob/animal/passive/dog/corgi, + "Squirrel" = /mob/living/simple_mob/vore/squirrel, + "Frog" = /mob/living/simple_mob/vore/aggressive/frog, + "Seagull" =/mob/living/simple_mob/vore/seagull, + "Fox" = /mob/living/simple_mob/animal/passive/fox, + //"Racoon" = /mob/living/simple_mob/animal/passive/raccoon_ch, //TODO: Port from Downstream + "Shantak" = /mob/living/simple_mob/animal/sif/shantak, + "Goose" = /mob/living/simple_mob/animal/space/goose, + "Space shark" = /mob/living/simple_mob/animal/space/shark, + //"Synx" = /mob/living/simple_mob/animal/synx, //TODO: Port from Downstream + "Dire wolf" = /mob/living/simple_mob/vore/wolf/direwolf, + "Construct Artificer" = /mob/living/simple_mob/construct/artificer, + "Tech golem" = /mob/living/simple_mob/mechanical/technomancer_golem, + //"Metroid" = /mob/living/simple_mob/metroid/juvenile/baby, //TODO: Port from Downstream + "Otie" = /mob/living/simple_mob/vore/otie/cotie/chubby, + "Red-eyed Shadekin" = /mob/living/simple_mob/shadekin/red, + "Blue-eyed Shadekin" = /mob/living/simple_mob/shadekin/blue, + "Purple-eyed Shadekin" = /mob/living/simple_mob/shadekin/purple, + "Green-eyed Shadekin" = /mob/living/simple_mob/shadekin/green, + "Yellow-eyed Shadekin" = /mob/living/simple_mob/shadekin/yellow, + "Slime" = /mob/living/simple_mob/slime/xenobio/metal, + "Corrupt hound" = /mob/living/simple_mob/vore/aggressive/corrupthound, + "Deathclaw" = /mob/living/simple_mob/vore/aggressive/deathclaw, //Downstream uses /den variant here. + "Weretiger" = /mob/living/simple_mob/vore/weretiger, + "Mimic" = /mob/living/simple_mob/vore/aggressive/mimic, //Downstream uses /floor/plating variant here + "Giant rat" = /mob/living/simple_mob/vore/aggressive/rat, + "Catslug" = /mob/living/simple_mob/vore/alienanimals/catslug, + "Dust jumper" = /mob/living/simple_mob/vore/alienanimals/dustjumper, + "Space ghost" = /mob/living/simple_mob/vore/alienanimals/spooky_ghost, + "Teppi" = /mob/living/simple_mob/vore/alienanimals/teppi, + "Bee" = /mob/living/simple_mob/vore/bee, + "Dragon" = /mob/living/simple_mob/vore/bigdragon/friendly, + "Riftwalker" = /mob/living/simple_mob/vore/demon, //Downstream uses /wendigo variant here + "Horse" = /mob/living/simple_mob/vore/horse/big, + "Morph" = /mob/living/simple_mob/vore/morph, + "Leopardmander" = /mob/living/simple_mob/vore/leopardmander, + "Rabbit" = /mob/living/simple_mob/vore/rabbit, + "Red panda" = /mob/living/simple_mob/vore/redpanda, + "Sect drone" = /mob/living/simple_mob/vore/sect_drone, + //"Armalis vox" = /mob/living/simple_mob/vox/armalis, //TODO: Port from Downstream + //"Xeno hunter" = /mob/living/simple_mob/xeno_ch/hunter, //TODO: Port from Downstream + //"Xeno queen" = /mob/living/simple_mob/xeno_ch/queen/maid, //TODO: Port from Downstream + //"Xeno sentinel" = /mob/living/simple_mob/xeno_ch/sentinel, //TODO: Port from Downstream + "Space carp" = /mob/living/simple_mob/animal/space/carp, + "Jelly blob" = /mob/living/simple_mob/vore/jelly, + //"SWOOPIE XL" = /mob/living/simple_mob/vore/aggressive/corrupthound/swoopie, //TODO: Port from Downstream + "Abyss lurker" = /mob/living/simple_mob/vore/vore_hostile/abyss_lurker, + "Abyss leaper" = /mob/living/simple_mob/vore/vore_hostile/leaper, + "Gelatinous cube" = /mob/living/simple_mob/vore/vore_hostile/gelatinous_cube, + //"Gryphon" = /mob/living/simple_mob/vore/gryphon //TODO: Port from Downstream + ) + +var/global/list/vr_mob_spawner_options = list( + "Parrot" = /mob/living/simple_mob/animal/passive/bird/parrot, + "Rabbit" = /mob/living/simple_mob/vore/rabbit, + "Cat" = /mob/living/simple_mob/animal/passive/cat, + "Fox" = /mob/living/simple_mob/animal/passive/fox, + "Cow" = /mob/living/simple_mob/animal/passive/cow, + "Dog" = /mob/living/simple_mob/vore/woof, + "Horse" = /mob/living/simple_mob/vore/horse/big, + "Hippo" = /mob/living/simple_mob/vore/hippo, + "Sheep" = /mob/living/simple_mob/vore/sheep, + "Squirrel" = /mob/living/simple_mob/vore/squirrel, + "Red panda" = /mob/living/simple_mob/vore/redpanda, + "Fennec" = /mob/living/simple_mob/vore/fennec, + "Seagull" =/mob/living/simple_mob/vore/seagull, + "Corgi" = /mob/living/simple_mob/animal/passive/dog/corgi, + //"Armadillo" = /mob/living/simple_mob/animal/passive/armadillo, //TODO: Port from Downstream + //"Racoon" = /mob/living/simple_mob/animal/passive/raccoon_ch, //TODO: Port from Downstream + "Goose" = /mob/living/simple_mob/animal/space/goose, + "Frog" = /mob/living/simple_mob/vore/aggressive/frog, + "Dust jumper" = /mob/living/simple_mob/vore/alienanimals/dustjumper, + "Dire wolf" = /mob/living/simple_mob/vore/wolf/direwolf, + "Space bumblebee" = /mob/living/simple_mob/vore/bee, + "Space bear" = /mob/living/simple_mob/animal/space/bear, + "Otie" = /mob/living/simple_mob/vore/otie, + "Mutated otie" =/mob/living/simple_mob/vore/otie/feral, + "Red otie" = /mob/living/simple_mob/vore/otie/red, + "Giant rat" = /mob/living/simple_mob/vore/aggressive/rat, + "Giant snake" = /mob/living/simple_mob/vore/aggressive/giant_snake, + //"Hyena" = /mob/living/simple_mob/animal/hyena, //TODO: Port from Downstream + "Space shark" = /mob/living/simple_mob/animal/space/shark, + "Shantak" = /mob/living/simple_mob/animal/sif/shantak, + "Kururak" = /mob/living/simple_mob/animal/sif/kururak, + "Teppi" = /mob/living/simple_mob/vore/alienanimals/teppi, + //"Slug" = /mob/living/simple_mob/vore/slug, //TODO: Port from Downstream + "Catslug" = /mob/living/simple_mob/vore/alienanimals/catslug, + "Weretiger" = /mob/living/simple_mob/vore/weretiger, + "Dust jumper" = /mob/living/simple_mob/vore/alienanimals/dustjumper, + "Star treader" = /mob/living/simple_mob/vore/alienanimals/startreader, + "Space ghost" = /mob/living/simple_mob/vore/alienanimals/spooky_ghost, + "Space carp" = /mob/living/simple_mob/animal/space/carp, + "Space jelly fish" = /mob/living/simple_mob/vore/alienanimals/space_jellyfish, + "Abyss lurker" = /mob/living/simple_mob/vore/vore_hostile/abyss_lurker, + "Abyss leaper" = /mob/living/simple_mob/vore/vore_hostile/leaper, + "Gelatinous cube" = /mob/living/simple_mob/vore/vore_hostile/gelatinous_cube, + "Panther" = /mob/living/simple_mob/vore/aggressive/panther, + //"Lizard man" = /mob/living/simple_mob/vore/aggressive/lizardman, //TODO: Port from Downstream + "Pakkun" = /mob/living/simple_mob/vore/pakkun, + //"Synx" = /mob/living/simple_mob/animal/synx, //TODO: Port from Downstream + "Jelly blob" = /mob/living/simple_mob/vore/jelly, + "Voracious lizard" = /mob/living/simple_mob/vore/aggressive/dino, + //"Baby metroid" = /mob/living/simple_mob/metroid/juvenile/baby, //TODO: Port from Downstream + //"Super metroid" = /mob/living/simple_mob/metroid/juvenile/super, //TODO: Port from Downstream + //"Alpha metroid" = /mob/living/simple_mob/metroid/juvenile/alpha, //TODO: Port from Downstream + //"Gamma metroid" = /mob/living/simple_mob/metroid/juvenile/gamma, //TODO: Port from Downstream + //"Zeta metroid" = /mob/living/simple_mob/metroid/juvenile/zeta, //TODO: Port from Downstream + //"Omega metroid" = /mob/living/simple_mob/metroid/juvenile/omega, //TODO: Port from Downstream + //"Queen metroid" = /mob/living/simple_mob/metroid/juvenile/queen, //TODO: Port from Downstream + "Xeno hunter" = /mob/living/simple_mob/animal/space/alien, + "Xeno sentinel" = /mob/living/simple_mob/animal/space/alien/sentinel, + "Xeno Praetorian" = /mob/living/simple_mob/animal/space/alien/sentinel/praetorian, + "Xeno queen" = /mob/living/simple_mob/animal/space/alien/queen, + "Xeno Empress" = /mob/living/simple_mob/animal/space/alien/queen/empress, + "Xeno Queen Mother" = /mob/living/simple_mob/animal/space/alien/queen/empress/mother, + "Defanged xeno" = /mob/living/simple_mob/vore/xeno_defanged, + "Sect drone" = /mob/living/simple_mob/vore/sect_drone, + "Sect queen" = /mob/living/simple_mob/vore/sect_queen, + "Deathclaw" = /mob/living/simple_mob/vore/aggressive/deathclaw, + "Great White Wolf" = /mob/living/simple_mob/vore/greatwolf, + "Great Black Wolf" = /mob/living/simple_mob/vore/greatwolf/black, + "Solar grub" = /mob/living/simple_mob/vore/solargrub, + //"Pitcher plant" = /mob/living/simple_mob/vore/pitcher_plant, //TODO: Port from Downstream + //"Red gummy kobold" = /mob/living/simple_mob/vore/candy/redcabold, //TODO: Port from Downstream + //"Blue gummy kobold" = /mob/living/simple_mob/vore/candy/bluecabold, //TODO: Port from Downstream + //"Yellow gummy kobold" = /mob/living/simple_mob/vore/candy/yellowcabold, //TODO: Port from Downstream + //"Marshmellow serpent" = /mob/living/simple_mob/vore/candy/marshmellowserpent, //TODO: Port from Downstream + "Riftwalker" = /mob/living/simple_mob/vore/demon, + //"Wendigo" = /mob/living/simple_mob/vore/demon/wendigo, //TODO: Port from Downstream + "Shadekin" = /mob/living/simple_mob/shadekin, + "Catgirl" = /mob/living/simple_mob/vore/catgirl, + "Wolfgirl" = /mob/living/simple_mob/vore/wolfgirl, + "Wolftaur" = /mob/living/simple_mob/vore/wolftaur, + "Lamia" = /mob/living/simple_mob/vore/lamia, + "Corrupt hound" = /mob/living/simple_mob/vore/aggressive/corrupthound, + "Corrupt corrupt hound" = /mob/living/simple_mob/vore/aggressive/corrupthound/prettyboi, + //"SWOOPIE XL" = /mob/living/simple_mob/vore/aggressive/corrupthound/swoopie, //TODO: Port from Downstream + //"Cultist Teshari" = /mob/living/simple_mob/humanoid/cultist/tesh, //TODO: Port from Downstream + //"Burning Mage" = /mob/living/simple_mob/humanoid/cultist/human/bloodjaunt/fireball, //TODO: Port from Downstream + //"Converted" = /mob/living/simple_mob/humanoid/cultist/noodle, //TODO: Port from Downstream + //"Cultist Teshari Mage" = /mob/living/simple_mob/humanoid/cultist/castertesh, //TODO: Port from Downstream + "Monkey" = /mob/living/carbon/human/monkey, + "Wolpin" = /mob/living/carbon/human/wolpin, + "Sparra" = /mob/living/carbon/human/sparram, + "Saru" = /mob/living/carbon/human/sergallingm, + "Sobaka" = /mob/living/carbon/human/sharkm, + "Farwa" = /mob/living/carbon/human/farwa, + "Neaera" = /mob/living/carbon/human/neaera, + "Stok" = /mob/living/carbon/human/stok, + //"Gryphon" = /mob/living/simple_mob/vore/gryphon // Disabled until tested + ) diff --git a/code/_helpers/global_lists_vr.dm b/code/_helpers/global_lists_vr.dm index dbe9ae4e7d..f245476d09 100644 --- a/code/_helpers/global_lists_vr.dm +++ b/code/_helpers/global_lists_vr.dm @@ -1034,7 +1034,8 @@ var/global/list/SPECIALS = list( /turf/unsimulated/wall/planetary, /area/submap/virgo2, /area/submap/event, - /area/submap/casino_event + /area/submap/casino_event, + /area/vr // /area/derelict //commented out, all hail derelict-rebuilders! ) diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm index 3cdc74d88f..afd1429178 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -91,6 +91,17 @@ var/mob/observer/dead/G = usr G.zMove(DOWN) +/obj/screen/ghost/vr + name = "Enter VR" + desc = "Enter virtual reality." + icon = 'icons/mob/screen_ghost.dmi' + icon_state = "entervr" + +/obj/screen/ghost/vr/Click() + ..() + var/mob/observer/dead/G = usr + G.fake_enter_vr() + /mob/observer/dead/create_mob_hud(datum/hud/HUD, apply_to_client = TRUE) ..() @@ -138,6 +149,11 @@ using.hud = src adding += using + using = new /obj/screen/ghost/vr() + using.screen_loc = ui_ghost_vr + using.hud = src + adding += using + /* using = new /obj/screen/language_menu using.icon = ui_style diff --git a/code/controllers/subsystems/mail.dm b/code/controllers/subsystems/mail.dm index d2b5ce84ff..874319a427 100644 --- a/code/controllers/subsystems/mail.dm +++ b/code/controllers/subsystems/mail.dm @@ -24,7 +24,7 @@ SUBSYSTEM_DEF(mail) // Collect recipients var/list/mail_recipients = list() for(var/mob/living/carbon/human/player_human in player_list) - if(player_human.stat != DEAD && player_human.client && player_human.client.inactivity <= 10 MINUTES && !player_is_antag(player_human.mind)) // Only alive, active and NT employeers should be getting mail. + if(player_human.stat != DEAD && player_human.client && player_human.client.inactivity <= 10 MINUTES && player_human.job != JOB_OUTSIDER && player_human.job != JOB_ANOMALY && player_human.job != JOB_VR && !player_is_antag(player_human.mind)) // Only alive, active and NT employeers should be getting mail. mail_recipients += player_human // Creates mail for all the mail waiting to arrive, if there's nobody to receive it, it will be a chance of junk mail. diff --git a/code/datums/outfits/jobs/misc.dm b/code/datums/outfits/jobs/misc.dm index 28cfa9e236..0365d0723a 100644 --- a/code/datums/outfits/jobs/misc.dm +++ b/code/datums/outfits/jobs/misc.dm @@ -9,3 +9,27 @@ /decl/hierarchy/outfit/job/silicon/cyborg name = OUTFIT_JOB_NAME(JOB_CYBORG) suit = /obj/item/clothing/suit/cardborg + +/datum/job/vr_avatar //So VR avatars dont spawn with PDAs and flood the servers + title = JOB_VR + disallow_jobhop = TRUE + total_positions = 99 + spawn_positions = 5 + latejoin_only = 1 + supervisors = "The VR world" + + flag = NONCREW + departments = list(DEPARTMENT_NONCREW) + department_flag = OTHER + faction = "Station" + assignable = FALSE + account_allowed = 0 + offmap_spawn = TRUE + outfit_type = /decl/hierarchy/outfit/noncrew/vr_avatar + +/decl/hierarchy/outfit/noncrew/vr_avatar + pda_slot = null + id_slot = null + r_pocket = null + l_pocket = null + flags = OUTFIT_HAS_BACKPACK diff --git a/code/game/jobs/jobs.dm b/code/game/jobs/jobs.dm index 41585d4a58..9f5dda07b0 100644 --- a/code/game/jobs/jobs.dm +++ b/code/game/jobs/jobs.dm @@ -52,6 +52,10 @@ var/const/PILOT =(1<<13) var/const/ENTERTAINER =(1<<14) var/const/ENTREPRENEUR =(1<<15) +var/const/OTHER =(1<<10) //VOREStation Note: Unused on VS. Used downstream. +var/const/NONCREW =(1<<0) +var/const/ANOMALY =(1<<0) //VOREStation Note: Unused on VS. Used downstream. + var/const/TALON =(1<<3) var/const/TALCAP =(1<<0) diff --git a/code/game/machinery/virtual_reality/ar_console.dm b/code/game/machinery/virtual_reality/ar_console.dm index 4047561120..b860b62f5d 100644 --- a/code/game/machinery/virtual_reality/ar_console.dm +++ b/code/game/machinery/virtual_reality/ar_console.dm @@ -16,6 +16,7 @@ var/randomize_species = FALSE var/list/possible_species // Do we make the newly produced body a random species? perfect_replica = TRUE //All alien VR sleepers make perfect replicas. + spawn_with_clothing = FALSE //alien VR sleepers do not spawn with clothing. /obj/machinery/vr_sleeper/alien/Initialize(mapload) . = ..() @@ -58,15 +59,19 @@ if(!occupant) return - if(!forced && avatar && avatar.stat != DEAD && tgui_alert(avatar, "Someone wants to remove you from virtual reality. Do you want to leave?", "Leave VR?", list("Yes", "No")) != "Yes") + if(!forced && avatar) + if(tgui_alert(avatar, "Someone wants to remove you from virtual reality. Do you want to leave?", "Leave VR?", list("Yes", "No")) != "Yes") + return + + + avatar.exit_vr() //We don't poof! We're a actual, living entity that isn't restrained by VR zones! + if(!occupant) //This whole thing needs cleaned up later, but this works for now. return - - avatar.exit_vr() - if(occupant && occupant.client) occupant.client.eye = occupant.client.mob occupant.client.perspective = MOB_PERSPECTIVE - occupant.forceMove(src) + occupant.forceMove(get_turf(src)) + occupant.vr_link = null //The machine remembers the avatar. 1 avatar per machine. So the vr_link isn't needed anymore. occupant = null for(var/atom/movable/A in src) // In case an object was dropped inside or something if(A == circuit) @@ -79,12 +84,15 @@ /obj/machinery/vr_sleeper/alien/enter_vr() + // No mob to transfer a mind from if(!occupant) return + // No mind to transfer if(!occupant.mind) return + // Mob doesn't have an active consciousness to send/receive from if(occupant.stat == DEAD && !occupant.client) return @@ -106,13 +114,20 @@ avatar = new(src, produce_species) else avatar = new(src, occupant.species.name) + + // If the user has a non-default (Human) bodyshape, make it match theirs. if(occupant.species.name != "Promethean" && occupant.species.name != "Human" && mirror_first_occupant) avatar.shapeshifter_change_shape(occupant.species.name) avatar.Sleeping(6) occupant.enter_vr(avatar) + if(spawn_with_clothing) + job_master.EquipRank(avatar,"Visitor", 1, FALSE) + add_verb(avatar,/mob/living/carbon/human/proc/exit_vr) + avatar.virtual_reality_mob = FALSE //THIS IS THE BIG DIFFERENCE WITH ALIEN VR PODS. THEY ARE NOT VR, THEY ARE REAL. //This handles all the 'We make it look like ourself' code. + //We do this BEFORE any mob tf so prefs carry over properly! if(perfect_replica) avatar.species.create_organs(avatar) // Reset our organs/limbs. avatar.restore_all_organs() @@ -135,7 +150,7 @@ var/newname = sanitize(tgui_input_text(avatar, "Your mind feels foggy. You're certain your name is [occupant.real_name], but it feels like it is [avatar.name]. Would you like to change it to something else?", "Name change", null, MAX_NAME_LEN), MAX_NAME_LEN) if(newname) avatar.real_name = newname - + avatar.name = newname occupant.enter_vr(avatar) diff --git a/code/game/machinery/virtual_reality/vr_console.dm b/code/game/machinery/virtual_reality/vr_console.dm index 5b21969aa7..1c090a5f9f 100644 --- a/code/game/machinery/virtual_reality/vr_console.dm +++ b/code/game/machinery/virtual_reality/vr_console.dm @@ -18,6 +18,8 @@ var/mirror_first_occupant = TRUE // Do we force the newly produced body to look like the occupant? + var/spawn_with_clothing = TRUE // Do we spawn the avatar with clothing? + /// If we have a perfect replica of the mob's species that is entering us! /// Because of our player population, I have defaulted this to TRUE. /// If you are a downstream and want to have people spawn as VR prometheans by default, change this to FALSE @@ -27,6 +29,7 @@ idle_power_usage = 15 active_power_usage = 200 light_color = "#FF0000" + //var/global/list/vr_mob_tf_options // Global var located in global_lists.dm /obj/machinery/vr_sleeper/perfect perfect_replica = TRUE @@ -38,8 +41,17 @@ update_icon() /obj/machinery/vr_sleeper/Destroy() + if(occupant && occupant.vr_link) + occupant.vr_link.exit_vr() + //The below deals with the edge case of there being no occupant but there IS things inside, somehow. + //Just in case some weirdness happened. + for(var/atom/movable/A in src) + if(A == circuit) + continue + if(A in component_parts) + continue + A.loc = src.loc . = ..() - go_out() /obj/machinery/vr_sleeper/process() if(stat & (NOPOWER|BROKEN)) @@ -54,6 +66,11 @@ /obj/machinery/vr_sleeper/update_icon() icon_state = "[base_state][occupant ? "1" : "0"]" +/obj/machinery/vr_sleeper/examine(mob/user) + . = ..() + if(occupant) + . += span_notice("[occupant] is inside.") + /obj/machinery/vr_sleeper/Topic(href, href_list) if(..()) return 1 @@ -148,7 +165,7 @@ /obj/machinery/vr_sleeper/relaymove(mob/user as mob) if(user.incapacitated()) return 0 //maybe they should be able to get out with cuffs, but whatever - go_out() + go_out(TRUE) /obj/machinery/vr_sleeper/proc/go_in(var/mob/M, var/mob/user) if(!M) @@ -157,6 +174,7 @@ return if(!ishuman(M)) to_chat(user, span_warning("\The [src] rejects [M] with a sharp beep.")) + return if(occupant) to_chat(user, span_warning("\The [src] is already occupied.")) return @@ -175,35 +193,31 @@ M.client.perspective = EYE_PERSPECTIVE M.client.eye = src M.loc = src - // update_use_power(USE_POWER_ACTIVE) //VOREstation edit: borer VR crash fix occupant = M update_icon() - //VOREstation edit - crashes borers if(!M.has_brain_worms()) update_use_power(USE_POWER_ACTIVE) enter_vr() else to_chat(user, span_warning("\The [src] rejects [M] with a sharp beep.")) - //VOREstation edit end return /obj/machinery/vr_sleeper/proc/go_out(var/forced = TRUE) if(!occupant) return - if(!forced && avatar && tgui_alert(avatar, "Someone wants to remove you from virtual reality. Do you want to leave?", "Leave VR?", list("Yes", "No")) != "Yes") - return + if(!forced && avatar) + if(tgui_alert(avatar, "Someone wants to remove you from virtual reality. Do you want to leave?", "Leave VR?", list("Yes", "No")) != "Yes") + return - if(avatar) - avatar.exit_vr() avatar = null if(occupant.client) occupant.client.eye = occupant.client.mob occupant.client.perspective = MOB_PERSPECTIVE - occupant.forceMove(src) + occupant.forceMove(get_turf(src)) occupant = null for(var/atom/movable/A in src) // In case an object was dropped inside or something if(A == circuit) @@ -249,6 +263,13 @@ if(!S) return 0 + var/tf = null + if(tgui_alert(occupant, "Would you like to play as a different creature?", "Join as a mob?", list("Yes", "No")) == "Yes") + var/k = tgui_input_list(occupant, "Please select a creature:", "Mob list", vr_mob_tf_options) + if(!k) + return 0 + tf = vr_mob_tf_options[k] + for(var/obj/effect/landmark/virtual_reality/i in landmarks_list) if(i.name == S) S = i @@ -264,9 +285,16 @@ avatar.shapeshifter_change_shape(occupant.species.name) avatar.forceMove(get_turf(S)) // Put the mob on the landmark, instead of inside it - occupant.enter_vr(avatar) + if(spawn_with_clothing) + job_master.EquipRank(avatar,"Visitor", 1, FALSE) + add_verb(avatar,/mob/living/carbon/human/proc/exit_vr) + add_verb(avatar,/mob/living/carbon/human/proc/vr_transform_into_mob) + add_verb(avatar,/mob/living/proc/set_size) + avatar.virtual_reality_mob = TRUE + //This handles all the 'We make it look like ourself' code. + //We do this BEFORE any mob tf so prefs carry over properly! if(perfect_replica) avatar.species.create_organs(avatar) // Reset our organs/limbs. avatar.restore_all_organs() @@ -276,13 +304,25 @@ avatar.sync_organ_dna() avatar.initialize_vessel() + if(tf) + var/mob/living/new_form = avatar.transform_into_mob(tf, TRUE) // No need to check prefs when the occupant already chose to transform. + if(isliving(new_form)) // Make sure the mob spawned properly. + add_verb(new_form,/mob/living/proc/vr_revert_mob_tf) + new_form.virtual_reality_mob = TRUE + add_verb(avatar, /mob/living/carbon/human/proc/exit_vr) //ahealing removes the prommie verbs and the VR verbs, giving it back avatar.Sleeping(1) // Prompt for username after they've enterred the body. var/newname = sanitize(tgui_input_text(avatar, "You are entering virtual reality. Your username is currently [src.name]. Would you like to change it to something else?", "Name change", null, MAX_NAME_LEN), MAX_NAME_LEN) - if (newname) + if(newname) avatar.real_name = newname + avatar.name = newname else + // If TFed, revert TF. Easier than coding mind transfer stuff for edge cases. + if(avatar.tfed_into_mob_check()) + var/mob/living/M = loc + if(istype(M)) // Sanity check, though shouldn't be needed since this is already checked by the proc. + M.revert_mob_tf() occupant.enter_vr(avatar) diff --git a/code/game/machinery/virtual_reality/vr_procs.dm b/code/game/machinery/virtual_reality/vr_procs.dm new file mode 100644 index 0000000000..0afd9e562e --- /dev/null +++ b/code/game/machinery/virtual_reality/vr_procs.dm @@ -0,0 +1,120 @@ +// Gross proc which is called on Life() to check for escaped VR mobs. Tried to do this with Exited() on area/vr but ended up being too heavy. +/mob/living/proc/handle_vr_derez() + if(virtual_reality_mob && !istype(get_area(src), /area/vr)) + log_debug("[src] escaped virtual reality") + visible_message("[src] blinks out of existence.") + return_from_vr() + for(var/obj/belly/B in vore_organs) // Assume anybody inside an escaped VR mob is also an escaped VR mob. + for(var/mob/living/L in B) + log_debug("[L] was inside an escaped VR mob and has been deleted.") + L.handle_vr_derez() //Recursive! Let's get EVERYONE properly out of here! + if(!QDELETED(L)) //This is so we don't double qdel() things when we're doing recursive removal. + qdel(L) + qdel(src) // Would like to convert escaped players into AR holograms in the future to encourage exploit finding. + +// This proc checks to see two things: 1. If we have a tf_mob_holder (we are a simple mob) and 2. If we are a human. If so, we try to exit VR properly. +/mob/living/proc/return_from_vr() + if(tf_mob_holder) + var/mob/living/carbon/human/our_holder = tf_mob_holder + our_holder.exit_vr() //If we have no vr_holder, perfect, we do nothing. If we DO, we get shoved back into our body. + else if(ishuman(src)) //Alright! We don't have a tf_mob_holder, so we must be a human in VR. + var/mob/living/carbon/human/our_holder = src + our_holder.exit_vr() + +/mob/living/carbon/human/proc/vr_transform_into_mob() + set name = "Transform Into Creature" + set category = "Abilities.VR" + set desc = "Become a different creature" + + var/tf = null + var/k = tgui_input_list(usr, "Please select a creature:", "Mob list", vr_mob_tf_options) + if(!k) + return 0 + tf = vr_mob_tf_options[k] + + var/mob/living/new_form = transform_into_mob(tf, TRUE, TRUE) + if(isliving(new_form)) // Sanity check + add_verb(new_form,/mob/living/proc/vr_revert_mob_tf) + new_form.virtual_reality_mob = TRUE + +/mob/living/proc/vr_revert_mob_tf() + set name = "Revert Transformation" + set category = "Abilities.VR" + + revert_mob_tf() + +// Exiting VR but for ghosts +/mob/living/carbon/human/proc/fake_exit_vr() + set name = "Log Out Of Virtual Reality" + set category = "Abilities.VR" + + if(tgui_alert(usr, "Would you like to log out of virtual reality?", "Log out?", list("Yes", "No")) == "Yes") + release_vore_contents(TRUE) + for(var/obj/item/I in src) + drop_from_inventory(I) + qdel(src) + +/mob/observer/dead/verb/fake_enter_vr() + set name = "Join virtual reality" + set category = "Ghost.Join" + set desc = "Log into NanoTrasen's local virtual reality server." + +/* Temp removal while I figure out how to reduce the respawn time to 1 minute + var/time_till_respawn = time_till_respawn() + if(time_till_respawn == -1) // Special case, never allowed to respawn + to_chat(usr, span_warning("Respawning is not allowed!")) + return + if(time_till_respawn) // Nonzero time to respawn + to_chat(usr, span_warning("You can't do that yet! You died too recently. You need to wait another [round(time_till_respawn/10/60, 0.1)] minutes.")) + return +*/ + var/datum/data/record/record_found + record_found = find_general_record("name", client.prefs.real_name) + // Found their record, they were spawned previously. Remind them corpses cannot play games. + if(record_found) + var/answer = tgui_alert(src,"You seem to have previously joined this round. If you are currently dead, you should not enter VR as this character. Would you still like to proceed?","Previously spawned",list("Yes","No")) + if(answer != "Yes") + return + + var/S = null + var/list/vr_landmarks = list() + for(var/obj/effect/landmark/virtual_reality/sloc in landmarks_list) + vr_landmarks += sloc.name + if(!LAZYLEN(vr_landmarks)) + to_chat(src, "There are no available spawn locations in virtual reality.") + return + S = tgui_input_list(usr, "Please select a location to spawn your avatar at:", "Spawn location", vr_landmarks) + if(!S) + return 0 + for(var/obj/effect/landmark/virtual_reality/i in landmarks_list) + if(i.name == S) + S = i + break + + var/mob/living/carbon/human/avatar = new(get_turf(S), client.prefs.species) + if(!avatar) + to_chat(src, "Something went wrong and spawning failed.") + return + + //Write the appearance and whatnot out to the character + var/client/C = client + C.prefs.copy_to(avatar) + avatar.key = key + for(var/lang in C.prefs.alternate_languages) + var/datum/language/chosen_language = GLOB.all_languages[lang] + if(chosen_language) + if(is_lang_whitelisted(usr,chosen_language) || (avatar.species && (chosen_language.name in avatar.species.secondary_langs))) + avatar.add_language(lang) + + avatar.regenerate_icons() + avatar.update_transform() + job_master.EquipRank(avatar,JOB_VR, 1, FALSE) + add_verb(avatar,/mob/living/carbon/human/proc/fake_exit_vr) + add_verb(avatar,/mob/living/carbon/human/proc/vr_transform_into_mob) + add_verb(avatar,/mob/living/proc/set_size) + avatar.virtual_reality_mob = TRUE + log_and_message_admins("[key_name_admin(avatar)] joined virtual reality from the ghost menu.") + + var/newname = sanitize(tgui_input_text(avatar, "You are entering virtual reality. Your username is currently [src.name]. Would you like to change it to something else?", "Name change", null, MAX_NAME_LEN), MAX_NAME_LEN) + if(newname) + avatar.real_name = newname diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index d6b6b0a287..047c774d41 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -586,3 +586,30 @@ steam.start() -- spawns the effect round(min(light, BOMBCAP_LIGHT_RADIUS)), round(min(flash, BOMBCAP_FLASH_RADIUS)) ) + +/obj/effect/effect/teleport_greyscale + name = "teleportation" + icon = 'icons/effects/effects.dmi' + icon_state = "teleport_greyscale" + anchored = 1 + mouse_opacity = 0 + plane = MOB_PLANE + layer = ABOVE_MOB_LAYER + +/obj/effect/effect/teleport_greyscale/Initialize(mapload) + . = ..() + QDEL_IN(src, 2 SECONDS) + +/datum/effect/effect/system/teleport_greyscale + var/color = "#FFFFFF" + +/datum/effect/effect/system/teleport_greyscale/set_up(cl, loca) + if(istype(loca, /turf/)) + location = loca + else + location = get_turf(loca) + color = cl + +/datum/effect/effect/system/teleport_greyscale/start() + var/obj/effect/effect/teleport_greyscale/tele = new /obj/effect/effect/teleport_greyscale(src.location) + tele.color = color diff --git a/code/modules/client/preference_setup/vore/04_resleeving.dm b/code/modules/client/preference_setup/vore/04_resleeving.dm index 1185288552..43835334fe 100644 --- a/code/modules/client/preference_setup/vore/04_resleeving.dm +++ b/code/modules/client/preference_setup/vore/04_resleeving.dm @@ -29,13 +29,14 @@ spawn(50) if(QDELETED(character) || QDELETED(pref)) return // They might have been deleted during the wait - if(pref.resleeve_scan) - var/datum/transhuman/body_record/BR = new() - BR.init_from_mob(character, pref.resleeve_scan, pref.resleeve_lock) - if(pref.mind_scan) - var/datum/transcore_db/our_db = SStranscore.db_by_key(null) - if(our_db) - our_db.m_backup(character.mind,character.nif,one_time = TRUE) + if(!character.virtual_reality_mob && !(/mob/living/carbon/human/proc/exit_vr in character.verbs)) //Janky fix to prevent resleeving VR avatars but beats refactoring transcore + if(pref.resleeve_scan) + var/datum/transhuman/body_record/BR = new() + BR.init_from_mob(character, pref.resleeve_scan, pref.resleeve_lock) + if(pref.mind_scan) + var/datum/transcore_db/our_db = SStranscore.db_by_key(null) + if(our_db) + our_db.m_backup(character.mind,character.nif,one_time = TRUE) if(pref.resleeve_lock) character.resleeve_lock = character.ckey character.original_player = character.ckey diff --git a/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm b/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm index e314a8217a..483f4f670d 100644 --- a/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm +++ b/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm @@ -18,10 +18,10 @@ assisted_langs = list() - male_cough_sounds = list('sound/effects/mob_effects/m_cougha.ogg','sound/effects/mob_effects/m_coughb.ogg', 'sound/effects/mob_effects/m_coughc.ogg') - female_cough_sounds = list('sound/effects/mob_effects/f_cougha.ogg','sound/effects/mob_effects/f_coughb.ogg') - male_sneeze_sound = 'sound/effects/mob_effects/sneeze.ogg' - female_sneeze_sound = 'sound/effects/mob_effects/f_sneeze.ogg' + // male_cough_sounds = list('sound/effects/mob_effects/m_cougha.ogg','sound/effects/mob_effects/m_coughb.ogg', 'sound/effects/mob_effects/m_coughc.ogg') + // female_cough_sounds = list('sound/effects/mob_effects/f_cougha.ogg','sound/effects/mob_effects/f_coughb.ogg') + // male_sneeze_sound = 'sound/effects/mob_effects/sneeze.ogg' + // female_sneeze_sound = 'sound/effects/mob_effects/f_sneeze.ogg' valid_transform_species = list(SPECIES_HUMAN, SPECIES_HUMAN_VATBORN, SPECIES_UNATHI, SPECIES_TAJARAN, SPECIES_SKRELL, SPECIES_DIONA, SPECIES_TESHARI, SPECIES_VOX, SPECIES_MONKEY, SPECIES_SKELETON) @@ -80,7 +80,6 @@ src.vr_link = avatar // Can't reuse vr_holder so that death can automatically eject users from VR // Move the mind - avatar.Sleeping(1) src.mind.transfer_to(avatar) to_chat(avatar, span_infoplain(span_bold("You have enterred Virtual Reality!\nAll normal gameplay rules still apply.\nWounds you suffer here won't persist when you leave VR, but some of the pain will.\nYou can leave VR at any time by using the \"Exit Virtual Reality\" verb in the Abilities tab, or by ghosting."))) //No more prommie VR thing, so removed tidbit about changing appearance to_chat(avatar, span_notice(" You black out for a moment, and wake to find yourself in a new body in virtual reality.")) // So this is what VR feels like? @@ -92,6 +91,11 @@ if(!vr_holder) return + if(tfed_into_mob_check()) //Make sure we're not TFed and revert if we are before checking for a mind. + var/mob/living/M = loc + if(istype(M)) // Sanity check, though shouldn't be needed since this is already checked by the proc. + M.revert_mob_tf() + if(!mind) return @@ -118,4 +122,16 @@ if(istype(vr_holder.loc, /obj/machinery/vr_sleeper)) var/obj/machinery/vr_sleeper/V = vr_holder.loc - V.go_out() + V.go_out(TRUE) + + if(died_in_vr) + addtimer(CALLBACK(src, PROC_REF(cleanup_vr)), 3000, TIMER_DELETE_ME) //Delete the body after 5 minutes + +/mob/living/carbon/human/proc/cleanup_vr() + var/list/slots = list(slot_back,slot_handcuffed,slot_l_store,slot_r_store,slot_wear_mask,slot_l_hand,slot_r_hand,slot_wear_id,slot_glasses,slot_gloves,slot_head,slot_shoes,slot_belt,slot_wear_suit,slot_w_uniform,slot_s_store,slot_l_ear,slot_r_ear) + for(var/slot in slots) + var/obj/item/I = get_equipped_item(slot = slot) + if(I) + unEquip(I,force = TRUE) + release_vore_contents(include_absorbed = TRUE, silent = TRUE) + qdel(src) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 14ab1c56cd..26958e1001 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -86,6 +86,8 @@ handle_dripping() + handle_vr_derez() + /mob/living/proc/handle_breathing() return diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 07da61030d..7f2772078c 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1421,12 +1421,12 @@ drop.drips |= drips // Update appearance. + drop.basecolor = rgb(H.r_skin,H.g_skin,H.b_skin) + drop.update_icon() drop.name = "drips of something" drop.desc = "It's thick and gooey. Perhaps it's the chef's cooking?" drop.dryname = "dried something" drop.drydesc = "It's dry and crusty. The janitor isn't doing their job." - drop.basecolor = rgb(H.r_skin,H.g_skin,H.b_skin) - drop.update_icon() drop.fluorescent = 0 drop.invisibility = 0 //else diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index de04366b18..c4344676f8 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -107,3 +107,8 @@ ) var/touch_reaction_flags + + var/virtual_reality_mob = FALSE // gross boolean for keeping VR mobs in VR + + var/mob/living/tf_form // Shapeshifter shenanigans + var/tf_form_ckey diff --git a/code/modules/vore/mob_tf.dm b/code/modules/vore/mob_tf.dm index b2403e4e50..bd6dc08589 100644 --- a/code/modules/vore/mob_tf.dm +++ b/code/modules/vore/mob_tf.dm @@ -11,6 +11,7 @@ if(!S.voremob_loaded) S.voremob_loaded = TRUE S.init_vore() + new /obj/effect/effect/teleport_greyscale(M.loc) for(var/obj/belly/B as anything in src.vore_organs) src.vore_organs -= B qdel(B) @@ -67,6 +68,37 @@ var/mob/living/ourmob = tf_mob_holder if(soulgem) //Should always be the case, but...Safety. Done here first soulgem.transfer_self(ourmob) + if(ourmob.loc != src) + if(isnull(ourmob.loc)) + to_chat(src,span_notice("You have no body.")) + tf_mob_holder = null + return + if(istype(ourmob.loc, /mob/living)) //Check for if body was transformed + ourmob = ourmob.loc + if(ourmob.ckey) + if(ourmob.tf_mob_holder && ourmob.tf_mob_holder == src) + //Body Swap + var/datum/mind/ourmind = src.mind + var/datum/mind/theirmind = ourmob.mind + ourmob.ghostize() + src.ghostize() + ourmob.mind = null + src.mind = null + ourmind.current = null + theirmind.current = null + ourmind.active = TRUE + ourmind.transfer_to(ourmob) + theirmind.active = TRUE + theirmind.transfer_to(src) + ourmob.tf_mob_holder = null + src.tf_mob_holder = null + else + to_chat(src,span_notice("Your body appears to be in someone else's control.")) + return + src.mind.transfer_to(ourmob) + tf_mob_holder = null + return + new /obj/effect/effect/teleport_greyscale(src.loc) if(ourmob.ai_holder) var/datum/ai_holder/our_AI = ourmob.ai_holder our_AI.set_stance(STANCE_IDLE) @@ -75,14 +107,15 @@ var/turf/get_dat_turf = get_turf(src) ourmob.loc = get_dat_turf ourmob.forceMove(get_dat_turf) - ourmob.vore_selected = vore_selected - vore_selected = null - for(var/obj/belly/B as anything in vore_organs) - B.loc = ourmob - B.forceMove(ourmob) - B.owner = ourmob - vore_organs -= B - ourmob.vore_organs += B + if(!tf_form_ckey) + ourmob.vore_selected = vore_selected + vore_selected = null + for(var/obj/belly/B as anything in vore_organs) + B.loc = ourmob + B.forceMove(ourmob) + B.owner = ourmob + vore_organs -= B + ourmob.vore_organs += B ourmob.Life(1) @@ -92,7 +125,15 @@ continue src.drop_from_inventory(W) - qdel(src) + if(tf_form == ourmob) + if(tf_form_ckey) + src.ckey = tf_form_ckey + else + src.mind = null + ourmob.tf_form = src + src.forceMove(ourmob) + else + qdel(src) /mob/living/proc/handle_tf_holder() if(!tf_mob_holder) @@ -161,3 +202,118 @@ new_mob.no_latejoin_prey_warning_persists = no_latejoin_prey_warning_persists new_mob.belly_rub_target = belly_rub_target new_mob.soulcatcher_pref_flags = soulcatcher_pref_flags + +// Requires a /mob/living type path for transformation. Returns the new mob on success, null in all other cases. +// Just handles mob TF right now, but maybe we'll want to do something similar for items in the future. +/mob/living/proc/transform_into_mob(mob/living/new_form, pref_override = FALSE, revert = FALSE, shapeshifting = FALSE) + if(!src.mind) + return + if(!src.allow_spontaneous_tf && !pref_override) + return + if(src.tf_mob_holder) //If we're already transformed + if(revert) + revert_mob_tf() + return + else + return + else + if(src.stat == DEAD) + return + if(!ispath(new_form, /mob/living) && !ismob(new_form)) + return + var/mob/living/new_mob + if(shapeshifting && src.tf_form) + new_mob = src.tf_form + add_verb(new_mob,/mob/living/proc/shapeshift_form) + new_mob.tf_form = src + new_mob.forceMove(src.loc) + visible_message(span_warning("[src] twists and contorts, shapeshifting into a different form!")) + if(new_mob.ckey) + new_mob.tf_form_ckey = new_mob.ckey + else + new_mob = new new_form(get_turf(src)) + + if(new_mob && isliving(new_mob)) + new_mob.faction = src.faction + if(istype(new_mob, /mob/living/simple_mob)) + var/mob/living/simple_mob/S = new_mob + if(!S.voremob_loaded) + S.voremob_loaded = TRUE + S.init_vore() + new /obj/effect/effect/teleport_greyscale(src.loc) + if(!new_mob.ckey) + for(var/obj/belly/B as anything in new_mob.vore_organs) + new_mob.vore_organs -= B + qdel(B) + new_mob.vore_organs = list() + new_mob.name = src.name + new_mob.real_name = src.real_name + for(var/lang in src.languages) + new_mob.languages |= lang + src.copy_vore_prefs_to_mob(new_mob) + new_mob.vore_selected = src.vore_selected + if(ishuman(src)) + var/mob/living/carbon/human/H = src + if(ishuman(new_mob)) + var/mob/living/carbon/human/N = new_mob + N.gender = H.gender + N.identifying_gender = H.identifying_gender + else + new_mob.gender = H.gender + else + new_mob.gender = src.gender + if(ishuman(new_mob)) + var/mob/living/carbon/human/N = new_mob + N.identifying_gender = src.gender + + for(var/obj/belly/B as anything in src.vore_organs) + B.loc = new_mob + B.forceMove(new_mob) + B.owner = new_mob + src.vore_organs -= B + new_mob.vore_organs += B + new_mob.nutrition = src.nutrition + + src.soulgem?.transfer_self(new_mob) + + new_mob.ckey = src.ckey + if(new_mob.tf_form_ckey) + src.ckey = new_mob.tf_form_ckey + if(src.ai_holder && new_mob.ai_holder) + var/datum/ai_holder/old_AI = src.ai_holder + old_AI.set_stance(STANCE_SLEEP) + var/datum/ai_holder/new_AI = new_mob.ai_holder + new_AI.hostile = old_AI.hostile + new_AI.retaliate = old_AI.retaliate + src.loc = new_mob + src.forceMove(new_mob) + new_mob.tf_mob_holder = src + return new_mob + +// Used to check if THIS MOB has been transformed into a different mob, as only the NEW mob uses tf_mob_holder. +// Necessary in niche cases where a proc interacts with the old body and needs to know it's been transformed (such as transforming into a mob then dying in virtual reality). +// Use this if you cannot use the tf_mob_holder var. Returns TRUE if transformed, FALSE if not. +/mob/living/proc/tfed_into_mob_check() + if(loc && isliving(loc)) + var/mob/living/M = loc + if(istype(M) && M.tf_mob_holder && (M.tf_mob_holder == src)) + return TRUE + else + return FALSE + else + return FALSE + +/mob/living/proc/shapeshift_form() + set name = "Shapeshift Form" + set category = "Abilities.Shapeshift" + set desc = "Shape shift between set mob forms. (Requires a spawned mob to be varedited into the user's tf_form var as mob reference.)" + if(!istype(tf_form)) + to_chat(src, span_notice("No shapeshift form set. (Requires a spawned mob to be varedited into the user's tf_form var as mob reference.)")) + return + else + transform_into_mob(tf_form, TRUE, TRUE, TRUE) + +/mob/living/set_dir(var/new_dir) + . = ..() + if(size_multiplier != 1 || icon_scale_x != 1 && center_offset > 0) + update_transform(TRUE) diff --git a/code/modules/vore/mouseray.dm b/code/modules/vore/mouseray.dm index 1e71d41a3b..dd2e1b2a00 100644 --- a/code/modules/vore/mouseray.dm +++ b/code/modules/vore/mouseray.dm @@ -85,23 +85,30 @@ var/mob/living/M = target if(!istype(M)) return + if(istype(M,/mob/living/carbon) && !M.mind) + return if(target != firer) //If you shot yourself, you probably want to be TFed so don't bother with prefs. if(!M.allow_spontaneous_tf && !tf_admin_pref_override) return - if(M.tf_mob_holder) + M.drop_both_hands() + if(M.tf_mob_holder && M.tf_mob_holder.loc == M) + new /obj/effect/effect/teleport_greyscale(M.loc) var/mob/living/ourmob = M.tf_mob_holder if(ourmob.ai_holder) var/datum/ai_holder/our_AI = ourmob.ai_holder our_AI.set_stance(STANCE_IDLE) M.tf_mob_holder = null - ourmob.ckey = M.ckey var/turf/get_dat_turf = get_turf(target) ourmob.loc = get_dat_turf ourmob.forceMove(get_dat_turf) - ourmob.vore_selected = M.vore_selected - M.vore_selected = null - ourmob.mob_belly_transfer(M) - M.soulgem.transfer_self(ourmob) // Soulcatcher + if(!M.tf_form_ckey) + ourmob.vore_selected = M.vore_selected + M.vore_selected = null + ourmob.mob_belly_transfer(M) + ourmob.nutrition = M.nutrition + M.soulgem.transfer_self(ourmob) + + ourmob.ckey = M.ckey ourmob.Life(1) if(ishuman(M)) @@ -110,13 +117,20 @@ continue M.drop_from_inventory(W) - qdel(target) + if(M.tf_form == ourmob) + if(M.tf_form_ckey) + M.ckey = M.tf_form_ckey + else + M.mind = null + ourmob.tf_form = M + M.forceMove(ourmob) + else + qdel(target) return else if(M.stat == DEAD) //We can let it undo the TF, because the person will be dead, but otherwise things get weird. return var/mob/living/new_mob = spawn_mob(M) - new_mob.faction = M.faction new_mob.mob_tf(M) @@ -182,19 +196,21 @@ var/datum/ai_holder/our_AI = ourmob.ai_holder our_AI.set_stance(STANCE_IDLE) M.tf_mob_holder = null - ourmob.ckey = M.ckey var/turf/get_dat_turf = get_turf(target) ourmob.loc = get_dat_turf ourmob.forceMove(get_dat_turf) - ourmob.vore_selected = M.vore_selected - M.vore_selected = null - for(var/obj/belly/B as anything in M.vore_organs) - B.loc = ourmob - B.forceMove(ourmob) - B.owner = ourmob - M.vore_organs -= B - ourmob.vore_organs += B - M.soulgem.transfer_self(ourmob) // Soulcatcher + if(!M.tf_form_ckey) + ourmob.vore_selected = M.vore_selected + M.vore_selected = null + for(var/obj/belly/B as anything in M.vore_organs) + B.loc = ourmob + B.forceMove(ourmob) + B.owner = ourmob + M.vore_organs -= B + ourmob.vore_organs += B + ourmob.nutrition = M.nutrition + M.soulgem.transfer_self(ourmob) + ourmob.ckey = M.ckey ourmob.Life(1) @@ -204,7 +220,15 @@ continue M.drop_from_inventory(W) - qdel(target) + if(M.tf_form == ourmob) + if(M.tf_form_ckey) + M.ckey = M.tf_form_ckey + else + M.mind = null + ourmob.tf_form = M + M.forceMove(ourmob) + else + qdel(target) firer.visible_message(span_notice("\The [shot_from] boops pleasantly.")) return else diff --git a/icons/mob/screen_ghost.dmi b/icons/mob/screen_ghost.dmi index 583dc6dc08..31eb23dcde 100644 Binary files a/icons/mob/screen_ghost.dmi and b/icons/mob/screen_ghost.dmi differ diff --git a/maps/virtual_reality/constructVR.dm b/maps/virtual_reality/constructVR.dm new file mode 100644 index 0000000000..96b23ea387 --- /dev/null +++ b/maps/virtual_reality/constructVR.dm @@ -0,0 +1,247 @@ +// -- Areas -- // + +/area/vr + name = "VR world" + icon_state = "green" + base_turf = /turf/simulated/floor/outdoors/dirt + flags = AREA_ALLOW_LARGE_SIZE | AREA_LIMIT_DARK_RESPITE + +/area/vr/space + name = "VR Space" //copypasta time + icon_state = "space" + requires_power = 1 + always_unpowered = 1 + dynamic_lighting = 0 + has_gravity = 0 + power_light = 0 + power_equip = 0 + power_environ = 0 + ambience = list('sound/ambience/ambispace.ogg','sound/music/title2.ogg','sound/music/space.ogg','sound/music/main.ogg','sound/music/traitor.ogg','sound/ambience/space/space_serithi.ogg','sound/music/freefallin.mid') + base_turf = /turf/space + ambience = AMBIENCE_SPACE + flags = AREA_FLAG_IS_NOT_PERSISTENT | AREA_ALLOW_LARGE_SIZE | AREA_LIMIT_DARK_RESPITE + +/area/space/atmosalert() + return + +/area/space/fire_alert() + return + +/area/space/fire_reset() + return + +/area/space/readyalert() + return + +/area/space/partyalert() + return + + +/area/vr/outdoors + outdoors = OUTDOORS_YES + ambience = AMBIENCE_SIF + +/area/vr/powered + name = "Powered VR World Inside" + icon_state = "away1" + requires_power = 0 + power_equip = 1 + power_environ = 1 + power_light = 1 + +/area/vr/powered/rocks + name = "VR World Underground" + icon_state = "darkred" + +/area/vr/outdoors/powered + name = "Powered VR World Outside" + icon_state = "away" + requires_power = 0 + power_equip = 1 + power_environ = 1 + power_light = 1 + +/area/vr/powered/bluebase + name = "Blue Base" + icon_state = "bluenew" + +/area/vr/powered/redbase + name = "Red Base" + icon_state = "red" + +/area/vr/outdoors/powered/lava + name = "VR Lava Bridge" + +/area/vr/powered/vendor + name = "VR Vendor Room" + icon_state = "green" + +/area/vr/powered/material + name = "VR Material Room" + icon_state = "green" + +/area/vr/powered/building1 + name = "VR Building 1" + icon_state = "green" + +/area/vr/powered/building2 + name = "VR Building 2" + icon_state = "green" + +/area/vr/powered/conspawn + name = "VR Construct Spawn" + icon_state = "green" + +/area/vr/powered/armory + name = "VR Armory" + icon_state = "firingrange" + +/area/vr/powered/cult + name = "VR Cultist Base" + icon_state = "green" + +/area/vr/outdoors/powered/cult + name = "Cultist Outside" + ambience = AMBIENCE_UNHOLY + +//City section +/area/vr/powered/bar + name = "VR Bar" + icon_state = "green" + +/area/vr/powered/cafe + name = "VR Ghost Cafe" + icon_state = "green" + +/area/vr/powered/gunshop + name = "VR GunShop" + icon_state = "green" + +/area/vr/powered/vet + name = "VR Vet" + icon_state = "green" + +/area/vr/powered/shop + name = "VR Shop" + icon_state = "green" + +/area/vr/powered/art + name = "VR Art Gallery" + icon_state = "green" + +/area/vr/powered/constore + name = "VR Convenience Store" + icon_state = "green" + +/area/vr/powered/nuke + name = "VR Violation" + icon_state = "green" + +/area/vr/powered/motel + name = "VR Motel" + icon_state = "green" + +/area/vr/powered/dungeon + name = "VR Dungeon" + icon_state = "darkred" + +/area/vr/powered/mall + name = "VR Mall" + icon_state = "yellow" + +/area/vr/powered/mall/vw + name = "VR Vibe" + icon_state = "pink" + ambience = list('sound/ambience/vaporwave.ogg') + +/area/vr/powered/mall/secondlife + name = "VR Thirdlife Bar" + icon_state = "pink" + ambience = list(AMBIENCE_GENERIC) + +/area/vr/powered/mall/dorms + name = "VR Mall Dorms" + icon_state = "Sleep" + flags = RAD_SHIELDED | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_LIMIT_DARK_RESPITE + +/area/vr/powered/mall/dorms/dorms1 + +/area/vr/powered/mall/dorms/dorms2 + +/area/vr/powered/mall/dorms/dorms3 + +/area/vr/powered/mall/dorms/dorms4 + +/area/vr/powered/mall/dorms/dorms5 + +/area/vr/powered/mall/dorms/dorms6 + +/area/vr/powered/mall/dorms/secondlife + name = "VR Third-Life Dorms" + +/area/vr/powered/mall/dorms/secondlife2 + +/area/vr/powered/mall/dorms/secondlife3 + +/area/vr/powered/mall/dorms/secondlife4 + +/area/vr/powered/mall/dorms/secondlife5 + + +/area/vr/outdoors/powered/mall + name = "VR Mall Outdoors" + icon_state = "bluenew" + +/area/vr/powered/mall/backrooms + name = "VR ???" + icon_state = "darkred" + forced_ambience = list('sound/ambience/fluorescentlight.ogg') + +/area/vr/powered/dungeon/indoors + name = "VR Sewers" + icon_state = "red" + +/area/vr/powered/space + name = "VR Space Vessel" + icon_state = "away2" + base_turf = /turf/space +/area/vr/powered/space/whiteship + name = "VR Whiteship" + icon_state = "green" + +/area/vr/powered/space/sciship + name = "VR Sci Ship" + icon_state = "green" + +/area/vr/powered/space/mechfactory + name = "VR Mech Factory" + icon_state = "green" + +// -- Objects -- // +/obj/effect/fake_sun/always_day + name = "fake sun" + advanced_lighting = TRUE + desc = "Deletes itself, but first updates all the lighting on outdoor turfs to cool colors." + possible_light_setups = list( + + list( + "brightness" = 6.0, + "color" = "#ABfff7" + ), + list( + "brightness" = 4.0, + "color" = "#F4EA55" + ), + list( + "brightness" = 2.5, + "color" = "#EE9AC6" + ), + list( + "brightness" = 1.0, + "color" = "#F07AD8" + ) + ) + +// VR EXCLUSIVE ITEMS +/obj/item/reagent_containers/glass/beaker/mayo + prefill = list("mayo" = 30) //;) diff --git a/sound/ambience/fluorescentlight.ogg b/sound/ambience/fluorescentlight.ogg new file mode 100644 index 0000000000..a49ffc3e06 Binary files /dev/null and b/sound/ambience/fluorescentlight.ogg differ diff --git a/sound/music/freefallin.mid b/sound/music/freefallin.mid new file mode 100644 index 0000000000..acde78101d Binary files /dev/null and b/sound/music/freefallin.mid differ diff --git a/vorestation.dme b/vorestation.dme index 9d56849b51..19b49c033f 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -1150,6 +1150,7 @@ #include "code\game\machinery\telecomms\traffic_control.dm" #include "code\game\machinery\virtual_reality\ar_console.dm" #include "code\game\machinery\virtual_reality\vr_console.dm" +#include "code\game\machinery\virtual_reality\vr_procs.dm" #include "code\game\magic\Uristrunes.dm" #include "code\game\mecha\mech_bay.dm" #include "code\game\mecha\mech_fabricator.dm" @@ -4559,6 +4560,7 @@ #include "maps\tether\tether_phoronlock.dm" #include "maps\tether\tether_shuttle_defs.dm" #include "maps\tether\tether_telecomm_defs.dm" +#include "maps\virtual_reality\constructVR.dm" #include "maps\~map_system\_map_selection.dm" #include "maps\~map_system\maps.dm" // END_INCLUDE