Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Movable Physics Subsystem #2880

Merged
merged 12 commits into from
May 27, 2024
1 change: 1 addition & 0 deletions code/__DEFINES/obj_flags.dm
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#define IN_STORAGE (1<<11) //is this item in the storage item, such as backpack? used for tooltips
#define SURGICAL_TOOL (1<<12) //Tool commonly used for surgery: won't attack targets in an active surgical operation on help intent (in case of mistakes)
#define EYE_STAB (1<<13) /// Item can be used to eyestab
#define NO_PIXEL_RANDOM_DROP (1<<14) //if dropped, it wont have a randomized pixel_x/pixel_y

// Flags for the clothing_flags var on /obj/item/clothing

Expand Down
1 change: 1 addition & 0 deletions code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@
#define FIRE_PRIORITY_PARALLAX 65
#define FIRE_PRIORITY_INSTRUMENTS 80
#define FIRE_PRIORITY_MOBS 100
#define FIRE_PRIORITY_MOVABLE_PHYSICS 105
#define FIRE_PRIORITY_TGUI 110
#define FIRE_PRIORITY_TICKER 200
#define FIRE_PRIORITY_ATMOS_ADJACENCY 300
Expand Down
1 change: 1 addition & 0 deletions code/_globalvars/bitfields.dm
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ DEFINE_BITFIELD(item_flags, list(
"NOBLUDGEON" = NOBLUDGEON,
"NO_MAT_REDEMPTION" = NO_MAT_REDEMPTION,
"SLOWS_WHILE_IN_HAND" = SLOWS_WHILE_IN_HAND,
"NO_PIXEL_RANDOM_DROP" = NO_PIXEL_RANDOM_DROP,
))

