Merge pull request #6082 from Citadel-Station-13/upstream-merge-36147

[MIRROR] [READY] ~SPELL CARDS~, homing projectiles, and more!
This commit is contained in:
LetterJay
2018-04-01 01:01:55 -05:00
committed by GitHub
19 changed files with 1456 additions and 1007 deletions
+14 -16
View File
@@ -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
+2
View File
@@ -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)
-38
View File
@@ -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
+50
View File
@@ -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)
+51
View File
@@ -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
+39
View File
@@ -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)
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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
+243
View File
@@ -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!")
+13 -13
View File
@@ -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
+3
View File
@@ -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)
+37 -15
View File
@@ -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
+77 -7
View File
@@ -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)