mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-10 18:32:03 +00:00
* Grue shadow shunt. * HUD sprite, sound, and other stuff. * Visual effect, cooldown scaling with sentients eaten. * Update grue.dm * Update grue.dm * Update grue.dm Euclidean distance. * Update grue.dm back to non-euclidean * Update grue.dm Play sound also at new location.
1129 lines
29 KiB
Plaintext
1129 lines
29 KiB
Plaintext
/* This is an attempt to make some easily reusable "particle" type effect, to stop the code
|
|
constantly having to be rewritten. An item like the jetpack that uses the ion_trail_follow system, just has one
|
|
defined, then set up when it is created with New(). Then this same system can just be reused each time
|
|
it needs to create more trails.A beaker could have a steam_trail_follow system set up, then the steam
|
|
would spawn and follow the beaker, even if it is carried or thrown.
|
|
*/
|
|
|
|
|
|
/obj/effect
|
|
name = "effect"
|
|
icon = 'icons/effects/effects.dmi'
|
|
mouse_opacity = 0
|
|
flags = 0
|
|
w_type = NOT_RECYCLABLE
|
|
pass_flags = PASSTABLE|PASSGRILLE|PASSMACHINE
|
|
|
|
/obj/effect/dissolvable()
|
|
return 0
|
|
|
|
/obj/effect/water
|
|
name = "water"
|
|
icon_state = "extinguish"
|
|
var/life = 15.0
|
|
|
|
/obj/effect/water/spray
|
|
name = "spray"
|
|
icon_state = "extinguish_gray"
|
|
|
|
/obj/effect/water/New()
|
|
. = ..()
|
|
//var/turf/T = src.loc
|
|
//if (istype(T, /turf))
|
|
// T.firelevel = 0 //TODO: FIX
|
|
|
|
spawn(70)
|
|
qdel(src)
|
|
|
|
/obj/effect/water/Destroy()
|
|
//var/turf/T = src.loc
|
|
//if (istype(T, /turf))
|
|
// T.firelevel = 0 //TODO: FIX
|
|
|
|
..()
|
|
|
|
/obj/effect/water/Move(NewLoc, Dir = 0, step_x = 0, step_y = 0, glide_size_override = 0)
|
|
//var/turf/T = src.loc
|
|
//if (istype(T, /turf))
|
|
// T.firelevel = 0 //TODO: FIX
|
|
|
|
if (--life < 1)
|
|
//SN src = null
|
|
qdel(src)
|
|
return 0
|
|
|
|
.=..()
|
|
|
|
/obj/effect/water/to_bump(atom/A)
|
|
if(reagents)
|
|
reagents.reaction(A)
|
|
return ..()
|
|
|
|
/datum/effect/system
|
|
var/number = 3
|
|
var/cardinals = 0
|
|
var/turf/location
|
|
var/atom/holder
|
|
var/setup = 0
|
|
|
|
/datum/effect/system/Destroy()
|
|
holder = null
|
|
..()
|
|
|
|
/datum/effect/system/proc/set_up(n = 3, c = 0, turf/loc)
|
|
if(n > 10)
|
|
n = 10
|
|
number = n
|
|
cardinals = c
|
|
location = loc
|
|
setup = 1
|
|
|
|
/datum/effect/system/proc/attach(atom/atom)
|
|
holder = atom
|
|
|
|
/datum/effect/system/proc/start() //why is this here? it neither overrides nor does anything else
|
|
//well now it does, since refactoring, previous commentor
|
|
|
|
/obj/effect/canSingulothPull(var/obj/machinery/singularity/singulo)
|
|
return 0
|
|
|
|
/obj/effect/blob_act()
|
|
return
|
|
|
|
/////////////////////////////////////////////
|
|
// GENERIC STEAM SPREAD SYSTEM
|
|
|
|
//Usage: set_up(number of bits of steam, use North/South/East/West only, spawn location)
|
|
// The attach(atom/atom) proc is optional, and can be called to attach the effect
|
|
// to something, like a smoking beaker, so then you can just call start() and the steam
|
|
// will always spawn at the items location, even if it's moved.
|
|
|
|
/* Example:
|
|
var/datum/effect/system/steam_spread/steam = new /datum/effect/system/steam_spread() -- creates new system
|
|
steam.set_up(5, 0, mob.loc) -- sets up variables
|
|
OPTIONAL: steam.attach(mob)
|
|
steam.start() -- spawns the effect
|
|
*/
|
|
/////////////////////////////////////////////
|
|
/obj/effect/steam
|
|
name = "steam"
|
|
icon_state = "extinguish"
|
|
density = 0
|
|
|
|
/datum/effect/system/steam_spread/set_up(n = 3, c = 0, turf/loc)
|
|
if(n > 10)
|
|
n = 10
|
|
number = n
|
|
cardinals = c
|
|
location = loc
|
|
|
|
/datum/effect/system/steam_spread/start()
|
|
var/i = 0
|
|
for(i=0, i<src.number, i++)
|
|
spawn(0)
|
|
if(holder)
|
|
src.location = get_turf(holder)
|
|
var/obj/effect/steam/steam = new /obj/effect/steam(src.location)
|
|
var/direction
|
|
if(src.cardinals)
|
|
direction = pick(cardinal)
|
|
else
|
|
direction = pick(alldirs)
|
|
for(i=0, i<pick(1,2,3), i++)
|
|
sleep(5)
|
|
step(steam,direction)
|
|
spawn(20)
|
|
if(steam)
|
|
qdel(steam)
|
|
|
|
/////////////////////////////////////////////
|
|
//SPARK SYSTEM (like steam system)
|
|
// The attach(atom/atom) proc is optional, and can be called to attach the effect
|
|
// to something, like the RCD, so then you can just call start() and the sparks
|
|
// will always spawn at the items location.
|
|
/////////////////////////////////////////////
|
|
|
|
/obj/effect/sparks
|
|
name = "sparks"
|
|
desc = "it's a spark what do you need to know?"
|
|
icon_state = "sparks"
|
|
anchored = 1
|
|
|
|
var/move_dir = 0
|
|
var/energy = 0
|
|
|
|
/obj/effect/sparks/New(var/travel_dir)
|
|
..()
|
|
var/turf/T = loc
|
|
if(istype(T))
|
|
T.hotspot_expose(1000, 100, surfaces = 1)
|
|
|
|
/obj/effect/sparks/proc/start(var/travel_dir, var/max_energy=3)
|
|
move_dir=travel_dir
|
|
energy=rand(1,max_energy)
|
|
processing_objects.Add(src)
|
|
var/turf/T = loc
|
|
if (istype(T, /turf))
|
|
T.hotspot_expose(1000, 100, surfaces = 1)
|
|
|
|
/obj/effect/sparks/Destroy()
|
|
processing_objects.Remove(src)
|
|
var/turf/T = src.loc
|
|
|
|
if (istype(T, /turf))
|
|
T.hotspot_expose(1000, 100, surfaces = 1)
|
|
|
|
..()
|
|
|
|
/obj/effect/sparks/Move(NewLoc, Dir = 0, step_x = 0, step_y = 0, glide_size_override = 0)
|
|
..()
|
|
var/turf/T = src.loc
|
|
if (istype(T, /turf))
|
|
T.hotspot_expose(1000,100, surfaces = 1)
|
|
return
|
|
|
|
/obj/effect/sparks/process()
|
|
if(energy==0)
|
|
processing_objects.Remove(src)
|
|
qdel(src)
|
|
return
|
|
else
|
|
step(src,move_dir)
|
|
energy--
|
|
|
|
/datum/effect/system/spark_spread/set_up(var/n = 3, var/use_cardinals = 0, loca)
|
|
number = min(10,n)
|
|
cardinals = use_cardinals
|
|
|
|
if (istype(loca, /turf/))
|
|
location = loca
|
|
else
|
|
location = get_turf(loca)
|
|
|
|
/datum/effect/system/spark_spread/start()
|
|
if (holder)
|
|
location = get_turf(holder)
|
|
if(!location)
|
|
return
|
|
var/list/directions
|
|
if (cardinals)
|
|
directions = cardinal.Copy()
|
|
else
|
|
directions = alldirs.Copy()
|
|
|
|
playsound(location, "sparks", 100, 1)
|
|
for (var/i = 1 to number)
|
|
var/nextdir=pick_n_take(directions)
|
|
if(nextdir)
|
|
var/obj/effect/sparks/sparks = new /obj/effect/sparks(location)
|
|
sparks.start(nextdir)
|
|
|
|
// This sparks.
|
|
/proc/spark(var/atom/loc, var/amount = 3, var/cardinals = TRUE)
|
|
loc = get_turf(loc)
|
|
var/datum/effect/system/spark_spread/S = new
|
|
S.set_up(amount, cardinals, loc)
|
|
S.start()
|
|
|
|
/////////////////////////////////////////////
|
|
//// SMOKE SYSTEMS
|
|
// direct can be optinally added when set_up, to make the smoke always travel in one direction
|
|
// in case you wanted a vent to always smoke north for example
|
|
/////////////////////////////////////////////
|
|
|
|
|
|
/obj/effect/smoke
|
|
name = "smoke"
|
|
icon_state = "smoke"
|
|
opacity = 1
|
|
anchored = 1
|
|
var/amount = 6.0
|
|
var/time_to_live = 100
|
|
|
|
//Remove this bit to use the old smoke
|
|
icon = 'icons/effects/96x96.dmi'
|
|
pixel_x = -WORLD_ICON_SIZE
|
|
pixel_y = -WORLD_ICON_SIZE
|
|
|
|
/obj/effect/smoke/New()
|
|
. = ..()
|
|
spawn(time_to_live)
|
|
qdel(src)
|
|
|
|
/obj/effect/smoke/Crossed(mob/living/carbon/M)
|
|
..()
|
|
if(istype(M))
|
|
affect(M)
|
|
|
|
/obj/effect/smoke/proc/affect(var/mob/living/carbon/M)
|
|
if (istype(M))
|
|
return 0
|
|
if (M.internal != null && M.wear_mask && (M.wear_mask.clothing_flags & MASKINTERNALS))
|
|
return 0
|
|
return 1
|
|
|
|
/obj/effect/smoke/Destroy()
|
|
if(reagents)
|
|
reagents.my_atom = null
|
|
qdel(reagents)
|
|
reagents = null
|
|
..()
|
|
|
|
/////////////////////////////////////////////
|
|
// Bad smoke
|
|
/////////////////////////////////////////////
|
|
|
|
/obj/effect/smoke/bad
|
|
time_to_live = 200
|
|
|
|
/obj/effect/smoke/bad/Move(NewLoc, Dir = 0, step_x = 0, step_y = 0, glide_size_override = 0)
|
|
..()
|
|
for(var/mob/living/carbon/M in get_turf(src))
|
|
affect(M)
|
|
|
|
/obj/effect/smoke/bad/affect(var/mob/living/carbon/M)
|
|
if (!..())
|
|
return 0
|
|
M.drop_item()
|
|
M.adjustOxyLoss(1)
|
|
if (M.coughedtime != 1)
|
|
M.coughedtime = 1
|
|
M.audible_cough()
|
|
spawn ( 20 )
|
|
M.coughedtime = 0
|
|
|
|
/obj/effect/smoke/bad/Cross(atom/movable/mover, turf/target, height=1.5, air_group = 0)
|
|
if(air_group || (height==0))
|
|
return 1
|
|
if(istype(mover, /obj/item/projectile/beam))
|
|
var/obj/item/projectile/beam/B = mover
|
|
B.damage = (B.damage/2)
|
|
return 1
|
|
/////////////////////////////////////////////
|
|
// Sleep smoke
|
|
/////////////////////////////////////////////
|
|
|
|
/obj/effect/smoke/sleepy
|
|
|
|
/obj/effect/smoke/sleepy/Move(NewLoc, Dir = 0, step_x = 0, step_y = 0, glide_size_override = 0)
|
|
..()
|
|
for(var/mob/living/carbon/M in get_turf(src))
|
|
affect(M)
|
|
|
|
/obj/effect/smoke/sleepy/affect(mob/living/carbon/M)
|
|
if (!..())
|
|
return 0
|
|
|
|
M.drop_item()
|
|
M:sleeping += 1
|
|
if (M.coughedtime != 1)
|
|
M.coughedtime = 1
|
|
M.audible_cough()
|
|
spawn ( 20 )
|
|
M.coughedtime = 0
|
|
/////////////////////////////////////////////
|
|
// Mustard Gas
|
|
/////////////////////////////////////////////
|
|
|
|
|
|
/obj/effect/smoke/mustard
|
|
name = "mustard gas"
|
|
icon_state = "mustard"
|
|
|
|
/obj/effect/smoke/mustard/Move(NewLoc, Dir = 0, step_x = 0, step_y = 0, glide_size_override = 0)
|
|
..()
|
|
for(var/mob/living/carbon/human/R in get_turf(src))
|
|
affect(R)
|
|
|
|
/obj/effect/smoke/mustard/affect(var/mob/living/carbon/human/R)
|
|
if (!..())
|
|
return 0
|
|
if (R.wear_suit != null)
|
|
return 0
|
|
|
|
R.burn_skin(0.75)
|
|
if (R.coughedtime != 1)
|
|
R.coughedtime = 1
|
|
R.emote("gasp", null, null, TRUE)
|
|
spawn (20)
|
|
R.coughedtime = 0
|
|
R.updatehealth()
|
|
return
|
|
|
|
/obj/effect/smoke/heat
|
|
name = "geyser smoke"
|
|
|
|
/obj/effect/smoke/heat/affect(var/mob/living/carbon/human/R)
|
|
if (!..())
|
|
return 0
|
|
if (R.wear_suit)
|
|
return 0
|
|
|
|
R.burn_skin(2)
|
|
R.bodytemperature = min(60, R.bodytemperature + (30 * TEMPERATURE_DAMAGE_COEFFICIENT))
|
|
|
|
/////////////////////////////////////////////
|
|
// Fire Smoke
|
|
/////////////////////////////////////////////
|
|
|
|
|
|
/obj/effect/smoke/fire
|
|
name = "fire smoke"
|
|
icon_state = "firesmoke"
|
|
|
|
/obj/effect/smoke/fire/Move(NewLoc, Dir = 0, step_x = 0, step_y = 0, glide_size_override = 0)
|
|
..()
|
|
for(var/mob/living/carbon/human/R in get_turf(src))
|
|
affect(R)
|
|
|
|
/obj/effect/smoke/fire/affect(var/mob/living/carbon/human/R)
|
|
if (!..())
|
|
return 0
|
|
if (R.wear_suit != null)
|
|
return 0
|
|
R.burn_skin(0.75)
|
|
if (R.resting) //crawling prevents suffocation but not burning
|
|
return 0
|
|
R.adjustOxyLoss(1)
|
|
if (R.coughedtime != 1)
|
|
R.coughedtime = 1
|
|
R.emote("gasp", null, null, TRUE)
|
|
spawn (20)
|
|
R.coughedtime = 0
|
|
R.updatehealth()
|
|
return
|
|
|
|
/obj/effect/smoke/transparent
|
|
opacity = FALSE
|
|
|
|
/////////////////////////////////////////////
|
|
// Smoke spread
|
|
/////////////////////////////////////////////
|
|
|
|
/datum/effect/system/smoke_spread
|
|
var/total_smoke = 0 // To stop it being spammed and lagging!
|
|
var/direction
|
|
var/smoke_type = /obj/effect/smoke
|
|
var/time_to_live = 10 SECONDS
|
|
|
|
/datum/effect/system/smoke_spread/set_up(n = 5, c = 0, loca, direct)
|
|
if(n > 10)
|
|
n = 10
|
|
number = n
|
|
cardinals = c
|
|
if(istype(loca, /turf/))
|
|
location = loca
|
|
else
|
|
location = get_turf(loca)
|
|
if(direct)
|
|
direction = direct
|
|
|
|
/datum/effect/system/smoke_spread/start()
|
|
var/i = 0
|
|
for(i=0, i<src.number, i++)
|
|
if(src.total_smoke > 20)
|
|
return
|
|
spawn(0)
|
|
if(holder)
|
|
src.location = get_turf(holder)
|
|
var/obj/effect/smoke/smoke = new smoke_type(src.location)
|
|
smoke.time_to_live = time_to_live
|
|
total_smoke++
|
|
var/direction = src.direction
|
|
if(!direction)
|
|
if(src.cardinals)
|
|
direction = pick(cardinal)
|
|
else
|
|
direction = pick(alldirs)
|
|
for(i=0, i<pick(0,1,1,1,2,2,2,3), i++)
|
|
sleep(10)
|
|
step(smoke,direction)
|
|
spawn(smoke.time_to_live*0.75+rand(10,30))
|
|
if (smoke)
|
|
qdel(smoke)
|
|
src.total_smoke--
|
|
|
|
|
|
/datum/effect/system/smoke_spread/bad
|
|
smoke_type = /obj/effect/smoke/bad
|
|
|
|
/datum/effect/system/smoke_spread/sleepy
|
|
smoke_type = /obj/effect/smoke/sleepy
|
|
|
|
/datum/effect/system/smoke_spread/mustard
|
|
smoke_type = /obj/effect/smoke/mustard
|
|
|
|
/datum/effect/system/smoke_spread/heat
|
|
smoke_type = /obj/effect/smoke/heat
|
|
|
|
/datum/effect/system/smoke_spread/transparent
|
|
smoke_type = /obj/effect/smoke/transparent
|
|
|
|
/datum/effect/system/smoke_spread/fire
|
|
smoke_type = /obj/effect/smoke/fire
|
|
|
|
/////////////////////////////////////////////
|
|
// Chem smoke
|
|
/////////////////////////////////////////////
|
|
/obj/effect/smoke/chem
|
|
icon = 'icons/effects/chemsmoke.dmi'
|
|
|
|
/obj/effect/smoke/chem/New()
|
|
. = ..()
|
|
create_reagents(500)
|
|
|
|
/obj/effect/smoke/chem/Move(NewLoc, Dir = 0, step_x = 0, step_y = 0, glide_size_override = 0)
|
|
..()
|
|
for(var/atom/A in view(2, src))
|
|
if(reagents.has_reagent(RADIUM)||reagents.has_reagent(URANIUM)||reagents.has_reagent(CARBON)||reagents.has_reagent(THERMITE)||reagents.has_reagent(BLEACH))//Prevents unholy radium spam by reducing the number of 'greenglows' down to something reasonable -Sieve
|
|
if(prob(5))
|
|
reagents.reaction(A)
|
|
else
|
|
reagents.reaction(A)
|
|
|
|
return
|
|
|
|
/obj/effect/smoke/chem/affect(mob/living/carbon/M)
|
|
reagents.reaction(M)
|
|
|
|
/datum/effect/system/smoke_spread/chem
|
|
smoke_type = /obj/effect/smoke/chem
|
|
var/obj/chemholder
|
|
|
|
/datum/effect/system/smoke_spread/chem/New()
|
|
..()
|
|
chemholder = new/obj()
|
|
var/datum/reagents/R = new/datum/reagents(500)
|
|
chemholder.reagents = R
|
|
R.my_atom = chemholder
|
|
|
|
/datum/effect/system/smoke_spread/chem/set_up(var/datum/reagents/carry = null, n = 5, c = 0, loca, direct)
|
|
if(n > 20)
|
|
n = 20
|
|
number = n
|
|
cardinals = c
|
|
if(carry)
|
|
carry.copy_to(chemholder, carry.total_volume)
|
|
|
|
|
|
if(istype(loca, /turf/))
|
|
location = loca
|
|
else
|
|
location = get_turf(loca)
|
|
if(direct)
|
|
direction = direct
|
|
|
|
/datum/effect/system/smoke_spread/chem/start()
|
|
var/i = 0
|
|
|
|
var/color = mix_color_from_reagents(chemholder.reagents.reagent_list)
|
|
|
|
for(i=0, i<src.number, i++)
|
|
if(src.total_smoke > 20)
|
|
return
|
|
spawn(0)
|
|
if(holder)
|
|
src.location = get_turf(holder)
|
|
var/obj/effect/smoke/chem/smoke = new /obj/effect/smoke/chem(src.location)
|
|
src.total_smoke++
|
|
var/direction = src.direction
|
|
if(!direction)
|
|
if(src.cardinals)
|
|
direction = pick(cardinal)
|
|
else
|
|
direction = pick(alldirs)
|
|
|
|
if(chemholder.reagents.total_volume != 1) // can't split 1 very well
|
|
chemholder.reagents.copy_to(smoke, chemholder.reagents.total_volume / number) // copy reagents to each smoke, divide evenly
|
|
|
|
if(color)
|
|
smoke.icon += color // give the smoke color, if it has any to begin with
|
|
else
|
|
// if no color, just use the old smoke icon
|
|
smoke.icon = 'icons/effects/96x96.dmi'
|
|
smoke.icon_state = "smoke"
|
|
|
|
for(i=0, i<pick(0,1,1,1,2,2,2,3), i++)
|
|
sleep(10)
|
|
step(smoke,direction)
|
|
spawn(150+rand(10,30))
|
|
if(smoke)
|
|
qdel(smoke)
|
|
smoke = null
|
|
src.total_smoke--
|
|
|
|
// Goon compat.
|
|
/datum/effect/system/smoke_spread/chem/fart/set_up(var/mob/M, n = 5, c = 0, loca, direct)
|
|
if(n > 20)
|
|
n = 20
|
|
number = n
|
|
cardinals = c
|
|
|
|
chemholder.reagents.add_reagent(SPACE_DRUGS, rand(1,10))
|
|
|
|
if(istype(loca, /turf/))
|
|
location = loca
|
|
else
|
|
location = get_turf(loca)
|
|
if(direct)
|
|
direction = direct
|
|
|
|
var/contained = "\[[chemholder.reagents.get_reagent_ids()]\]"
|
|
var/area/A = get_area(location)
|
|
|
|
var/where = "[A.name] | [location.x], [location.y]"
|
|
var/whereLink=formatJumpTo(location,where)
|
|
|
|
var/more = "(<A HREF='?_src_=holder;adminmoreinfo=\ref[M]'>?</a>)"
|
|
message_admins("[M][more] produced a toxic fart in ([whereLink])[contained].", 0, 1)
|
|
log_game("[M][more] produced a toxic fart in ([where])[contained].")
|
|
|
|
|
|
/////////////////////////////////////////////
|
|
//////// Attach an Ion trail to any object, that spawns when it moves (like for the jetpack)
|
|
/// just pass in the object to attach it to in set_up
|
|
/// Then do start() to start it and stop() to stop it, obviously
|
|
/// and don't call start() in a loop that will be repeated otherwise it'll get spammed!
|
|
/////////////////////////////////////////////
|
|
|
|
/obj/effect/trails
|
|
name = ""
|
|
icon_state = ""
|
|
anchored = 1
|
|
|
|
var/base_name="ion"
|
|
|
|
/obj/effect/trails/New()
|
|
..()
|
|
name = "[base_name] trails"
|
|
icon_state = "[base_name]_trails"
|
|
|
|
/obj/effect/trails/proc/Play()
|
|
flick("[base_name]_fade", src)
|
|
icon_state = "blank"
|
|
spawn( 20 )
|
|
if(src)
|
|
qdel(src)
|
|
|
|
/obj/effect/trails/ion
|
|
base_name = "ion"
|
|
|
|
/datum/effect/system/trail
|
|
var/turf/oldposition
|
|
var/processing = 1
|
|
var/on = 1
|
|
|
|
var/trail_type=/obj/effect/trails/ion
|
|
|
|
/datum/effect/system/trail/set_up(atom/atom)
|
|
attach(atom)
|
|
oldposition = get_turf(atom)
|
|
|
|
/datum/effect/system/trail/start()
|
|
if(!src.on)
|
|
src.on = 1
|
|
src.processing = 1
|
|
if(src.processing)
|
|
src.processing = 0
|
|
spawn(0)
|
|
var/turf/T = get_turf(src.holder)
|
|
if(T != src.oldposition)
|
|
if(istype(T, /turf/space) || istype(T, /turf/simulated/open))
|
|
var/obj/effect/trails/I = new trail_type(src.oldposition)
|
|
src.oldposition = T
|
|
I.dir = src.holder.dir
|
|
I.Play()
|
|
spawn(2)
|
|
if(src.on)
|
|
src.processing = 1
|
|
src.start()
|
|
|
|
/datum/effect/system/trail/proc/stop()
|
|
src.processing = 0
|
|
src.on = 0
|
|
|
|
/datum/effect/system/trail/space_trail
|
|
var/turf/oldloc // secondary ion trail loc
|
|
var/turf/currloc
|
|
|
|
/datum/effect/system/trail/space_trail/start()
|
|
if(!src.on)
|
|
src.on = 1
|
|
src.processing = 1
|
|
if(src.processing)
|
|
src.processing = 0
|
|
spawn(0)
|
|
var/turf/T = get_turf(src.holder)
|
|
if(currloc != T)
|
|
switch(holder.dir)
|
|
if(NORTH)
|
|
src.oldposition = T
|
|
src.oldposition = get_step(oldposition, SOUTH)
|
|
src.oldloc = get_step(oldposition,EAST)
|
|
if(SOUTH) // More difficult, offset to the north!
|
|
src.oldposition = get_step(holder,NORTH)
|
|
src.oldposition = get_step(oldposition,NORTH)
|
|
src.oldloc = get_step(oldposition,EAST)
|
|
if(EAST) // Just one to the north should suffice
|
|
src.oldposition = T
|
|
src.oldposition = get_step(oldposition, WEST)
|
|
src.oldloc = get_step(oldposition,NORTH)
|
|
if(WEST) // One to the east and north from there
|
|
src.oldposition = get_step(holder,EAST)
|
|
src.oldposition = get_step(oldposition,EAST)
|
|
src.oldloc = get_step(oldposition,NORTH)
|
|
if(istype(T, /turf/space) || istype(T, /turf/simulated/open))
|
|
var/obj/effect/trails/ion/I = new /obj/effect/trails/ion(src.oldposition)
|
|
var/obj/effect/trails/ion/II = new /obj/effect/trails/ion(src.oldloc)
|
|
I.dir = src.holder.dir
|
|
II.dir = src.holder.dir
|
|
flick("ion_fade", I)
|
|
flick("ion_fade", II)
|
|
I.icon_state = "blank"
|
|
II.icon_state = "blank"
|
|
spawn( 20 )
|
|
if(I)
|
|
qdel(I)
|
|
if(II)
|
|
qdel(II)
|
|
|
|
spawn(2)
|
|
if(src.on)
|
|
src.processing = 1
|
|
src.start()
|
|
currloc = T
|
|
|
|
|
|
/////////////////////////////////////////////
|
|
//////// Attach a steam trail to an object (eg. a reacting beaker) that will follow it
|
|
// even if it's carried of thrown.
|
|
/////////////////////////////////////////////
|
|
|
|
/datum/effect/system/steam_trail_follow
|
|
var/turf/oldposition
|
|
var/processing = 1
|
|
var/on = 1
|
|
|
|
/datum/effect/system/steam_trail_follow/set_up(atom/atom)
|
|
attach(atom)
|
|
oldposition = get_turf(atom)
|
|
|
|
/datum/effect/system/steam_trail_follow/start()
|
|
if(!src.on)
|
|
src.on = 1
|
|
src.processing = 1
|
|
if(src.processing)
|
|
src.processing = 0
|
|
spawn(0)
|
|
if(src.number < 3)
|
|
var/obj/effect/steam/I = new /obj/effect/steam(src.oldposition)
|
|
src.number++
|
|
src.oldposition = get_turf(holder)
|
|
I.dir = src.holder.dir
|
|
spawn(10)
|
|
if(I)
|
|
qdel(I)
|
|
src.number--
|
|
spawn(2)
|
|
if(src.on)
|
|
src.processing = 1
|
|
src.start()
|
|
else
|
|
spawn(2)
|
|
if(src.on)
|
|
src.processing = 1
|
|
src.start()
|
|
|
|
/datum/effect/system/steam_trail_follow/proc/stop()
|
|
src.processing = 0
|
|
src.on = 0
|
|
|
|
|
|
|
|
// Foam
|
|
// Similar to smoke, but spreads out more
|
|
// metal foams leave behind a foamed metal wall
|
|
|
|
/obj/effect/foam
|
|
name = "foam"
|
|
icon_state = "foam"
|
|
opacity = 0
|
|
anchored = 1
|
|
density = 0
|
|
layer = ABOVE_HUMAN_PLANE
|
|
var/amount = 3
|
|
var/expand = 1
|
|
animate_movement = 0
|
|
var/metal = 0
|
|
var/lowest_temperature = T0C
|
|
|
|
/obj/effect/foam/fire
|
|
name = "fire supression foam"
|
|
icon_state = "mfoam"
|
|
|
|
/obj/effect/foam/fire/enhanced
|
|
lowest_temperature = 16
|
|
|
|
/obj/effect/foam/New(loc, var/ismetal=0)
|
|
. = ..(loc)
|
|
icon_state = "[ismetal ? "m":""]foam"
|
|
metal = ismetal
|
|
playsound(src, 'sound/effects/bubbles2.ogg', 80, 1, -3)
|
|
spawn(3 + metal*3)
|
|
process()
|
|
spawn(120)
|
|
processing_objects.Remove(src)
|
|
sleep(30)
|
|
|
|
if(metal)
|
|
var/turf/T = get_turf(src)
|
|
if(istype(T, /turf/space) || istype(T, /turf/simulated/open))
|
|
T.ChangeTurf(/turf/simulated/floor/foamedmetal)
|
|
if(metal == 2)
|
|
var/obj/structure/foamedmetal/M = new(src.loc)
|
|
M.metal = metal
|
|
M.updateicon()
|
|
|
|
flick("[icon_state]-disolve", src)
|
|
sleep(5)
|
|
qdel(src)
|
|
|
|
/obj/effect/foam/fire/New(loc, datum/reagents/R)
|
|
reagents = R
|
|
reagents.my_atom = src
|
|
var/ccolor = mix_color_from_reagents(reagents.reagent_list)
|
|
if(ccolor)
|
|
color = ccolor
|
|
var/savedtemp
|
|
if(reagents.has_reagent(WATER))
|
|
var/turf/simulated/T = get_turf(src)
|
|
var/datum/gas_mixture/old_air = T.return_air()
|
|
savedtemp = old_air.temperature
|
|
if(istype(T) && savedtemp > lowest_temperature)
|
|
var/datum/gas_mixture/lowertemp = old_air.remove_volume(CELL_VOLUME)
|
|
lowertemp.add_thermal_energy(max(lowertemp.get_thermal_energy_change(lowest_temperature), -(15*CELL_VOLUME)*max(1,lowertemp.return_temperature()/2)))
|
|
T.assume_air(lowertemp)
|
|
spawn(3)
|
|
process()
|
|
spawn(120)
|
|
processing_objects.Remove(src)
|
|
sleep(30)
|
|
flick("[icon_state]-disolve", src)
|
|
sleep(5)
|
|
qdel(src)
|
|
|
|
/obj/effect/foam/fire/process()
|
|
if(--amount < 0)
|
|
return
|
|
|
|
// on delete, transfer any reagents to the floor
|
|
/obj/effect/foam/Destroy()
|
|
if(!metal && reagents && !istype(src, /obj/effect/foam/fire))
|
|
for(var/atom/A in oview(0,src))
|
|
if(A == src)
|
|
continue
|
|
reagents.reaction(A, 1, 1)
|
|
..()
|
|
|
|
|
|
/obj/effect/foam/process()
|
|
if(--amount < 0)
|
|
return
|
|
|
|
|
|
for(var/direction in cardinal)
|
|
|
|
|
|
var/turf/T = get_step(src,direction)
|
|
if(!T)
|
|
continue
|
|
|
|
if(!T.Enter(src, loc, TRUE))
|
|
continue
|
|
|
|
var/obj/effect/foam/F = locate() in T
|
|
if(F)
|
|
continue
|
|
|
|
F = new(T, metal)
|
|
F.amount = amount
|
|
if(!metal)
|
|
F.create_reagents(10)
|
|
if (reagents)
|
|
for(var/datum/reagent/R in reagents.reagent_list)
|
|
F.reagents.add_reagent(R.id,1)
|
|
|
|
// foam disolves when heated
|
|
// except metal foams
|
|
/obj/effect/foam/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
|
|
if(!metal && prob(max(0, exposed_temperature - 475)))
|
|
flick("[icon_state]-disolve", src)
|
|
|
|
spawn(5)
|
|
qdel(src)
|
|
|
|
|
|
/obj/effect/foam/Crossed(var/atom/movable/AM)
|
|
if(metal)
|
|
return
|
|
if(istype(src, /obj/effect/foam/fire))
|
|
if(isliving(AM))
|
|
var/mob/living/M = AM
|
|
reagents.reaction(M)
|
|
return
|
|
|
|
if(istype(AM, /mob/living/carbon))
|
|
var/mob/living/carbon/M = AM
|
|
M.Slip(5, 2, 1, onwhat = "the foam")
|
|
|
|
/datum/effect/system/foam_spread
|
|
var/amount = 5 // the size of the foam spread.
|
|
var/list/carried_reagents // the IDs of reagents present when the foam was mixed
|
|
var/metal = 0 // 0=foam, 1=metalfoam, 2=ironfoam
|
|
|
|
/datum/effect/system/foam_spread/set_up(amt=5, loca, var/datum/reagents/carry = null, var/metalfoam = 0)
|
|
amount = round(sqrt(amt / 3), 1)
|
|
if(istype(loca, /turf/))
|
|
location = loca
|
|
else
|
|
location = get_turf(loca)
|
|
|
|
carried_reagents = list()
|
|
metal = metalfoam
|
|
|
|
|
|
// bit of a hack here. Foam carries along any reagent also present in the glass it is mixed
|
|
// with (defaults to water if none is present). Rather than actually transfer the reagents,
|
|
// this makes a list of the reagent ids and spawns 1 unit of that reagent when the foam disolves.
|
|
|
|
|
|
if(carry && !metal)
|
|
for(var/datum/reagent/R in carry.reagent_list)
|
|
carried_reagents += R.id
|
|
|
|
/datum/effect/system/foam_spread/start()
|
|
spawn(0)
|
|
var/obj/effect/foam/F = locate() in location
|
|
if(F)
|
|
F.amount += amount
|
|
return
|
|
|
|
F = new(src.location, metal)
|
|
F.amount = amount
|
|
|
|
if(!metal) // don't carry other chemicals if a metal foam
|
|
F.create_reagents(10)
|
|
|
|
if(carried_reagents)
|
|
for(var/id in carried_reagents)
|
|
F.reagents.add_reagent(id,1)
|
|
else
|
|
F.reagents.add_reagent(WATER, 1)
|
|
|
|
// wall formed by metal foams
|
|
// dense and opaque, but easy to break
|
|
|
|
/obj/structure/foamedmetal
|
|
icon = 'icons/effects/effects.dmi'
|
|
icon_state = "metalfoam"
|
|
density = 1
|
|
opacity = 1 // changed in New()
|
|
anchored = 1
|
|
name = "foamed metal wall"
|
|
desc = "A lightweight foamed metal wall."
|
|
var/metal = 1 // 1=aluminum, 2=iron
|
|
|
|
/obj/structure/foamedmetal/proc/updateicon()
|
|
|
|
if(metal == 1)
|
|
icon_state = "metalfoam"
|
|
else
|
|
icon_state = "ironfoam"
|
|
|
|
/obj/structure/foamedmetal/ex_act(severity)
|
|
qdel(src)
|
|
|
|
/obj/structure/foamedmetal/blob_act()
|
|
qdel(src)
|
|
|
|
/obj/structure/foamedmetal/bullet_act()
|
|
if(metal==1 || prob(50))
|
|
qdel(src)
|
|
return ..()
|
|
|
|
/obj/structure/foamedmetal/attack_paw(var/mob/user)
|
|
attack_hand(user)
|
|
return
|
|
|
|
/obj/structure/foamedmetal/attack_hand(var/mob/living/user)
|
|
user.delayNextAttack(10)
|
|
if ((M_HULK in user.mutations) || (prob(75 - metal*25)))
|
|
user.do_attack_animation(src, user)
|
|
user.visible_message("<span class='warning'>[user] smashes through \the [src].</span>","<span class='notice'>You smash through \the [src].</span>")
|
|
qdel(src)
|
|
else
|
|
to_chat(user, "<span class='notice'>You hit \the [src] but bounce off it.</span>")
|
|
return
|
|
|
|
/obj/structure/foamedmetal/kick_act()
|
|
..()
|
|
|
|
if(prob(75 - metal*25))
|
|
qdel(src)
|
|
|
|
/obj/structure/foamedmetal/attackby(var/obj/item/I, var/mob/living/user)
|
|
user.do_attack_animation(src, I)
|
|
user.delayNextAttack(10)
|
|
if (istype(I, /obj/item/weapon/grab))
|
|
var/obj/item/weapon/grab/G = I
|
|
G.affecting.forceMove(src.loc)
|
|
visible_message("<span class='warning'>[G.assailant] smashes [G.affecting] through \the [src].</span>")
|
|
qdel(I)
|
|
qdel(src)
|
|
return
|
|
|
|
if(prob(I.force*20 - metal*25))
|
|
user.visible_message("<span class='warning'>[user] smashes through \the [src].</span>","<span class='notice'>You smash through \the [src] with \the [I].</span>")
|
|
qdel(src)
|
|
else
|
|
to_chat(user, "<span class='notice'>You hit \the [src] to no effect.</span>")
|
|
|
|
/obj/structure/foamedmetal/Cross(atom/movable/mover, turf/target, height=1.5, air_group = 0)
|
|
if(air_group)
|
|
return 0
|
|
return !density
|
|
|
|
/obj/structure/foamedmetal/New()
|
|
. = ..()
|
|
update_nearby_tiles()
|
|
|
|
/obj/structure/foamedmetal/Destroy()
|
|
update_nearby_tiles()
|
|
..()
|
|
|
|
/turf/simulated/floor/foamedmetal
|
|
name = "foamed metal floor"
|
|
desc = "A lightweight foamed metal floor."
|
|
icon_state = "foamedmetal"
|
|
icon_regular_floor = "foamedmetal"
|
|
icon_plating = "foamedmetal"
|
|
can_exist_under_lattice = 1
|
|
plane = PLATING_PLANE
|
|
|
|
/turf/simulated/floor/foamedmetal/attack_hand(mob/living/user as mob)
|
|
user.delayNextAttack(10)
|
|
if ((M_HULK in user.mutations) || (prob(50)))
|
|
user.do_attack_animation(src, user)
|
|
user.visible_message("<span class='warning'>[user] smashes through \the [src].</span>","<span class='notice'>You smash through \the [src].</span>")
|
|
src.ChangeTurf(get_base_turf(src.z))
|
|
else
|
|
to_chat(user, "<span class='notice'>You hit \the [src] but bounce off it.</span>")
|
|
|
|
/turf/simulated/floor/foamedmetal/attackby(obj/item/C, mob/living/user)
|
|
if(!(locate(/obj/structure/lattice) in contents))
|
|
if(istype(C, /obj/item/stack/rods))
|
|
return
|
|
else if(istype(C, /obj/item/stack/tile))
|
|
return
|
|
user.delayNextAttack(10)
|
|
user.do_attack_animation(src, C)
|
|
if (istype(C, /obj/item/weapon/grab))
|
|
var/obj/item/weapon/grab/G = C
|
|
G.affecting.forceMove(src.loc)
|
|
visible_message("<span class='warning'>[G.assailant] smashes [G.affecting] through \the [src].</span>")
|
|
qdel(C)
|
|
src.ChangeTurf(get_base_turf(src.z))
|
|
return
|
|
|
|
if(prob(C.force*20 - 25))
|
|
user.visible_message("<span class='warning'>[user] smashes through \the [src].</span>","<span class='notice'>You smash through \the [src] with \the [C].</span>")
|
|
src.ChangeTurf(get_base_turf(src.z))
|
|
else
|
|
to_chat(user, "<span class='notice'>You hit \the [src] to no effect.</span>")
|
|
|
|
/turf/simulated/floor/foamedmetal/canBuildCatwalk()
|
|
if(locate(/obj/structure/catwalk) in contents)
|
|
return BUILD_FAILURE
|
|
return locate(/obj/structure/lattice) in contents
|
|
|
|
/turf/simulated/floor/foamedmetal/canBuildLattice(var/material)
|
|
if(src.x >= (world.maxx - TRANSITIONEDGE) || src.x <= TRANSITIONEDGE)
|
|
return BUILD_FAILURE
|
|
else if (src.y >= (world.maxy - TRANSITIONEDGE || src.y <= TRANSITIONEDGE ))
|
|
return BUILD_FAILURE
|
|
else if(!(locate(/obj/structure/lattice) in contents) && !(istype(material,/obj/item/stack/sheet/wood)))
|
|
return 1
|
|
return BUILD_FAILURE
|
|
|
|
/turf/simulated/floor/foamedmetal/canBuildPlating(var/material)
|
|
if(src.x >= (world.maxx - TRANSITIONEDGE) || src.x <= TRANSITIONEDGE)
|
|
return BUILD_FAILURE
|
|
else if (src.y >= (world.maxy - TRANSITIONEDGE || src.y <= TRANSITIONEDGE ))
|
|
return BUILD_FAILURE
|
|
else if((locate(/obj/structure/lattice) in contents) && !(istype(material,/obj/item/stack/tile/wood)))
|
|
return 1
|
|
return BUILD_FAILURE
|
|
|
|
/datum/effect/system/reagents_explosion
|
|
var/amount // TNT equivalent
|
|
var/flashing = 0 // does explosion creates flash effect?
|
|
var/flashing_factor = 0 // factor of how powerful the flash effect relatively to the explosion
|
|
var/mob/user //for investigation
|
|
|
|
/datum/effect/system/reagents_explosion/set_up (amt, loc, flash = 0, flash_fact = 0, var/mob/whodunnit)
|
|
amount = amt
|
|
if(istype(loc, /turf/))
|
|
location = loc
|
|
else
|
|
location = get_turf(loc)
|
|
|
|
flashing = flash
|
|
flashing_factor = flash_fact
|
|
user = whodunnit
|
|
|
|
return
|
|
|
|
/datum/effect/system/reagents_explosion/start()
|
|
if (amount <= 2)
|
|
spark(location, 2)
|
|
|
|
for(var/mob/M in viewers(5, location))
|
|
to_chat(M, "<span class='warning'>The solution violently explodes.</span>")
|
|
for(var/mob/M in viewers(1, location))
|
|
if (prob (50 * amount))
|
|
to_chat(M, "<span class='warning'>The explosion knocks you down.</span>")
|
|
var/incapacitation_duration = rand(1,5)
|
|
M.Knockdown(incapacitation_duration)
|
|
M.Stun(incapacitation_duration)
|
|
return
|
|
else
|
|
var/devastation = -1
|
|
var/heavy = -1
|
|
var/light = -1
|
|
var/flash = -1
|
|
var/range = 0
|
|
// Clamp all values to MAX_EXPLOSION_RANGE
|
|
range = min (MAX_EXPLOSION_RANGE, light + round(amount/3))
|
|
devastation = round(min(3, range * 0.25)) // clamps to 3 devastation for grenades
|
|
heavy = round(min(5, range * 0.5)) // clamps to 5 heavy range for grenades
|
|
light = min(7, range) // clamps to 7 light range for grenades
|
|
flash = range * 1.5
|
|
for(var/mob/M in viewers(8, location))
|
|
to_chat(M, "<span class='warning'>The solution violently explodes.</span>")
|
|
|
|
explosion(location, devastation, heavy, light, flash, whodunnit = user)
|
|
|
|
/datum/effect/system/reagents_explosion/proc/holder_damage(var/atom/holder)
|
|
if(holder)
|
|
var/dmglevel = 4
|
|
|
|
if (round(amount/8) > 0)
|
|
dmglevel = 1
|
|
else if (round(amount/4) > 0)
|
|
dmglevel = 2
|
|
else if (round(amount/2) > 0)
|
|
dmglevel = 3
|
|
|
|
if(dmglevel<4)
|
|
holder.ex_act(dmglevel)
|