diff --git a/code/__defines/nifsoft.dm b/code/__defines/nifsoft.dm
index cca614f1fb4..114ee327794 100644
--- a/code/__defines/nifsoft.dm
+++ b/code/__defines/nifsoft.dm
@@ -40,9 +40,10 @@
#define NIF_SIZECHANGE 33
#define NIF_SOULCATCHER 34
#define NIF_WORLDBEND 35
+#define NIF_MALWARE 36
// Must be equal to the highest number above
-#define TOTAL_NIF_SOFTWARE 35
+#define TOTAL_NIF_SOFTWARE 36
//////////////////////
// NIF flag list hints
diff --git a/code/_onclick/hud/popups_vr.dm b/code/_onclick/hud/popups_vr.dm
new file mode 100644
index 00000000000..38864717153
--- /dev/null
+++ b/code/_onclick/hud/popups_vr.dm
@@ -0,0 +1,70 @@
+/obj/screen/popup
+ name = "popup"
+ desc = "NOTICE ME!"
+
+ icon = 'icons/mob/screen1_popups.dmi'
+ layer = INFINITY
+
+ var/close_button_x_start
+ var/close_button_x_end
+ var/close_button_y_start
+ var/close_button_y_end
+
+ var/client/holder
+
+/obj/screen/popup/Click(location, control,params)
+ var/list/PL = params2list(params)
+ var/icon_x = text2num(PL["icon-x"])
+ var/icon_y = text2num(PL["icon-y"])
+ if(check_click_spot(icon_x, icon_y))
+ close_popup()
+ else
+ popup_action()
+
+/obj/screen/popup/proc/popup_action()
+ return
+
+/obj/screen/popup/proc/close_popup()
+ holder.screen -= src
+ qdel(src)
+
+/obj/screen/popup/proc/check_click_spot(click_x, click_y)
+ if((click_x <= close_button_x_end) && (click_x >= close_button_x_start))
+ if((click_y <= close_button_y_end) && (click_y >= close_button_y_start))
+ return TRUE
+ return FALSE
+
+/obj/screen/popup/proc/get_random_screen_location()
+ var/loc_x = rand(1,11)
+ var/loc_x_offset = rand(0,16)
+ var/loc_y = rand(1,12)
+ var/loc_y_offset = rand(0,16)
+ return "[loc_x]:[loc_x_offset],[loc_y]:[loc_y_offset]"
+
+/client/proc/create_fake_ad_popup(popup_type)
+ if(!src)
+ return
+ var/obj/screen/popup/ad = new popup_type()
+ ad.screen_loc = ad.get_random_screen_location()
+ src.screen |= ad
+ ad.holder = src
+
+/client/proc/create_fake_ad_popup_multiple(popup_type, popup_amount)
+ if(!src)
+ return
+ for(var/i = 0, i < popup_amount, i++)
+ create_fake_ad_popup(popup_type)
+
+/obj/screen/popup/default
+ name = "CLICK ME"
+
+ icon_state = "popup1"
+
+ close_button_x_start = 118
+ close_button_x_end = 126
+ close_button_y_start = 86
+ close_button_y_end = 94
+
+/obj/screen/popup/default/New()
+ ..()
+ icon_state = "popup[rand(1,4)]"
\ No newline at end of file
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index c0c23f9420c..d8127b02b9e 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -475,8 +475,8 @@
//Handle job slot/tater cleanup.
var/job = to_despawn.mind.assigned_role
-
job_master.FreeRole(job)
+ to_despawn.mind.assigned_role = null
if(to_despawn.mind.objectives.len)
qdel(to_despawn.mind.objectives)
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index eb2aefeb5c3..c22ddaa46f2 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -24,6 +24,18 @@
icon_state = "smoke"
duration = 50
+/obj/effect/temp_visual/glitch
+ icon_state = "glitch"
+ duration = 5
+
+/obj/effect/temp_visual/confuse
+ icon_state = "confuse"
+ duration = 5
+
+/obj/effect/temp_visual/pre_confuse
+ icon_state = "pre_confuse"
+ duration = 5
+
/obj/effect/temp_visual/impact_effect
icon_state = "impact_bullet"
plane = PLANE_LIGHTING_ABOVE // So they're visible even in a shootout in maint.
diff --git a/code/game/objects/items/falling_object_attack_vr.dm b/code/game/objects/items/falling_object_attack_vr.dm
new file mode 100644
index 00000000000..879fee9fde9
--- /dev/null
+++ b/code/game/objects/items/falling_object_attack_vr.dm
@@ -0,0 +1,45 @@
+/obj/effect/calldown_attack
+ anchored = TRUE
+ density = FALSE
+ unacidable = TRUE
+ mouse_opacity = 0
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "drop_marker"
+
+/obj/effect/calldown_attack/Initialize(mapload)
+ ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/effect/calldown_attack/LateInitialize()
+ var/delay = rand(25, 30)
+ spawn(delay-7)
+ new /obj/effect/falling_effect/calldown_attack(src.loc)
+ spawn(delay)
+ qdel(src)
+
+
+/obj/effect/falling_effect/calldown_attack
+ falling_type = /obj/effect/illusionary_fall
+ crushing = FALSE
+
+
+/obj/effect/illusionary_fall
+ anchored = TRUE
+ density = FALSE
+ mouse_opacity = 0
+ icon = 'icons/effects/random_stuff_vr.dmi'
+
+/obj/effect/illusionary_fall/Initialize(mapload)
+ .=..()
+ icon_state = "[rand(1,33)]"
+
+/obj/effect/illusionary_fall/end_fall(var/crushing = FALSE)
+ for(var/mob/living/L in loc)
+ var/target_zone = ran_zone()
+ var/blocked = L.run_armor_check(target_zone, "melee")
+ var/soaked = L.get_armor_soak(target_zone, "melee")
+
+ if(!L.apply_damage(35, BRUTE, target_zone, blocked, soaked))
+ break
+ playsound(src, 'sound/effects/clang2.ogg', 50, 1)
+ qdel(src)
\ No newline at end of file
diff --git a/code/game/objects/items/falling_object_vr.dm b/code/game/objects/items/falling_object_vr.dm
index 2769b7f0138..f1c6d235bd7 100644
--- a/code/game/objects/items/falling_object_vr.dm
+++ b/code/game/objects/items/falling_object_vr.dm
@@ -8,9 +8,10 @@
var/falling_type = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/margherita
var/crushing = TRUE
-/obj/effect/falling_effect/Initialize(mapload, type = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/margherita)
+/obj/effect/falling_effect/Initialize(mapload, type)
..()
- falling_type = type
+ if(type)
+ falling_type = type
return INITIALIZE_HINT_LATELOAD
/obj/effect/falling_effect/LateInitialize()
diff --git a/code/modules/mob/dead/observer/free_vr.dm b/code/modules/mob/dead/observer/free_vr.dm
index 2cfd6ad024f..a3c63de1976 100644
--- a/code/modules/mob/dead/observer/free_vr.dm
+++ b/code/modules/mob/dead/observer/free_vr.dm
@@ -18,7 +18,7 @@ var/global/list/prevent_respawns = list()
//Why are you clicking this button?
if(!mind || !mind.assigned_role)
- to_chat(src,"Either you haven't played this round, or you already used this verb.")
+ to_chat(src,"Either you haven't played this round, you already used this verb or you left round properly already.")
return
//Add them to the nope list
diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/unknown.dm b/code/modules/mob/living/simple_mob/subtypes/occult/unknown.dm
new file mode 100644
index 00000000000..7ad77018e83
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/occult/unknown.dm
@@ -0,0 +1,347 @@
+#define GA_ADS 0
+#define GA_CALLDOWN 1
+#define GA_SPEEDUP 2
+#define GA_ILLUSION 3
+#define GA_BULLETHELL 4
+#define GA_LINES 5
+#define GA_CONFUSION 6
+
+/mob/living/simple_mob/glitch_boss
+ name = "CLICK ME!!!"
+ desc = "WELCOME TO %location_data% THIS IS YOUR HOME NOW PLEASE INPUT CREDIT CARD CREDENTIALS BELOW"
+ tt_desc = "BEST TOOLBAR PROVIDER SINCE 2098"
+ icon = 'icons/mob/unknown_boss.dmi'
+ icon_state = "glitch_boss"
+ icon_living = "glitch_boss"
+ icon_dead = "glitch_boss_dead"
+
+ faction = "MATH"
+
+ maxHealth = 2000
+ health = 2000
+ evasion = -75 // Its hitbox is broken ;_;
+
+ melee_damage_lower = 20
+ melee_damage_upper = 40
+ attack_armor_pen = 20
+
+ base_attack_cooldown = 2.5 SECONDS
+
+ projectiletype = /obj/item/projectile/energy/slow_orb
+ projectilesound = 'sound/effects/uncloak.ogg'
+
+ special_attack_min_range = 0
+ special_attack_max_range = 10
+ special_attack_cooldown = 20 SECONDS
+ ai_holder_type = /datum/ai_holder/simple_mob/ranged/aggressive/bossmob_glitch
+
+ var/next_special_attack = GA_ADS
+ var/recently_used_attack = GA_SPEEDUP
+ var/all_special_attacks = list(GA_ADS, GA_CALLDOWN, GA_LINES, GA_BULLETHELL, GA_ILLUSION, GA_CONFUSION, GA_SPEEDUP)
+
+ loot_list = list(/obj/item/device/nif/glitch = 100)
+
+/obj/item/projectile/energy/slow_orb
+ name = "TROJAN"
+ icon_state = "glitch"
+ damage = 50
+ speed = 6
+ damage_type = ELECTROCUTE
+ agony = 15
+ check_armour = "energy"
+ armor_penetration = 40
+
+ fire_sound = 'sound/effects/uncloak.ogg'
+ combustion = TRUE
+
+/mob/living/simple_mob/glitch_boss/death(gibbed, deathmessage="suddenly %runtime error in unknown.dm, line 56%")
+ . = ..()
+ new /obj/effect/temp_visual/glitch(get_turf(src))
+ qdel(src)
+
+/mob/living/simple_mob/glitch_boss/updatehealth()
+ . = ..()
+
+ if(health < maxHealth*0.25)
+ special_attack_cooldown = 5 SECONDS
+ icon_state = "glitch_boss_25"
+ icon_living = "glitch_boss_25"
+ else if(health < maxHealth*0.5)
+ special_attack_cooldown = 10 SECONDS
+ icon_state = "glitch_boss_50"
+ icon_living = "glitch_boss_50"
+ else if (health < maxHealth*0.75)
+ special_attack_cooldown = 15 SECONDS
+ icon_state = "glitch_boss_75"
+ icon_living = "glitch_boss_75"
+
+/mob/living/simple_mob/glitch_boss/proc/create_illusions(atom/A)
+ var/list/possible_turfs = list()
+ for(var/turf/T in view(4, src))
+ if(T.density || T == get_turf(src)) // Our turf is always eligible
+ continue
+ var/blocked = FALSE
+ for(var/atom/movable/AM in T)
+ if(AM.density)
+ blocked = TRUE
+ break
+ if(!blocked)
+ possible_turfs += T
+
+ if(possible_turfs.len <= 1)
+ return
+
+ var/illusion_amount = min(possible_turfs.len, 4) // Not including our spot
+ var/list/actual_turfs = list()
+ actual_turfs += get_turf(src)
+ for(var/i = 0, i < illusion_amount, i++)
+ var/turf_to_add = pick(possible_turfs)
+ actual_turfs += turf_to_add
+ possible_turfs -= turf_to_add
+
+ for(var/i = 0, i < illusion_amount, i++)
+ var/chosen_turf = pick(actual_turfs)
+ var/type_to_spawn = prob(15) ? /mob/living/simple_mob/glitch_boss_fake/strong : /mob/living/simple_mob/glitch_boss_fake
+ var/mob/living/simple_mob/newmob = new type_to_spawn(chosen_turf)
+ newmob.icon_living = src.icon_living
+ newmob.icon_state = src.icon_state
+ new /obj/effect/temp_visual/glitch(chosen_turf)
+ actual_turfs -= chosen_turf
+
+ var/move_turf = pick(actual_turfs)
+ src.forceMove(move_turf)
+ new /obj/effect/temp_visual/glitch(move_turf)
+
+/mob/living/simple_mob/glitch_boss/proc/make_ads(atom/A)
+ var/list/potential_targets = list()
+ for(var/mob/living/mob in view(7, src))
+ if(mob.client && mob.faction != faction)
+ potential_targets += mob
+ if(potential_targets.len)
+ var/iteration = clamp(potential_targets.len, 1, 4)
+ for(var/i = 0, i < iteration, i++)
+ if(!(potential_targets.len))
+ break
+ var/mob/target = pick(potential_targets)
+ potential_targets -= target
+ if(target.client)
+ target.client.create_fake_ad_popup_multiple(/obj/screen/popup/default, 5)
+
+/mob/living/simple_mob/glitch_boss/proc/bombardment(atom/A)
+ var/list/potential_targets = ai_holder.list_targets()
+ for(var/atom/entry in potential_targets)
+ if(istype(entry, /mob/living/simple_mob/glitch_boss_fake))
+ potential_targets -= entry
+ if(potential_targets.len)
+ var/iteration = clamp(potential_targets.len, 1, 3)
+ for(var/i = 0, i < iteration, i++)
+ if(!(potential_targets.len))
+ break
+ var/mob/target = pick(potential_targets)
+ potential_targets -= target
+ spawn_bombardments(target)
+
+/mob/living/simple_mob/glitch_boss/proc/spawn_bombardments(atom/target)
+ var/list/bomb_range = block(locate(target.x-1, target.y-1, target.z), locate(target.x+1, target.y+1, target.z))
+ new /obj/effect/calldown_attack(get_turf(target))
+ bomb_range -= get_turf(target)
+ for(var/i = 0, i < 4, i++)
+ var/turf/T = pick(bomb_range)
+ new /obj/effect/calldown_attack(T)
+ bomb_range -= T
+
+/mob/living/simple_mob/glitch_boss/proc/bomb_lines(atom/A)
+ var/list/potential_targets = ai_holder.list_targets()
+ for(var/atom/entry in potential_targets)
+ if(istype(entry, /mob/living/simple_mob/glitch_boss_fake))
+ potential_targets -= entry
+ if(potential_targets.len)
+ var/iteration = clamp(potential_targets.len, 1, 3)
+ for(var/i = 0, i < iteration, i++)
+ if(!(potential_targets.len))
+ break
+ var/mob/target = pick(potential_targets)
+ potential_targets -= target
+ spawn_lines(target)
+
+/mob/living/simple_mob/glitch_boss/proc/spawn_lines(atom/target)
+ var/alignment = rand(1,2) // 1 for vertical, 2 for horizontal
+ var/list/line_range = list()
+ var/turf/T = get_turf(target)
+ line_range += T
+ for(var/i = 1, i <= 7, i++)
+ switch(alignment)
+ if(1)
+ if(T.x-i > 0)
+ line_range += locate(T.x-i, T.y, T.z)
+ if(T.x+i <= world.maxx)
+ line_range += locate(T.x+i, T.y, T.z)
+ if(2)
+ if(T.y-i > 0)
+ line_range += locate(T.x, T.y-i, T.z)
+ if(T.y+i <= world.maxy)
+ line_range += locate(T.x, T.y+i, T.z)
+ for(var/turf/dropspot in line_range)
+ new /obj/effect/calldown_attack(dropspot)
+
+/mob/living/simple_mob/glitch_boss/proc/confuse_inflict(atom/A)
+ var/list/potential_targets = ai_holder.list_targets()
+ for(var/atom/entry in potential_targets)
+ if(istype(entry, /mob/living/simple_mob/glitch_boss_fake))
+ potential_targets -= entry
+ if(potential_targets.len < 2)
+ return
+ potential_targets -= A
+ var/mob/living/target
+ while(!target && potential_targets.len)
+ var/candidate = pick(potential_targets)
+ if(isliving(candidate))
+ target = candidate
+ break
+ else
+ potential_targets -= candidate
+
+ if(target && istype(target))
+ if(target.client)
+ to_chat(target, "You feel as though you are losing your sense of direction! Brace yourself!")
+ new /obj/effect/temp_visual/pre_confuse(get_turf(target))
+ spawn(5 SECONDS)
+ if(target)
+ target.Confuse(3)
+ if(target.client)
+ to_chat(target, "You feel confused!")
+ new /obj/effect/temp_visual/confuse(get_turf(target))
+
+/mob/living/simple_mob/glitch_boss/proc/bullethell(atom/A)
+ set waitfor = FALSE
+
+ var/sd = dir2angle(dir)
+ var/list/offsets = list(45, 45, 20, 10)
+
+ for(var/i = 0, i<4, i++)
+ for(var/j = 0, j <4, j++)
+ var/obj/item/projectile/energy/slow_orb/shot = new(get_turf(src))
+ shot.firer = src
+ shot.fire(sd)
+ sd += 90
+ sd += pick(offsets)
+ sleep(20)
+
+/mob/living/simple_mob/glitch_boss/proc/speed_up_boost(atom/A)
+ if(base_attack_cooldown == initial(base_attack_cooldown))
+ base_attack_cooldown = 1 SECOND
+ var/duration = (special_attack_cooldown == 5 SECONDS) ? 5 SECONDS : 10 SECONDS
+ spawn(duration)
+ base_attack_cooldown = initial(base_attack_cooldown)
+
+/mob/living/simple_mob/glitch_boss/do_special_attack(atom/A)
+ . = TRUE
+ recently_used_attack = next_special_attack
+ switch(next_special_attack)
+ if(GA_ADS)
+ make_ads(A)
+ if(GA_CALLDOWN)
+ bombardment(A)
+ if(GA_LINES)
+ bomb_lines(A)
+ if(GA_BULLETHELL)
+ bullethell(A)
+ if(GA_ILLUSION)
+ create_illusions(A)
+ if(GA_CONFUSION)
+ confuse_inflict(A)
+ if(GA_SPEEDUP)
+ speed_up_boost(A)
+
+/datum/ai_holder/simple_mob/ranged/aggressive/bossmob_glitch
+ wander = TRUE
+ pointblank = TRUE
+ intelligence_level = AI_SMART
+ vision_range = 10
+ closest_distance = 4
+
+/datum/ai_holder/simple_mob/ranged/bossmob_glitch/pre_special_attack(atom/A)
+ var/mob/living/simple_mob/glitch_boss/GB
+ if(istype(holder, /mob/living/simple_mob/glitch_boss))
+ GB = holder
+ if(GB)
+ if(isliving(A) || ismecha(A))
+ var/list/possible_attacks = list()
+ possible_attacks += GB.all_special_attacks - GB.recently_used_attack
+ var/illusion_count = 0
+ var/list/potential_targets = list_targets()
+ for(var/atom/illusion_maybe in potential_targets)
+ if(istype(illusion_maybe, /mob/living/simple_mob/glitch_boss_fake))
+ illusion_count++
+ potential_targets -= illusion_maybe
+ if(potential_targets.len < 2)
+ possible_attacks -= GA_CONFUSION
+ possible_attacks += GA_SPEEDUP // Double chance when fighting single target
+ if(illusion_count > 4)
+ possible_attacks -= GA_ILLUSION
+ if(!(possible_attacks.len))
+ GB.next_special_attack = GA_BULLETHELL
+ else
+ GB.next_special_attack = pick(possible_attacks)
+ else
+ GB.next_special_attack = GA_BULLETHELL
+
+
+
+/mob/living/simple_mob/glitch_boss_fake
+ name = "CLICK ME!!!"
+ desc = "WELCOME TO %location_data% THIS IS YOUR HOME NOW PLEASE INPUT CREDIT CARD CREDENTIALS BELOW"
+ tt_desc = "BEST TOOLBAR PROVIDER SINCE 2098"
+ icon = 'icons/mob/unknown_boss.dmi'
+ icon_state = "glitch_boss"
+ icon_living = "glitch_boss"
+ icon_dead = "glitch_boss_dead"
+ faction = "MATH"
+
+ maxHealth = 20
+ health = 20
+ evasion = -75
+
+ melee_damage_lower = 0
+ melee_damage_upper = 0
+ attack_armor_pen = 0
+
+ base_attack_cooldown = 2.5 SECONDS
+
+ projectiletype = /obj/item/projectile/energy/slow_orb_fake
+ projectilesound = 'sound/effects/uncloak.ogg'
+
+ var/prob_respawn = 15
+
+ ai_holder_type = /datum/ai_holder/simple_mob/ranged/aggressive/bossmob_glitch_fake
+
+/mob/living/simple_mob/glitch_boss_fake/strong
+ maxHealth = 100
+ health = 100
+ prob_respawn = 60
+
+/mob/living/simple_mob/glitch_boss_fake/death(gibbed, deathmessage="disappears in cloud of static.")
+ new /obj/effect/temp_visual/glitch(get_turf(src))
+ if(prob(prob_respawn))
+ new /mob/living/simple_mob/glitch_boss_fake(get_turf(src))
+ qdel(src)
+
+/obj/item/projectile/energy/slow_orb_fake
+ name = "TROJAN"
+ icon_state = "glitch"
+ damage = 0
+ speed = 6
+ damage_type = ELECTROCUTE
+ agony = 0
+ check_armour = "energy"
+ armor_penetration = 0
+
+ fire_sound = 'sound/effects/uncloak.ogg'
+ combustion = TRUE
+
+/datum/ai_holder/simple_mob/ranged/aggressive/bossmob_glitch_fake //Same AI, but without special attack calculation stuff
+ wander = TRUE
+ pointblank = TRUE
+ intelligence_level = AI_SMART
+ vision_range = 9
+ closest_distance = 4
\ No newline at end of file
diff --git a/code/modules/nifsoft/nif.dm b/code/modules/nifsoft/nif.dm
index 4e5937c6898..2f8573dbd0c 100644
--- a/code/modules/nifsoft/nif.dm
+++ b/code/modules/nifsoft/nif.dm
@@ -455,6 +455,10 @@ You can also set the stat of a NIF to NIF_TEMPFAIL without any issues to disable
if(!NS || NS != old_soft)
return FALSE //what??
+ if(!NS.can_uninstall)
+ notify("The software \"[NS]\" refuses to be uninstalled.",TRUE)
+ return FALSE
+
nifsofts[old_soft.list_pos] = null
power_usage -= old_soft.p_drain
@@ -627,6 +631,18 @@ You can also set the stat of a NIF to NIF_TEMPFAIL without any issues to disable
bioadap = TRUE
gib_nodrop = TRUE
+/obj/item/device/nif/glitch
+ name = "weird NIF"
+ desc = "A NIF of a very dubious origin. It seems to be more durable than normal one... But are you sure about this?"
+ durability = 300
+ bioadap = TRUE
+ starting_software = list(
+ /datum/nifsoft/commlink,
+ /datum/nifsoft/soulcatcher,
+ /datum/nifsoft/ar_civ,
+ /datum/nifsoft/malware
+ )
+
////////////////////////////////
// Special Promethean """surgery"""
/obj/item/device/nif/attack(mob/living/M, mob/living/user, var/target_zone)
diff --git a/code/modules/nifsoft/nifsoft.dm b/code/modules/nifsoft/nifsoft.dm
index f4f3135b8ea..06490cf3c54 100644
--- a/code/modules/nifsoft/nifsoft.dm
+++ b/code/modules/nifsoft/nifsoft.dm
@@ -38,6 +38,8 @@
var/vision_flags_mob = 0
var/darkness_view = 0
+ var/can_uninstall = TRUE
+
var/list/planes_enabled = null // List of vision planes this nifsoft enables when active
var/vision_exclusive = FALSE //Whether or not this NIFSoft provides exclusive vision modifier
@@ -67,6 +69,8 @@
//Called when the software is removed from the NIF
/datum/nifsoft/proc/uninstall()
+ if(!can_uninstall)
+ return nif.uninstall(src)
if(nif)
if(active)
deactivate()
diff --git a/code/modules/nifsoft/software/15_misc.dm b/code/modules/nifsoft/software/15_misc.dm
index 8e7101395f7..1c6bfb2e5f6 100644
--- a/code/modules/nifsoft/software/15_misc.dm
+++ b/code/modules/nifsoft/software/15_misc.dm
@@ -173,3 +173,29 @@
var/mob/living/carbon/human/H = human
H.hide_alt_appearance("animals", justme)
alt_farmanimals -= nif.human
+
+/datum/nifsoft/malware
+ name = "Cool Kidz Toolbar"
+ desc = "Best toolbar in business since 2098."
+ list_pos = NIF_MALWARE
+ cost = 1987
+ wear = 0
+ illegal = TRUE
+ vended = FALSE
+ tick_flags = NIF_ALWAYSTICK
+ var/last_ads
+ can_uninstall = FALSE
+
+/datum/nifsoft/malware/activate()
+ if((. = ..()))
+ to_chat(nif.human,"Runtime error in 15_misc.dm, line 189.")
+
+/datum/nifsoft/malware/install()
+ if((. = ..()))
+ last_ads = world.time
+
+/datum/nifsoft/malware/life()
+ if((. = ..()))
+ if(nif.human.client && world.time - last_ads > rand(10 MINUTES, 15 MINUTES) && prob(1))
+ last_ads = world.time
+ nif.human.client.create_fake_ad_popup_multiple(/obj/screen/popup/default, 5)
\ No newline at end of file
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index cea0d0c58f3..5defea72425 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/random_stuff_vr.dmi b/icons/effects/random_stuff_vr.dmi
new file mode 100644
index 00000000000..a089f5033ca
Binary files /dev/null and b/icons/effects/random_stuff_vr.dmi differ
diff --git a/icons/mob/screen1_popups.dmi b/icons/mob/screen1_popups.dmi
new file mode 100644
index 00000000000..cbdbcc7b8bd
Binary files /dev/null and b/icons/mob/screen1_popups.dmi differ
diff --git a/icons/mob/unknown_boss.dmi b/icons/mob/unknown_boss.dmi
new file mode 100644
index 00000000000..e086e0a3f0a
Binary files /dev/null and b/icons/mob/unknown_boss.dmi differ
diff --git a/icons/mob/vore32x64.dmi b/icons/mob/vore32x64.dmi
index d29cb9e6b43..66464ac3c8b 100644
Binary files a/icons/mob/vore32x64.dmi and b/icons/mob/vore32x64.dmi differ
diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi
index b2b6e0a0c79..080f2cd5577 100644
Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ
diff --git a/vorestation.dme b/vorestation.dme
index 7a07b1fa89d..21a1873a244 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -181,6 +181,7 @@
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\other_mobs.dm"
#include "code\_onclick\hud\picture_in_picture.dm"
+#include "code\_onclick\hud\popups_vr.dm"
#include "code\_onclick\hud\radial.dm"
#include "code\_onclick\hud\radial_persistent.dm"
#include "code\_onclick\hud\robot.dm"
@@ -1158,6 +1159,7 @@
#include "code\game\objects\items\contraband.dm"
#include "code\game\objects\items\contraband_vr.dm"
#include "code\game\objects\items\crayons.dm"
+#include "code\game\objects\items\falling_object_attack_vr.dm"
#include "code\game\objects\items\falling_object_vr.dm"
#include "code\game\objects\items\glassjar.dm"
#include "code\game\objects\items\gunbox.dm"
@@ -3114,6 +3116,7 @@
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\ward\ward.dm"
#include "code\modules\mob\living\simple_mob\subtypes\occult\creature.dm"
#include "code\modules\mob\living\simple_mob\subtypes\occult\faithless.dm"
+#include "code\modules\mob\living\simple_mob\subtypes\occult\unknown.dm"
#include "code\modules\mob\living\simple_mob\subtypes\occult\constructs\_construct.dm"
#include "code\modules\mob\living\simple_mob\subtypes\occult\constructs\artificer.dm"
#include "code\modules\mob\living\simple_mob\subtypes\occult\constructs\harvester.dm"