Skip to content

Commit

Permalink
adds autofire
Browse files Browse the repository at this point in the history
  • Loading branch information
johndoe2013 committed Jul 16, 2023
1 parent 44fd728 commit b63376c
Show file tree
Hide file tree
Showing 29 changed files with 681 additions and 266 deletions.
4 changes: 4 additions & 0 deletions code/__DEFINES/autofire.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Controls how many buckets should be kept, each representing a tick. Max is ten seconds, to have better perf.
#define AUTOFIRE_BUCKET_LEN (world.fps * 10)
/// Helper for getting the correct bucket
#define AUTOFIRE_BUCKET_POS(next_fire) (((round((next_fire - SSautomatedfire.head_offset) / world.tick_lag) + 1) % AUTOFIRE_BUCKET_LEN) || AUTOFIRE_BUCKET_LEN)
32 changes: 13 additions & 19 deletions code/__DEFINES/conflict.dm
Original file line number Diff line number Diff line change
Expand Up @@ -54,30 +54,24 @@
#define GUN_TRIGGER_SAFETY (1<<1)
#define GUN_UNUSUAL_DESIGN (1<<2)
#define GUN_SILENCED (1<<3)
#define GUN_AUTOMATIC (1<<4)
///If checking for ammo with current.mag you have to check it against numerical values, as booleans will not trigger.
#define GUN_INTERNAL_MAG (1<<5)
#define GUN_AUTO_EJECTOR (1<<6)
#define GUN_AMMO_COUNTER (1<<7)
#define GUN_BURST_ON (1<<8)
#define GUN_BURST_FIRING (1<<9)
#define GUN_FLASHLIGHT_ON (1<<10)
#define GUN_WY_RESTRICTED (1<<11)
#define GUN_SPECIALIST (1<<12)
#define GUN_WIELDED_FIRING_ONLY (1<<13)
#define GUN_HAS_FULL_AUTO (1<<14)
#define GUN_FULL_AUTO_ON (1<<15)
#define GUN_INTERNAL_MAG (1<<4)
#define GUN_AUTO_EJECTOR (1<<5)
#define GUN_AMMO_COUNTER (1<<6)
#define GUN_BURST_FIRING (1<<7)
#define GUN_FLASHLIGHT_ON (1<<8)
#define GUN_WY_RESTRICTED (1<<9)
#define GUN_SPECIALIST (1<<10)
#define GUN_WIELDED_FIRING_ONLY (1<<11)
/// removes unwielded accuracy and scatter penalties (not recoil)
#define GUN_ONE_HAND_WIELDED (1<<16)
#define GUN_ANTIQUE (1<<17)
#define GUN_ONE_HAND_WIELDED (1<<12)
#define GUN_ANTIQUE (1<<13)
/// Whether the gun has been fired by its current user (reset upon `dropped()`)
#define GUN_RECOIL_BUILDUP (1<<18)
#define GUN_RECOIL_BUILDUP (1<<14)
/// support weapon, bipod will grant IFF
#define GUN_SUPPORT_PLATFORM (1<<19)
#define GUN_BURST_ONLY (1<<20)
#define GUN_FULL_AUTO_ONLY (1<<21)
#define GUN_SUPPORT_PLATFORM (1<<15)
/// No gun description, only base desc
#define GUN_NO_DESCRIPTION (1<<22)
#define GUN_NO_DESCRIPTION (1<<16)
// NOTE: Don't add flags past 1<<23, it'll break things due to BYOND limitations. You can usually use a Component instead.

#define USES_STREAKS (1<<0)
Expand Down
6 changes: 6 additions & 0 deletions code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,9 @@
#define COMSIG_MOB_STAT_SET_DEAD "mob_stat_set_dead"

#define COMSIG_GHOST_MOVED "ghost_moved"

