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