diff --git a/code/datums/spells/wizard.dm b/code/datums/spells/wizard.dm index 1adcfc279e0..03aa46653a9 100644 --- a/code/datums/spells/wizard.dm +++ b/code/datums/spells/wizard.dm @@ -147,6 +147,20 @@ action_icon_state = "spell_forcewall" +/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop + name = "Stop Time" + desc = "This spell stops time for everyone except for you, allowing you to move freely while your enemies and even projectiles are frozen." + charge_max = 500 + clothes_req = 1 + invocation = "TOKI WO TOMARE" + invocation_type = "shout" + range = 0 + cooldown_min = 100 + summon_amt = 1 + action_icon_state = "time" + + summon_type = list(/obj/effect/timestop/wizard) + /obj/effect/proc_holder/spell/aoe_turf/conjure/carp name = "Summon Carp" desc = "This spell conjures a simple carp." diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm index e54e2567ce4..c8d64fb1b27 100644 --- a/code/game/gamemodes/wizard/spellbook.dm +++ b/code/game/gamemodes/wizard/spellbook.dm @@ -126,6 +126,12 @@ spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse log_name = "RP" +/datum/spellbook_entry/timestop + name = "Time Stop" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/timestop + log_name = "TS" + category = "Defensive" + /datum/spellbook_entry/smoke name = "Smoke" spell_type = /obj/effect/proc_holder/spell/targeted/smoke diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 647d8d14a2c..3716cb32d81 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -31,6 +31,7 @@ var/atom/original = null // the original target clicked var/turf/starting = null // the projectile's starting turf var/list/permutated = list() // we've passed through these atoms, don't try to hit them again + var/paused = FALSE //for suspending the projectile midair var/p_x = 16 var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center @@ -202,73 +203,74 @@ if(kill_count < 1) del(src) return - kill_count-- - if((!( current ) || loc == current)) - current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) + if(!paused) + kill_count-- + if((!( current ) || loc == current)) + current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) - if(!Angle) - Angle=round(Get_Angle(src,current)) - //world << "[Angle] angle" - //overlays.Cut() - //var/icon/I=new(initial(icon),icon_state) //using initial(icon) makes sure that the angle for that is reset as well - //I.Turn(Angle) - //I.DrawBox(rgb(255,0,0,50),1,1,32,32) - //icon = I - if(spread) //Chaotic spread - Angle += (rand() - 0.5) * spread - var/matrix/M = new//matrix(transform) - M.Turn(Angle) - transform = M + if(!Angle) + Angle=round(Get_Angle(src,current)) + //world << "[Angle] angle" + //overlays.Cut() + //var/icon/I=new(initial(icon),icon_state) //using initial(icon) makes sure that the angle for that is reset as well + //I.Turn(Angle) + //I.DrawBox(rgb(255,0,0,50),1,1,32,32) + //icon = I + if(spread) //Chaotic spread + Angle += (rand() - 0.5) * spread + var/matrix/M = new//matrix(transform) + M.Turn(Angle) + transform = M - var/Pixel_x=round(sin(Angle)+16*sin(Angle)*2) - var/Pixel_y=round(cos(Angle)+16*cos(Angle)*2) - var/pixel_x_offset = pixel_x + Pixel_x - var/pixel_y_offset = pixel_y + Pixel_y - var/new_x = x - var/new_y = y - //Not sure if using whiles for this is good - while(pixel_x_offset > 16) - //world << "Pre-adjust coords (x++): xy [pixel_x] xy offset [pixel_x_offset]" - pixel_x_offset -= 32 - pixel_x -= 32 - new_x++// x++ - while(pixel_x_offset < -16) - //world << "Pre-adjust coords (x--): xy [pixel_x] xy offset [pixel_x_offset]" - pixel_x_offset += 32 - pixel_x += 32 - new_x-- + var/Pixel_x=round(sin(Angle)+16*sin(Angle)*2) + var/Pixel_y=round(cos(Angle)+16*cos(Angle)*2) + var/pixel_x_offset = pixel_x + Pixel_x + var/pixel_y_offset = pixel_y + Pixel_y + var/new_x = x + var/new_y = y + //Not sure if using whiles for this is good + while(pixel_x_offset > 16) + //world << "Pre-adjust coords (x++): xy [pixel_x] xy offset [pixel_x_offset]" + pixel_x_offset -= 32 + pixel_x -= 32 + new_x++// x++ + while(pixel_x_offset < -16) + //world << "Pre-adjust coords (x--): xy [pixel_x] xy offset [pixel_x_offset]" + pixel_x_offset += 32 + pixel_x += 32 + new_x-- - while(pixel_y_offset > 16) - //world << "Pre-adjust coords (y++): py [pixel_y] py offset [pixel_y_offset]" - pixel_y_offset -= 32 - pixel_y -= 32 - new_y++ - while(pixel_y_offset < -16) - //world << "Pre-adjust coords (y--): py [pixel_y] py offset [pixel_y_offset]" - pixel_y_offset += 32 - pixel_y += 32 - new_y-- + while(pixel_y_offset > 16) + //world << "Pre-adjust coords (y++): py [pixel_y] py offset [pixel_y_offset]" + pixel_y_offset -= 32 + pixel_y -= 32 + new_y++ + while(pixel_y_offset < -16) + //world << "Pre-adjust coords (y--): py [pixel_y] py offset [pixel_y_offset]" + pixel_y_offset += 32 + pixel_y += 32 + new_y-- - speed = round(speed) //Just in case. - step_towards(src, locate(new_x, new_y, z)) //Original projectiles stepped towards 'current' - if(speed <= 1) //We should really only animate at speed 2 - pixel_x = pixel_x_offset - pixel_y = pixel_y_offset - else - animate(src, pixel_x = pixel_x_offset, pixel_y = pixel_y_offset, time = max(1, (speed <= 3 ? speed - 1 : speed))) + speed = round(speed) //Just in case. + step_towards(src, locate(new_x, new_y, z)) //Original projectiles stepped towards 'current' + if(speed <= 1) //We should really only animate at speed 2 + pixel_x = pixel_x_offset + pixel_y = pixel_y_offset + else + animate(src, pixel_x = pixel_x_offset, pixel_y = pixel_y_offset, time = max(1, (speed <= 3 ? speed - 1 : speed))) -/* - var/turf/T = get_turf(src) - if(T) - T.color = "#6666FF" - spawn(10) - T.color = initial(T.color) -*/ + /* + var/turf/T = get_turf(src) + if(T) + T.color = "#6666FF" + spawn(10) + T.color = initial(T.color) + */ - if(!bumped && ((original && original.layer>=2.75) || ismob(original))) - if(loc == get_turf(original)) - if(!(original in permutated)) - Bump(original) + if(!bumped && ((original && original.layer>=2.75) || ismob(original))) + if(loc == get_turf(original)) + if(!(original in permutated)) + Bump(original) Range() sleep(max(1, speed)) else @@ -277,19 +279,20 @@ if(kill_count < 1) del(src) return - kill_count-- - if((!( current ) || loc == current)) - current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) - if(!Angle) - Angle=round(Get_Angle(src,current)) - var/matrix/M = new//matrix(transform) - M.Turn(Angle) - transform = M //So there's no need to give icons directions again - step_towards(src, current) - if(!bumped && ((original && original.layer>=2.75) || ismob(original))) - if(loc == get_turf(original)) - if(!(original in permutated)) - Bump(original) + if(!paused) + kill_count-- + if((!( current ) || loc == current)) + current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) + if(!Angle) + Angle=round(Get_Angle(src,current)) + var/matrix/M = new//matrix(transform) + M.Turn(Angle) + transform = M //So there's no need to give icons directions again + step_towards(src, current) + if(!bumped && ((original && original.layer>=2.75) || ismob(original))) + if(loc == get_turf(original)) + if(!(original in permutated)) + Bump(original) Range() sleep(1) diff --git a/code/modules/reagents/oldchem/chemical_reaction/chemical_reaction_slime.dm b/code/modules/reagents/oldchem/chemical_reaction/chemical_reaction_slime.dm index 4257b1c9be6..8894c903a27 100644 --- a/code/modules/reagents/oldchem/chemical_reaction/chemical_reaction_slime.dm +++ b/code/modules/reagents/oldchem/chemical_reaction/chemical_reaction_slime.dm @@ -491,11 +491,28 @@ var/obj/item/weapon/slimesteroid2/P = new /obj/item/weapon/slimesteroid2 P.loc = get_turf(holder.my_atom) //Sepia + slimestop + name = "Slime Stop" + id = "m_stop" + result = null + required_reagents = list("plasma" = 1) + result_amount = 1 + required_container = /obj/item/slime_extract/sepia + required_other = 1 + on_reaction(datum/reagents/holder) + feedback_add_details("slime_cores_used","[type]") + spawn(0) //otherwise the reagent won't be used up until after the full timstop is gone through. + var/mob/mob = get_mob_by_key(holder.my_atom.fingerprintslast) + var/obj/effect/timestop/T = new (get_turf(holder.my_atom)) + T.immune += mob + T.timestop() + + slimecamera name = "Slime Camera" id = "m_camera" result = null - required_reagents = list("plasma" = 1) + required_reagents = list("water" = 1) result_amount = 1 required_container = /obj/item/slime_extract/sepia required_other = 1 diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index a0df8e2740a..3fce14135e8 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -205,7 +205,7 @@ /obj/item/weapon/sentience_potion/afterattack(mob/living/M, mob/user) if(being_used || !ismob(M)) return - if(!isslime(M) && !isanimal(M) || M.ckey) //only works on animals that aren't player controlled + if(!isanimal(M) || M.ckey) //only works on animals that aren't player controlled user << "[M] is already too intelligent for this to work!" return ..() if(M.stat) @@ -351,6 +351,81 @@ if(!uses) qdel(src) + + +/obj/effect/timestop + anchored = 1 + name = "chronofield" + desc = "ZA WARUDO" + icon = 'icons/effects/160x160.dmi' + icon_state = "time" + layer = FLY_LAYER + pixel_x = -64 + pixel_y = -64 + unacidable = 1 + mouse_opacity = 0 + var/mob/living/immune = list() // the one who creates the timestop is immune + var/list/stopped_atoms = list() + var/freezerange = 2 + var/duration = 140 + alpha = 125 + +/obj/effect/timestop/New() + ..() + for(var/mob/living/M in player_list) + for(var/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop/T in M.mind.spell_list) //People who can stop time are immune to timestop + immune |= M + + +/obj/effect/timestop/proc/timestop() + playsound(get_turf(src), 'sound/magic/TIMEPARADOX2.ogg', 100, 1, -1) + for(var/i in 1 to duration-1) + for(var/A in orange (freezerange, src.loc)) + if(istype(A, /mob/living)) + var/mob/living/M = A + if(M in immune) + continue + M.stunned = 10 + M.anchored = 1 + if(istype(M, /mob/living/simple_animal/hostile)) + var/mob/living/simple_animal/hostile/H = M + H.AIStatus = AI_OFF + H.LoseTarget() + stopped_atoms |= M + else if(istype(A, /obj/item/projectile)) + var/obj/item/projectile/P = A + P.paused = TRUE + stopped_atoms |= P + + for(var/mob/living/M in stopped_atoms) + if(get_dist(get_turf(M),get_turf(src)) > freezerange) //If they lagged/ran past the timestop somehow, just ignore them + unfreeze_mob(M) + stopped_atoms -= M + sleep(1) + + //End + for(var/mob/living/M in stopped_atoms) + unfreeze_mob(M) + + for(var/obj/item/projectile/P in stopped_atoms) + P.paused = FALSE + qdel(src) + return + +/obj/effect/timestop/proc/unfreeze_mob(mob/living/M) + M.stunned = 0 + M.anchored = 0 + if(istype(M, /mob/living/simple_animal/hostile)) + var/mob/living/simple_animal/hostile/H = M + H.AIStatus = initial(H.AIStatus) + +/obj/effect/timestop/wizard + duration = 100 + +/obj/effect/timestop/wizard/New() + ..() + timestop() + /obj/item/stack/tile/bluespace name = "bluespace floor tile" singular_name = "floor tile" diff --git a/html/changelogs/halt-timestop-fox-mccloud.yml b/html/changelogs/halt-timestop-fox-mccloud.yml new file mode 100644 index 00000000000..0fabcdcc094 --- /dev/null +++ b/html/changelogs/halt-timestop-fox-mccloud.yml @@ -0,0 +1,9 @@ + +author: Fox McCloud + +delete-after: True + +changes: + - rscadd: "Implements the timestop spell." + - rscadd: "Implements new sepia slime reaction which halts time in an AoE." + - tweak: "sentience potions no longer can make slimes sentient." \ No newline at end of file diff --git a/icons/effects/160x160.dmi b/icons/effects/160x160.dmi index e540660bbe0..309a32e615d 100644 Binary files a/icons/effects/160x160.dmi and b/icons/effects/160x160.dmi differ diff --git a/sound/magic/TIMEPARADOX2.ogg b/sound/magic/TIMEPARADOX2.ogg new file mode 100644 index 00000000000..1a025a895b0 Binary files /dev/null and b/sound/magic/TIMEPARADOX2.ogg differ