Merge pull request #6082 from Citadel-Station-13/upstream-merge-36147
[MIRROR] [READY] ~SPELL CARDS~, homing projectiles, and more!
This commit is contained in:
+14
-16
@@ -116,6 +116,20 @@
|
||||
|
||||
#define GET_ANGLE_OF_INCIDENCE(face, input) (MODULUS((face) - (input), 360))
|
||||
|
||||
//Finds the shortest angle that angle A has to change to get to angle B. Aka, whether to move clock or counterclockwise.
|
||||
/proc/closer_angle_difference(a, b)
|
||||
if(!isnum(a) || !isnum(b))
|
||||
return
|
||||
a = SIMPLIFY_DEGREES(a)
|
||||
b = SIMPLIFY_DEGREES(b)
|
||||
var/inc = b - a
|
||||
if(inc < 0)
|
||||
inc += 360
|
||||
var/dec = a - b
|
||||
if(dec < 0)
|
||||
dec += 360
|
||||
. = inc > dec? -dec : inc
|
||||
|
||||
//A logarithm that converts an integer to a number scaled between 0 and 1.
|
||||
//Currently, this is used for hydroponics-produce sprite transforming, but could be useful for other transform functions.
|
||||
#define TRANSFORM_USING_VARIABLE(input, max) ( sin((90*(input))/(max))**2 )
|
||||
@@ -146,22 +160,6 @@
|
||||
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"] && client)
|
||||
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/list/screenview = getviewsize(client.view)
|
||||
var/screenviewX = screenview[1] * world.icon_size
|
||||
var/screenviewY = screenview[2] * world.icon_size
|
||||
var/ox = round(screenviewX/2) - client.pixel_x //"origin" x
|
||||
var/oy = round(screenviewY/2) - client.pixel_y //"origin" y
|
||||
var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox))
|
||||
return angle
|
||||
|
||||
/proc/get_turf_in_angle(angle, turf/starting, increments)
|
||||
var/pixel_x = 0
|
||||
var/pixel_y = 0
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#define UNSETEMPTY(L) if (L && !L.len) L = null
|
||||
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!L.len) { L = null; } }
|
||||
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
|
||||
#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
|
||||
#define LAZYFIND(L, V) L ? L.Find(V) : 0
|
||||
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= L.len ? L[I] : null) : L[I]) : null)
|
||||
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
|
||||
#define LAZYLEN(L) length(L)
|
||||
|
||||
@@ -381,44 +381,6 @@
|
||||
active_players++
|
||||
return active_players
|
||||
|
||||
/datum/projectile_data
|
||||
var/src_x
|
||||
var/src_y
|
||||
var/time
|
||||
var/distance
|
||||
var/power_x
|
||||
var/power_y
|
||||
var/dest_x
|
||||
var/dest_y
|
||||
|
||||
/datum/projectile_data/New(var/src_x, var/src_y, var/time, var/distance, \
|
||||
var/power_x, var/power_y, var/dest_x, var/dest_y)
|
||||
src.src_x = src_x
|
||||
src.src_y = src_y
|
||||
src.time = time
|
||||
src.distance = distance
|
||||
src.power_x = power_x
|
||||
src.power_y = power_y
|
||||
src.dest_x = dest_x
|
||||
src.dest_y = dest_y
|
||||
|
||||
/proc/projectile_trajectory(src_x, src_y, rotation, angle, power)
|
||||
|
||||
// returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle],
|
||||
// rotated at [rotation] and with the power of [power]
|
||||
// Thanks to VistaPOWA for this function
|
||||
|
||||
var/power_x = power * cos(angle)
|
||||
var/power_y = power * sin(angle)
|
||||
var/time = 2* power_y / 10 //10 = g
|
||||
|
||||
var/distance = time * power_x
|
||||
|
||||
var/dest_x = src_x + distance*sin(rotation);
|
||||
var/dest_y = src_y + distance*cos(rotation);
|
||||
|
||||
return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y)
|
||||
|
||||
/proc/showCandidatePollWindow(mob/M, poll_time, Question, list/candidates, ignore_category, time_passed, flashwindow = TRUE)
|
||||
set waitfor = 0
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/proc/mouse_angle_from_client(client/client)
|
||||
var/list/mouse_control = params2list(client.mouseParams)
|
||||
if(mouse_control["screen-loc"] && client)
|
||||
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/list/screenview = getviewsize(client.view)
|
||||
var/screenviewX = screenview[1] * world.icon_size
|
||||
var/screenviewY = screenview[2] * world.icon_size
|
||||
var/ox = round(screenviewX/2) - client.pixel_x //"origin" x
|
||||
var/oy = round(screenviewY/2) - client.pixel_y //"origin" y
|
||||
var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox))
|
||||
return angle
|
||||
|
||||
//Wow, specific name!
|
||||
/proc/mouse_absolute_datum_map_position_from_client(client/client)
|
||||
if(!isloc(client.mob.loc))
|
||||
return
|
||||
var/list/mouse_control = params2list(client.mouseParams)
|
||||
var/cx = client.mob.x
|
||||
var/cy = client.mob.y
|
||||
var/cz = client.mob.z
|
||||
if(mouse_control["screen-loc"])
|
||||
var/x = 0
|
||||
var/y = 0
|
||||
var/z = 0
|
||||
var/p_x = 0
|
||||
var/p_y = 0
|
||||
//Split screen-loc up into X+Pixel_X and Y+Pixel_Y
|
||||
var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",")
|
||||
//Split X+Pixel_X up into list(X, Pixel_X)
|
||||
var/list/screen_loc_X = splittext(screen_loc_params[1],":")
|
||||
//Split Y+Pixel_Y up into list(Y, Pixel_Y)
|
||||
var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
|
||||
var/sx = text2num(screen_loc_X[1])
|
||||
var/sy = text2num(screen_loc_Y[1])
|
||||
//Get the resolution of the client's current screen size.
|
||||
var/list/screenview = getviewsize(client.view)
|
||||
var/svx = screenview[1]
|
||||
var/svy = screenview[2]
|
||||
var/cox = round((svx - 1) / 2)
|
||||
var/coy = round((svy - 1) / 2)
|
||||
x = cx + (sx - 1 - cox)
|
||||
y = cy + (sy - 1 - coy)
|
||||
z = cz
|
||||
p_x = text2num(screen_loc_X[2])
|
||||
p_y = text2num(screen_loc_Y[2])
|
||||
return new /datum/position(x, y, z, p_x, p_y)
|
||||
@@ -1576,3 +1576,54 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
|
||||
|
||||
/proc/get_random_drink()
|
||||
return pick(subtypesof(/obj/item/reagent_containers/food/drinks))
|
||||
|
||||
//For these two procs refs MUST be ref = TRUE format like typecaches!
|
||||
/proc/weakref_filter_list(list/things, list/refs)
|
||||
if(!islist(things) || !islist(refs))
|
||||
return
|
||||
if(!refs.len)
|
||||
return things
|
||||
if(things.len > refs.len)
|
||||
var/list/f = list()
|
||||
for(var/i in refs)
|
||||
var/datum/weakref/r = i
|
||||
var/datum/d = r.resolve()
|
||||
if(d)
|
||||
f |= d
|
||||
return things & f
|
||||
|
||||
else
|
||||
. = list()
|
||||
for(var/i in things)
|
||||
if(!refs[WEAKREF(i)])
|
||||
continue
|
||||
. |= i
|
||||
|
||||
/proc/weakref_filter_list_reverse(list/things, list/refs)
|
||||
if(!islist(things) || !islist(refs))
|
||||
return
|
||||
if(!refs.len)
|
||||
return things
|
||||
if(things.len > refs.len)
|
||||
var/list/f = list()
|
||||
for(var/i in refs)
|
||||
var/datum/weakref/r = i
|
||||
var/datum/d = r.resolve()
|
||||
if(d)
|
||||
f |= d
|
||||
|
||||
return things - f
|
||||
else
|
||||
. = list()
|
||||
for(var/i in things)
|
||||
if(refs[WEAKREF(i)])
|
||||
continue
|
||||
. |= i
|
||||
|
||||
/proc/special_list_filter(list/L, datum/callback/condition)
|
||||
if(!islist(L) || !length(L) || !istype(condition))
|
||||
return list()
|
||||
. = list()
|
||||
for(var/i in L)
|
||||
if(condition.Invoke(i))
|
||||
. |= i
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
|
||||
/datum/projectile_data
|
||||
var/src_x
|
||||
var/src_y
|
||||
var/time
|
||||
var/distance
|
||||
var/power_x
|
||||
var/power_y
|
||||
var/dest_x
|
||||
var/dest_y
|
||||
|
||||
/datum/projectile_data/New(var/src_x, var/src_y, var/time, var/distance, \
|
||||
var/power_x, var/power_y, var/dest_x, var/dest_y)
|
||||
src.src_x = src_x
|
||||
src.src_y = src_y
|
||||
src.time = time
|
||||
src.distance = distance
|
||||
src.power_x = power_x
|
||||
src.power_y = power_y
|
||||
src.dest_x = dest_x
|
||||
src.dest_y = dest_y
|
||||
|
||||
/proc/projectile_trajectory(src_x, src_y, rotation, angle, power)
|
||||
|
||||
// returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle],
|
||||
// rotated at [rotation] and with the power of [power]
|
||||
// Thanks to VistaPOWA for this function
|
||||
|
||||
var/power_x = power * cos(angle)
|
||||
var/power_y = power * sin(angle)
|
||||
var/time = 2* power_y / 10 //10 = g
|
||||
|
||||
var/distance = time * power_x
|
||||
|
||||
var/dest_x = src_x + distance*sin(rotation);
|
||||
var/dest_y = src_y + distance*cos(rotation);
|
||||
|
||||
return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y)
|
||||
@@ -77,10 +77,10 @@
|
||||
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)
|
||||
for(var/datum/D in mob.mousemove_intercept_objects)
|
||||
D.onMouseMove(object, location, control, params)
|
||||
|
||||
/obj/item/proc/onMouseMove(object, location, control, params)
|
||||
/datum/proc/onMouseMove(object, location, control, params)
|
||||
return
|
||||
|
||||
/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params)
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
var/datum/callback_select/CS = new(count, savereturns)
|
||||
for (var/i in 1 to count)
|
||||
CS.invoke_callback(i, callbacks[i], callback_args[i], savereturns)
|
||||
|
||||
|
||||
while(CS.pendingcount)
|
||||
sleep(resolution*world.tick_lag)
|
||||
return CS.finished
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7
|
||||
#define LOCKON_IGNORE_RESULT "ignore_my_result"
|
||||
#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT}
|
||||
|
||||
/datum/component/lockon_aiming
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
var/lock_icon = 'icons/mob/blob.dmi'
|
||||
var/lock_icon_state = "marker"
|
||||
var/mutable_appearance/lock_appearance
|
||||
var/list/image/lock_images
|
||||
var/list/target_typecache
|
||||
var/list/immune_weakrefs //list(weakref = TRUE)
|
||||
var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious!
|
||||
var/lock_amount = 1
|
||||
var/lock_cursor_range = 5
|
||||
var/list/locked_weakrefs
|
||||
var/update_disabled = FALSE
|
||||
var/current_ranging_id = 0
|
||||
var/list/last_location
|
||||
var/datum/callback/on_lock
|
||||
var/datum/callback/can_target_callback
|
||||
|
||||
/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback)
|
||||
if(!ismob(parent))
|
||||
. = COMPONENT_INCOMPATIBLE
|
||||
CRASH("Lockon aiming component attempted to be added to a non mob!")
|
||||
if(target_callback)
|
||||
can_target_callback = target_callback
|
||||
else
|
||||
can_target_callback = CALLBACK(src, .proc/can_target)
|
||||
if(range)
|
||||
lock_cursor_range = range
|
||||
if(typecache)
|
||||
target_typecache = typecache
|
||||
if(amount)
|
||||
lock_amount = amount
|
||||
immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want..
|
||||
if(immune)
|
||||
for(var/i in immune)
|
||||
if(isweakref(i))
|
||||
immune_weakrefs[i] = TRUE
|
||||
else if(isatom(i))
|
||||
immune_weakrefs[WEAKREF(i)] = TRUE
|
||||
if(when_locked)
|
||||
on_lock = when_locked
|
||||
if(icon)
|
||||
lock_icon = icon
|
||||
if(icon_state)
|
||||
lock_icon_state = icon_state
|
||||
generate_lock_visuals()
|
||||
var/mob/M = parent
|
||||
LAZYOR(M.mousemove_intercept_objects, src)
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/datum/component/lockon_aiming/Destroy()
|
||||
var/mob/M = parent
|
||||
clear_visuals()
|
||||
LAZYREMOVE(M.mousemove_intercept_objects, src)
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
return ..()
|
||||
|
||||
/datum/component/lockon_aiming/proc/show_visuals()
|
||||
LAZYINITLIST(lock_images)
|
||||
var/mob/M = parent
|
||||
if(!M.client)
|
||||
return
|
||||
for(var/i in locked_weakrefs)
|
||||
var/datum/weakref/R = i
|
||||
var/atom/A = R.resolve()
|
||||
if(!A)
|
||||
continue //It'll be cleared by processing.
|
||||
var/image/I = new
|
||||
I.appearance = lock_appearance
|
||||
I.loc = A
|
||||
M.client.images |= I
|
||||
lock_images |= I
|
||||
|
||||
/datum/component/lockon_aiming/proc/clear_visuals()
|
||||
var/mob/M = parent
|
||||
if(!M.client)
|
||||
return
|
||||
if(!lock_images)
|
||||
return
|
||||
for(var/i in lock_images)
|
||||
M.client.images -= i
|
||||
qdel(i)
|
||||
lock_images.Cut()
|
||||
|
||||
/datum/component/lockon_aiming/proc/refresh_visuals()
|
||||
clear_visuals()
|
||||
show_visuals()
|
||||
|
||||
/datum/component/lockon_aiming/proc/generate_lock_visuals()
|
||||
lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER)
|
||||
|
||||
/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE)
|
||||
LAZYCLEARLIST(locked_weakrefs)
|
||||
if(refresh_vis)
|
||||
refresh_visuals()
|
||||
|
||||
/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE)
|
||||
if(!A.weak_reference)
|
||||
return
|
||||
LAZYREMOVE(locked_weakrefs, A.weak_reference)
|
||||
if(refresh_vis)
|
||||
refresh_visuals()
|
||||
|
||||
/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE)
|
||||
LAZYOR(locked_weakrefs, WEAKREF(A))
|
||||
if(refresh_vis)
|
||||
refresh_visuals()
|
||||
|
||||
/datum/component/lockon_aiming/proc/add_immune_atom(atom/A)
|
||||
var/datum/weakref/R = WEAKREF(A)
|
||||
if(immune_weakrefs && (immune_weakrefs[R]))
|
||||
return
|
||||
LAZYSET(immune_weakrefs, R, TRUE)
|
||||
|
||||
/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A)
|
||||
if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list?
|
||||
return
|
||||
LAZYREMOVE(immune_weakrefs, A.weak_reference)
|
||||
|
||||
/datum/component/lockon_aiming/onMouseMove(object,location,control,params)
|
||||
var/mob/M = parent
|
||||
if(!istype(M) || !M.client)
|
||||
return
|
||||
var/datum/position/P = mouse_absolute_datum_map_position_from_client(M.client)
|
||||
if(!P)
|
||||
return
|
||||
var/turf/T = P.return_turf()
|
||||
LAZYINITLIST(last_location)
|
||||
if(length(last_location) == 3 && last_location[1] == T.x && last_location[2] == T.y && last_location[3] == T.z)
|
||||
return //Same turf, don't bother.
|
||||
if(last_location)
|
||||
last_location.Cut()
|
||||
else
|
||||
last_location = list()
|
||||
last_location.len = 3
|
||||
last_location[1] = T.x
|
||||
last_location[2] = T.y
|
||||
last_location[3] = T.z
|
||||
autolock()
|
||||
|
||||
/datum/component/lockon_aiming/process()
|
||||
if(update_disabled)
|
||||
return
|
||||
if(!last_location)
|
||||
return
|
||||
var/changed = FALSE
|
||||
for(var/i in locked_weakrefs)
|
||||
var/datum/weakref/R = i
|
||||
if(istype(R))
|
||||
var/atom/thing = R.resolve()
|
||||
if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range))
|
||||
unlock(R)
|
||||
changed = TRUE
|
||||
else
|
||||
unlock(R)
|
||||
changed = TRUE
|
||||
if(changed)
|
||||
autolock()
|
||||
|
||||
/datum/component/lockon_aiming/proc/autolock()
|
||||
var/mob/M = parent
|
||||
if(!M.client)
|
||||
return FALSE
|
||||
var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client)
|
||||
var/turf/target = current.return_turf()
|
||||
var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range)
|
||||
if(targets == LOCKON_IGNORE_RESULT)
|
||||
return
|
||||
unlock_all(FALSE)
|
||||
for(var/i in targets)
|
||||
if(immune_weakrefs[WEAKREF(i)])
|
||||
continue
|
||||
lock(i, FALSE)
|
||||
refresh_visuals()
|
||||
on_lock.Invoke(locked_weakrefs)
|
||||
|
||||
/datum/component/lockon_aiming/proc/can_target(atom/A)
|
||||
var/mob/M = A
|
||||
return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)]
|
||||
|
||||
/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range)
|
||||
current_ranging_id++
|
||||
var/this_id = current_ranging_id
|
||||
var/list/L = list()
|
||||
var/turf/center = get_turf(T)
|
||||
if(amount < 1 || range < 0 || !istype(center) || !islist(typecache))
|
||||
return
|
||||
if(range == 0)
|
||||
return typecache_filter_list(T.contents + T, typecache)
|
||||
var/x = 0
|
||||
var/y = 0
|
||||
var/cd = 0
|
||||
while(cd <= range)
|
||||
x = center.x - cd + 1
|
||||
y = center.y + cd
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
for(x in x to center.x + cd)
|
||||
T = locate(x, y, center.z)
|
||||
if(T)
|
||||
L |= special_list_filter(T.contents, can_target_callback)
|
||||
if(L.len >= amount)
|
||||
L.Cut(amount+1)
|
||||
return L
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
y = center.y + cd - 1
|
||||
x = center.x + cd
|
||||
for(y in center.y - cd to y)
|
||||
T = locate(x, y, center.z)
|
||||
if(T)
|
||||
L |= special_list_filter(T.contents, can_target_callback)
|
||||
if(L.len >= amount)
|
||||
L.Cut(amount+1)
|
||||
return L
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
y = center.y - cd
|
||||
x = center.x + cd - 1
|
||||
for(x in center.x - cd to x)
|
||||
T = locate(x, y, center.z)
|
||||
if(T)
|
||||
L |= special_list_filter(T.contents, can_target_callback)
|
||||
if(L.len >= amount)
|
||||
L.Cut(amount+1)
|
||||
return L
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
y = center.y - cd + 1
|
||||
x = center.x - cd
|
||||
for(y in y to center.y + cd)
|
||||
T = locate(x, y, center.z)
|
||||
if(T)
|
||||
L |= special_list_filter(T.contents, can_target_callback)
|
||||
if(L.len >= amount)
|
||||
L.Cut(amount+1)
|
||||
return L
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
cd++
|
||||
CHECK_TICK
|
||||
|
||||
/datum/component/lockon_aiming/OnTransfer(datum/new_parent)
|
||||
CRASH("Warning: Lockon aiming component transfer attempted, but transfer behavior is not implemented!")
|
||||
@@ -9,6 +9,19 @@
|
||||
#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) {new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)}
|
||||
#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) {new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)}
|
||||
|
||||
/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below.
|
||||
var/datum/point/P = new
|
||||
P.x = a.x + (b.x - a.x) / 2
|
||||
P.y = a.y + (b.y - a.y) / 2
|
||||
P.z = a.z
|
||||
return P
|
||||
|
||||
/proc/pixel_length_between_points(datum/point/a, datum/point/b)
|
||||
return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
|
||||
|
||||
/proc/angle_between_points(datum/point/a, datum/point/b)
|
||||
return ATAN2((b.y - a.y), (b.x - a.x))
|
||||
|
||||
/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess.
|
||||
var/x = 0
|
||||
var/y = 0
|
||||
@@ -53,19 +66,6 @@
|
||||
/datum/position/proc/return_point()
|
||||
return new /datum/point(src)
|
||||
|
||||
/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below.
|
||||
var/datum/point/P = new
|
||||
P.x = a.x + (b.x - a.x) / 2
|
||||
P.y = a.y + (b.y - a.y) / 2
|
||||
P.z = a.z
|
||||
return P
|
||||
|
||||
/proc/pixel_length_between_points(datum/point/a, datum/point/b)
|
||||
return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
|
||||
|
||||
/proc/angle_between_points(datum/point/a, datum/point/b)
|
||||
return ATAN2((b.y - a.y), (b.x - a.x))
|
||||
|
||||
/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP!
|
||||
var/x = 0
|
||||
var/y = 0
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
input.weak_reference = new /datum/weakref(input)
|
||||
return input.weak_reference
|
||||
|
||||
/datum/proc/create_weakref() //Forced creation for admin proccalls
|
||||
return WEAKREF(src)
|
||||
|
||||
/datum/weakref
|
||||
var/reference
|
||||
|
||||
|
||||
@@ -776,7 +776,6 @@
|
||||
impact_effect_type = /obj/effect/temp_visual/dir_setting/bloodsplatter
|
||||
|
||||
/obj/item/projectile/magic/arcane_barrage/blood/Collide(atom/target)
|
||||
colliding = TRUE
|
||||
var/turf/T = get_turf(target)
|
||||
playsound(T, 'sound/effects/splat.ogg', 50, TRUE)
|
||||
if(iscultist(target))
|
||||
@@ -790,7 +789,6 @@
|
||||
M.adjustHealth(-5)
|
||||
new /obj/effect/temp_visual/cult/sparks(T)
|
||||
qdel(src)
|
||||
colliding = FALSE
|
||||
else
|
||||
..()
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -254,9 +254,7 @@
|
||||
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))
|
||||
var/difference = abs(closer_angle_difference(lastangle, angle))
|
||||
delay_penalty(difference * aiming_time_increase_angle_multiplier)
|
||||
lastangle = angle
|
||||
|
||||
@@ -292,7 +290,7 @@
|
||||
current_user = null
|
||||
if(istype(user))
|
||||
current_user = user
|
||||
LAZYADD(current_user.mousemove_intercept_objects, src)
|
||||
LAZYOR(current_user.mousemove_intercept_objects, src)
|
||||
mobhook = user.AddComponent(/datum/component/redirect, list(COMSIG_MOVABLE_MOVED), CALLBACK(src, .proc/on_mob_move))
|
||||
|
||||
/obj/item/gun/energy/beam_rifle/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
|
||||
|
||||
@@ -46,8 +46,6 @@
|
||||
var/ricochets_max = 2
|
||||
var/ricochet_chance = 30
|
||||
|
||||
var/colliding = FALSE //pause processing..
|
||||
|
||||
//Hitscan
|
||||
var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
|
||||
var/list/beam_segments //assoc list of datum/point or datum/point/vector, start = end. Used for hitscan effect generation.
|
||||
@@ -57,6 +55,15 @@
|
||||
var/muzzle_type
|
||||
var/impact_type
|
||||
|
||||
//Homing
|
||||
var/homing = FALSE
|
||||
var/atom/homing_target
|
||||
var/homing_turn_speed = 10 //Angle per tick.
|
||||
var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target.
|
||||
var/homing_inaccuracy_max = 0
|
||||
var/homing_offset_x = 0
|
||||
var/homing_offset_y = 0
|
||||
|
||||
var/ignore_source_check = FALSE
|
||||
|
||||
var/damage = 10
|
||||
@@ -192,7 +199,6 @@
|
||||
beam_segments[beam_index] = null
|
||||
|
||||
/obj/item/projectile/Collide(atom/A)
|
||||
colliding = TRUE
|
||||
var/datum/point/pcache = trajectory.copy_to()
|
||||
if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max)
|
||||
ricochets++
|
||||
@@ -208,7 +214,6 @@
|
||||
trajectory_ignore_forcemove = TRUE
|
||||
forceMove(get_turf(A))
|
||||
trajectory_ignore_forcemove = FALSE
|
||||
colliding = FALSE
|
||||
return FALSE
|
||||
|
||||
var/distance = get_dist(get_turf(A), starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
|
||||
@@ -227,7 +232,6 @@
|
||||
trajectory_ignore_forcemove = TRUE
|
||||
forceMove(target_turf)
|
||||
trajectory_ignore_forcemove = FALSE
|
||||
colliding = FALSE
|
||||
return FALSE
|
||||
|
||||
var/permutation = A.bullet_act(src, def_zone) // searches for return value, could be deleted after run so check A isn't null
|
||||
@@ -237,17 +241,14 @@
|
||||
trajectory_ignore_forcemove = FALSE
|
||||
if(A)
|
||||
permutated.Add(A)
|
||||
colliding = FALSE
|
||||
return FALSE
|
||||
else
|
||||
var/atom/alt = select_target(A)
|
||||
if(alt)
|
||||
if(!prehit(alt))
|
||||
colliding = FALSE
|
||||
return FALSE
|
||||
alt.bullet_act(src, def_zone)
|
||||
qdel(src)
|
||||
colliding = FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/item/projectile/proc/select_target(atom/A) //Selects another target from a wall if we hit a wall.
|
||||
@@ -350,7 +351,7 @@
|
||||
trajectory_ignore_forcemove = TRUE
|
||||
forceMove(starting)
|
||||
trajectory_ignore_forcemove = FALSE
|
||||
trajectory = new(starting.x, starting.y, starting.z, 0, 0, Angle, pixel_speed)
|
||||
trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, Angle, pixel_speed)
|
||||
last_projectile_move = world.time
|
||||
fired = TRUE
|
||||
if(hitscan)
|
||||
@@ -417,6 +418,8 @@
|
||||
var/matrix/M = new
|
||||
M.Turn(Angle)
|
||||
transform = M
|
||||
if(homing)
|
||||
process_homing()
|
||||
trajectory.increment(trajectory_multiplier)
|
||||
var/turf/T = trajectory.return_turf()
|
||||
if(!istype(T))
|
||||
@@ -445,10 +448,32 @@
|
||||
Collide(original)
|
||||
Range()
|
||||
|
||||
/obj/item/projectile/proc/process_homing() //may need speeding up in the future performance wise.
|
||||
if(!homing_target)
|
||||
return FALSE
|
||||
var/datum/point/PT = RETURN_PRECISE_POINT(homing_target)
|
||||
PT.x += CLAMP(homing_offset_x, 1, world.maxx)
|
||||
PT.y += CLAMP(homing_offset_y, 1, world.maxy)
|
||||
var/angle = closer_angle_difference(Angle, angle_between_points(RETURN_PRECISE_POINT(src), PT))
|
||||
setAngle(Angle + CLAMP(angle, -homing_turn_speed, homing_turn_speed))
|
||||
|
||||
/obj/item/projectile/proc/set_homing_target(atom/A)
|
||||
if(!A || (!isturf(A) && !isturf(A.loc)))
|
||||
return FALSE
|
||||
homing = TRUE
|
||||
homing_target = A
|
||||
homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max)
|
||||
homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max)
|
||||
if(prob(50))
|
||||
homing_offset_x = -homing_offset_x
|
||||
if(prob(50))
|
||||
homing_offset_y = -homing_offset_y
|
||||
|
||||
//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)
|
||||
return (target && ((target.layer >= PROJECTILE_HIT_THRESHHOLD_LAYER) || ismob(target)) && (loc == get_turf(target)) && (!(target in passthrough)))
|
||||
|
||||
//Spread is FORCED!
|
||||
/obj/item/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0)
|
||||
var/turf/curloc = get_turf(source)
|
||||
var/turf/targloc = get_turf(target)
|
||||
@@ -460,7 +485,7 @@
|
||||
if(targloc || !params)
|
||||
yo = targloc.y - curloc.y
|
||||
xo = targloc.x - curloc.x
|
||||
setAngle(Get_Angle(src, targloc))
|
||||
setAngle(Get_Angle(src, targloc) + spread)
|
||||
|
||||
//CIT CHANGES START HERE - makes it so laying down makes you unable to shoot through most objects
|
||||
if(iscarbon(source))
|
||||
@@ -474,14 +499,11 @@
|
||||
p_x = calculated[2]
|
||||
p_y = calculated[3]
|
||||
|
||||
if(spread)
|
||||
setAngle(calculated[1] + spread)
|
||||
else
|
||||
setAngle(calculated[1])
|
||||
setAngle(calculated[1] + spread)
|
||||
else if(targloc)
|
||||
yo = targloc.y - curloc.y
|
||||
xo = targloc.x - curloc.x
|
||||
setAngle(Get_Angle(src, targloc))
|
||||
setAngle(Get_Angle(src, targloc) + spread)
|
||||
else
|
||||
stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!")
|
||||
qdel(src)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/obj/item/projectile/spellcard
|
||||
name = "enchanted card"
|
||||
desc = "A piece of paper enchanted to give it extreme durability and stiffness, along with a very hot burn to anyone unfortunate enough to get hit by a charged one."
|
||||
icon_state = "spellcard"
|
||||
damage_type = BURN
|
||||
damage = 2
|
||||
@@ -9,6 +9,7 @@
|
||||
var/list/projectile_var_overrides = list()
|
||||
var/projectile_amount = 1 //Projectiles per cast.
|
||||
var/current_amount = 0 //How many projectiles left.
|
||||
var/projectiles_per_fire = 1 //Projectiles per fire. Probably not a good thing to use unless you override ready_projectile().
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/Click()
|
||||
var/mob/living/user = usr
|
||||
@@ -26,10 +27,18 @@
|
||||
charge_counter = charge_max * refund_percent
|
||||
start_recharge()
|
||||
remove_ranged_ability(msg)
|
||||
on_deactivation(user)
|
||||
else
|
||||
msg = "<span class='notice'>[active_msg]<B>Left-click to shoot it at a target!</B></span>"
|
||||
current_amount = projectile_amount
|
||||
add_ranged_ability(user, msg, TRUE)
|
||||
on_activation(user)
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/proc/on_activation(mob/user)
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/proc/on_deactivation(mob/user)
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/update_icon()
|
||||
if(!action)
|
||||
@@ -60,19 +69,25 @@
|
||||
remove_ranged_ability() //Auto-disable the ability once you run out of bullets.
|
||||
charge_counter = 0
|
||||
start_recharge()
|
||||
on_deactivation(user)
|
||||
return TRUE
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/proc/fire_projectile(mob/living/user, atom/target)
|
||||
current_amount--
|
||||
var/obj/item/projectile/P = new projectile_type(user.loc)
|
||||
P.firer = user
|
||||
P.preparePixelProjectile(target, user)
|
||||
for(var/V in projectile_var_overrides)
|
||||
if(P.vars[V])
|
||||
P.vv_edit_var(V, projectile_var_overrides[V])
|
||||
P.fire()
|
||||
for(var/i in 1 to projectiles_per_fire)
|
||||
var/obj/item/projectile/P = new projectile_type(user.loc)
|
||||
P.firer = user
|
||||
P.preparePixelProjectile(target, user)
|
||||
for(var/V in projectile_var_overrides)
|
||||
if(P.vars[V])
|
||||
P.vv_edit_var(V, projectile_var_overrides[V])
|
||||
ready_projectile(P, target, user, i)
|
||||
P.fire()
|
||||
return TRUE
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/proc/ready_projectile(obj/item/projectile/P, atom/target, mob/user, iteration)
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/lightningbolt
|
||||
name = "Lightning Bolt"
|
||||
desc = "Fire a high powered lightning bolt at your foes!"
|
||||
@@ -108,3 +123,58 @@
|
||||
active_msg = "You prepare to cast your fireball spell!"
|
||||
deactive_msg = "You extinguish your fireball... for now."
|
||||
active = FALSE
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/spell_cards
|
||||
name = "Spell Cards"
|
||||
desc = "Blazing hot rapid-fire homing cards. Banish your foes with its mystical power!"
|
||||
school = "evocation"
|
||||
charge_max = 50
|
||||
clothes_req = 0
|
||||
invocation = "Sigi'lu M'Fan 'Tasia"
|
||||
invocation_type = "shout"
|
||||
range = 40
|
||||
cooldown_min = 10
|
||||
projectile_amount = 5
|
||||
projectiles_per_fire = 7
|
||||
projectile_type = /obj/item/projectile/spellcard
|
||||
var/datum/weakref/current_target_weakref
|
||||
var/projectile_turnrate = 10
|
||||
var/projectile_pixel_homing_spread = 32
|
||||
var/projectile_initial_spread_amount = 30
|
||||
var/projectile_location_spread_amount = 12
|
||||
var/datum/component/lockon_aiming/lockon_component
|
||||
ranged_clickcd_override = 1
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/spell_cards/on_activation(mob/M)
|
||||
QDEL_NULL(lockon_component)
|
||||
lockon_component = M.AddComponent(/datum/component/lockon_aiming, 5, typecacheof(list(/mob/living)), 1, null, CALLBACK(src, .proc/on_lockon_component))
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/spell_cards/proc/on_lockon_component(list/locked_weakrefs)
|
||||
if(!length(locked_weakrefs))
|
||||
current_target_weakref = null
|
||||
return
|
||||
current_target_weakref = locked_weakrefs[1]
|
||||
var/atom/A = current_target_weakref.resolve()
|
||||
if(A)
|
||||
var/mob/M = lockon_component.parent
|
||||
M.face_atom(A)
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/spell_cards/on_deactivation(mob/M)
|
||||
QDEL_NULL(lockon_component)
|
||||
|
||||
/obj/effect/proc_holder/spell/aimed/spell_cards/ready_projectile(obj/item/projectile/P, atom/target, mob/user, iteration)
|
||||
if(current_target_weakref)
|
||||
var/atom/A = current_target_weakref.resolve()
|
||||
if(A && get_dist(A, user) < 7)
|
||||
P.homing_turn_speed = projectile_turnrate
|
||||
P.homing_inaccuracy_min = projectile_pixel_homing_spread
|
||||
P.homing_inaccuracy_max = projectile_pixel_homing_spread
|
||||
P.set_homing_target(current_target_weakref.resolve())
|
||||
var/rand_spr = rand()
|
||||
var/total_angle = projectile_initial_spread_amount * 2
|
||||
var/adjusted_angle = total_angle - ((projectile_initial_spread_amount / projectiles_per_fire) * 0.5)
|
||||
var/one_fire_angle = adjusted_angle / projectiles_per_fire
|
||||
var/current_angle = iteration * one_fire_angle * rand_spr - (projectile_initial_spread_amount / 2)
|
||||
P.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount)
|
||||
P.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount)
|
||||
P.preparePixelProjectile(target, user, null, current_angle)
|
||||
|
||||
Reference in New Issue
Block a user