#define COMSIG_MOB_MOUSEDOWN "mob_mousedown" //from /client/MouseDown(): (atom/object, turf/location, control, params)
#define COMSIG_MOB_MOUSEUP "mob_mouseup" //from /client/MouseUp(): (atom/object, turf/location, control, params)
#define COMSIG_MOB_MOUSEDRAG "mob_mousedrag" //from /client/MouseDrag(): (atom/src_object, atom/over_object, turf/src_location, turf/over_location, src_control, over_control, params)
#define COMSIG_MOB_CLICK_CANCELED (1<<0)
#define COMSIG_MOB_CLICK_HANDLED (1<<1)
16 changes: 16 additions & 0 deletions code/__DEFINES/dcs/signals/atom/signals_item.dm
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,19 @@
#define COMSIG_ITEM_ZOOM "item_zoom"
/// from /obj/item/proc/unzoom() : (mob/user)
#define COMSIG_ITEM_UNZOOM "item_unzoom"

//Signals for automatic fire at component
#define COMSIG_AUTOMATIC_SHOOTER_START_SHOOTING_AT "start_shooting_at"
#define COMSIG_AUTOMATIC_SHOOTER_STOP_SHOOTING_AT "stop_shooting_at"
#define COMSIG_AUTOMATIC_SHOOTER_SHOOT "shoot"

//Signals for gun auto fire component
#define COMSIG_GET_BURST_FIRE "get_burst_fire"
#define BURST_FIRING (1<<0)

#define COMSIG_GUN_FIRE "gun_fire"
#define COMSIG_GUN_STOP_FIRE "gun_stop_fire"
#define COMSIG_GUN_FIRE_MODE_TOGGLE "gun_fire_mode_toggle"
#define COMSIG_GUN_AUTOFIREDELAY_MODIFIED "gun_autofiredelay_modified"
#define COMSIG_GUN_BURST_SHOTS_TO_FIRE_MODIFIED "gun_burst_shots_to_fire_modified"
#define COMSIG_GUN_BURST_SHOT_DELAY_MODIFIED "gun_burst_shot_delay_modified"
8 changes: 8 additions & 0 deletions code/__DEFINES/guns.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@
#define REVOLVER_TIP_COLOR_INCENDIARY AMMO_BAND_COLOR_INCENDIARY
#define REVOLVER_TIP_COLOR_PENETRATING AMMO_BAND_COLOR_PENETRATING
#define REVOLVER_TIP_COLOR_TOXIN AMMO_BAND_COLOR_TOXIN

#define GUN_FIREMODE_SEMIAUTO "semi-auto fire mode"
#define GUN_FIREMODE_BURSTFIRE "burst-fire mode"
#define GUN_FIREMODE_AUTOMATIC "automatic fire mode"

//autofire component fire callback return flags
#define AUTOFIRE_CONTINUE (1<<0)
#define AUTOFIRE_SUCCESS (1<<1)
1 change: 1 addition & 0 deletions code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@

#define SS_PRIORITY_INPUT 1000
#define SS_PRIORITY_TIMER 700
#define SS_PRIORITY_AUTOFIRE 450
#define SS_PRIORITY_SOUND 250
#define SS_PRIORITY_TICKER 200
#define SS_PRIORITY_NIGHTMARE 180
Expand Down
6 changes: 0 additions & 6 deletions code/_globalvars/bitfields.dm
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,18 @@ DEFINE_BITFIELD(flags_gun_features, list(
"GUN_TRIGGER_SAFETY" = GUN_TRIGGER_SAFETY,
"GUN_UNUSUAL_DESIGN" = GUN_UNUSUAL_DESIGN,
"GUN_SILENCED" = GUN_SILENCED,
"GUN_AUTOMATIC" = GUN_AUTOMATIC,
"GUN_INTERNAL_MAG" = GUN_INTERNAL_MAG,
"GUN_AUTO_EJECTOR" = GUN_AUTO_EJECTOR,
"GUN_AMMO_COUNTER" = GUN_AMMO_COUNTER,
"GUN_BURST_ON" = GUN_BURST_ON,
"GUN_BURST_FIRING" = GUN_BURST_FIRING,
"GUN_FLASHLIGHT_ON" = GUN_FLASHLIGHT_ON,
"GUN_WY_RESTRICTED" = GUN_WY_RESTRICTED,
"GUN_SPECIALIST" = GUN_SPECIALIST,
"GUN_WIELDED_FIRING_ONLY" = GUN_WIELDED_FIRING_ONLY,
"GUN_HAS_FULL_AUTO" = GUN_HAS_FULL_AUTO,
"GUN_FULL_AUTO_ON" = GUN_FULL_AUTO_ON,
"GUN_ONE_HAND_WIELDED" = GUN_ONE_HAND_WIELDED,
"GUN_ANTIQUE" = GUN_ANTIQUE,
"GUN_RECOIL_BUILDUP" = GUN_RECOIL_BUILDUP,
"GUN_SUPPORT_PLATFORM" = GUN_SUPPORT_PLATFORM,
"GUN_BURST_ONLY" = GUN_BURST_ONLY,
"GUN_FULL_AUTO_ONLY" = GUN_FULL_AUTO_ONLY,
))