DEFINE_BITFIELD(machine_stat, list(
Expand Down
24 changes: 24 additions & 0 deletions code/controllers/subsystem/processing/movable_physics.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
///Real fast ticking subsystem for moving movables via modifying pixel_x/y/z
PROCESSING_SUBSYSTEM_DEF(movablephysics)
name = "Movable Physics"
wait = 0.05 SECONDS
stat_tag = "MP"
priority = FIRE_PRIORITY_MOVABLE_PHYSICS

/datum/controller/subsystem/processing/movablephysics/fire(resumed = FALSE)
if (!resumed)
currentrun = processing.Copy()
//cache for sanic speed (lists are references anyways)
var/list/current_run = currentrun

while(current_run.len)
var/datum/component/thing = current_run[current_run.len]
current_run.len--
if(QDELETED(thing))
processing -= thing
else
if(thing.process(wait * 0.1) == PROCESS_KILL)
// fully stop so that a future START_PROCESSING will work
STOP_PROCESSING(src, thing)
if (MC_TICK_CHECK)
return
151 changes: 151 additions & 0 deletions code/datums/components/movable_physics.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
///Remove the component as soon as there's zero velocity, useful for movables that will no longer move after being initially moved (blood splatters)
#define QDEL_WHEN_NO_MOVEMENT (1<<0)

///Stores information related to the movable's physics and keeping track of relevant signals to trigger movement
/datum/component/movable_physics
///Modifies the pixel_x/pixel_y of an object every process()
var/horizontal_velocity
///Modifies the pixel_z of an object every process(), movables aren't Move()'d into another turf if pixel_z exceeds 16, so try not to supply a super high vertical value if you don't want the movable to clip through multiple turfs
var/vertical_velocity
///The horizontal_velocity is reduced by this every process(), this doesn't take into account the object being in the air vs gravity pushing it against the ground
var/horizontal_friction
///The vertical_velocity is reduced by this every process()
var/z_gravity
///The pixel_z that the object will no longer be influenced by gravity for a 32x32 turf, keep this value between -16 to 0 so it's visuals matches up with it physically being in the turf
var/z_floor
///The angle of the path the object takes on the x/y plane
var/angle_of_movement
///Flags for turning on certain physic properties, see the top of the file for more information on flags
var/physic_flags
///The cached animate_movement of the parent; any kind of gliding when doing Move() makes the physics look derpy, so we'll just make Move() be instant
var/cached_animate_movement
///The sound effect to play when bouncing off of something
var/bounce_sound

///set when the spin animation starts, unset when it ends. prevents jittery spinanim spam on bounces
var/spun = FALSE

/datum/component/movable_physics/Initialize(_horizontal_velocity = 0, _vertical_velocity = 0, _horizontal_friction = 0, _z_gravity = 0, _z_floor = 0, _angle_of_movement = 0, _physic_flags = 0, _bounce_sound)
. = ..()
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, PROC_REF(throw_impact_ricochet), override = TRUE)
horizontal_velocity = _horizontal_velocity
vertical_velocity = _vertical_velocity
horizontal_friction = _horizontal_friction
z_gravity = _z_gravity
z_floor = _z_floor
angle_of_movement = _angle_of_movement
physic_flags = _physic_flags
bounce_sound = _bounce_sound
if(vertical_velocity || horizontal_velocity)
start_movement()

///Let's get moving
/datum/component/movable_physics/proc/start_movement()
var/atom/movable/moving_atom = parent
cached_animate_movement = moving_atom.animate_movement
moving_atom.animate_movement = NO_STEPS
START_PROCESSING(SSmovablephysics, src)
moving_atom.SpinAnimation(speed = 1 SECONDS, loops = 1)

///Alright it's time to stop
/datum/component/movable_physics/proc/stop_movement()
var/atom/movable/moving_atom = parent
moving_atom.animate_movement = cached_animate_movement
STOP_PROCESSING(SSmovablephysics, src)
if(physic_flags & QDEL_WHEN_NO_MOVEMENT)
qdel(src)

/datum/component/movable_physics/UnregisterFromParent()
UnregisterSignal(parent, COMSIG_MOVABLE_IMPACT)

/datum/component/movable_physics/proc/throw_impact_ricochet(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum)
SIGNAL_HANDLER
var/atom/movable/atom_source = source
ricochet(atom_source, Get_Angle(atom_source, throwingdatum.target_turf))

/datum/component/movable_physics/proc/z_floor_bounce(atom/movable/moving_atom)
var/numbounce = 1
angle_of_movement += rand(-3000, 3000) / 100
var/turf/a_turf = get_turf(moving_atom)
if(istype(moving_atom, /obj/item/ammo_casing))
playsound(moving_atom, a_turf.bullet_bounce_sound, 50, TRUE)
else
playsound(moving_atom, bounce_sound, 50, TRUE)
moving_atom.SpinAnimation(speed = 1 SECONDS / numbounce, loops = 1)
moving_atom.pixel_z = z_floor
horizontal_velocity = max(0, horizontal_velocity + (vertical_velocity * -0.8))
vertical_velocity = max(0, ((vertical_velocity * -0.8) - 0.2))
numbounce += 1
Sun-Soaked marked this conversation as resolved.
Show resolved Hide resolved

/datum/component/movable_physics/proc/ricochet(atom/movable/moving_atom, bounce_angle)
angle_of_movement = ((180 - bounce_angle) - angle_of_movement)
if(angle_of_movement < 0)
angle_of_movement += 360
//var/turf/a_turf = get_turf(moving_atom)
//playsound(src, a_turf.bullet_bounce_sound, 50, TRUE)

/datum/component/movable_physics/proc/fix_angle(angle, atom/moving_atom)//fixes an angle below 0 or above 360
if(!(angle_of_movement > 360) && !(angle_of_movement < 0))
return angle //early return if it doesn't need to change
var/new_angle
if(angle_of_movement > 360)
new_angle = angle_of_movement - 360
if(angle_of_movement < 0)
new_angle = angle_of_movement + 360
return new_angle

/datum/component/movable_physics/process(delta_time)
var/atom/movable/moving_atom = parent
var/turf/location = get_turf(moving_atom)

angle_of_movement = fix_angle(angle_of_movement, moving_atom)
if(horizontal_velocity <= 0 && moving_atom.pixel_z == 0)
horizontal_velocity = 0
stop_movement()
return

moving_atom.pixel_x += (horizontal_velocity * (sin(angle_of_movement)))
moving_atom.pixel_y += (horizontal_velocity * (cos(angle_of_movement)))

horizontal_velocity = max(0, horizontal_velocity - horizontal_friction)

moving_atom.pixel_z = max(z_floor, moving_atom.pixel_z + vertical_velocity)
if(moving_atom.pixel_z > z_floor)
vertical_velocity -= (z_gravity * 0.05)

if(moving_atom.pixel_z <= z_floor && (vertical_velocity != 0) && moving_atom.has_gravity(location)) //z bounce
z_floor_bounce(moving_atom)

if(moving_atom.pixel_x > 16)
if(moving_atom.Move(get_step(moving_atom, EAST)))
moving_atom.pixel_x = -16
else
moving_atom.pixel_x = 16
ricochet(moving_atom, 0)
return

if(moving_atom.pixel_x < -16)
if(moving_atom.Move(get_step(moving_atom, WEST)))
moving_atom.pixel_x = 16
else
moving_atom.pixel_x = -16
ricochet(moving_atom, 0)
return

if(moving_atom.pixel_y > 16)
if(moving_atom.Move(get_step(moving_atom, NORTH)))
moving_atom.pixel_y = -16
else
moving_atom.pixel_y = 16
ricochet(moving_atom, 180)
return

if(moving_atom.pixel_y < -16)
if(moving_atom.Move(get_step(moving_atom, SOUTH)))
moving_atom.pixel_y = 16
else
moving_atom.pixel_y = -16
ricochet(moving_atom, 180)

2 changes: 1 addition & 1 deletion code/game/objects/items.dm
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
if (callback) //call the original callback
. = callback.Invoke()
item_flags &= ~IN_INVENTORY
if(!pixel_y && !pixel_x)
if(!pixel_y && !pixel_x && !(item_flags & NO_PIXEL_RANDOM_DROP))
pixel_x = rand(-8,8)
pixel_y = rand(-8,8)

Expand Down
1 change: 1 addition & 0 deletions code/game/objects/items/devices/powersink.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
w_class = WEIGHT_CLASS_BULKY
flags_1 = CONDUCT_1
item_flags = NO_PIXEL_RANDOM_DROP
throwforce = 5
throw_speed = 1
throw_range = 2
Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/inventory.dm
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@
*/
/mob/proc/dropItemToGround(obj/item/I, force = FALSE, silent = FALSE)
. = doUnEquip(I, force, drop_location(), FALSE, silent = silent)
if(. && I) //ensure the item exists and that it was dropped properly.
if(. && I && !(I.item_flags & NO_PIXEL_RANDOM_DROP)) //ensure the item exists and that it was dropped properly.
I.pixel_x = rand(-6,6)
I.pixel_y = rand(-6,6)

Expand Down
10 changes: 8 additions & 2 deletions code/modules/projectiles/ammunition/_ammunition.dm
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
BB = new projectile_type(src)
pixel_x = base_pixel_x + rand(-10, 10)
pixel_y = base_pixel_y + rand(-10, 10)
item_flags |= NO_PIXEL_RANDOM_DROP
if(auto_rotate)
transform = transform.Turn(pick(0, 90, 180, 270))
update_appearance()
Expand Down Expand Up @@ -100,9 +101,14 @@
bounce_away(FALSE, NONE)
. = ..()

/obj/item/ammo_casing/proc/on_eject()
/obj/item/ammo_casing/proc/on_eject(atom/shooter)
forceMove(drop_location()) //Eject casing onto ground.
bounce_away(TRUE)
pixel_x = rand(-4, 4)
pixel_y = rand(-4, 4)
pixel_z = 8 //bounce time
var/angle_of_movement = !isnull(shooter) ? (rand(-3000, 3000) / 100) + dir2angle(turn(shooter.dir, 180)) : rand(-3000, 3000) / 100
AddComponent(/datum/component/movable_physics, _horizontal_velocity = rand(400, 450) / 100, _vertical_velocity = rand(400, 450) / 100, _horizontal_friction = rand(20, 24) / 100, _z_gravity = 9.80665, _z_floor = 0, _angle_of_movement = angle_of_movement)
Sun-Soaked marked this conversation as resolved.
Show resolved Hide resolved


/obj/item/ammo_casing/proc/bounce_away(still_warm = FALSE, bounce_delay = 3)
if(!heavy_metal)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
firing_effect_type = null
heavy_metal = FALSE

/obj/item/ammo_casing/caseless/on_eject()
/obj/item/ammo_casing/caseless/on_eject(atom/shooter)
qdel(src)

// Overridden; caseless ammo does not distinguish between "live" and "empty"/"spent" icon states (because it has no casing).
Expand Down
6 changes: 3 additions & 3 deletions code/modules/projectiles/gun.dm
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@
zoom(user, user.dir, FALSE) //we can only stay zoomed in if it's in our hands //yeah and we only unzoom if we're actually zoomed using the gun!!

//called after the gun has successfully fired its chambered ammo.
/obj/item/gun/proc/process_chamber()
/obj/item/gun/proc/process_chamber(atom/shooter)
SEND_SIGNAL(src, COMSIG_GUN_CHAMBER_PROCESSED)
return FALSE

Expand Down Expand Up @@ -417,7 +417,7 @@
shoot_with_empty_chamber(user)
firing_burst = FALSE
return FALSE
process_chamber()
process_chamber(shooter = user)
update_appearance()
return TRUE

Expand Down Expand Up @@ -468,7 +468,7 @@
else
shoot_with_empty_chamber(user)
return
process_chamber()
process_chamber(shooter = user)
update_appearance()
if(fire_delay)
semicd = TRUE
Expand Down
13 changes: 8 additions & 5 deletions code/modules/projectiles/guns/ballistic.dm
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,13 @@
if(!chambered && empty_indicator)
. += "[icon_state]_empty"

/obj/item/gun/ballistic/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
/obj/item/gun/ballistic/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE, atom/shooter)
if(!semi_auto && from_firing)
return
var/obj/item/ammo_casing/casing = chambered //Find chambered round
if(istype(casing)) //there's a chambered round
if(casing_ejector || !from_firing)
casing.on_eject()
casing.on_eject(shooter)
chambered = null
else if(empty_chamber)
chambered = null
Expand Down Expand Up @@ -179,7 +179,7 @@
bolt_locked = FALSE
if (user)
to_chat(user, "<span class='notice'>You rack the [bolt_wording] of \the [src].</span>")
process_chamber(!chambered, FALSE)
process_chamber(!chambered, FALSE, shooter = user)
if (bolt_type == BOLT_TYPE_LOCKING && !chambered)
bolt_locked = TRUE
playsound(src, lock_back_sound, lock_back_sound_volume, lock_back_sound_vary)
Expand Down Expand Up @@ -267,7 +267,7 @@
if (istype(A, /obj/item/ammo_casing) || istype(A, /obj/item/ammo_box))
if (bolt_type == BOLT_TYPE_NO_BOLT || internal_magazine)
if (chambered && !chambered.BB)
chambered.on_eject()
chambered.on_eject(shooter = user)
chambered = null
var/num_loaded = magazine.attackby(A, user, params)
if (num_loaded)
Expand Down Expand Up @@ -364,7 +364,10 @@
var/num_unloaded = 0
for(var/obj/item/ammo_casing/CB in get_ammo_list(FALSE, TRUE))
CB.forceMove(drop_location())
CB.bounce_away(FALSE, NONE)

