diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index ae6943d553a..444077aacd7 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -134,4 +134,6 @@
//resistance_flags
#define INDESTRUCTIBLE 64 //doesn't take damage
+#define CHECK_RICOCHET_1 (1<<4)
+
GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768))
diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm
index dd0045a2105..7115cc677f6 100644
--- a/code/__DEFINES/math.dm
+++ b/code/__DEFINES/math.dm
@@ -38,6 +38,10 @@
#define SHORT_REAL_LIMIT 16777216
+// Real modulus that handles decimals
+#define MODULUS(x, y) ( (x) - (y) * round((x) / (y)) )
+
+
//"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks
//percent_of_tick_used * (ticklag * 100(to convert to ms)) / 100(percent ratio)
//collapsed to percent_of_tick_used * tick_lag
@@ -53,3 +57,10 @@
// round() acts like floor(x, 1) by default but can't handle other values
#define FLOOR(x, y) ( round((x) / (y)) * (y) )
+
+
+// Will filter out extra rotations and negative rotations
+// E.g: 540 becomes 180. -180 becomes 180.
+#define SIMPLIFY_DEGREES(degrees) (MODULUS((degrees), 360))
+
+#define GET_ANGLE_OF_INCIDENCE(face, input) (MODULUS((face) - (input), 360))
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 4398229391b..30b901875b3 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -15,6 +15,7 @@
var/atom_say_verb = "says"
var/dont_save = 0 // For atoms that are temporary by necessity - like lighting overlays
+
///Chemistry.
var/container_type = NONE
var/datum/reagents/reagents = null
@@ -698,6 +699,9 @@ var/list/blood_splatter_icons = list()
/atom/proc/ratvar_act()
return
+/atom/proc/handle_ricochet(obj/item/projectile/P)
+ return
+
//This proc is called on the location of an atom when the atom is Destroy()'d
/atom/proc/handle_atom_del(atom/A)
return
diff --git a/code/game/gamemodes/blob/blobs/shield.dm b/code/game/gamemodes/blob/blobs/shield.dm
index 5ae1a040ab6..1fb293be2cf 100644
--- a/code/game/gamemodes/blob/blobs/shield.dm
+++ b/code/game/gamemodes/blob/blobs/shield.dm
@@ -3,9 +3,9 @@
icon = 'icons/mob/blob.dmi'
icon_state = "blob_idle"
desc = "Some blob creature thingy"
- health = 75
+ health = 75
fire_resist = 2
-
+ var/maxHealth = 75
/obj/structure/blob/shield/update_icon()
if(health <= 0)
@@ -19,3 +19,34 @@
/obj/structure/blob/shield/CanPass(atom/movable/mover, turf/target, height=0)
if(istype(mover) && mover.checkpass(PASSBLOB)) return 1
return 0
+
+/obj/structure/blob/shield/reflective
+ name = "reflective blob"
+ desc = "A solid wall of slightly twitching tendrils with a reflective glow."
+ icon_state = "blob_idle_glow"
+ brute_resist = 0
+ health = 50
+ maxHealth = 50
+ flags_2 = CHECK_RICOCHET_1
+ var/reflect_chance = 80 //80% chance to reflect
+
+/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/projectile/P)
+ if(P.is_reflectable && prob(reflect_chance))
+ var/P_turf = get_turf(P)
+ var/face_direction = get_dir(src, P_turf)
+ var/face_angle = dir2angle(face_direction)
+ var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180))
+ if(abs(incidence_s) > 90 && abs(incidence_s) < 270)
+ return FALSE
+ var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
+ P.setAngle(new_angle_s)
+ P.firer = src //so people who fired the lasers are not immune to them when it reflects
+ visible_message("[P] reflects off [src]!")
+ return -1// complete projectile permutation
+ else
+ playsound(src, P.hitsound, 50, 1)
+ visible_message("[src] is hit by \a [P]!")
+ take_damage(P.damage, P.damage_type)
+
+/obj/structure/blob/shield/reflective/bullet_act()
+ return
diff --git a/code/game/gamemodes/blob/overmind.dm b/code/game/gamemodes/blob/overmind.dm
index d65fcb24ee6..c0f2e4a4a9e 100644
--- a/code/game/gamemodes/blob/overmind.dm
+++ b/code/game/gamemodes/blob/overmind.dm
@@ -56,6 +56,7 @@
to_chat(src, "You are the overmind and can control the blob! You can expand, which will attack people, and place new blob pieces such as...")
to_chat(src, "Normal Blob will expand your reach and allow you to upgrade into special blobs that perform certain functions.")
to_chat(src, "Shield Blob is a strong and expensive blob which can take more damage. It is fireproof and can block air, use this to protect yourself from station fires.")
+ to_chat(src, "Reflective Blobis an upgraded Shield Blob which has a high chance of deflecting energy projectiles, but is vulnerable to ballistics and brute damage.")
to_chat(src, "Resource Blob is a blob which will collect more resources for you, try to build these earlier to get a strong income. It will benefit from being near your core or multiple nodes, by having an increased resource rate; put it alone and it won't create resources at all.")
to_chat(src, "Node Blob is a blob which will grow, like the core. Unlike the core it won't give you a small income but it can power resource and factory blobs to increase their rate.")
to_chat(src, "Factory Blob is a blob which will spawn blob spores which will attack nearby food. Putting this nearby nodes and your core will increase the spawn rate; put it alone and it will not spawn any spores.")
diff --git a/code/game/gamemodes/blob/powers.dm b/code/game/gamemodes/blob/powers.dm
index 35f3c935013..4491b80d2e4 100644
--- a/code/game/gamemodes/blob/powers.dm
+++ b/code/game/gamemodes/blob/powers.dm
@@ -33,34 +33,51 @@
/mob/camera/blob/verb/create_shield_power()
set category = "Blob"
- set name = "Create Shield Blob (10)"
- set desc = "Create a shield blob."
+ set name = "Create/Upgrade Shield Blob (15)"
+ set desc = "Create/Upgrade a shield blob. Using this on an existing shield blob turns it into a reflective blob, capable of reflecting most energy projectiles but making it much weaker than usual to brute attacks."
var/turf/T = get_turf(src)
create_shield(T)
/mob/camera/blob/proc/create_shield(var/turf/T)
- var/obj/structure/blob/B = (locate(/obj/structure/blob) in T)
+ var/obj/structure/blob/B = locate(/obj/structure/blob) in T
+ var/obj/structure/blob/shield/S = locate(/obj/structure/blob/shield) in T
+
+ if(!S)
+ if(!B)//We are on a blob
+ to_chat(src, "There is no blob here!")
+ return
- if(!B)//We are on a blob
- to_chat(src, "There is no blob here!")
- return
+ else if(!istype(B, /obj/structure/blob/normal))
+ to_chat(src, "Unable to use this blob, find a normal one.")
+ return
- if(!istype(B, /obj/structure/blob/normal))
- to_chat(src, "Unable to use this blob, find a normal one.")
- return
+ else if(!can_buy(15))
+ return
- if(!can_buy(10))
- return
+ B.color = blob_reagent_datum.color
+ B.change_to(/obj/structure/blob/shield)
+ else
+
+ if(istype(S, /obj/structure/blob/shield/reflective))
+ to_chat(src, "There's already a reflector blob here!")
+ return
- B.color = blob_reagent_datum.color
- B.change_to(/obj/structure/blob/shield)
+ else if(S.health < S.maxHealth * 0.5)
+ to_chat(src, "This shield blob is too damaged to be modified properly!")
+ return
+
+ else if (!can_buy(15))
+ return
+
+ to_chat(src, "You secrete a reflective ooze over the shield blob, allowing it to reflect energy projectiles at the cost of reduced intregrity.")
+
+ S.change_to(/obj/structure/blob/shield/reflective)
+ S.color = blob_reagent_datum.color
return
-
-
/mob/camera/blob/verb/create_resource()
set category = "Blob"
set name = "Create Resource Blob (40)"
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 112b5d109e0..949983d45ba 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -30,7 +30,9 @@
var/spread = 0 //amount (in degrees) of projectile spread
var/legacy = FALSE //legacy projectile system
animate_movement = 0
-
+
+ var/ignore_source_check = FALSE
+
var/damage = 10
var/tile_dropoff = 0 //how much damage should be decremented as the bullet moves
var/tile_dropoff_s = 0 //same as above but for stamina
@@ -54,6 +56,9 @@
var/jitter = 0
var/forcedodge = 0 //to pass through everything
var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all.
+ var/ricochets = 0
+ var/ricochets_max = 2
+ var/ricochet_chance = 0
var/log_override = FALSE //whether print to admin attack logs or just keep it in the diary
@@ -157,6 +162,14 @@
/obj/item/projectile/Bump(atom/A, yes)
if(!yes) //prevents double bumps.
return
+
+ if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max)
+ ricochets++
+ if(A.handle_ricochet(src))
+ on_ricochet(A)
+ ignore_source_check = TRUE
+ range = initial(range)
+ return TRUE
if(firer)
if(A == firer || (A == firer.loc && istype(A, /obj/mecha))) //cannot shoot yourself or your mech
loc = A.loc
@@ -280,3 +293,21 @@ obj/item/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a pro
/obj/item/projectile/proc/dumbfire(var/dir)
current = get_ranged_target_turf(src, dir, world.maxx) //world.maxx is the range. Not sure how to handle this better.
fire()
+
+
+/obj/item/projectile/proc/on_ricochet(atom/A)
+ return
+
+/obj/item/projectile/proc/check_ricochet()
+ if(prob(ricochet_chance))
+ return TRUE
+ return FALSE
+
+/obj/item/projectile/proc/check_ricochet_flag(atom/A)
+ if(A.flags_2 & CHECK_RICOCHET_1)
+ return TRUE
+ return FALSE
+
+/obj/item/projectile/proc/setAngle(new_angle) //wrapper for overrides.
+ Angle = new_angle
+ return TRUE
diff --git a/icons/mob/blob.dmi b/icons/mob/blob.dmi
index 414334e105b..56c39b9a75d 100644
Binary files a/icons/mob/blob.dmi and b/icons/mob/blob.dmi differ