Skip to content

Commit

Permalink
Movable Physics Subsystem (shiptest-ss13#2880)
Browse files Browse the repository at this point in the history
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->
Ports some mojave code (originally written on Daedalus by Spyroshark)
hat allows you to fling ingame objects with a simple, bouncing physics
quality.
This is currently implemented on bullet casings.

Technical issues currently:

- [x] Makes the bouncing less dramatic

- [x] Physics must have a "low grav" version for tiles without gravity,
since mojave didn't think gravity was real

- [x] The code handling the angle physics objects travel at is fucky and
needs to be fixed up

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->
the hit feature from 2003, coming to a shiptest near you
<!-- Please add a short description of why you think these changes would
benefit the game. If you can't justify it in words, it might not be
worth adding. -->

:cl: Spyroshark, Sun-Soaked
add: A movable physics subsystem, deployed using a component.
add: Bullet casings now drop using movable physics
code: ports NO_PIXEL_RANDOM_DROP from TG.
/:cl:

<!-- Both :cl:'s are required for the changelog to work! You can put
your name to the right of the first :cl: if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

---------

Co-authored-by: Sun-Soaked <[email protected]>
  • Loading branch information
2 people authored and MysticalFaceLesS committed Jun 1, 2024
1 parent 8ae3bf9 commit 36def0f
Show file tree
Hide file tree
Showing 22 changed files with 222 additions and 27 deletions.
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 @@
#define PHYSICS_GRAV_STANDARD 9.80665

///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

var/numbounce = 1

/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)
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 += 0.5

/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 @@ -694,7 +694,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 @@ -51,6 +51,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 @@ -107,9 +108,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 = PHYSICS_GRAV_STANDARD, _z_floor = 0, _angle_of_movement = angle_of_movement)


/obj/item/ammo_casing/proc/bounce_away(still_warm = FALSE, bounce_delay = 3)
if(!heavy_metal)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/projectiles/ammunition/caseless/_caseless.dm
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)
// [CELADON-EDIT] - CELADON_FIXES
// qdel(src) // CELADON-EDIT - ORIGINAL
if(BB) // Проверяем, что гильза не пустая
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 @@ -209,7 +209,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 @@ -357,7 +357,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 @@ -408,7 +408,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 = PHYSICS_GRAV_STANDARD, _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 = PHYSICS_GRAV_STANDARD, _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 = PHYSICS_GRAV_STANDARD, _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 @@ -303,7 +303,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 = PHYSICS_GRAV_STANDARD, _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

0 comments on commit 36def0f

Please sign in to comment.