DEFINE_BITFIELD(flags_magazine, list(
Expand Down
9 changes: 9 additions & 0 deletions code/_onclick/click_hold.dm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
mouse_trace_history = null
LAZYADD(mouse_trace_history, A)

if(SEND_SIGNAL(mob, COMSIG_MOB_MOUSEDOWN, A, T, skin_ctl, params) & COMSIG_MOB_CLICK_CANCELED)
return

var/list/mods = params2list(params)
if(mods["left"])
SEND_SIGNAL(src, COMSIG_CLIENT_LMB_DOWN, A, mods)
Expand Down Expand Up @@ -62,6 +65,9 @@
params += ";click_catcher=1"
holding_click = FALSE

if(SEND_SIGNAL(mob, COMSIG_MOB_MOUSEUP, A, T, skin_ctl, params) & COMSIG_MOB_CLICK_CANCELED)
return

var/list/mods = params2list(params)
if(mods["left"])
SEND_SIGNAL(src, COMSIG_CLIENT_LMB_UP, A, params)
Expand All @@ -75,6 +81,9 @@
if(click_catcher_click)
params += ";click_catcher=1"

if(SEND_SIGNAL(mob, COMSIG_MOB_MOUSEDRAG, src_obj, over_obj, src_loc, over_loc, src_ctl, over_ctl, params) & COMSIG_MOB_CLICK_CANCELED)
return

var/list/mods = params2list(params)
if(mods["left"])
SEND_SIGNAL(src, COMSIG_CLIENT_LMB_DRAG, src_obj, over_obj, params)
Expand Down
85 changes: 85 additions & 0 deletions code/controllers/subsystem/autofire.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* # Autofire Subsystem
*
* Maintains a timer-like system to handle autofiring. Much of this code is modeled
* after or adapted from TGMC's runechat subsytem.
*
* Note that this has the same structure for storing and queueing shooter component as the timer subsystem does
* for handling timers: the bucket_list is a list of autofire component, each of which are the head
* of a linked list. Any given index in bucket_list could be null, representing an empty bucket.
*
* Doesn't support any event scheduled for more than 100 ticks in the future, as it has no secondary queue by design
*/
SUBSYSTEM_DEF(automatedfire)
name = "Automated fire"
flags = SS_TICKER | SS_NO_INIT
wait = 1
priority = SS_PRIORITY_AUTOFIRE

/// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets
var/head_offset = 0
/// Index of the first non-empty bucket
var/practical_offset = 1
///How many buckets for every frame of world.fps
var/bucket_resolution = 0
/// How many shooter are in the buckets
var/shooter_count = 0
/// List of buckets, each bucket holds every shooter that has to shoot this byond tick
var/list/bucket_list = list()
/// Reference to the next shooter before we clean shooter.next
var/datum/component/automatedfire/next_shooter

/datum/controller/subsystem/automatedfire/PreInit()
bucket_list.len = AUTOFIRE_BUCKET_LEN
head_offset = world.time
bucket_resolution = world.tick_lag

/datum/controller/subsystem/automatedfire/stat_entry(msg = "ActShooters: [shooter_count]")
return ..()

/datum/controller/subsystem/automatedfire/fire(resumed = FALSE)
// Check for when we need to loop the buckets, this occurs when
// the head_offset is approaching AUTOFIRE_BUCKET_LEN ticks in the past
if (practical_offset > AUTOFIRE_BUCKET_LEN)
head_offset += TICKS2DS(AUTOFIRE_BUCKET_LEN)
practical_offset = 1
resumed = FALSE

// Check for when we have to reset buckets, typically from auto-reset
if ((length(bucket_list) != AUTOFIRE_BUCKET_LEN) || (world.tick_lag != bucket_resolution))
reset_buckets()
bucket_list = src.bucket_list
resumed = FALSE

// Store a reference to the 'working' shooter so that we can resume if the MC
// has us stop mid-way through processing
var/static/datum/component/automatedfire/shooter
if (!resumed)
shooter = null

// Iterate through each bucket starting from the practical offset
while (practical_offset <= AUTOFIRE_BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time)
if(!shooter)
shooter = bucket_list[practical_offset]
bucket_list[practical_offset] = null

while (shooter)
next_shooter = shooter.next
INVOKE_ASYNC(shooter, TYPE_PROC_REF(/datum/component/automatedfire, process_shot))

SSautomatedfire.shooter_count--
shooter = next_shooter
if (MC_TICK_CHECK)
return

// Move to the next bucket
practical_offset++

/datum/controller/subsystem/automatedfire/Recover()
bucket_list |= SSautomatedfire.bucket_list

///In the event of a change of world.tick_lag, we refresh the size of the bucket and the bucket resolution
/datum/controller/subsystem/automatedfire/proc/reset_buckets()
bucket_list.len = AUTOFIRE_BUCKET_LEN
head_offset = world.time
bucket_resolution = world.tick_lag
49 changes: 49 additions & 0 deletions code/datums/components/autofire/_automated_fire.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/datum/component/automatedfire
///The owner of this component
var/atom/shooter
/// Contains the scheduled fire time, used for scheduling EOL
var/next_fire
/// Contains the reference to the next component in the bucket, used by autofire subsystem
var/datum/component/automatedfire/next
/// Contains the reference to the previous component in the bucket, used by autofire subsystem
var/datum/component/automatedfire/prev


/// schedule the shooter into the system, inserting it into the next fire queue
/datum/component/automatedfire/proc/schedule_shot()
//We move to another bucket, so we clean the reference from the former linked list
next = null
prev = null
var/list/bucket_list = SSautomatedfire.bucket_list

// Ensure the next_fire time is properly bound to avoid missing a scheduled event
next_fire = max(CEILING(next_fire, world.tick_lag), world.time + world.tick_lag)

// Get bucket position and a local reference to the datum var, it's faster to access this way
var/bucket_pos = AUTOFIRE_BUCKET_POS(next_fire)

// Get the bucket head for that bucket, increment the bucket count
var/datum/component/automatedfire/bucket_head = bucket_list[bucket_pos]
SSautomatedfire.shooter_count++

// If there is no existing head of this bucket, we can set this shooter to be that head
if (!bucket_head)
bucket_list[bucket_pos] = src
return

// Otherwise it's a simple insertion into the double-linked list
if (bucket_head.next)
next = bucket_head.next
next.prev = src

bucket_head.next = src
prev = bucket_head

//Something went wrong, probably a lag spike or something. To prevent infinite loops, we reschedule it to a another next fire
if(prev == src)
next_fire += 1
schedule_shot()

///Handle the firing of the autofire component
/datum/component/automatedfire/proc/process_shot()
return
Loading

0 comments on commit b63376c

Please sign in to comment.