You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

980 lines
32 KiB

  1. /atom/movable
  2. layer = OBJ_LAYER
  3. var/last_move = null
  4. var/last_move_time = 0
  5. var/anchored = FALSE
  6. var/move_resist = MOVE_RESIST_DEFAULT
  7. var/move_force = MOVE_FORCE_DEFAULT
  8. var/pull_force = PULL_FORCE_DEFAULT
  9. var/datum/thrownthing/throwing = null
  10. var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported
  11. var/throw_range = 7
  12. var/mob/pulledby = null
  13. var/initial_language_holder = /datum/language_holder
  14. var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence.
  15. var/verb_say = "says"
  16. var/verb_ask = "asks"
  17. var/verb_exclaim = "exclaims"
  18. var/verb_whisper = "whispers"
  19. var/verb_sing = "sings"
  20. var/verb_yell = "yells"
  21. var/speech_span
  22. var/inertia_dir = 0
  23. var/atom/inertia_last_loc
  24. var/inertia_moving = 0
  25. var/inertia_next_move = 0
  26. var/inertia_move_delay = 5
  27. var/pass_flags = NONE
  28. /// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour
  29. var/generic_canpass = TRUE
  30. var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
  31. var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
  32. var/list/client_mobs_in_contents // This contains all the client mobs within this container
  33. var/list/acted_explosions //for explosion dodging
  34. glide_size = 8
  35. appearance_flags = TILE_BOUND|PIXEL_SCALE
  36. var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
  37. var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc.
  38. var/atom/movable/pulling
  39. var/grab_state = 0
  40. var/throwforce = 0
  41. var/datum/component/orbiter/orbiting
  42. var/can_be_z_moved = TRUE
  43. var/zfalling = FALSE
  44. ///Last location of the atom for demo recording purposes
  45. var/atom/demo_last_loc
  46. /// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE]
  47. var/blocks_emissive = FALSE
  48. ///Internal holder for emissive blocker object, do not use directly use blocks_emissive
  49. var/atom/movable/emissive_blocker/em_block
  50. /atom/movable/Initialize(mapload)
  51. . = ..()
  52. switch(blocks_emissive)
  53. if(EMISSIVE_BLOCK_GENERIC)
  54. update_emissive_block()
  55. if(EMISSIVE_BLOCK_UNIQUE)
  56. render_target = ref(src)
  57. em_block = new(src, render_target)
  58. vis_contents += em_block
  59. /atom/movable/Destroy()
  60. QDEL_NULL(em_block)
  61. return ..()
  62. /atom/movable/proc/update_emissive_block()
  63. if(blocks_emissive != EMISSIVE_BLOCK_GENERIC)
  64. return
  65. if(length(managed_vis_overlays))
  66. for(var/a in managed_vis_overlays)
  67. var/obj/effect/overlay/vis/vs
  68. if(vs.plane == EMISSIVE_BLOCKER_PLANE)
  69. SSvis_overlays.remove_vis_overlay(src, list(vs))
  70. break
  71. SSvis_overlays.add_vis_overlay(src, icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE, dir)
  72. /atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction)
  73. if(!direction)
  74. direction = DOWN
  75. if(!source)
  76. source = get_turf(src)
  77. if(!source)
  78. return FALSE
  79. if(!target)
  80. target = get_step_multiz(source, direction)
  81. if(!target)
  82. return FALSE
  83. return !(movement_type & FLYING) && has_gravity(source) && !throwing
  84. /atom/movable/proc/onZImpact(turf/T, levels)
  85. var/atom/highest = T
  86. for(var/i in T.contents)
  87. var/atom/A = i
  88. if(!A.density)
  89. continue
  90. if(isobj(A) || ismob(A))
  91. if(A.layer > highest.layer)
  92. highest = A
  93. INVOKE_ASYNC(src, .proc/SpinAnimation, 5, 2)
  94. throw_impact(highest)
  95. return TRUE
  96. //For physical constraints to travelling up/down.
  97. /atom/movable/proc/can_zTravel(turf/destination, direction)
  98. var/turf/T = get_turf(src)
  99. if(!T)
  100. return FALSE
  101. if(!direction)
  102. if(!destination)
  103. return FALSE
  104. direction = get_dir(T, destination)
  105. if(direction != UP && direction != DOWN)
  106. return FALSE
  107. if(!destination)
  108. destination = get_step_multiz(src, direction)
  109. if(!destination)
  110. return FALSE
  111. return T.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, T)
  112. /atom/movable/vv_edit_var(var_name, var_value)
  113. var/static/list/banned_edits = list("step_x", "step_y", "step_size", "bounds")
  114. var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height")
  115. if(var_name in banned_edits)
  116. return FALSE //PLEASE no.
  117. if((var_name in careful_edits) && (var_value % world.icon_size) != 0)
  118. return FALSE
  119. switch(var_name)
  120. if("x")
  121. var/turf/T = locate(var_value, y, z)
  122. if(T)
  123. forceMove(T)
  124. return TRUE
  125. return FALSE
  126. if("y")
  127. var/turf/T = locate(x, var_value, z)
  128. if(T)
  129. forceMove(T)
  130. return TRUE
  131. return FALSE
  132. if("z")
  133. var/turf/T = locate(x, y, var_value)
  134. if(T)
  135. forceMove(T)
  136. return TRUE
  137. return FALSE
  138. if("loc")
  139. if(istype(var_value, /atom))
  140. forceMove(var_value)
  141. return TRUE
  142. else if(isnull(var_value))
  143. moveToNullspace()
  144. return TRUE
  145. return FALSE
  146. return ..()
  147. /atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE)
  148. if(QDELETED(AM))
  149. return FALSE
  150. if(!(AM.can_be_pulled(src, state, force)))
  151. return FALSE
  152. // If we're pulling something then drop what we're currently pulling and pull this instead.
  153. if(pulling)
  154. if(state == 0)
  155. stop_pulling()
  156. return FALSE
  157. // Are we trying to pull something we are already pulling? Then enter grab cycle and end.
  158. if(AM == pulling)
  159. setGrabState(state)
  160. if(istype(AM,/mob/living))
  161. var/mob/living/AMob = AM
  162. AMob.grabbedby(src)
  163. return TRUE
  164. stop_pulling()
  165. SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, AM, state, force)
  166. if(AM.pulledby)
  167. log_combat(AM, AM.pulledby, "pulled from", src)
  168. AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
  169. pulling = AM
  170. AM.pulledby = src
  171. setGrabState(state)
  172. if(ismob(AM))
  173. var/mob/M = AM
  174. log_combat(src, M, "grabbed", addition="passive grab")
  175. if(!supress_message)
  176. M.visible_message("<span class='warning'>[src] grabs [M] passively.</span>", \
  177. "<span class='danger'>[src] grabs you passively.</span>")
  178. return TRUE
  179. /atom/movable/proc/stop_pulling()
  180. if(pulling)
  181. pulling.pulledby = null
  182. var/mob/living/ex_pulled = pulling
  183. pulling = null
  184. setGrabState(0)
  185. if(isliving(ex_pulled))
  186. var/mob/living/L = ex_pulled
  187. L.update_mobility()// mob gets up if it was lyng down in a chokehold
  188. /atom/movable/proc/Move_Pulled(atom/A)
  189. if(!pulling)
  190. return
  191. if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src))
  192. stop_pulling()
  193. return
  194. if(isliving(pulling))
  195. var/mob/living/L = pulling
  196. if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it
  197. stop_pulling()
  198. return
  199. if(A == loc && pulling.density)
  200. return
  201. if(!Process_Spacemove(get_dir(pulling.loc, A)))
  202. return
  203. step(pulling, get_dir(pulling.loc, A))
  204. return TRUE
  205. /mob/living/Move_Pulled(atom/A)
  206. . = ..()
  207. if(!. || !isliving(A))
  208. return
  209. var/mob/living/L = A
  210. set_pull_offsets(L, grab_state)
  211. /atom/movable/proc/check_pulling()
  212. if(pulling)
  213. var/atom/movable/pullee = pulling
  214. if(pullee && get_dist(src, pullee) > 1)
  215. stop_pulling()
  216. return
  217. if(!isturf(loc))
  218. stop_pulling()
  219. return
  220. if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove().
  221. log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.")
  222. stop_pulling()
  223. return
  224. if(pulling.anchored || pulling.move_resist > move_force)
  225. stop_pulling()
  226. return
  227. if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move.
  228. pulledby.stop_pulling()
  229. ////////////////////////////////////////
  230. // Here's where we rewrite how byond handles movement except slightly different
  231. // To be removed on step_ conversion
  232. // All this work to prevent a second bump
  233. /atom/movable/Move(atom/newloc, direct=0)
  234. . = FALSE
  235. if(!newloc || newloc == loc)
  236. return
  237. if(!direct)
  238. direct = get_dir(src, newloc)
  239. setDir(direct)
  240. if(!loc.Exit(src, newloc))
  241. return
  242. if(!newloc.Enter(src, src.loc))
  243. return
  244. if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
  245. return
  246. // Past this is the point of no return
  247. var/atom/oldloc = loc
  248. var/area/oldarea = get_area(oldloc)
  249. var/area/newarea = get_area(newloc)
  250. loc = newloc
  251. . = TRUE
  252. oldloc.Exited(src, newloc)
  253. if(oldarea != newarea)
  254. oldarea.Exited(src, newloc)
  255. for(var/i in oldloc)
  256. if(i == src) // Multi tile objects
  257. continue
  258. var/atom/movable/thing = i
  259. thing.Uncrossed(src)
  260. newloc.Entered(src, oldloc)
  261. if(oldarea != newarea)
  262. newarea.Entered(src, oldloc)
  263. for(var/i in loc)
  264. if(i == src) // Multi tile objects
  265. continue
  266. var/atom/movable/thing = i
  267. thing.Crossed(src)
  268. ////////////////////////////////////////
  269. /atom/movable/Move(atom/newloc, direct)
  270. var/atom/movable/pullee = pulling
  271. var/turf/T = loc
  272. if(!moving_from_pull)
  273. check_pulling()
  274. if(!loc || !newloc)
  275. return FALSE
  276. var/atom/oldloc = loc
  277. if(loc != newloc)
  278. if (!(direct & (direct - 1))) //Cardinal move
  279. . = ..()
  280. else //Diagonal move, split it into cardinal moves
  281. moving_diagonally = FIRST_DIAG_STEP
  282. var/first_step_dir
  283. // The `&& moving_diagonally` checks are so that a forceMove taking
  284. // place due to a Crossed, Bumped, etc. call will interrupt
  285. // the second half of the diagonal movement, or the second attempt
  286. // at a first half if step() fails because we hit something.
  287. if (direct & NORTH)
  288. if (direct & EAST)
  289. if (step(src, NORTH) && moving_diagonally)
  290. first_step_dir = NORTH
  291. moving_diagonally = SECOND_DIAG_STEP
  292. . = step(src, EAST)
  293. else if (moving_diagonally && step(src, EAST))
  294. first_step_dir = EAST
  295. moving_diagonally = SECOND_DIAG_STEP
  296. . = step(src, NORTH)
  297. else if (direct & WEST)
  298. if (step(src, NORTH) && moving_diagonally)
  299. first_step_dir = NORTH
  300. moving_diagonally = SECOND_DIAG_STEP
  301. . = step(src, WEST)
  302. else if (moving_diagonally && step(src, WEST))
  303. first_step_dir = WEST
  304. moving_diagonally = SECOND_DIAG_STEP
  305. . = step(src, NORTH)
  306. else if (direct & SOUTH)
  307. if (direct & EAST)
  308. if (step(src, SOUTH) && moving_diagonally)
  309. first_step_dir = SOUTH
  310. moving_diagonally = SECOND_DIAG_STEP
  311. . = step(src, EAST)
  312. else if (moving_diagonally && step(src, EAST))
  313. first_step_dir = EAST
  314. moving_diagonally = SECOND_DIAG_STEP
  315. . = step(src, SOUTH)
  316. else if (direct & WEST)
  317. if (step(src, SOUTH) && moving_diagonally)
  318. first_step_dir = SOUTH
  319. moving_diagonally = SECOND_DIAG_STEP
  320. . = step(src, WEST)
  321. else if (moving_diagonally && step(src, WEST))
  322. first_step_dir = WEST
  323. moving_diagonally = SECOND_DIAG_STEP
  324. . = step(src, SOUTH)
  325. if(moving_diagonally == SECOND_DIAG_STEP)
  326. if(!.)
  327. setDir(first_step_dir)
  328. else if (!inertia_moving)
  329. inertia_next_move = world.time + inertia_move_delay
  330. newtonian_move(direct)
  331. moving_diagonally = 0
  332. return
  333. if(!loc || (loc == oldloc && oldloc != newloc))
  334. last_move = 0
  335. return
  336. if(.)
  337. Moved(oldloc, direct)
  338. if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move.
  339. if(pulling.anchored)
  340. stop_pulling()
  341. else
  342. var/pull_dir = get_dir(src, pulling)
  343. //puller and pullee more than one tile away or in diagonal position
  344. if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir)))
  345. pulling.moving_from_pull = src
  346. pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position
  347. pulling.moving_from_pull = null
  348. check_pulling()
  349. last_move = direct
  350. setDir(direct)
  351. if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s)
  352. return FALSE
  353. //Called after a successful Move(). By this point, we've already moved
  354. /atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE)
  355. SHOULD_CALL_PARENT(TRUE)
  356. SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced)
  357. if (!inertia_moving)
  358. inertia_next_move = world.time + inertia_move_delay
  359. newtonian_move(Dir)
  360. if (length(client_mobs_in_contents))
  361. update_parallax_contents()
  362. SSdemo.mark_dirty(src)
  363. return TRUE
  364. /atom/movable/Destroy(force)
  365. QDEL_NULL(proximity_monitor)
  366. QDEL_NULL(language_holder)
  367. unbuckle_all_mobs(force=1)
  368. . = ..()
  369. if(loc)
  370. //Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary)
  371. if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc))
  372. CanAtmosPass = ATMOS_PASS_YES
  373. air_update_turf(TRUE)
  374. loc.handle_atom_del(src)
  375. for(var/atom/movable/AM in contents)
  376. qdel(AM)
  377. moveToNullspace()
  378. invisibility = INVISIBILITY_ABSTRACT
  379. if(pulledby)
  380. pulledby.stop_pulling()
  381. if(orbiting)
  382. orbiting.end_orbit(src)
  383. orbiting = null
  384. // Make sure you know what you're doing if you call this, this is intended to only be called by byond directly.
  385. // You probably want CanPass()
  386. /atom/movable/Cross(atom/movable/AM)
  387. . = TRUE
  388. SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, AM)
  389. return CanPass(AM, AM.loc, TRUE)
  390. //oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called!
  391. /atom/movable/Crossed(atom/movable/AM, oldloc)
  392. SHOULD_CALL_PARENT(TRUE)
  393. . = ..()
  394. SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
  395. /atom/movable/Uncross(atom/movable/AM, atom/newloc)
  396. . = ..()
  397. if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS)
  398. return FALSE
  399. if(isturf(newloc) && !CheckExit(AM, newloc))
  400. return FALSE
  401. /atom/movable/Uncrossed(atom/movable/AM)
  402. SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM)
  403. /atom/movable/Bump(atom/A)
  404. if(!A)
  405. CRASH("Bump was called with no argument.")
  406. SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
  407. . = ..()
  408. if(!QDELETED(throwing))
  409. throwing.hit_atom(A)
  410. . = TRUE
  411. if(QDELETED(A))
  412. return
  413. A.Bumped(src)
  414. /atom/movable/proc/forceMove(atom/destination)
  415. . = FALSE
  416. if(destination)
  417. . = doMove(destination)
  418. else
  419. CRASH("No valid destination passed into forceMove")
  420. /atom/movable/proc/moveToNullspace()
  421. return doMove(null)
  422. /atom/movable/proc/doMove(atom/destination)
  423. . = FALSE
  424. if(destination)
  425. if(pulledby)
  426. pulledby.stop_pulling()
  427. var/atom/oldloc = loc
  428. var/same_loc = oldloc == destination
  429. var/area/old_area = get_area(oldloc)
  430. var/area/destarea = get_area(destination)
  431. loc = destination
  432. moving_diagonally = 0
  433. if(!same_loc)
  434. if(oldloc)
  435. oldloc.Exited(src, destination)
  436. if(old_area && old_area != destarea)
  437. old_area.Exited(src, destination)
  438. for(var/atom/movable/AM in oldloc)
  439. AM.Uncrossed(src)
  440. var/turf/oldturf = get_turf(oldloc)
  441. var/turf/destturf = get_turf(destination)
  442. var/old_z = (oldturf ? oldturf.z : null)
  443. var/dest_z = (destturf ? destturf.z : null)
  444. if (old_z != dest_z)
  445. onTransitZ(old_z, dest_z)
  446. destination.Entered(src, oldloc)
  447. if(destarea && old_area != destarea)
  448. destarea.Entered(src, oldloc)
  449. for(var/atom/movable/AM in destination)
  450. if(AM == src)
  451. continue
  452. AM.Crossed(src, oldloc)
  453. Moved(oldloc, NONE, TRUE)
  454. . = TRUE
  455. //If no destination, move the atom into nullspace (don't do this unless you know what you're doing)
  456. else
  457. . = TRUE
  458. if (loc)
  459. var/atom/oldloc = loc
  460. var/area/old_area = get_area(oldloc)
  461. oldloc.Exited(src, null)
  462. if(old_area)
  463. old_area.Exited(src, null)
  464. loc = null
  465. /atom/movable/proc/onTransitZ(old_z,new_z)
  466. SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_z, new_z)
  467. for (var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care.
  468. var/atom/movable/AM = item
  469. AM.onTransitZ(old_z,new_z)
  470. /atom/movable/proc/setMovetype(newval)
  471. movement_type = newval
  472. /**
  473. * Called whenever an object moves and by mobs when they attempt to move themselves through space
  474. * And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move]
  475. *
  476. * Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting
  477. *
  478. * Mobs should return 1 if they should be able to move of their own volition, see [/client/Move]
  479. *
  480. * Arguments:
  481. * * movement_dir - 0 when stopping or any dir when trying to move
  482. */
  483. /atom/movable/proc/Process_Spacemove(movement_dir = 0)
  484. if(has_gravity(src))
  485. return 1
  486. if(pulledby)
  487. return 1
  488. if(throwing)
  489. return 1
  490. if(!isturf(loc))
  491. return 1
  492. if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier
  493. return 1
  494. return 0
  495. /// Only moves the object if it's under no gravity
  496. /atom/movable/proc/newtonian_move(direction)
  497. if(!loc || Process_Spacemove(0))
  498. inertia_dir = 0
  499. return 0
  500. inertia_dir = direction
  501. if(!direction)
  502. return 1
  503. inertia_last_loc = loc
  504. SSspacedrift.processing[src] = src
  505. return 1
  506. /atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
  507. set waitfor = 0
  508. var/hitpush = TRUE
  509. var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
  510. if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH)
  511. hitpush = FALSE // hacky, tie this to something else or a proper workaround later
  512. if(impact_signal & ~COMPONENT_MOVABLE_IMPACT_NEVERMIND) // in case a signal interceptor broke or deleted the thing before we could process our hit
  513. return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush)
  514. /atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum)
  515. if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO))))
  516. step(src, AM.dir)
  517. ..()
  518. /atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE)
  519. if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY))
  520. return
  521. return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle)
  522. ///If this returns FALSE then callback will not be called.
  523. /atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE)
  524. . = FALSE
  525. if (!target || speed <= 0)
  526. return
  527. if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW)
  528. return
  529. if (pulledby)
  530. pulledby.stop_pulling()
  531. //They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw?
  532. if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2)
  533. var/user_momentum = thrower.cached_multiplicative_slowdown
  534. if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead.
  535. user_momentum = world.tick_lag
  536. user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses.
  537. if (get_dir(thrower, target) & last_move)
  538. user_momentum = user_momentum //basically a noop, but needed
  539. else if (get_dir(target, thrower) & last_move)
  540. user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly
  541. else
  542. user_momentum = 0
  543. if (user_momentum)
  544. //first lets add that momentum to range.
  545. range *= (user_momentum / speed) + 1
  546. //then lets add it to speed
  547. speed += user_momentum
  548. if (speed <= 0)
  549. return//no throw speed, the user was moving too fast.
  550. . = TRUE // No failure conditions past this point.
  551. var/datum/thrownthing/TT = new()
  552. TT.thrownthing = src
  553. TT.target = target
  554. TT.target_turf = get_turf(target)
  555. TT.init_dir = get_dir(src, target)
  556. TT.maxrange = range
  557. TT.speed = speed
  558. TT.thrower = thrower
  559. TT.diagonals_first = diagonals_first
  560. TT.force = force
  561. TT.gentle = gentle
  562. TT.callback = callback
  563. if(!QDELETED(thrower))
  564. TT.target_zone = thrower.zone_selected
  565. var/dist_x = abs(target.x - src.x)
  566. var/dist_y = abs(target.y - src.y)
  567. var/dx = (target.x > src.x) ? EAST : WEST
  568. var/dy = (target.y > src.y) ? NORTH : SOUTH
  569. if (dist_x == dist_y)
  570. TT.pure_diagonal = 1
  571. else if(dist_x <= dist_y)
  572. var/olddist_x = dist_x
  573. var/olddx = dx
  574. dist_x = dist_y
  575. dist_y = olddist_x
  576. dx = dy
  577. dy = olddx
  578. TT.dist_x = dist_x
  579. TT.dist_y = dist_y
  580. TT.dx = dx
  581. TT.dy = dy
  582. TT.diagonal_error = dist_x/2 - dist_y
  583. TT.start_time = world.time
  584. if(pulledby)
  585. pulledby.stop_pulling()
  586. throwing = TT
  587. if(spin)
  588. SpinAnimation(5, 1)
  589. SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin)
  590. SSthrowing.processing[src] = TT
  591. if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun))
  592. SSthrowing.currentrun[src] = TT
  593. TT.tick()
  594. /atom/movable/proc/handle_buckled_mob_movement(newloc,direct)
  595. for(var/m in buckled_mobs)
  596. var/mob/living/buckled_mob = m
  597. if(!buckled_mob.Move(newloc, direct))
  598. forceMove(buckled_mob.loc)
  599. last_move = buckled_mob.last_move
  600. inertia_dir = last_move
  601. buckled_mob.inertia_dir = last_move
  602. return 0
  603. return 1
  604. /atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
  605. return FALSE
  606. /atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE)
  607. . = AM.force_pushed(src, force, direction)
  608. if(!silent && .)
  609. visible_message("<span class='warning'>[src] forcefully pushes against [AM]!</span>", "<span class='warning'>You forcefully push against [AM]!</span>")
  610. /atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE)
  611. . = AM.move_crushed(src, force, direction)
  612. if(!silent && .)
  613. visible_message("<span class='danger'>[src] crushes past [AM]!</span>", "<span class='danger'>You crush [AM]!</span>")
  614. /atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
  615. return FALSE
  616. /atom/movable/CanAllowThrough(atom/movable/mover, turf/target)
  617. . = ..()
  618. if(mover in buckled_mobs)
  619. return TRUE
  620. /// Returns true or false to allow src to move through the blocker, mover has final say
  621. /atom/movable/proc/CanPassThrough(atom/blocker, turf/target, blocker_opinion)
  622. SHOULD_CALL_PARENT(TRUE)
  623. SHOULD_BE_PURE(TRUE)
  624. return blocker_opinion
  625. /// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called.
  626. /atom/movable/proc/on_exit_storage(datum/component/storage/concrete/S)
  627. return
  628. /// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item.
  629. /atom/movable/proc/on_enter_storage(datum/component/storage/concrete/S)
  630. return
  631. /atom/movable/proc/get_spacemove_backup()
  632. var/atom/movable/dense_object_backup
  633. for(var/A in orange(1, get_turf(src)))
  634. if(isarea(A))
  635. continue
  636. else if(isturf(A))
  637. var/turf/turf = A
  638. if(!turf.density)
  639. continue
  640. return turf
  641. else
  642. var/atom/movable/AM = A
  643. if(!AM.CanPass(src) || AM.density)
  644. if(AM.anchored)
  645. return AM
  646. dense_object_backup = AM
  647. break
  648. . = dense_object_backup
  649. ///called when a mob resists while inside a container that is itself inside something.
  650. /atom/movable/proc/relay_container_resist(mob/living/user, obj/O)
  651. return
  652. /atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
  653. if(!no_effect && (visual_effect_icon || used_item))
  654. do_item_attack_animation(A, visual_effect_icon, used_item)
  655. if(A == src)
  656. return //don't do an animation if attacking self
  657. var/pixel_x_diff = 0
  658. var/pixel_y_diff = 0
  659. var/direction = get_dir(src, A)
  660. if(direction & NORTH)
  661. pixel_y_diff = 8
  662. else if(direction & SOUTH)
  663. pixel_y_diff = -8
  664. if(direction & EAST)
  665. pixel_x_diff = 8
  666. else if(direction & WEST)
  667. pixel_x_diff = -8
  668. animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2)
  669. animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2)
  670. /atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item)
  671. var/image/I
  672. if(visual_effect_icon)
  673. I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1)
  674. else if(used_item)
  675. I = image(icon = used_item, loc = A, layer = A.layer + 0.1)
  676. I.plane = GAME_PLANE
  677. // Scale the icon.
  678. I.transform *= 0.75
  679. // The icon should not rotate.
  680. I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
  681. // Set the direction of the icon animation.
  682. var/direction = get_dir(src, A)
  683. if(direction & NORTH)
  684. I.pixel_y = -16
  685. else if(direction & SOUTH)
  686. I.pixel_y = 16
  687. if(direction & EAST)
  688. I.pixel_x = -16
  689. else if(direction & WEST)
  690. I.pixel_x = 16
  691. if(!direction) // Attacked self?!
  692. I.pixel_z = 16
  693. if(!I)
  694. return
  695. flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second
  696. // And animate the attack!
  697. animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3)
  698. /atom/movable/vv_get_dropdown()
  699. . = ..()
  700. . += "<option value='?_src_=holder;[HrefToken()];adminplayerobservefollow=[REF(src)]'>Follow</option>"
  701. . += "<option value='?_src_=holder;[HrefToken()];admingetmovable=[REF(src)]'>Get</option>"
  702. /atom/movable/proc/ex_check(ex_id)
  703. if(!ex_id)
  704. return TRUE
  705. LAZYINITLIST(acted_explosions)
  706. if(ex_id in acted_explosions)
  707. return FALSE
  708. acted_explosions += ex_id
  709. return TRUE
  710. //TODO: Better floating
  711. /atom/movable/proc/float(on)
  712. if(throwing)
  713. return
  714. if(on && !(movement_type & FLOATING))
  715. animate(src, pixel_y = pixel_y + 2, time = 10, loop = -1)
  716. sleep(10)
  717. animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1)
  718. setMovetype(movement_type | FLOATING)
  719. else if (!on && (movement_type & FLOATING))
  720. animate(src, pixel_y = initial(pixel_y), time = 10)
  721. setMovetype(movement_type & ~FLOATING)
  722. /* Language procs
  723. * Unless you are doing something very specific, these are the ones you want to use.
  724. */
  725. /// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one.
  726. /atom/movable/proc/get_language_holder(get_minds = TRUE)
  727. if(!language_holder)
  728. language_holder = new initial_language_holder(src)
  729. return language_holder
  730. /// Grants the supplied language and sets omnitongue true.
  731. /atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM)
  732. var/datum/language_holder/LH = get_language_holder()
  733. return LH.grant_language(language, understood, spoken, source)
  734. /// Grants every language.
  735. /atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND)
  736. var/datum/language_holder/LH = get_language_holder()
  737. return LH.grant_all_languages(understood, spoken, grant_omnitongue, source)
  738. /// Removes a single language.
  739. /atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL)
  740. var/datum/language_holder/LH = get_language_holder()
  741. return LH.remove_language(language, understood, spoken, source)
  742. /// Removes every language and sets omnitongue false.
  743. /atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE)
  744. var/datum/language_holder/LH = get_language_holder()
  745. return LH.remove_all_languages(source, remove_omnitongue)
  746. /// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later.
  747. /atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM)
  748. var/datum/language_holder/LH = get_language_holder()
  749. return LH.add_blocked_language(language, source)
  750. /// Removes a language from the blocked language list.
  751. /atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM)
  752. var/datum/language_holder/LH = get_language_holder()
  753. return LH.remove_blocked_language(language, source)
  754. /// Checks if atom has the language. If spoken is true, only checks if atom can speak the language.
  755. /atom/movable/proc/has_language(language, spoken = FALSE)
  756. var/datum/language_holder/LH = get_language_holder()
  757. return LH.has_language(language, spoken)
  758. /// Checks if atom can speak the language.
  759. /atom/movable/proc/can_speak_language(language)
  760. var/datum/language_holder/LH = get_language_holder()
  761. return LH.can_speak_language(language)
  762. /// Returns the result of tongue specific limitations on spoken languages.
  763. /atom/movable/proc/could_speak_language(language)
  764. return TRUE
  765. /// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible.
  766. /atom/movable/proc/get_selected_language()
  767. var/datum/language_holder/LH = get_language_holder()
  768. return LH.get_selected_language()
  769. /// Gets a random understood language, useful for hallucinations and such.
  770. /atom/movable/proc/get_random_understood_language()
  771. var/datum/language_holder/LH = get_language_holder()
  772. return LH.get_random_understood_language()
  773. /// Gets a random spoken language, useful for forced speech and such.
  774. /atom/movable/proc/get_random_spoken_language()
  775. var/datum/language_holder/LH = get_language_holder()
  776. return LH.get_random_spoken_language()
  777. /// Copies all languages into the supplied atom/language holder. Source should be overridden when you
  778. /// do not want the language overwritten by later atom updates or want to avoid blocked languages.
  779. /atom/movable/proc/copy_languages(from_holder, source_override)
  780. if(isatom(from_holder))
  781. var/atom/movable/thing = from_holder
  782. from_holder = thing.get_language_holder()
  783. var/datum/language_holder/LH = get_language_holder()
  784. return LH.copy_languages(from_holder, source_override)
  785. /// Empties out the atom specific languages and updates them according to the current atoms language holder.
  786. /// As a side effect, it also creates missing language holders in the process.
  787. /atom/movable/proc/update_atom_languages()
  788. var/datum/language_holder/LH = get_language_holder()
  789. return LH.update_atom_languages(src)
  790. /* End language procs */
  791. /atom/movable/proc/ConveyorMove(movedir)
  792. set waitfor = FALSE
  793. if(!anchored && has_gravity())
  794. step(src, movedir)
  795. //Returns an atom's power cell, if it has one. Overload for individual items.
  796. /atom/movable/proc/get_cell()
  797. return
  798. /atom/movable/proc/can_be_pulled(user, grab_state, force)
  799. if(src == user || !isturf(loc))
  800. return FALSE
  801. if(anchored || throwing)
  802. return FALSE
  803. if(force < (move_resist * MOVE_FORCE_PULL_RATIO))
  804. return FALSE
  805. return TRUE
  806. /**
  807. * Updates the grab state of the movable
  808. *
  809. * This exists to act as a hook for behaviour
  810. */
  811. /atom/movable/proc/setGrabState(newstate)
  812. grab_state = newstate
  813. /obj/item/proc/do_pickup_animation(atom/target)
  814. set waitfor = FALSE
  815. if(!istype(loc, /turf))
  816. return
  817. var/image/I = image(icon = src, loc = loc, layer = layer + 0.1)
  818. I.plane = GAME_PLANE
  819. I.transform *= 0.75
  820. I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
  821. var/turf/T = get_turf(src)
  822. var/direction
  823. var/to_x = 0
  824. var/to_y = 0
  825. if(!QDELETED(T) && !QDELETED(target))
  826. direction = get_dir(T, target)
  827. if(direction & NORTH)
  828. to_y = 32
  829. else if(direction & SOUTH)
  830. to_y = -32
  831. if(direction & EAST)
  832. to_x = 32
  833. else if(direction & WEST)
  834. to_x = -32
  835. if(!direction)
  836. to_y = 16
  837. flick_overlay(I, GLOB.clients, 6)
  838. var/matrix/M = new
  839. M.Turn(pick(-30, 30))
  840. animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING)
  841. sleep(1)
  842. animate(I, alpha = 0, transform = matrix(), time = 1)