diff --git a/code/modules/mob/living/simple_mob/combat.dm b/code/modules/mob/living/simple_mob/combat.dm index 8cb60cd177..0f3205fb8c 100644 --- a/code/modules/mob/living/simple_mob/combat.dm +++ b/code/modules/mob/living/simple_mob/combat.dm @@ -127,9 +127,12 @@ // Otherwise default to the mob's firing sound. playsound(src, P.fire_sound ? P.fire_sound : projectilesound, 80, 1) - P.firer = src // So we can't shoot ourselves. - P.old_style_target(A, src) - P.fire() + // For some reason there isn't an argument for accuracy, so access the projectile directly instead. + // Also, placing dispersion here instead of in forced_spread will randomize the chosen angle between dispersion and -dispersion in fire() instead of having to do that here. + P.accuracy += calculate_accuracy() + P.dispersion += calculate_dispersion() + + P.launch_projectile(target = A, target_zone = null, user = src, params = null, angle_override = null, forced_spread = 0) if(needs_reload) reload_count++ @@ -149,6 +152,25 @@ . = FALSE set_AI_busy(FALSE) +/mob/living/simple_mob/proc/calculate_dispersion() + . = projectile_dispersion // Start with the basic var. + + // Some modifiers change dispersion. This makes simple_mobs respect that. + for(var/datum/modifier/M in modifiers) + if(!isnull(M.accuracy_dispersion)) + . += M.accuracy_dispersion + + // Make sure we don't go under zero dispersion. + . = max(., 0) + +/mob/living/simple_mob/proc/calculate_accuracy() + . = projectile_accuracy // Start with the basic var. + + // Some modifiers make it harder or easier to hit things. + for(var/datum/modifier/M in modifiers) + if(!isnull(M.accuracy)) + . += M.accuracy + // Can we currently do a special attack? /mob/living/simple_mob/proc/can_special_attack(atom/A) // Validity check. diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm index 9ad2ec58be..ed01226490 100644 --- a/code/modules/mob/living/simple_mob/simple_mob.dm +++ b/code/modules/mob/living/simple_mob/simple_mob.dm @@ -82,6 +82,8 @@ //Attack ranged settings var/projectiletype // The projectiles I shoot var/projectilesound // The sound I make when I do it + var/projectile_accuracy = 0 // Accuracy modifier to add onto the bullet when its fired. + var/projectile_dispersion = 0 // How many degrees to vary when I do it. var/casingtype // What to make the hugely laggy casings pile out of // Reloading settings, part of ranged code diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 1d67ebb3d8..aa4dc1bbcd 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -406,7 +406,7 @@ /obj/item/projectile/proc/old_style_target(atom/target, atom/source) if(!source) source = get_turf(src) - starting = source + starting = get_turf(source) original = target setAngle(Get_Angle(source, target)) @@ -435,7 +435,7 @@ beam_segments[beam_index] = pcache generate_hitscan_tracers(null, null, impacting) -/obj/item/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE) +/obj/item/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 5, impacting = TRUE) if(!length(beam_segments)) return if(tracer_type) diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm index ef61558518..97069db1fb 100644 --- a/code/modules/projectiles/projectile/arc.dm +++ b/code/modules/projectiles/projectile/arc.dm @@ -9,11 +9,12 @@ /obj/item/projectile/arc name = "arcing shot" icon_state = "fireball" // WIP - speed = 2 // Travel a bit slower, to really sell the arc visuals. movement_type = UNSTOPPABLE plane = ABOVE_PLANE // Since projectiles are 'in the air', they might visually overlap mobs while in flight, so the projectile needs to be above their plane. - var/target_distance = null // How many tiles the impact site is. var/fired_dir = null // Which direction was the projectile fired towards. Needed to invert the projectile turning based on if facing left or right. + var/distance_to_fly = null // How far, in pixels, to fly for. Will call on_range() when this is passed. + var/visual_y_offset = -16 // Adjusts how high the projectile and its shadow start, visually. This is so the projectile and shadow align with the center of the tile. + var/projectile_speed_modifier = 0.5 // Slows it down to make the arcing more noticable, and improve pixel calculation accuracy. var/obj/effect/projectile_shadow/shadow = null // Visual indicator for the projectile's 'true' position. Needed due to being bound to two dimensions in reality. /obj/item/projectile/arc/Bump() @@ -27,59 +28,76 @@ QDEL_NULL(shadow) return ..() -// This is a test projectile in the sense that its testing the code to make sure it works, -// as opposed to a 'can I hit this thing' projectile. -/obj/item/projectile/arc/test/on_impact(turf/T) - new /obj/effect/explosion(T) - return ..() -/obj/item/projectile/arc/old_style_target(target, source) - var/source_loc = get_turf(source) || get_turf(src) - var/expected_distance = get_dist(target, source_loc) - range = expected_distance // So the projectile "hits the ground." - target_distance = expected_distance - fired_dir = get_dir(source_loc, target) - ..() - if(fired_dir & EAST) - transform = turn(transform, -45) - else if(fired_dir & WEST) - transform = turn(transform, 45) +/obj/item/projectile/arc/proc/calculate_initial_pixel_distance(atom/user, atom/target) + var/datum/point/A = new(user) + var/datum/point/B = new(target) + + // Set the pixel offsets if they exist. + A.x += (p_x - 16) + A.y += (p_y - 16) + + return pixel_length_between_points(A, B) + +/obj/item/projectile/arc/proc/distance_flown() + var/datum/point/current_point = new(src) + var/datum/point/starting_point = new(starting) + return pixel_length_between_points(current_point, starting_point) /obj/item/projectile/arc/on_range() - on_impact(loc) + if(loc) + on_impact(loc) return ..() -// Visuals. -/obj/item/projectile/arc/after_move() - if(QDELETED(src)) - return - // Handle projectile turning in flight. - // This won't turn if fired north/south, as it looks weird. - var/turn_per_step = 90 / target_distance - if(fired_dir & EAST) - transform = turn(transform, turn_per_step) - else if(fired_dir & WEST) - transform = turn(transform, -turn_per_step) - // Now for the fake height. - // We need to know how far along our "arc" we are. - var/arc_progress = get_dist(src, original) - var/arc_max_height = (target_distance * world.icon_size) / 2 // TODO: Real math. -// var/arc_center = target_distance / 2 -// var/projectile_position = abs(arc_progress - arc_center) -// var/height_multiplier = projectile_position / arc_center -// height_multiplier = abs(height_multiplier - 1) -// height_multiplier = height_multiplier ** 2 +/obj/item/projectile/arc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) + fired_dir = get_dir(user, target) // Used to determine if the projectile should turn in the air. + distance_to_fly = calculate_initial_pixel_distance(user, target) // Calculates how many pixels to travel before hitting the ground. + ..() // Does the actual launching. The projectile will be out after this. + +// For legacy. +/obj/item/projectile/arc/old_style_target(atom/target, atom/source) + ..() + fired_dir = get_dir(source, target) + distance_to_fly = calculate_initial_pixel_distance(source, target) -// animate(src, pixel_z = arc_max_height * height_multiplier, time = step_delay) - var/projectile_position = arc_progress / target_distance - var/sine_position = projectile_position * 180 - var/pixel_z_position = arc_max_height * sin(sine_position) - animate(src, pixel_z = pixel_z_position, time = speed) +/obj/item/projectile/arc/fire(angle, atom/direct_target) + ..() // The trajectory must exist for set_pixel_speed() to work. + set_pixel_speed(projectile_speed_modifier) // Slows it down and makes the distance checking more accurate. + +/obj/item/projectile/arc/pixel_move(trajectory_multiplier, hitscanning = FALSE) + // Do the other important stuff first. + ..(trajectory_multiplier, hitscanning) + + // Test to see if its time to 'hit the ground'. + var/pixels_flown = distance_flown() + + if(pixels_flown >= distance_to_fly) + on_range() // This will also cause the projectile to be deleted. + + else + // Handle visual projectile turning in flight. + var/arc_progress = between(0, pixels_flown / distance_to_fly, 1) + var/new_visual_degree = LERP(45, 135, arc_progress) + + if(fired_dir & EAST) + adjust_rotation(new_visual_degree) + else if(fired_dir & WEST) + adjust_rotation(-new_visual_degree) + + // Now for the fake height. + var/arc_max_pixel_height = distance_to_fly / 2 + var/sine_position = arc_progress * 180 + var/pixel_z_position = (arc_max_pixel_height * sin(sine_position)) + visual_y_offset + animate(src, pixel_z = pixel_z_position, time = 1, flags = ANIMATION_END_NOW) + + // Update our shadow. + if(shadow) + shadow.forceMove(loc) + shadow.pixel_x = pixel_x + shadow.pixel_y = pixel_y + visual_y_offset - // Update our shadow. - shadow.forceMove(loc) /obj/effect/projectile_shadow name = "shadow" @@ -87,11 +105,18 @@ icon = 'icons/obj/projectiles.dmi' icon_state = "arc_shadow" anchored = TRUE + animate_movement = 0 // Just like the projectile it's following. ////////////// // Subtypes ////////////// +// This is a test projectile in the sense that its testing the code to make sure it works, +// as opposed to a 'can I hit this thing' projectile. +/obj/item/projectile/arc/test/on_impact(turf/T) + new /obj/effect/explosion(T) + T.color = "#FF0000" + // Generic, Hivebot related /obj/item/projectile/arc/blue_energy name = "energy missile" @@ -99,6 +124,10 @@ damage = 15 damage_type = BURN +/obj/item/projectile/arc/blue_energy/on_impact(turf/T) + for(var/mob/living/L in T) + attack_mob(L) // Everything on the turf it lands gets hit. + // Fragmentation arc shot /obj/item/projectile/arc/fragmentation name = "fragmentation shot" @@ -125,11 +154,9 @@ /obj/item/projectile/arc/emp_blast/on_impact(turf/T) empulse(T, 2, 4, 7, 10) // Normal EMP grenade. - return ..() /obj/item/projectile/arc/emp_blast/weak/on_impact(turf/T) empulse(T, 1, 2, 3, 4) // Sec EMP grenade. - return ..() // Radiation arc shot /obj/item/projectile/arc/radioactive @@ -139,4 +166,4 @@ var/rad_power = 50 /obj/item/projectile/arc/radioactive/on_impact(turf/T) - radiation_repository.radiate(T, rad_power) \ No newline at end of file + radiation_repository.radiate(T, rad_power)