var/angle_of_movement =(rand(-3000, 3000) / 100) + dir2angle(turn(user.dir, 180))
CB.AddComponent(/datum/component/movable_physics, _horizontal_velocity = rand(350, 450) / 100, _vertical_velocity = rand(400, 450) / 100, _horizontal_friction = rand(20, 24) / 100, _z_gravity = 9.80665, _z_floor = 0, _angle_of_movement = angle_of_movement)

num_unloaded++
SSblackbox.record_feedback("tally", "station_mess_created", 1, CB.name)
if (num_unloaded)
Expand Down
11 changes: 7 additions & 4 deletions code/modules/projectiles/guns/ballistic/revolver.dm
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
. += "[base_icon_state || initial(icon_state)][safety ? "_hammer_up" : "_hammer_down"]"


/obj/item/gun/ballistic/revolver/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
/obj/item/gun/ballistic/revolver/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE, atom/shooter)
SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD)
return ..()

Expand All @@ -84,7 +84,9 @@
if(!casing_to_eject)
continue
casing_to_eject.forceMove(drop_location())
casing_to_eject.bounce_away(FALSE, NONE)
var/angle_of_movement =(rand(-3000, 3000) / 100) + dir2angle(turn(user.dir, 180))
casing_to_eject.AddComponent(/datum/component/movable_physics, _horizontal_velocity = rand(450, 550) / 100, _vertical_velocity = rand(400, 450) / 100, _horizontal_friction = rand(20, 24) / 100, _z_gravity = 9.80665, _z_floor = 0, _angle_of_movement = angle_of_movement)

