diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm index a5f2c5d0f6..cf98755a36 100644 --- a/code/__DEFINES/DNA.dm +++ b/code/__DEFINES/DNA.dm @@ -53,6 +53,7 @@ #define GELADIKINESIS /datum/mutation/human/geladikinesis #define CRYOKINESIS /datum/mutation/human/cryokinesis #define SPIDER_WEB /datum/mutation/human/webbing +#define CLUWNEMUT /datum/mutation/human/cluwne #define UI_CHANGED "ui changed" #define UE_CHANGED "ue changed" diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index f6293454ee..bd6e549d15 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -82,6 +82,7 @@ #define ADMIN_PUNISHMENT_PICKLE "Pickle-ify" #define ADMIN_PUNISHMENT_FRY "Fry" #define ADMIN_PUNISHMENT_PERFORATE ":B:erforate" +#define ADMIN_PUNISHMENT_CLUWNE "Cluwne" #define AHELP_ACTIVE 1 #define AHELP_CLOSED 2 diff --git a/code/datums/mutations/cluwne.dm b/code/datums/mutations/cluwne.dm new file mode 100644 index 0000000000..59b7752372 --- /dev/null +++ b/code/datums/mutations/cluwne.dm @@ -0,0 +1,57 @@ +/datum/mutation/human/cluwne + + name = "Cluwne" + quality = NEGATIVE + locked = TRUE + text_gain_indication = "You feel like your brain is tearing itself apart." + +/datum/mutation/human/cluwne/on_acquiring(mob/living/carbon/human/owner) + if(..()) + return + owner.dna.add_mutation(CLOWNMUT) + owner.dna.add_mutation(EPILEPSY) + owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199) + + var/mob/living/carbon/human/H = owner + + if(!istype(H.wear_mask, /obj/item/clothing/mask/gas/cluwne)) + if(!H.dropItemToGround(H.wear_mask)) + qdel(H.wear_mask) + H.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/cluwne(H), SLOT_WEAR_MASK) + if(!istype(H.w_uniform, /obj/item/clothing/under/cluwne)) + if(!H.dropItemToGround(H.w_uniform)) + qdel(H.w_uniform) + H.equip_to_slot_or_del(new /obj/item/clothing/under/cluwne(H), SLOT_W_UNIFORM) + if(!istype(H.shoes, /obj/item/clothing/shoes/clown_shoes/cluwne)) + if(!H.dropItemToGround(H.shoes)) + qdel(H.shoes) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/clown_shoes/cluwne(H), SLOT_SHOES) + + owner.equip_to_slot_or_del(new /obj/item/clothing/gloves/color/white(owner), SLOT_GLOVES) // this is purely for cosmetic purposes incase they aren't wearing anything in that slot + owner.equip_to_slot_or_del(new /obj/item/storage/backpack/clown(owner), SLOT_BACK) // ditto + +/datum/mutation/human/cluwne/on_life(mob/living/carbon/human/owner) + if((prob(15) && owner.IsUnconscious())) + owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199) // there I changed it to setBrainLoss + switch(rand(1, 6)) + if(1) + owner.say("HONK") + if(2 to 5) + owner.emote("scream") + if(6) + owner.Stun(1) + owner.Knockdown(20) + owner.Jitter(500) + +/datum/mutation/human/cluwne/on_losing(mob/living/carbon/human/owner) + owner.adjust_fire_stacks(1) + owner.IgniteMob() + owner.dna.add_mutation(CLUWNEMUT) + +/mob/living/carbon/human/proc/cluwneify() + dna.add_mutation(CLUWNEMUT) + emote("scream") + regenerate_icons() + visible_message("[src]'s body glows green, the glow dissipating only to leave behind a cluwne formerly known as [src]!", \ + "Your brain feels like it's being torn apart, there is only the honkmother now.") + flash_act() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 9e784e72cb..89fd7036a2 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -116,6 +116,7 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list( /client/proc/show_tip, /client/proc/smite, /client/proc/admin_away, + /client/proc/spawn_floor_cluwne, /client/proc/cmd_admin_toggle_fov, //CIT CHANGE - FOV /client/proc/roll_dices //CIT CHANGE - Adds dice verb )) diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 59eee83b35..72ec804d28 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1333,9 +1333,10 @@ Traitors and the like can also be revived with the previous role mostly intact. ADMIN_PUNISHMENT_SHOES, ADMIN_PUNISHMENT_PICKLE, ADMIN_PUNISHMENT_FRY, - ADMIN_PUNISHMENT_CRACK, - ADMIN_PUNISHMENT_BLEED, - ADMIN_PUNISHMENT_SCARIFY) + ADMIN_PUNISHMENT_CRACK, + ADMIN_PUNISHMENT_BLEED, + ADMIN_PUNISHMENT_SCARIFY, + ADMIN_PUNISHMENT_CLUWNE) var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in punishment_list @@ -1501,6 +1502,11 @@ Traitors and the like can also be revived with the previous role mostly intact. to_chat(usr,"[C] does not have knottable shoes!") return sick_kicks.adjust_laces(SHOES_KNOTTED) + if(ADMIN_PUNISHMENT_CLUWNE) + if(!iscarbon(target)) + to_chat(usr,"This must be used on a carbon mob.") + return + target.cluwneify() punish_log(target, punishment) @@ -1691,3 +1697,23 @@ Traitors and the like can also be revived with the previous role mostly intact. if(!source) return REMOVE_TRAIT(D,chosen_trait,source) + +/client/proc/spawn_floor_cluwne() + set category = "Admin.Fun" + set name = "Unleash Floor Cluwne" + set desc = "Pick a specific target or just let it select randomly and spawn the floor cluwne mob on the station. Be warned: spawning more than one may cause issues!" + var/target + + if(!check_rights(R_FUN)) + return + + var/turf/T = get_turf(usr) + target = input("Any specific target in mind? Please note only live, non cluwned, human targets are valid.", "Target", target) as null|anything in GLOB.player_list + if(target && ishuman(target)) + var/mob/living/carbon/human/H = target + var/mob/living/simple_animal/hostile/floor_cluwne/FC = new /mob/living/simple_animal/hostile/floor_cluwne(T) + FC.Acquire_Victim(H) + else + new /mob/living/simple_animal/hostile/floor_cluwne(T) + log_admin("[key_name(usr)] spawned floor cluwne.") + message_admins("[key_name(usr)] spawned floor cluwne.") diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm index 9b2def48d7..21de660edb 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook.dm @@ -252,6 +252,10 @@ name = "Barnyard Curse" spell_type = /obj/effect/proc_holder/spell/pointed/barnyardcurse +/datum/spellbook_entry/cluwne + name = "Cluwne Curse" + spell_type = /obj/effect/proc_holder/spell/targeted/cluwnecurse + /datum/spellbook_entry/charge name = "Charge" spell_type = /obj/effect/proc_holder/spell/targeted/charge diff --git a/code/modules/clothing/masks/cluwne.dm b/code/modules/clothing/masks/cluwne.dm new file mode 100644 index 0000000000..a1177e2b47 --- /dev/null +++ b/code/modules/clothing/masks/cluwne.dm @@ -0,0 +1,106 @@ +/obj/item/clothing/mask/gas/cluwne + name = "clown wig and mask" + desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask." + flags_cover = MASKCOVERSEYES + icon_state = "cluwne" + item_state = "cluwne" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + clothing_flags = ALLOWINTERNALS + item_flags = ABSTRACT | DROPDEL + flags_inv = HIDEEARS|HIDEEYES + var/voicechange = TRUE + var/last_sound = 0 + var/delay = 15 + +/obj/item/clothing/mask/gas/cluwne/Initialize() + .=..() + ADD_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT) + ADD_TRAIT(src, CURSED_ITEM_TRAIT, CLOTHING_TRAIT) + +/obj/item/clothing/mask/gas/cluwne/proc/play_laugh1() + if(world.time - delay > last_sound) + playsound (src, 'sound/voice/cluwnelaugh1.ogg', 30, 1) + last_sound = world.time + +/obj/item/clothing/mask/gas/cluwne/proc/play_laugh2() + if(world.time - delay > last_sound) + playsound (src, 'sound/voice/cluwnelaugh2.ogg', 30, 1) + last_sound = world.time + +/obj/item/clothing/mask/gas/cluwne/proc/play_laugh3() + if(world.time - delay > last_sound) + playsound (src, 'sound/voice/cluwnelaugh3.ogg', 30, 1) + last_sound = world.time + +/obj/item/clothing/mask/gas/cluwne/equipped(mob/user, slot) //when you put it on + var/mob/living/carbon/C = user + if((C.wear_mask == src) && (voicechange)) + play_laugh1() + return ..() + +/obj/item/clothing/mask/gas/cluwne/handle_speech(datum/source, list/speech_args) + if(voicechange) + if(prob(5)) //the brain isn't fully gone yet... + speech_args[SPEECH_MESSAGE] = pick("HELP ME!!","PLEASE KILL ME!!","I WANT TO DIE!!", "END MY SUFFERING", "I CANT TAKE THIS ANYMORE!!" ,"SOMEBODY STOP ME!!") + play_laugh2() + if(prob(3)) + speech_args[SPEECH_MESSAGE] = pick("HOOOOINKKKKKKK!!", "HOINK HOINK HOINK HOINK!!","HOINK HOINK!!","HOOOOOOIIINKKKK!!") //but most of the time they cant speak, + play_laugh3() + else + speech_args[SPEECH_MESSAGE] = pick("HEEEENKKKKKK!!", "HONK HONK HONK HONK!!","HONK HONK!!","HOOOOOONKKKK!!") //More sounds, + play_laugh1() + return SPEECH_MESSAGE + +/obj/item/clothing/mask/gas/cluwne/equipped(mob/user, slot) + . = ..() + if(!ishuman(user)) + return + if(slot == SLOT_WEAR_MASK) + var/mob/living/carbon/human/H = user + H.dna.add_mutation(CLUWNEMUT) + return + +/obj/item/clothing/mask/gas/cluwne/happy_cluwne + name = "Happy Cluwne Mask" + desc = "The mask of a poor cluwne that has been scrubbed of its curse by the Nanotrasen supernatural machinations division. Guaranteed to be %99 curse free and %99.9 not haunted. " + item_flags = ABSTRACT + var/can_cluwne = FALSE + var/is_cursed = FALSE //i don't care that this is *slightly* memory wasteful, it's just one more byte and it's not like some madman is going to spawn thousands of these + var/is_very_cursed = FALSE + +/obj/item/clothing/mask/gas/cluwne/happy_cluwne/Initialize() + .=..() + if(prob(1)) //this function pre-determines the logic of the cluwne mask. applying and reapplying the mask does not alter or change anything + is_cursed = TRUE + is_very_cursed = FALSE + else if(prob(0.1)) + is_cursed = FALSE + is_very_cursed = TRUE + +/obj/item/clothing/mask/gas/cluwne/happy_cluwne/attack_self(mob/user) + voicechange = !voicechange + to_chat(user, "You turn the voice box [voicechange ? "on" : "off"]!") + if(voicechange) + play_laugh1() + +/obj/item/clothing/mask/gas/cluwne/happy_cluwne/equipped(mob/user, slot) + . = ..() + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(slot == SLOT_WEAR_MASK) + if(is_cursed && can_cluwne) //logic predetermined + log_admin("[key_name(H)] was made into a cluwne by [src]") + message_admins("[key_name(H)] got cluwned by [src]") + to_chat(H, "The masks straps suddenly tighten to your face and your thoughts are erased by a horrible green light!") + H.dropItemToGround(src) + H.cluwneify() + qdel(src) + else if(is_very_cursed && can_cluwne) + var/turf/T = get_turf(src) + var/mob/living/simple_animal/hostile/floor_cluwne/S = new(T) + S.Acquire_Victim(user) + log_admin("[key_name(user)] summoned a floor cluwne using the [src]") + message_admins("[key_name(user)] summoned a floor cluwne using the [src]") + to_chat(H, "The mask suddenly slips off your face and... slides under the floor?") + to_chat(H, "...dneirf uoy ot gnoleb ton seod tahT") diff --git a/code/modules/clothing/shoes/cluwne.dm b/code/modules/clothing/shoes/cluwne.dm new file mode 100644 index 0000000000..fbe1ba624b --- /dev/null +++ b/code/modules/clothing/shoes/cluwne.dm @@ -0,0 +1,29 @@ +/obj/item/clothing/shoes/clown_shoes/cluwne + desc = "The prankster's standard-issue clowning shoes. Damn, they're huge!" + name = "clown shoes" + icon_state = "cluwne" + item_state = "cluwne" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + item_flags = DROPDEL + var/footstep = 1 + +/obj/item/clothing/shoes/clown_shoes/cluwne/Initialize() + .=..() + ADD_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT) + ADD_TRAIT(src, CURSED_ITEM_TRAIT, CLOTHING_TRAIT) + +/obj/item/clothing/shoes/clown_shoes/cluwne/step_action() + if(footstep > 1) + playsound(src, "clownstep", 50, 1) + footstep = 0 + else + footstep++ + +/obj/item/clothing/shoes/clown_shoes/cluwne/equipped(mob/user, slot) + . = ..() + if(!ishuman(user)) + return + if(slot == SLOT_SHOES) + var/mob/living/carbon/human/H = user + H.dna.add_mutation(CLUWNEMUT) + return diff --git a/code/modules/clothing/under/cluwne.dm b/code/modules/clothing/under/cluwne.dm new file mode 100644 index 0000000000..5a908c5663 --- /dev/null +++ b/code/modules/clothing/under/cluwne.dm @@ -0,0 +1,22 @@ +/obj/item/clothing/under/cluwne + name = "clown suit" + desc = "'HONK!'" + icon_state = "greenclown" + item_state = "greenclown" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + item_flags = DROPDEL + can_adjust = 0 + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON + +/obj/item/clothing/under/cluwne/Initialize() + .=..() + ADD_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT) + ADD_TRAIT(src, CURSED_ITEM_TRAIT, CLOTHING_TRAIT) + +/obj/item/clothing/under/cluwne/equipped(mob/living/carbon/user, slot) + if(!ishuman(user)) + return + if(slot == SLOT_W_UNIFORM) + var/mob/living/carbon/human/H = user + H.dna.add_mutation(CLUWNEMUT) + return ..() diff --git a/code/modules/events/floorcluwne.dm b/code/modules/events/floorcluwne.dm new file mode 100644 index 0000000000..3df636809a --- /dev/null +++ b/code/modules/events/floorcluwne.dm @@ -0,0 +1,23 @@ +/datum/round_event_control/floor_cluwne + name = "Floor Cluwne" + typepath = /datum/round_event/floor_cluwne + max_occurrences = 1 + min_players = 20 + weight = 10 + + +/datum/round_event/floor_cluwne/start() + var/list/spawn_locs = list() + for(var/X in GLOB.xeno_spawn) + spawn_locs += X + + if(!spawn_locs.len) + message_admins("No valid spawn locations found, aborting...") + return MAP_ERROR + + var/turf/T = get_turf(pick(spawn_locs)) + var/mob/living/simple_animal/hostile/floor_cluwne/S = new(T) + playsound(S, 'sound/misc/bikehorn_creepy.ogg', 50, 1, -1) + message_admins("A floor cluwne has been spawned at [COORD(T)][ADMIN_JMP(T)]") + log_game("A floor cluwne has been spawned at [COORD(T)]") + return SUCCESSFUL_SPAWN diff --git a/code/modules/mob/living/simple_animal/hostile/floor_cluwne.dm b/code/modules/mob/living/simple_animal/hostile/floor_cluwne.dm new file mode 100644 index 0000000000..b6d5db7d6d --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/floor_cluwne.dm @@ -0,0 +1,457 @@ +GLOBAL_VAR_INIT(floor_cluwnes, 0) + +#define STAGE_HAUNT 1 +#define STAGE_SPOOK 2 +#define STAGE_TORMENT 3 +#define STAGE_ATTACK 4 +#define MANIFEST_DELAY 9 + +/mob/living/simple_animal/hostile/floor_cluwne + name = "???" + desc = "...." + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "cluwne" + icon_living = "cluwne" + icon_gib = "clown_gib" + maxHealth = 250 + health = 250 + speed = -1 + attack_verb_continuous = "attacks" + attack_verb_simple = "attack" + attack_sound = 'sound/items/bikehorn.ogg' + del_on_death = TRUE + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB | LETPASSTHROW | PASSGLASS | PASSBLOB//it's practically a ghost when unmanifested (under the floor) + loot = list(/obj/item/clothing/mask/gas/cluwne) + wander = FALSE + minimum_distance = 2 + move_to_delay = 1 + movement_type = FLYING + environment_smash = FALSE + lose_patience_timeout = FALSE + pixel_y = 8 + pressure_resistance = 200 + minbodytemp = 0 + maxbodytemp = 1500 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + var/mob/living/carbon/human/current_victim + var/manifested = FALSE + var/switch_stage = 60 + var/stage = STAGE_HAUNT + var/interest = 0 + var/target_area + var/invalid_area_typecache = list(/area/space, /area/lavaland, /area/centcom, /area/reebe, /area/shuttle/syndicate) + var/eating = FALSE + var/dontkill = FALSE //for if we just wanna curse a fucker + var/obj/effect/dummy/floorcluwne_orbit/poi + var/obj/effect/temp_visual/fcluwne_manifest/cluwnehole + move_resist = INFINITY + hud_type = /datum/hud/ghost + hud_possible = list(ANTAG_HUD) + + +/mob/living/simple_animal/hostile/floor_cluwne/Initialize() + . = ..() + access_card = new /obj/item/card/id(src) + var/datum/job/captain/C = new /datum/job/captain + access_card.access = C.get_access() + invalid_area_typecache = typecacheof(invalid_area_typecache) + Manifest() + if(!current_victim) + Acquire_Victim() + poi = new(src) + +/mob/living/simple_animal/hostile/floor_cluwne/med_hud_set_health() + return //we use a different hud + +/mob/living/simple_animal/hostile/floor_cluwne/med_hud_set_status() + return //we use a different hud + +/mob/living/simple_animal/hostile/floor_cluwne/Destroy() + QDEL_NULL(poi) + return ..() + + +/mob/living/simple_animal/hostile/floor_cluwne/attack_hand(mob/living/carbon/human/M) + ..() + playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1) + + +/mob/living/simple_animal/hostile/floor_cluwne/CanPass(atom/A, turf/target) + return TRUE + + +/mob/living/simple_animal/hostile/floor_cluwne/Life() + do_jitter_animation(1000) + pixel_y = 8 + + var/area/A = get_area(src.loc) + if(is_type_in_typecache(A, invalid_area_typecache) || !is_station_level(z)) + var/area = pick(GLOB.teleportlocs) + var/area/tp = GLOB.teleportlocs[area] + forceMove(pick(get_area_turfs(tp.type))) + + if(!current_victim) + Acquire_Victim() + + if(stage && !manifested) + INVOKE_ASYNC(src, .proc/On_Stage) + + if(stage == STAGE_ATTACK) + playsound(src, 'sound/misc/cluwne_breathing.ogg', 75, 1) + + if(eating) + return + + var/turf/T = get_turf(current_victim) + A = get_area(T) + if(prob(5))//checks roughly every 20 ticks + if(current_victim.stat == DEAD || current_victim.dna.check_mutation(CLUWNEMUT) || is_type_in_typecache(A, invalid_area_typecache) || !is_station_level(current_victim.z)) + if(!Found_You()) + Acquire_Victim() + + if(get_dist(src, current_victim) > 9 && !manifested && !is_type_in_typecache(A, invalid_area_typecache))//if cluwne gets stuck he just teleports + do_teleport(src, T) + + interest++ + if(interest >= switch_stage * 4 && !dontkill) + stage = STAGE_ATTACK + + else if(interest >= switch_stage * 2) + stage = STAGE_TORMENT + + else if(interest >= switch_stage) + stage = STAGE_SPOOK + + else if(interest < switch_stage) + stage = STAGE_HAUNT + + ..() + +/mob/living/simple_animal/hostile/floor_cluwne/Goto(target, delay, minimum_distance) + var/area/A = get_area(current_victim.loc) + if(!manifested && !is_type_in_typecache(A, invalid_area_typecache) && is_station_level(current_victim.z)) + walk_to(src, target, minimum_distance, delay) + else + walk_to(src,0) + + +/mob/living/simple_animal/hostile/floor_cluwne/FindTarget() + return current_victim + + +/mob/living/simple_animal/hostile/floor_cluwne/CanAttack(atom/the_target)//you will not escape + return TRUE + + +/mob/living/simple_animal/hostile/floor_cluwne/AttackingTarget() + return + + +/mob/living/simple_animal/hostile/floor_cluwne/LoseTarget() + return + + +/mob/living/simple_animal/hostile/floor_cluwne/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)//prevents runtimes with machine fuckery + return FALSE + +/mob/living/simple_animal/hostile/floor_cluwne/proc/Found_You() + for(var/obj/structure/closet/hiding_spot in orange(7,src)) + if(current_victim.loc == hiding_spot) + hiding_spot.bust_open() + current_victim.Paralyze(40) + to_chat(current_victim, "...edih t'nac uoY") + return TRUE + return FALSE + +/mob/living/simple_animal/hostile/floor_cluwne/proc/Acquire_Victim(specific) + for(var/I in GLOB.player_list)//better than a potential recursive loop + var/mob/living/carbon/human/H = pick(GLOB.player_list)//so the check is fair + var/area/A + + if(specific) + H = specific + A = get_area(H.loc) + if(H.stat != DEAD && H.has_dna() && !H.dna.check_mutation(CLUWNEMUT) && !is_type_in_typecache(A, invalid_area_typecache) && is_station_level(H.z)) + return target = current_victim + + A = get_area(H.loc) + if(H && ishuman(H) && H.stat != DEAD && H != current_victim && H.has_dna() && !H.dna.check_mutation(CLUWNEMUT) && !is_type_in_typecache(A, invalid_area_typecache) && is_station_level(H.z)) + current_victim = H + interest = 0 + stage = STAGE_HAUNT + return target = current_victim + + message_admins("Floor Cluwne was deleted due to a lack of valid targets, if this was a manually targeted instance please re-evaluate your choice.") + qdel(src) + + +/mob/living/simple_animal/hostile/floor_cluwne/proc/Manifest()//handles disappearing and appearance anim + if(manifested) + mobility_flags &= ~MOBILITY_MOVE + update_mobility() + cluwnehole = new(src.loc) + addtimer(CALLBACK(src, /mob/living/simple_animal/hostile/floor_cluwne/.proc/Appear), MANIFEST_DELAY) + else + layer = GAME_PLANE + invisibility = INVISIBILITY_OBSERVER + density = FALSE + mobility_flags |= MOBILITY_MOVE + update_mobility() + if(cluwnehole) + qdel(cluwnehole) + + +/mob/living/simple_animal/hostile/floor_cluwne/proc/Appear()//handled in a seperate proc so floor cluwne doesn't appear before the animation finishes + layer = LYING_MOB_LAYER + invisibility = FALSE + density = TRUE + +/mob/living/simple_animal/hostile/floor_cluwne/proc/Reset_View(screens, colour, mob/living/carbon/human/H) + if(screens) + for(var/whole_screen in screens) + animate(whole_screen, transform = matrix(), time = 5, easing = QUAD_EASING) + if(colour && H) + H.client.color = colour + + +/mob/living/simple_animal/hostile/floor_cluwne/proc/On_Stage() + var/mob/living/carbon/human/H = current_victim + switch(stage) + + if(STAGE_HAUNT) + + if(prob(5)) + H.blur_eyes(1) + + if(prob(5)) + H.playsound_local(src,'sound/voice/cluwnelaugh2_reversed.ogg', 1) + + if(prob(5)) + H.playsound_local(src,'sound/misc/bikehorn_creepy.ogg', 5) + + if(prob(3)) + var/obj/item/I = locate() in orange(H, 8) + if(I && !I.anchored) + I.throw_at(H, 4, 3) + to_chat(H, "What threw that?") + + if(STAGE_SPOOK) + + if(prob(4)) + var/turf/T = get_turf(H) + T.handle_slip(H, 20) + to_chat(H, "The floor shifts underneath you!") + + if(prob(5)) + H.playsound_local(src,'sound/voice/cluwnelaugh2.ogg', 2) + + if(prob(5)) + H.playsound_local(src,'sound/voice/cluwnelaugh2_reversed.ogg', 2) + + if(prob(5)) + H.playsound_local(src,'sound/misc/bikehorn_creepy.ogg', 10) + to_chat(H, "knoh") + + if(prob(5)) + var/obj/item/I = locate() in orange(H, 8) + if(I && !I.anchored) + I.throw_at(H, 4, 3) + to_chat(H, "What threw that?") + + if(prob(2)) + to_chat(H, "yalp ot tnaw I") + Appear() + manifested = FALSE + addtimer(CALLBACK(src, /mob/living/simple_animal/hostile/floor_cluwne/.proc/Manifest), 1) + + if(STAGE_TORMENT) + + if(prob(5)) + var/turf/T = get_turf(H) + T.handle_slip(H, 20) + to_chat(H, "The floor shifts underneath you!") + + if(prob(3)) + playsound(src,pick('sound/spookoween/scary_horn.ogg', 'sound/spookoween/scary_horn2.ogg', 'sound/spookoween/scary_horn3.ogg'), 30, 1) + + if(prob(3)) + playsound(src,'sound/voice/cluwnelaugh1.ogg', 30, 1) + + if(prob(3)) + playsound(src,'sound/voice/cluwnelaugh2_reversed.ogg', 30, 1) + + if(prob(5)) + playsound(src,'sound/misc/bikehorn_creepy.ogg', 30, 1) + + if(prob(4)) + for(var/obj/item/I in orange(8, H)) + if(!I.anchored) + I.throw_at(H, 4, 3) + to_chat(H, "What the hell?!") + + if(prob(2)) + to_chat(H, "Something feels very wrong...") + H.playsound_local(src,'sound/hallucinations/behind_you1.ogg', 25) + H.flash_act() + + if(prob(2)) + to_chat(H, "!?REHTOMKNOH eht esiarp uoy oD") + to_chat(H, "Something grabs your foot!") + H.playsound_local(src,'sound/hallucinations/i_see_you1.ogg', 25) + H.Stun(20) + + if(prob(3)) + to_chat(H, "KNOH ?od nottub siht seod tahW") + for(var/turf/open/O in RANGE_TURFS(6, src)) + O.MakeSlippery(TURF_WET_WATER, 10) + playsound(src, 'sound/effects/meteorimpact.ogg', 30, 1) + + if(prob(1)) + to_chat(H, "WHAT THE FUCK IS THAT?!") + to_chat(H, ".KNOH !nuf hcum os si uoy htiw gniyalP .KNOH KNOH KNOH") + H.playsound_local(src,'sound/hallucinations/im_here1.ogg', 25) + H.reagents.add_reagent(/datum/reagent/toxin/mindbreaker, 3) + H.reagents.add_reagent(/datum/reagent/consumable/laughter, 5) + H.reagents.add_reagent(/datum/reagent/mercury, 3) + Appear() + manifested = FALSE + addtimer(CALLBACK(src, /mob/living/simple_animal/hostile/floor_cluwne/.proc/Manifest), 2) + for(var/obj/machinery/light/L in range(8, H)) + L.flicker() + + + if(STAGE_ATTACK) + if(dontkill) + stage = STAGE_TORMENT + return + if(!eating) + Found_You() + for(var/I in getline(src,H)) + var/turf/T = I + if(T.density) + forceMove(H.loc) + for(var/obj/structure/O in T) + if(O.density || istype(O, /obj/machinery/door/airlock)) + forceMove(H.loc) + to_chat(H, "You feel the floor closing in on your feet!") + H.Paralyze(300) + INVOKE_ASYNC(H, /mob.proc/emote, "scream") + H.adjustBruteLoss(10) + manifested = TRUE + Manifest() + if(!eating) + addtimer(CALLBACK(src, /mob/living/simple_animal/hostile/floor_cluwne/.proc/Grab, H), 50, TIMER_OVERRIDE|TIMER_UNIQUE) + for(var/turf/open/O in RANGE_TURFS(6, src)) + O.MakeSlippery(TURF_WET_LUBE, 20) + playsound(src, 'sound/effects/meteorimpact.ogg', 30, 1) + eating = TRUE + + +/mob/living/simple_animal/hostile/floor_cluwne/proc/Grab(mob/living/carbon/human/H) + if (H != current_victim) + message_admins("Cluwne tried to grab someone who's not the target. Returning to life stage.") + return + to_chat(H, "You feel a cold, gloved hand clamp down on your ankle!") + for(var/I in 1 to get_dist(src, H)) + if(do_after(src, 5, target = H)) + step_towards(H, src) + playsound(H, pick('sound/effects/bodyscrape-01.ogg', 'sound/effects/bodyscrape-02.ogg'), 20, 1, -4) + if(prob(40)) + H.emote("scream") + else if(prob(25)) + H.say(pick("HELP ME!!","IT'S GOT ME!!","DON'T LET IT TAKE ME!!",";SOMETHING'S KILLING ME!!","HOLY FUCK!!")) + playsound(src, pick('sound/voice/cluwnelaugh1.ogg', 'sound/voice/cluwnelaugh2.ogg', 'sound/voice/cluwnelaugh3.ogg'), 50, 1) + + if(get_dist(src,H) <= 1) + visible_message("[src] begins dragging [H] under the floor!") + if(do_after(src, 50, target = H) && eating) + H.become_blind() + H.layer = GAME_PLANE + H.invisibility = INVISIBILITY_OBSERVER + H.density = FALSE + H.anchored = TRUE + addtimer(CALLBACK(src, /mob/living/simple_animal/hostile/floor_cluwne/.proc/Kill, H), 100, TIMER_OVERRIDE|TIMER_UNIQUE) + visible_message("[src] pulls [H] under!") + to_chat(H, "[src] drags you underneath the floor!") + else + eating = FALSE + else + eating = FALSE + manifested = FALSE + Manifest() + + +/mob/living/simple_animal/hostile/floor_cluwne/proc/Kill(mob/living/carbon/human/H) + if (H != current_victim) + message_admins("Cluwne tried to kill someone who's not the target. Returning to life stage.") + H.invisibility = initial(H.invisibility) + return + if(!istype(H) || !H.client) + H.invisibility = initial(H.invisibility) + Acquire_Victim() + return + playsound(H, 'sound/effects/cluwne_feast.ogg', 100, 0, -4) + var/old_color = H.client.color + var/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0) + var/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0) + H.client.color = pure_red + animate(H.client,color = red_splash, time = 10, easing = SINE_EASING|EASE_OUT) + for(var/turf/T in orange(H, 4)) + H.add_splatter_floor(T) + if(do_after(src, 50, target = H)) + H.unequip_everything()//more runtime prevention + if(prob(75)) + H.gib(FALSE) + else + H.cluwneify() + H.adjustBruteLoss(30) + H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100, 100) + H.cure_blind() + H.layer = initial(H.layer) + H.invisibility = initial(H.invisibility) + H.density = initial(H.density) + H.anchored = initial(H.anchored) + H.blur_eyes(10) + animate(H.client,color = old_color, time = 20) + + eating = FALSE + switch_stage = switch_stage * 0.75 //he gets faster after each feast + for(var/mob/M in GLOB.player_list) + M.playsound_local(get_turf(M), 'sound/misc/honk_echo_distant.ogg', 50, 1, pressure_affected = FALSE) + + interest = 0 + stage = STAGE_HAUNT + Acquire_Victim() + +//manifestation animation +/obj/effect/temp_visual/fcluwne_manifest + icon = 'icons/turf/floors.dmi' + icon_state = "fcluwne_open" + layer = TURF_LAYER + duration = 600 + randomdir = FALSE + +/obj/effect/temp_visual/fcluwne_manifest/Initialize() + . = ..() + playsound(src, 'sound/misc/floor_cluwne_emerge.ogg', 100, 1) + flick("fcluwne_manifest",src) + +/obj/effect/dummy/floorcluwne_orbit + name = "floor cluwne" + desc = "If you have this, tell a coder or admin!" + +/obj/effect/dummy/floorcluwne_orbit/Initialize() + . = ..() + GLOB.floor_cluwnes++ + name += " ([GLOB.floor_cluwnes])" + GLOB.poi_list += src + +/obj/effect/dummy/floorcluwne_orbit/Destroy() + . = ..() + GLOB.poi_list -= src + +#undef STAGE_HAUNT +#undef STAGE_SPOOK +#undef STAGE_TORMENT +#undef STAGE_ATTACK +#undef MANIFEST_DELAY diff --git a/code/modules/spells/spell_types/cluwnecurse.dm b/code/modules/spells/spell_types/cluwnecurse.dm new file mode 100644 index 0000000000..2da87b9e50 --- /dev/null +++ b/code/modules/spells/spell_types/cluwnecurse.dm @@ -0,0 +1,36 @@ +/obj/effect/proc_holder/spell/targeted/cluwnecurse + name = "Curse of the Cluwne" + desc = "This spell dooms the fate of any unlucky soul to the live of a pitiful cluwne, a terrible creature that is hunted for fun." + school = "transmutation" + charge_type = "recharge" + charge_max = 600 + charge_counter = 0 + clothes_req = SPELL_WIZARD_GARB + stat_allowed = 0 + invocation = "CLU WO'NIS CA'TE'BEST'IS MAXIMUS!" + invocation_type = "shout" + range = 3 + cooldown_min = 75 + selection_type = "range" + var/list/compatible_mobs = list(/mob/living/carbon/human) + action_icon = 'icons/mob/actions/actions_spells.dmi' + action_icon_state = "cluwne" + +/obj/effect/proc_holder/spell/targeted/cluwnecurse/cast(list/targets, mob/user = usr) + if(!targets.len) + to_chat(user, "No target found in range.") + return + var/mob/living/carbon/target = targets[1] + if(!(target.type in compatible_mobs)) + to_chat(user, "You are unable to curse [target]!") + return + if(!(target in oview(range))) + to_chat(user, "They are too far away!") + return + var/mob/living/carbon/human/H = target + H.cluwneify() + +/datum/action/spell_action/New(Target) + ..() + var/obj/effect/proc_holder/spell/S = Target + icon_icon = S.action_icon diff --git a/icons/mob/actions/actions_spells.dmi b/icons/mob/actions/actions_spells.dmi index 72e72ad6f3..9c2a30031e 100644 Binary files a/icons/mob/actions/actions_spells.dmi and b/icons/mob/actions/actions_spells.dmi differ diff --git a/icons/mob/clothing/feet.dmi b/icons/mob/clothing/feet.dmi index de50a0d9dc..df40636049 100644 Binary files a/icons/mob/clothing/feet.dmi and b/icons/mob/clothing/feet.dmi differ diff --git a/icons/mob/clothing/feet_digi.dmi b/icons/mob/clothing/feet_digi.dmi index 1cc1e801c7..7215875f2c 100644 Binary files a/icons/mob/clothing/feet_digi.dmi and b/icons/mob/clothing/feet_digi.dmi differ diff --git a/icons/mob/clothing/mask.dmi b/icons/mob/clothing/mask.dmi index d06f9cf899..60c2e34b39 100644 Binary files a/icons/mob/clothing/mask.dmi and b/icons/mob/clothing/mask.dmi differ diff --git a/icons/mob/clothing/mask_muzzled.dmi b/icons/mob/clothing/mask_muzzled.dmi index 807d944172..9da3545c65 100644 Binary files a/icons/mob/clothing/mask_muzzled.dmi and b/icons/mob/clothing/mask_muzzled.dmi differ diff --git a/icons/obj/clothing/masks.dmi b/icons/obj/clothing/masks.dmi index 9f1c0cbd2a..532d4f5df7 100644 Binary files a/icons/obj/clothing/masks.dmi and b/icons/obj/clothing/masks.dmi differ diff --git a/icons/obj/clothing/shoes.dmi b/icons/obj/clothing/shoes.dmi index cb2391b5ba..bde07bd54d 100644 Binary files a/icons/obj/clothing/shoes.dmi and b/icons/obj/clothing/shoes.dmi differ diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi index 19d4dca13d..e1b128e10b 100644 Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ diff --git a/sound/effects/bodyscrape-01.ogg b/sound/effects/bodyscrape-01.ogg new file mode 100644 index 0000000000..48c7563412 Binary files /dev/null and b/sound/effects/bodyscrape-01.ogg differ diff --git a/sound/effects/bodyscrape-02.ogg b/sound/effects/bodyscrape-02.ogg new file mode 100644 index 0000000000..b870b5d7b0 Binary files /dev/null and b/sound/effects/bodyscrape-02.ogg differ diff --git a/sound/effects/cluwne_feast.ogg b/sound/effects/cluwne_feast.ogg new file mode 100644 index 0000000000..47c23e6445 Binary files /dev/null and b/sound/effects/cluwne_feast.ogg differ diff --git a/sound/misc/bikehorn_creepy.ogg b/sound/misc/bikehorn_creepy.ogg new file mode 100644 index 0000000000..7fb3290f5c Binary files /dev/null and b/sound/misc/bikehorn_creepy.ogg differ diff --git a/sound/misc/cluwne_breathing.ogg b/sound/misc/cluwne_breathing.ogg new file mode 100644 index 0000000000..30de4d7931 Binary files /dev/null and b/sound/misc/cluwne_breathing.ogg differ diff --git a/sound/misc/floor_cluwne_emerge.ogg b/sound/misc/floor_cluwne_emerge.ogg new file mode 100644 index 0000000000..cf8254fc21 Binary files /dev/null and b/sound/misc/floor_cluwne_emerge.ogg differ diff --git a/sound/misc/honk_echo_distant.ogg b/sound/misc/honk_echo_distant.ogg new file mode 100644 index 0000000000..32e95884bc Binary files /dev/null and b/sound/misc/honk_echo_distant.ogg differ diff --git a/sound/voice/cluwnelaugh1.ogg b/sound/voice/cluwnelaugh1.ogg new file mode 100644 index 0000000000..6a128cf3a3 Binary files /dev/null and b/sound/voice/cluwnelaugh1.ogg differ diff --git a/sound/voice/cluwnelaugh2.ogg b/sound/voice/cluwnelaugh2.ogg new file mode 100644 index 0000000000..e118709ce2 Binary files /dev/null and b/sound/voice/cluwnelaugh2.ogg differ diff --git a/sound/voice/cluwnelaugh2_reversed.ogg b/sound/voice/cluwnelaugh2_reversed.ogg new file mode 100644 index 0000000000..5dc7e9b7c3 Binary files /dev/null and b/sound/voice/cluwnelaugh2_reversed.ogg differ diff --git a/sound/voice/cluwnelaugh3.ogg b/sound/voice/cluwnelaugh3.ogg new file mode 100644 index 0000000000..5ff795c94c Binary files /dev/null and b/sound/voice/cluwnelaugh3.ogg differ diff --git a/tgstation.dme b/tgstation.dme index bae593cf0c..8c9d6cd621 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -682,6 +682,7 @@ #include "code\datums\mutations\antenna.dm" #include "code\datums\mutations\body.dm" #include "code\datums\mutations\chameleon.dm" +#include "code\datums\mutations\cluwne.dm" #include "code\datums\mutations\cold.dm" #include "code\datums\mutations\combined.dm" #include "code\datums\mutations\hulk.dm" @@ -1986,6 +1987,7 @@ #include "code\modules\clothing\masks\_masks.dm" #include "code\modules\clothing\masks\boxing.dm" #include "code\modules\clothing\masks\breath.dm" +#include "code\modules\clothing\masks\cluwne.dm" #include "code\modules\clothing\masks\gasmask.dm" #include "code\modules\clothing\masks\hailer.dm" #include "code\modules\clothing\masks\miscellaneous.dm" @@ -1998,6 +2000,7 @@ #include "code\modules\clothing\outfits\vv_outfit.dm" #include "code\modules\clothing\shoes\_shoes.dm" #include "code\modules\clothing\shoes\bananashoes.dm" +#include "code\modules\clothing\shoes\cluwne.dm" #include "code\modules\clothing\shoes\colour.dm" #include "code\modules\clothing\shoes\magboots.dm" #include "code\modules\clothing\shoes\miscellaneous.dm" @@ -2021,6 +2024,7 @@ #include "code\modules\clothing\suits\wiz_robe.dm" #include "code\modules\clothing\under\_under.dm" #include "code\modules\clothing\under\accessories.dm" +#include "code\modules\clothing\under\cluwne.dm" #include "code\modules\clothing\under\color.dm" #include "code\modules\clothing\under\costume.dm" #include "code\modules\clothing\under\miscellaneous.dm" @@ -2077,6 +2081,7 @@ #include "code\modules\events\electrical_storm.dm" #include "code\modules\events\fake_virus.dm" #include "code\modules\events\false_alarm.dm" +#include "code\modules\events\floorcluwne.dm" #include "code\modules\events\fugitive_spawning.dm" #include "code\modules\events\ghost_role.dm" #include "code\modules\events\grid_check.dm" @@ -2783,6 +2788,7 @@ #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\eyeballs.dm" #include "code\modules\mob\living\simple_animal\hostile\faithless.dm" +#include "code\modules\mob\living\simple_animal\hostile\floor_cluwne.dm" #include "code\modules\mob\living\simple_animal\hostile\giant_spider.dm" #include "code\modules\mob\living\simple_animal\hostile\goose.dm" #include "code\modules\mob\living\simple_animal\hostile\headcrab.dm" @@ -3432,6 +3438,7 @@ #include "code\modules\spells\spell_types\area_teleport.dm" #include "code\modules\spells\spell_types\bloodcrawl.dm" #include "code\modules\spells\spell_types\charge.dm" +#include "code\modules\spells\spell_types\cluwnecurse.dm" #include "code\modules\spells\spell_types\cone_spells.dm" #include "code\modules\spells\spell_types\conjure.dm" #include "code\modules\spells\spell_types\construct_spells.dm"