diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm index c5e3a97218..41d2f721f5 100644 --- a/code/__HELPERS/maths.dm +++ b/code/__HELPERS/maths.dm @@ -182,3 +182,41 @@ GLOBAL_LIST_INIT(sqrtTable, list(1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, gaussian_next = R2 * working return (mean + stddev * R1) #undef ACCURACY + +/proc/mouse_angle_from_client(client/client) + var/list/mouse_control = params2list(client.mouseParams) + if(mouse_control["screen-loc"]) + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32) + var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32) + var/screenview = (client.view * 2 + 1) * world.icon_size //Refer to http://www.byond.com/docs/ref/info.html#/client/var/view for mad maths + var/ox = round(screenview/2) - client.pixel_x //"origin" x + var/oy = round(screenview/2) - client.pixel_y //"origin" y + var/angle = NORM_ROT(Atan2(y - oy, x - ox)) + return angle + +/proc/get_turf_in_angle(angle, turf/starting, increments) + var/pixel_x = 0 + var/pixel_y = 0 + for(var/i in 1 to increments) + pixel_x += sin(angle)+16*sin(angle)*2 + pixel_y += cos(angle)+16*cos(angle)*2 + var/new_x = starting.x + var/new_y = starting.y + while(pixel_x > 16) + pixel_x -= 32 + new_x++ + while(pixel_x < -16) + pixel_x += 32 + new_x-- + while(pixel_y > 16) + pixel_y -= 32 + new_y++ + while(pixel_y < -16) + pixel_y += 32 + new_y-- + new_x = Clamp(new_x, 0, world.maxx) + new_y = Clamp(new_y, 0, world.maxy) + return locate(new_x, new_y, starting.z) diff --git a/code/_onclick/autoclick.dm b/code/_onclick/autoclick.dm index 23d07e2968..29a704ee7a 100644 --- a/code/_onclick/autoclick.dm +++ b/code/_onclick/autoclick.dm @@ -24,17 +24,6 @@ active_mousedown_item.onMouseUp(object, location, params, mob) active_mousedown_item = null -/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params) - mouseParams = params - mouseLocation = over_location - mouseObject = over_object - mouseControlObject = over_control - if(selected_target[1] && over_object && over_object.IsAutoclickable()) - selected_target[1] = over_object - selected_target[2] = params - if(active_mousedown_item) - active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - /mob/proc/CanMobAutoclick(object, location, params) /mob/living/carbon/CanMobAutoclick(atom/object, location, params) @@ -63,9 +52,6 @@ /obj/item/proc/onMouseUp(object, location, params, mob) return -/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - return - /obj/item var/canMouseDown = FALSE @@ -90,3 +76,23 @@ mouseLocation = location mouseObject = object mouseControlObject = control + if(mob && LAZYLEN(mob.mousemove_intercept_objects)) + for(var/obj/item/I in mob.mousemove_intercept_objects) + I.onMouseMove(object, location, control, params) + +/obj/item/proc/onMouseMove(object, location, control, params) + return + +/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params) + mouseParams = params + mouseLocation = over_location + mouseObject = over_object + mouseControlObject = over_control + if(selected_target[1] && over_object && over_object.IsAutoclickable()) + selected_target[1] = over_object + selected_target[2] = params + if(active_mousedown_item) + active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + +/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + return diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 7820b29691..7e38d59998 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -432,14 +432,26 @@ /obj/screen/click_catcher icon = 'icons/mob/screen_gen.dmi' - icon_state = "click_catcher" + icon_state = "flash" plane = CLICKCATCHER_PLANE mouse_opacity = 2 screen_loc = "CENTER" -/obj/screen/click_catcher/New() - ..() - transform = matrix(200, 0, 0, 0, 200, 0) +/obj/screen/click_catcher/proc/UpdateGreed(view_size_x = 7, view_size_y = 7) + var/icon/newicon = icon('icons/mob/screen_gen.dmi', "flash") + if(view_size_x > 16 || view_size_y > 16) + newicon.Scale((16 * 2 + 1) * world.icon_size,(16 * 2 + 1) * world.icon_size) + icon = newicon + var/tx = view_size_x/16 + var/ty = view_size_y/16 + var/matrix/M = new + M.Scale(tx, ty) + transform = M + screen_loc = "CENTER-16,CENTER-16" + else + screen_loc = "CENTER-[view_size_x],CENTER-[view_size_y]" + newicon.Scale((view_size_x * 2 + 1) * world.icon_size,(view_size_y * 2 + 1) * world.icon_size) + icon = newicon /obj/screen/click_catcher/Click(location, control, params) var/list/modifiers = params2list(params) @@ -453,7 +465,6 @@ T.Click(location, control, params) . = 1 - /* MouseWheelOn */ /mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 4074d7d3f3..8c1c5a983c 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -145,6 +145,7 @@ return 0 screenmob.client.screen = list() + screenmob.client.apply_clickcatcher() var/display_hud_version = version if(!display_hud_version) //If 0 or blank, display the next hud version diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 55dbb86b29..a63066709a 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -450,6 +450,7 @@ for(var/i in 1 to allowed_contents.len) var/thing = allowed_contents[i] qdel(thing, force=TRUE) + var/turf/newT = ChangeTurf(turf_type, baseturf_type, FALSE, FALSE, forceop = forceop) diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 6461ad2a23..de066837ee 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -649,9 +649,9 @@ Traitors and the like can also be revived with the previous role mostly intact. set desc = "switches between 1x and custom views" if(view == world.view) - view = input("Select view range:", "FUCK YE", 7) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128) + change_view(input("Select view range:", "FUCK YE", 7) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128)) else - view = world.view + change_view(world.view) log_admin("[key_name(usr)] changed their view range to [view].") //message_admins("\blue [key_name_admin(usr)] changed their view range to [view].") //why? removed by order of XSI diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 06e921399e..5dfd9ecd4b 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -48,7 +48,7 @@ preload_rsc = PRELOAD_RSC - var/global/obj/screen/click_catcher/void + var/obj/screen/click_catcher/void // Used by html_interface module. var/hi_last_pos diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index aa54bb224e..4455af7ca9 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -301,10 +301,8 @@ GLOBAL_LIST(external_rsc_urls) send_resources() - if(!void) - void = new() - - screen += void + generate_clickcatcher() + apply_clickcatcher() if(prefs.lastchangelog != GLOB.changelog_hash) //bolds the changelog button on the interface so we know there are updates. to_chat(src, "You have unread updates in the changelog.") @@ -667,6 +665,16 @@ GLOBAL_LIST(external_rsc_urls) CRASH("change_view called without argument.") view = new_size + apply_clickcatcher() + +/client/proc/generate_clickcatcher() + if(!void) + void = new() + screen += void + +/client/proc/apply_clickcatcher() + generate_clickcatcher() + void.UpdateGreed(view,view) /client/proc/AnnouncePR(announcement) if(prefs && prefs.chat_toggles & CHAT_PULLR) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 0c098256cb..d8bbc559c5 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -326,7 +326,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp if(mind.current.key && copytext(mind.current.key,1,2)!="@") //makes sure we don't accidentally kick any clients to_chat(usr, "Another consciousness is in your body...It is resisting you.") return - client.view = world.view + client.change_view(world.view) SStgui.on_transfer(src, mind.current) // Transfer NanoUIs. mind.current.key = key return 1 @@ -468,16 +468,16 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp views |= i var/new_view = input("Choose your new view", "Modify view range", 7) as null|anything in views if(new_view) - client.view = Clamp(new_view, 1, max_view) + client.change_view(Clamp(new_view, 1, max_view)) else - client.view = world.view + client.change_view(world.view) /mob/dead/observer/verb/add_view_range(input as num) set name = "Add View Range" set hidden = TRUE var/max_view = client.prefs.unlock_content ? GHOST_MAX_VIEW_RANGE_MEMBER : GHOST_MAX_VIEW_RANGE_DEFAULT if(input) - client.view = Clamp(client.view + input, 1, max_view) + client.change_view(Clamp(client.view + input, 1, max_view)) /mob/dead/observer/verb/boo() set category = "Ghost" diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 2f5b0b8b93..7cd296e4f1 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -44,7 +44,7 @@ var/mob/dead/observe = M if(observe.client) observe.client.screen -= I - I.loc = src + I.forceMove(src) I.layer = ABOVE_HUD_LAYER I.plane = ABOVE_HUD_PLANE I.appearance_flags |= NO_CLIENT_COLOR diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 8e732940a1..bed14bdff4 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -39,7 +39,7 @@ if(client) client.click_intercept = null - client.view = world.view // Resets the client.view in case it was changed. + client.change_view(world.view) // Resets the client.view in case it was changed. if(!GLOB.individual_log_list[ckey]) GLOB.individual_log_list[ckey] = logging diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 56e56bfcaa..1da8a82c51 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -141,3 +141,5 @@ var/list/progressbars = null //for stacking do_after bars var/list/can_ride_typecache = list() + + var/list/mousemove_intercept_objects diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 9542f6af1a..156a5ee81e 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -488,11 +488,11 @@ if(WEST) _x = -zoom_amt - user.client.view = zoom_out_amt + user.client.change_view(zoom_out_amt) user.client.pixel_x = world.icon_size*_x user.client.pixel_y = world.icon_size*_y else - user.client.view = world.view + user.client.change_view(world.view) user.client.pixel_x = 0 user.client.pixel_y = 0 return zoomed diff --git a/code/modules/projectiles/guns/beam_rifle.dm b/code/modules/projectiles/guns/beam_rifle.dm index 0133edaa19..7617c57100 100644 --- a/code/modules/projectiles/guns/beam_rifle.dm +++ b/code/modules/projectiles/guns/beam_rifle.dm @@ -1,4 +1,16 @@ +#define ZOOM_LOCK_AUTOZOOM_FREEMOVE 0 +#define ZOOM_LOCK_AUTOZOOM_ANGLELOCK 1 +#define ZOOM_LOCK_CENTER_VIEW 2 +#define ZOOM_LOCK_OFF 3 + +#define ZOOM_SPEED_STEP 0 +#define ZOOM_SPEED_INSTANT 1 + +#define AUTOZOOM_PIXEL_STEP_FACTOR 48 + +#define AIMING_BEAM_ANGLE_CHANGE_THRESHOLD 0.1 + /obj/item/weapon/gun/energy/beam_rifle name = "particle acceleration rifle" desc = "An energy-based anti material marksman rifle that uses highly charged particle beams moving at extreme velocities to decimate whatever is unfortunate enough to be targetted by one. \ @@ -12,22 +24,13 @@ force = 15 materials = list() origin_tech = "" - recoil = 5 + recoil = 4 ammo_x_offset = 3 ammo_y_offset = 3 modifystate = FALSE - zoomable = TRUE - zoom_amt = 17 - zoom_out_amt = 20 weapon_weight = WEAPON_HEAVY w_class = WEIGHT_CLASS_BULKY ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan) - var/hipfire_inaccuracy = 2 - var/hipfire_recoil = 10 - var/scoped_inaccuracy = 0 - var/scoped_recoil = 3 - var/scoped = FALSE - var/noscope = FALSE //Can you fire this without a scope? cell_type = /obj/item/weapon/stock_parts/cell/beam_rifle canMouseDown = TRUE pin = null @@ -37,13 +40,14 @@ var/aiming_time_left = 7 var/aiming_time_increase_user_movement = 3 var/scoped_slow = 1 - var/aiming_time_increase_angle_multiplier = 0.6 + var/aiming_time_increase_angle_multiplier = 0.3 var/lastangle = 0 + var/aiming_lastangle = 0 var/mob/current_user = null - var/list/obj/effect/temp_visual/current_tracers = list() + var/obj/effect/projectile_beam/current_tracer - var/structure_piercing = 2 //This doesn't always work! + var/structure_piercing = 2 //Amount * 2. For some reason structures aren't respecting this unless you have it doubled. Probably with the objects in question's Bump() code instead of this but I'll deal with this later. var/structure_bleed_coeff = 0.7 var/wall_pierce_amount = 0 var/wall_devastate = 0 @@ -60,19 +64,122 @@ var/delay = 65 var/lastfire = 0 + //ZOOMING + var/zoom_current_view_increase = 0 + var/zoom_target_view_increase = 10 + var/zoom_speed = ZOOM_SPEED_STEP + var/zooming = FALSE + var/zoom_lock = ZOOM_LOCK_AUTOZOOM_FREEMOVE + var/zooming_angle + var/current_zoom_x = 0 + var/current_zoom_y = 0 + var/zoom_animating = 0 + var/static/image/charged_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_charged") var/static/image/drained_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_empty") + var/datum/action/item_action/zoom_speed_action/zoom_speed_action + var/datum/action/item_action/zoom_lock_action/zoom_lock_action + /obj/item/weapon/gun/energy/beam_rifle/debug delay = 0 cell_type = /obj/item/weapon/stock_parts/cell/infinite aiming_time = 0 recoil = 0 - scoped_recoil = 0 - hipfire_recoil = 0 - hipfire_inaccuracy = 0 - scoped_inaccuracy = 0 - noscope = 1 + pin = /obj/item/device/firing_pin + +/obj/item/weapon/gun/energy/beam_rifle/equipped(mob/user) + set_user(user) + . = ..() + +/obj/item/weapon/gun/energy/beam_rifle/pickup(mob/user) + set_user(user) + . = ..() + +/obj/item/weapon/gun/energy/beam_rifle/dropped() + set_user() + . = ..() + +/obj/item/weapon/gun/energy/beam_rifle/ui_action_click(owner, action) + if(istype(action, /datum/action/item_action/zoom_speed_action)) + zoom_speed++ + if(zoom_speed > 1) + zoom_speed = ZOOM_SPEED_STEP + switch(zoom_speed) + if(ZOOM_SPEED_STEP) + to_chat(owner, "You switch [src]'s digital zoom to stepper mode.") + if(ZOOM_SPEED_INSTANT) + to_chat(owner, "You switch [src]'s digital zoom to instant mode.") + if(istype(action, /datum/action/item_action/zoom_lock_action)) + zoom_lock++ + if(zoom_lock > 3) + zoom_lock = 0 + switch(zoom_lock) + if(ZOOM_LOCK_AUTOZOOM_FREEMOVE) + to_chat(owner, "You switch [src]'s zooming processor to free directional.") + if(ZOOM_LOCK_AUTOZOOM_ANGLELOCK) + to_chat(owner, "You switch [src]'s zooming processor to locked directional.") + if(ZOOM_LOCK_CENTER_VIEW) + to_chat(owner, "You switch [src]'s zooming processor to center mode.") + if(ZOOM_LOCK_OFF) + to_chat(owner, "You disable [src]'s zooming system.") + reset_zooming() + +/obj/item/weapon/gun/energy/beam_rifle/proc/smooth_zooming(delay_override = null) + if(!check_user() || !zooming || zoom_lock == ZOOM_LOCK_OFF || zoom_lock == ZOOM_LOCK_CENTER_VIEW) + return + if(zoom_animating && delay_override != 0) + return smooth_zooming(zoom_animating + delay_override) //Automatically compensate for ongoing zooming actions. + var/total_time = SSfastprocess.wait + if(delay_override) + total_time = delay_override + if(zoom_speed == ZOOM_SPEED_INSTANT) + total_time = 0 + zoom_animating = total_time + animate(current_user.client, pixel_x = current_zoom_x, pixel_y = current_zoom_y , total_time, SINE_EASING, ANIMATION_PARALLEL) + zoom_animating = 0 + +/obj/item/weapon/gun/energy/beam_rifle/proc/set_autozoom_pixel_offsets_immediate(current_angle) + if(zoom_lock == ZOOM_LOCK_CENTER_VIEW || zoom_lock == ZOOM_LOCK_OFF) + return + current_zoom_x = sin(current_angle) + sin(current_angle) * AUTOZOOM_PIXEL_STEP_FACTOR * zoom_current_view_increase + current_zoom_y = cos(current_angle) + cos(current_angle) * AUTOZOOM_PIXEL_STEP_FACTOR * zoom_current_view_increase + +/obj/item/weapon/gun/energy/beam_rifle/proc/handle_zooming() + if(!zooming || !check_user()) + return + if(zoom_speed == ZOOM_SPEED_INSTANT) + current_user.client.change_view(world.view + zoom_target_view_increase) + zoom_current_view_increase = zoom_target_view_increase + set_autozoom_pixel_offsets_immediate(zooming_angle) + smooth_zooming() + return + if(zoom_current_view_increase > zoom_target_view_increase) + return + zoom_current_view_increase++ + current_user.client.change_view(zoom_current_view_increase + world.view) + set_autozoom_pixel_offsets_immediate(zooming_angle) + smooth_zooming(SSfastprocess.wait * zoom_target_view_increase * zoom_speed) + +/obj/item/weapon/gun/energy/beam_rifle/proc/start_zooming() + if(zoom_lock == ZOOM_LOCK_OFF) + return + zooming = TRUE + +/obj/item/weapon/gun/energy/beam_rifle/proc/stop_zooming() + zooming = FALSE + reset_zooming() + +/obj/item/weapon/gun/energy/beam_rifle/proc/reset_zooming() + if(!check_user(FALSE)) + return + zoom_animating = 0 + animate(current_user.client, pixel_x = 0, pixel_y = 0, 0, FALSE, LINEAR_EASING, ANIMATION_END_NOW) + zoom_current_view_increase = 0 + current_user.client.change_view(world.view) + zooming_angle = 0 + current_zoom_x = 0 + current_zoom_y = 0 /obj/item/weapon/gun/energy/beam_rifle/update_icon() cut_overlays() @@ -85,9 +192,10 @@ /obj/item/weapon/gun/energy/beam_rifle/attack_self(mob/user) projectile_setting_pierce = !projectile_setting_pierce to_chat(user, "You set \the [src] to [projectile_setting_pierce? "pierce":"impact"] mode.") + aiming_beam() /obj/item/weapon/gun/energy/beam_rifle/proc/update_slowdown() - if(scoped) + if(aiming) slowdown = scoped_slow else slowdown = initial(slowdown) @@ -95,140 +203,160 @@ /obj/item/weapon/gun/energy/beam_rifle/Initialize() . = ..() START_PROCESSING(SSfastprocess, src) + zoom_speed_action = new(src) + zoom_lock_action = new(src) /obj/item/weapon/gun/energy/beam_rifle/Destroy() STOP_PROCESSING(SSfastprocess, src) - ..() - -/obj/item/weapon/gun/energy/beam_rifle/zoom(user, forced_zoom) - . = ..() - scope(user, .) - -/obj/item/weapon/gun/energy/beam_rifle/proc/scope(mob/user, forced) - var/scoping - switch(forced) - if(TRUE) - scoping = TRUE - if(FALSE) - scoping = FALSE - else - scoping = !scoped - if(scoping) - spread = scoped_inaccuracy - recoil = scoped_recoil - scoped = TRUE - user << "You bring your [src] up and use its scope..." - else - spread = hipfire_inaccuracy - recoil = hipfire_recoil - scoped = FALSE - user << "You lower your [src]." - update_slowdown() - -/obj/item/weapon/gun/energy/beam_rifle/can_trigger_gun(var/mob/living/user) - if(!scoped && !noscope) - user << "This beam rifle can only be used while scoped!" - return FALSE - . = ..(user) + set_user(null) + QDEL_NULL(current_tracer) + return ..() /obj/item/weapon/gun/energy/beam_rifle/emp_act(severity) chambered = null recharge_newshot() -/obj/item/weapon/gun/energy/beam_rifle/proc/aiming_beam() - var/atom/A = current_user.client.mouseObject - if(!istype(A) || !A.loc) - return - var/turf/T = get_turf(current_user.client.mouseObject) - if(!istype(T)) +/obj/item/weapon/gun/energy/beam_rifle/proc/aiming_beam(force_update = FALSE) + var/diff = abs(aiming_lastangle - lastangle) + check_user() + if(diff < AIMING_BEAM_ANGLE_CHANGE_THRESHOLD && !force_update) return + aiming_lastangle = lastangle var/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/P = new P.gun = src P.wall_pierce_amount = wall_pierce_amount P.structure_pierce_amount = structure_piercing P.do_pierce = projectile_setting_pierce - P.preparePixelProjectile(current_user.client.mouseObject, T, current_user, current_user.client.mouseParams, 0) if(aiming_time) var/percent = ((100/aiming_time)*aiming_time_left) P.color = rgb(255 * percent,255 * ((100 - percent) / 100),0) else P.color = rgb(0, 255, 0) - clear_tracers() - P.fire() - -/obj/item/weapon/gun/energy/beam_rifle/proc/clear_tracers() - for(var/I in current_tracers) - current_tracers -= I - var/obj/effect/temp_visual/projectile_beam/PB = I - qdel(PB) - -/obj/item/weapon/gun/energy/beam_rifle/proc/terminate_aiming() - stop_aiming() - clear_tracers() + var/turf/curloc = get_turf(src) + var/turf/targloc = get_turf(current_user.client.mouseObject) + if(!istype(targloc)) + if(!istype(curloc)) + return + targloc = get_turf_in_angle(lastangle, curloc, 10) + P.preparePixelProjectile(targloc, targloc, current_user, current_user.client.mouseParams, 0) + P.fire(lastangle) /obj/item/weapon/gun/energy/beam_rifle/process() if(!aiming) return - if(!istype(current_user) || !isturf(current_user.loc) || !(src in current_user.held_items) || current_user.incapacitated()) //Doesn't work if you're not holding it! - terminate_aiming() - return + check_user() + handle_zooming() if(aiming_time_left > 0) aiming_time_left-- - aiming_beam() - process_aim() + aiming_beam(TRUE) + +/obj/item/weapon/gun/energy/beam_rifle/proc/check_user(automatic_cleanup = TRUE) + if(!istype(current_user) || !isturf(current_user.loc) || !(src in current_user.held_items) || current_user.incapacitated()) //Doesn't work if you're not holding it! + if(automatic_cleanup) + stop_aiming() + set_user(null) + return FALSE + return TRUE /obj/item/weapon/gun/energy/beam_rifle/proc/process_aim() - if(current_user.client.mouseParams) - var/list/mouse_control = params2list(current_user.client.mouseParams) - if(isturf(current_user.client.mouseLocation)) - current_user.face_atom(current_user.client.mouseLocation) - if(mouse_control["screen-loc"]) - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32) - var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32) - var/screenview = (current_user.client.view * 2 + 1) * world.icon_size //Refer to http://www.byond.com/docs/ref/info.html#/client/var/view for mad maths - var/ox = round(screenview/2) - current_user.client.pixel_x //"origin" x - var/oy = round(screenview/2) - current_user.client.pixel_y //"origin" y - var/angle = NORM_ROT(Atan2(y - oy, x - ox)) - var/difference = abs(lastangle - angle) - delay_penalty(difference * aiming_time_increase_angle_multiplier) - lastangle = angle + if(istype(current_user) && current_user.client && current_user.client.mouseParams) + var/angle = mouse_angle_from_client(current_user.client) + switch(angle) + if(316 to 360) + current_user.setDir(NORTH) + if(0 to 45) + current_user.setDir(NORTH) + if(46 to 135) + current_user.setDir(EAST) + if(136 to 225) + current_user.setDir(SOUTH) + if(226 to 315) + current_user.setDir(WEST) + var/difference = abs(lastangle - angle) + if(difference > 350) //Too lazy to properly math, detects 360 --> 0 changes. + difference = (lastangle > 350? ((360 - lastangle) + angle) : ((360 - angle) + lastangle)) + delay_penalty(difference * aiming_time_increase_angle_multiplier) + lastangle = angle /obj/item/weapon/gun/energy/beam_rifle/on_mob_move() - delay_penalty(aiming_time_increase_user_movement) + check_user() + if(aiming) + delay_penalty(aiming_time_increase_user_movement) + process_aim() + aiming_beam(TRUE) /obj/item/weapon/gun/energy/beam_rifle/proc/start_aiming() aiming_time_left = aiming_time aiming = TRUE + process_aim() + aiming_beam(TRUE) + zooming_angle = lastangle + start_zooming() /obj/item/weapon/gun/energy/beam_rifle/proc/stop_aiming() + set waitfor = FALSE aiming_time_left = aiming_time aiming = FALSE + QDEL_NULL(current_tracer) + stop_zooming() + +/obj/item/weapon/gun/energy/beam_rifle/proc/set_user(mob/user) + if(user == current_user) + return + stop_aiming() + if(istype(current_user)) + LAZYREMOVE(current_user.mousemove_intercept_objects, src) + current_user = null + if(istype(user)) + current_user = user + LAZYADD(current_user.mousemove_intercept_objects, src) /obj/item/weapon/gun/energy/beam_rifle/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - current_user = mob + if(aiming) + process_aim() + aiming_beam() + if(zoom_lock == ZOOM_LOCK_AUTOZOOM_FREEMOVE) + zooming_angle = lastangle + set_autozoom_pixel_offsets_immediate(zooming_angle) + smooth_zooming(2) + return ..() -/obj/item/weapon/gun/energy/beam_rifle/onMouseDown(object, location, params, mob) +/obj/item/weapon/gun/energy/beam_rifle/onMouseDown(object, location, params, mob/mob) + if(istype(mob)) + set_user(mob) + if(istype(object, /obj/screen) && !istype(object, /obj/screen/click_catcher)) + return + if((object in mob.contents) || (object == mob)) + return start_aiming() - current_user = mob + return ..() /obj/item/weapon/gun/energy/beam_rifle/onMouseUp(object, location, params, mob/M) + if(istype(object, /obj/screen) && !istype(object, /obj/screen/click_catcher)) + return process_aim() - if(aiming_time_left <= aiming_time_fire_threshold) + if(aiming_time_left <= aiming_time_fire_threshold && check_user()) sync_ammo() afterattack(M.client.mouseObject, M, FALSE, M.client.mouseParams, passthrough = TRUE) stop_aiming() - clear_tracers() + QDEL_NULL(current_tracer) + return ..() /obj/item/weapon/gun/energy/beam_rifle/afterattack(atom/target, mob/living/user, flag, params, passthrough = FALSE) + if(flag) //It's adjacent, is the user, or is on the user's person + if(target in user.contents) //can't shoot stuff inside us. + return + if(!ismob(target) || user.a_intent == INTENT_HARM) //melee attack + return + if(target == user && user.zone_selected != "mouth") //so we can't shoot ourselves (unless mouth selected) + return if(!passthrough && (aiming_time > aiming_time_fire_threshold)) return if(lastfire > world.time + delay) return lastfire = world.time - . = ..() + stop_aiming() + return ..() /obj/item/weapon/gun/energy/beam_rifle/proc/sync_ammo() for(var/obj/item/ammo_casing/energy/beam_rifle/AC in contents) @@ -298,6 +426,25 @@ HS_BB.do_pierce = do_pierce HS_BB.gun = host +/obj/item/ammo_casing/energy/beam_rifle/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread) + var/turf/curloc = get_turf(user) + if(!istype(curloc) || !BB) + return FALSE + var/obj/item/weapon/gun/energy/beam_rifle/gun = loc + if(!targloc && gun) + targloc = get_turf_in_angle(gun.lastangle, curloc, 10) + else if(!targloc) + return FALSE + var/firing_dir + if(BB.firer) + firing_dir = BB.firer.dir + if(!BB.suppressed && firing_effect_type) + new firing_effect_type(get_turf(src), firing_dir) + BB.preparePixelProjectile(target, targloc, user, params, spread) + BB.fire(gun? gun.lastangle : null, null) + BB = null + return TRUE + /obj/item/ammo_casing/energy/beam_rifle/hitscan projectile_type = /obj/item/projectile/beam/beam_rifle/hitscan select_name = "beam" @@ -330,6 +477,7 @@ var/impact_structure_damage = 0 var/impact_direct_damage = 0 var/turf/cached + var/list/pierced = list() /obj/item/projectile/beam/beam_rifle/proc/AOE(turf/epicenter) set waitfor = FALSE @@ -351,20 +499,29 @@ /obj/item/projectile/beam/beam_rifle/proc/check_pierce(atom/target) if(!do_pierce) return FALSE + if(pierced[target]) //we already pierced them go away + loc = get_turf(target) + return TRUE if(isclosedturf(target)) if(wall_pierce++ < wall_pierce_amount) loc = target if(prob(wall_devastate)) - target.ex_act(EXPLODE_HEAVY) + if(istype(target, /turf/closed/wall)) + var/turf/closed/wall/W = target + W.dismantle_wall(TRUE, TRUE) + else + target.ex_act(EXPLODE_HEAVY) return TRUE if(ismovableatom(target)) var/atom/movable/AM = target if(AM.density && !AM.CanPass(src, get_turf(target)) && !ismob(AM)) - if(structure_pierce++ < structure_pierce_amount) + if(structure_pierce < structure_pierce_amount) if(isobj(AM)) var/obj/O = AM O.take_damage((impact_structure_damage + aoe_structure_damage) * structure_bleed_coeff * get_damage_coeff(AM), BURN, "energy", FALSE) + pierced[AM] = TRUE loc = get_turf(AM) + structure_pierce++ return TRUE return FALSE @@ -396,22 +553,69 @@ handle_impact(target) /obj/item/projectile/beam/beam_rifle/Collide(atom/target) + paused = TRUE if(check_pierce(target)) permutated += target return FALSE if(!QDELETED(target)) cached = get_turf(target) + paused = FALSE . = ..() /obj/item/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE) + paused = TRUE if(!QDELETED(target)) cached = get_turf(target) handle_hit(target) + paused = FALSE . = ..() /obj/item/projectile/beam/beam_rifle/hitscan icon_state = "" - var/tracer_type = /obj/effect/temp_visual/projectile_beam/tracer + var/tracer_type = /obj/effect/projectile_beam/tracer + var/starting_z + var/starting_p_x + var/starting_p_y + var/constant_tracer = FALSE + var/travelled_p_x = 0 + var/travelled_p_y = 0 + var/tracer_spawned = FALSE + +/obj/item/projectile/beam/beam_rifle/hitscan/Destroy() + paused = TRUE //STOP HITTING WHEN YOU'RE ALREADY BEING DELETED! + spawn_tracer(constant_tracer) + return ..() + +/obj/item/projectile/beam/beam_rifle/hitscan/proc/spawn_tracer(put_in_rifle = FALSE) + if(tracer_spawned) + return + tracer_spawned = TRUE + //Remind me to port baystation trajectories so this shit isn't needed... + var/pixels_travelled = round(sqrt(travelled_p_x**2 + travelled_p_y**2),1) + var/scaling = pixels_travelled/world.icon_size + var/midpoint_p_x = round(starting_p_x + (travelled_p_x / 2)) + var/midpoint_p_y = round(starting_p_y + (travelled_p_y / 2)) + var/tracer_px = midpoint_p_x % world.icon_size + var/tracer_py = midpoint_p_y % world.icon_size + var/tracer_lx = (midpoint_p_x - tracer_px) / world.icon_size + var/tracer_ly = (midpoint_p_y - tracer_py) / world.icon_size + var/obj/effect/projectile_beam/PB = new tracer_type(src) + PB.apply_vars(Angle, tracer_px, tracer_py, color, scaling, locate(tracer_lx,tracer_ly,starting_z)) + if(put_in_rifle && istype(gun)) + if(gun.current_tracer) + QDEL_NULL(gun.current_tracer) + gun.current_tracer = PB + else + QDEL_IN(PB, 5) + +/obj/item/projectile/beam/beam_rifle/hitscan/proc/check_for_turf_edge(turf/T) + if(!istype(T)) + return TRUE + var/tx = T.x + var/ty = T.y + if(tx < 10 || tx > (world.maxx - 10) || ty < 10 || ty > (world.maxy-10)) + return TRUE + return FALSE /obj/item/projectile/beam/beam_rifle/hitscan/fire(setAngle, atom/direct_target) //oranges didn't let me make this a var the first time around so copypasta time set waitfor = 0 @@ -423,6 +627,12 @@ var/old_pixel_x = pixel_x var/old_pixel_y = pixel_y var/safety = 0 //The code works fine, but... just in case... + var/turf/c2 + var/starting_x = loc.x + var/starting_y = loc.y + starting_z = loc.z + starting_p_x = starting_x * world.icon_size + pixel_x + starting_p_y = starting_y * world.icon_size + pixel_y while(loc) if(++safety > (range * 3)) //If it's looping for way, way too long... return //Kill! @@ -437,6 +647,8 @@ transform = M var/Pixel_x=sin(Angle)+16*sin(Angle)*2 var/Pixel_y=cos(Angle)+16*cos(Angle)*2 + travelled_p_x += Pixel_x + travelled_p_y += Pixel_y var/pixel_x_offset = old_pixel_x + Pixel_x var/pixel_y_offset = old_pixel_y + Pixel_y var/new_x = x @@ -469,27 +681,23 @@ animate(src, pixel_x = pixel_x_offset, pixel_y = pixel_y_offset, time = max(1, (delay <= 3 ? delay - 1 : delay)), flags = ANIMATION_END_NOW) old_pixel_x = pixel_x_offset old_pixel_y = pixel_y_offset - if(original && (original.layer>=2.75) || ismob(original)) - if(loc == get_turf(original)) - if(!(original in permutated)) - Collide(original) + if(can_hit_target(original, permutated)) + Collide(original) + c2 = loc Range() - -/obj/item/projectile/beam/beam_rifle/hitscan/Range() - spawn_tracer_effect() - if(!QDELETED(src) && loc) - cached = get_turf(src) - -/obj/item/projectile/beam/beam_rifle/hitscan/proc/spawn_tracer_effect() - QDEL_IN((new tracer_type(loc, time = 5, angle_override = Angle, p_x = pixel_x, p_y = pixel_y, color_override = color)), 5) + if(check_for_turf_edge(loc)) + spawn_tracer(constant_tracer) + if(istype(c2)) + cached = c2 /obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam - tracer_type = /obj/effect/temp_visual/projectile_beam/tracer/aiming + tracer_type = /obj/effect/projectile_beam/tracer/aiming name = "aiming beam" hitsound = null hitsound_wall = null nodamage = TRUE damage = 0 + constant_tracer = TRUE /obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit(atom/target) qdel(src) @@ -499,37 +707,69 @@ qdel(src) return FALSE -/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/spawn_tracer_effect() - var/obj/effect/temp_visual/projectile_beam/T = new tracer_type(loc, time = 5, angle_override = Angle, p_x = pixel_x, p_y = pixel_y, color_override = color) - if(istype(gun) && istype(T)) - gun.current_tracers[T] = TRUE - -/obj/effect/temp_visual/projectile_beam +/obj/effect/projectile_beam icon = 'icons/obj/projectiles.dmi' layer = ABOVE_MOB_LAYER anchored = TRUE - duration = 5 - randomdir = FALSE light_power = 1 light_range = 2 light_color = "#00ffff" + mouse_opacity = 0 + flags = ABSTRACT + appearance_flags = 0 -/obj/effect/temp_visual/projectile_beam/New(time = 5, angle_override, p_x, p_y, color_override) - duration = time +/obj/effect/projectile_beam/proc/scale_to(nx,ny,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Scale(nx,ny) + transform = M + +/obj/effect/projectile_beam/proc/turn_to(angle,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Turn(angle) + transform = M + +/obj/effect/projectile_beam/New(angle_override, p_x, p_y, color_override, scaling = 1) + if(angle_override && p_x && p_y && color_override && scaling) + apply_vars(angle_override, p_x, p_y, color_override, scaling) + return ..() + +/obj/effect/projectile_beam/proc/apply_vars(angle_override, p_x, p_y, color_override, scaling = 1, new_loc, increment = 0) var/mutable_appearance/look = new(src) look.pixel_x = p_x look.pixel_y = p_y if(color_override) look.color = color_override - var/matrix/M = new - M.Turn(angle_override) - look.transform = M appearance = look - ..() + scale_to(1,scaling, FALSE) + turn_to(angle_override, FALSE) + if(!isnull(new_loc)) //If you want to null it just delete it... + forceMove(new_loc) + for(var/i in 1 to increment) + pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) + pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) -/obj/effect/temp_visual/projectile_beam/tracer +/obj/effect/projectile_beam/tracer icon_state = "tracer_beam" -/obj/effect/temp_visual/projectile_beam/tracer/aiming +/obj/effect/projectile_beam/tracer/aiming icon_state = "gbeam" - duration = 1 + +/datum/action/item_action/zoom_speed_action + name = "Toggle Zooming Speed" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "projectile" + background_icon_state = "bg_tech" + +/datum/action/item_action/zoom_lock_action + name = "Switch Zoom Mode" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "zoom_mode" + background_icon_state = "bg_tech" diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 528c33e631..50f00478e2 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -264,11 +264,8 @@ animate(src, pixel_x = pixel_x_offset, pixel_y = pixel_y_offset, time = max(1, (delay <= 3 ? delay - 1 : delay)), flags = ANIMATION_END_NOW) old_pixel_x = pixel_x_offset old_pixel_y = pixel_y_offset - - if(original && (original.layer >= PROJECTILE_HIT_THRESHHOLD_LAYER) || ismob(original)) - if(loc == get_turf(original)) - if(!(original in permutated)) - Collide(original) + if(can_hit_target(original, permutated)) + Collide(original) Range() if (delay > 0) sleep(delay) @@ -280,13 +277,19 @@ if((!( current ) || loc == current)) current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) step_towards(src, current) - if(original && (original.layer >= PROJECTILE_HIT_THRESHHOLD_LAYER) || ismob(original)) - if(loc == get_turf(original)) - if(!(original in permutated)) - Collide(original) + if(can_hit_target(original, permutated)) + Collide(original) Range() sleep(config.run_speed * 0.9) +//Returns true if the target atom is on our current turf and above the right layer +/obj/item/projectile/proc/can_hit_target(atom/target, var/list/passthrough) + if(target && (target.layer >= PROJECTILE_HIT_THRESHHOLD_LAYER) || ismob(target)) + if(loc == get_turf(target)) + if(!(target in passthrough)) + return TRUE + return FALSE + /obj/item/projectile/proc/preparePixelProjectile(atom/target, var/turf/targloc, mob/living/user, params, spread) var/turf/curloc = get_turf(user) forceMove(get_turf(user))