num_unloaded++
SSblackbox.record_feedback("tally", "station_mess_created", 1, casing_to_eject.name)
chamber_round(FALSE)
Expand Down Expand Up @@ -120,8 +122,9 @@
to_chat(user, "<span class='warning'>There's nothing in the gate to eject from [src]!</span>")
return FALSE
playsound(src, eject_sound, eject_sound_volume, eject_sound_vary)
casing_to_eject.forceMove(drop_location())
casing_to_eject.bounce_away(FALSE, NONE)
var/angle_of_movement =(rand(-3000, 3000) / 100) + dir2angle(turn(user.dir, 180))
casing_to_eject.AddComponent(/datum/component/movable_physics, _horizontal_velocity = rand(350, 450) / 100, _vertical_velocity = rand(400, 450) / 100, _horizontal_friction = rand(20, 24) / 100, _z_gravity = 9.80665, _z_floor = 0, _angle_of_movement = angle_of_movement)

SSblackbox.record_feedback("tally", "station_mess_created", 1, casing_to_eject.name)
if(!gate_loaded)
magazine.stored_ammo[casing_index] = null
Expand Down
4 changes: 2 additions & 2 deletions code/modules/projectiles/guns/ballistic/rifle.dm
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
. = ..()
. += "[icon_state]_bolt[bolt_locked ? "_locked" : ""]"

/obj/item/gun/ballistic/rifle/rack(mob/user = null)
/obj/item/gun/ballistic/rifle/rack(mob/living/user)
if (bolt_locked == FALSE)
to_chat(user, "<span class='notice'>You open the bolt of \the [src].</span>")
playsound(src, rack_sound, rack_sound_volume, rack_sound_vary)
process_chamber(FALSE, FALSE, FALSE)
process_chamber(FALSE, FALSE, FALSE, shooter = user)
bolt_locked = TRUE
update_appearance()
if (magazine && !magazine?.ammo_count() && empty_autoeject && !internal_magazine)
Expand Down
4 changes: 3 additions & 1 deletion code/modules/projectiles/guns/ballistic/shotgun.dm
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,9 @@
var/num_unloaded = 0
for(var/obj/item/ammo_casing/casing_bullet in get_ammo_list(FALSE, TRUE))
casing_bullet.forceMove(drop_location())
casing_bullet.bounce_away(FALSE, NONE)
var/angle_of_movement =(rand(-3000, 3000) / 100) + dir2angle(turn(user.dir, 180))
casing_bullet.AddComponent(/datum/component/movable_physics, _horizontal_velocity = rand(450, 550) / 100, _vertical_velocity = rand(400, 450) / 100, _horizontal_friction = rand(20, 24) / 100, _z_gravity = 9.80665, _z_floor = 0, _angle_of_movement = angle_of_movement)

num_unloaded++
SSblackbox.record_feedback("tally", "station_mess_created", 1, casing_bullet.name)
if (num_unloaded)
Expand Down
Loading
Loading