Command: Weapon Speed modifier

A place for requesting new Source.Python plugins to be made for your server.

Please request only one plugin per thread.
Manifest
Junior Member
Posts: 10
Joined: Thu Jun 18, 2015 7:24 pm

Command: Weapon Speed modifier

Postby Manifest » Mon Dec 31, 2018 4:59 pm

Hi there everyone!


I recently stumbled upon a plugin that provided a command which would allow you to modify the speed of a weapon's attack, and also provided a command that could enable and disable recoil on weapons.

The plugin was written in SourcePawn, I tried to fix it to the best of my abilities, which in all honesty ain't a lot, yet unsuccesfully. I hope that someone on this forum knows how to make something similar to this but of course using source.python. It is the last commands I need in order to make my old server able to transition onto CS:GO. :smile:

It would be great if this plugin supports CS:S as well as CS:GO as I intend to share some of the content using this with other users once I have tested it thoroughly and made sure it all works as intended.

I hope something like this hasn't already been posted as I have tried to find it on the forum, but I haven't been able to find it elsewhere, if it is out there and I have overlooked it, then I'm sorry for the inconvenience.

Have a wonderful new year's eve everyone! :smile:


EDIT: Below I've attached the code of the plugin i originally found.
Attachments
wcs_fire.rar
(1.33 KiB) Downloaded 57 times
User avatar
VinciT
Senior Member
Posts: 114
Joined: Thu Dec 18, 2014 2:41 am

Re: Command: Weapon Speed modifier

Postby VinciT » Sun Feb 10, 2019 7:56 pm

Was a bit busy lately so I couldn't make this sooner, hope you still need this.
First things first, the fire rate part of the plugin works great in both CSS and CSGO, but the no spread / no recoil part only works flawlessly in CSS. In CSS, no matter what the player is doing while shooting (jumping, walking, running, flying, swimming, noclipping, and so on), their bullets will go to where their crosshair is. In CSGO however, it will only work if the player is standing still or crouch walking. Maybe someone else knows why this is happening in CSGO?

Anyway, here's the plugin:

Syntax: Select all

# ../wcs_fire/wcs_fire.py

# Source.Python
from commands.typed import TypedServerCommand
from core import GAME_NAME, echo_console
from cvars import ConVar
from engines.server import global_vars
from entities.helpers import index_from_pointer
from events import Event
from events.hooks import PreEvent
from listeners import OnPlayerRunCommand
from mathlib import NULL_VECTOR
from memory import make_object
from players import UserCmd
from players.dictionary import PlayerDictionary
from players.entity import Player
from weapons.dictionary import WeaponDictionary


recoil_cvars_default = {}
recoil_cvars_modified = {
'weapon_accuracy_nospread': 1,
'weapon_air_spread_scale': 0,
'weapon_recoil_cooldown': 0,
'weapon_recoil_decay1_exp': 99999,
'weapon_recoil_decay2_exp': 99999,
'weapon_recoil_decay2_lin': 99999,
'weapon_recoil_decay_coefficient': 0,
'weapon_recoil_scale': 0,
'weapon_recoil_scale_motion_controller': 0,
'weapon_recoil_suppression_factor': 0,
'weapon_recoil_suppression_shots': 500,
'weapon_recoil_variance': 0,
'weapon_recoil_vel_decay': 0,
'weapon_recoil_view_punch_extra': 0
}


if GAME_NAME == 'csgo':
# Go through all the convars in the 'recoil_cvars_modified' dictionary, get
# their default values, and save them in the 'recoil_cvars_default'
# dictionary.
for cvar in recoil_cvars_modified:
recoil_cvars_default[cvar] = ConVar(cvar).default

# CSGO properties for controlling recoil and spread.
weapon_fire_post_properties = (
# This is used to remove the spray pattern / spread.
'localdata.m_Local.m_aimPunchAngleVel',
# And these two are used for reducing the aimpunch / viewpunch after
# firing a weapon.
'localdata.m_Local.m_aimPunchAngle',
'localdata.m_Local.m_viewPunchAngle'
)
else:
# Since CSS doesn't have the same convars as CSGO, clear the dictionary so
# that the players don't get their console spammed with these errors:
# ConVarRef weapon_recoil_scale doesn't point to an existing ConVar
# SetConVar: No such cvar ( weapon_recoil_scale set to 0), skipping
recoil_cvars_modified.clear()

# CSS (and possibly other Source games) properties for controlling recoil.
weapon_fire_post_properties = (
# Both of these are used for reducing the aimpunch / viewpunch after
# firing a weapon.
'localdata.m_Local.m_vecPunchAngle',
'localdata.m_Local.m_vecPunchAngleVel'
)


class PlayerModified(Player):

def __init__(self, index):
super().__init__(index)

self._fire_rate = 1.0
self.fire_rate_changed = False
self.fired_weapon = None
self.no_recoil = False

def disable_recoil(self):
self.no_recoil = True

# Modify the player's convars to reduce their recoil.
for cvar in recoil_cvars_modified:
self.send_convar_value(cvar, recoil_cvars_modified[cvar])

def enable_recoil(self):
self.no_recoil = False

# Restore the player's recoil convars back to normal.
for cvar in recoil_cvars_default:
self.send_convar_value(cvar, recoil_cvars_default[cvar])

def get_fire_rate(self):
return self._fire_rate

def set_fire_rate(self, new_fire_rate):
# If the given value is 0, set the 'fire_rate' back to default.
if new_fire_rate == 0.0:
new_fire_rate = 1.0

# Are we setting the 'fire_rate' back to default?
if new_fire_rate == 1.0:
self.fire_rate_changed = False
else:
self.fire_rate_changed = True

# Get the current time.
cur_time = global_vars.current_time
# In order to avoid the player not being able to fire their weapon
# after their 'fire_rate' has been changed, go through all of their
# weapons and reset the 'm_flNextPrimaryAttack' property.
for index in self.weapon_indexes():
weapon_instances[index].set_network_property_float(
'LocalActiveWeaponData.m_flNextPrimaryAttack', cur_time
)

# Change the 'fire_rate'.
self._fire_rate = new_fire_rate

fire_rate = property(get_fire_rate, set_fire_rate)


player_instances = PlayerDictionary(PlayerModified)
weapon_instances = WeaponDictionary()


@OnPlayerRunCommand
def on_player_run_command(player, user_cmd):
# Is the player dead?
if player.get_datamap_property_bool('pl.deadflag'):
return

# Get the PlayerModified instance.
player = player_instances[player.index]

# Does the player have their recoil enabled?
if not player.no_recoil:
return

# Remove the randomness of the spread. This will also make sure there's no
# spread while the player is moving and shooting.
# NOTE: This doesn't seem to work in CSGO.
user_cmd.random_seed = 0


@PreEvent('weapon_fire')
def weapon_fire_pre(event):
player = player_instances.from_userid(event['userid'])

# Don't go further if both the fire rate and the recoil are unchanged.
if not player.fire_rate_changed and not player.no_recoil:
return

# If either the fire rate or the recoil have been changed, add a new key
# called '_modified' with the value True to the 'weapon_fire' event.
event.set_bool('_modified', True)

# Get the weapon the player is firing.
weapon = weapon_instances.from_inthandle(player.active_weapon_handle)
# Store the weapon instance for later use (in weapon_fire_post()).
player.fired_weapon = weapon

if not player.no_recoil:
return

# Remove the recoil by making the game think this is still the first shot.
player.set_network_property_uchar('cslocaldata.m_iShotsFired', 0)
# Remove the accuracy penalty for jumping / falling.
# NOTE: Again, this doesn't seem to work in CSGO.
weapon.set_network_property_float('m_fAccuracyPenalty', 0.0)


@Event('weapon_fire')
def weapon_fire(event):
# Is this not a '_modified' event?
if not event.get_bool('_modified'):
return

player = player_instances.from_userid(event['userid'])
# The next couple of changes need to happen after the 'weapon_fire' event,
# so we delay them by a single frame.
player.delay(0, weapon_fire_post, (player,))


def weapon_fire_post(player):
if player.no_recoil:
# Try to reduce the recoil and remove the spray pattern.
for prop in weapon_fire_post_properties:
player.set_network_property_vector(prop, NULL_VECTOR)

# Don't go further if the 'fire_rate' hasn't been modified.
if not player.fire_rate_changed:
return

# Get the weapon instance we saved earlier (in weapon_fire_pre()).
weapon = player.fired_weapon
# Get the current time.
cur_time = global_vars.current_time

# Get the next primary attack time.
next_attack = weapon.get_datamap_property_float('m_flNextPrimaryAttack')
# Calculate when the next primary attack should happen.
next_attack = (next_attack - cur_time) * 1.0 / player.fire_rate + cur_time

weapon.set_datamap_property_float('m_flNextPrimaryAttack', next_attack)
player.set_datamap_property_float('m_flNextAttack', cur_time)


# wcs_speedfire <userid> <fire rate>
@TypedServerCommand('wcs_speedfire')
def wcs_speedfire_command(command_info, userid:int, fire_rate:float):
try:
player = player_instances.from_userid(userid)
except ValueError:
echo_console(f'wcs_speedfire: invalid userid {userid}')

player.fire_rate = abs(fire_rate)


# wcs_speedfireoff <userid>
@TypedServerCommand('wcs_speedfireoff')
def wcs_speedfireoff_command(command_info, userid:int):
try:
player = player_instances.from_userid(userid)
except ValueError:
echo_console(f'wcs_speedfireoff: invalid userid {userid}')

player.fire_rate = 1.0


# wcs_rapidfire <userid>
@TypedServerCommand('wcs_rapidfire')
def wcs_rapidfire_command(command_info, userid:int):
try:
player = player_instances.from_userid(userid)
except ValueError:
echo_console(f'wcs_rapidfire: invalid userid {userid}')

if player.no_recoil:
player.enable_recoil()
player.fire_rate = 1.0
else:
player.disable_recoil()
player.fire_rate = 1.3
I've added the same commands that the SM plugin you attached uses, but in case you want finer control, replace them with these (or just add them so you can use both):

Syntax: Select all

# wcs_disable_recoil <userid>
@TypedServerCommand('wcs_disable_recoil')
def wcs_disable_recoil_command(command_info, userid:int):
try:
player = player_instances.from_userid(userid)
except ValueError:
echo_console(f'wcs_disable_recoil: invalid userid {userid}')

player.disable_recoil()


# wcs_enable_recoil <userid>
@TypedServerCommand('wcs_enable_recoil')
def wcs_enable_recoil_command(command_info, userid:int):
try:
player = player_instances.from_userid(userid)
except ValueError:
echo_console(f'wcs_enable_recoil: invalid userid {userid}')

player.enable_recoil()


# wcs_fire_rate <userid> <fire rate>
@TypedServerCommand('wcs_fire_rate')
def wcs_fire_rate_command(command_info, userid:int, fire_rate:float):
try:
player = player_instances.from_userid(userid)
except ValueError:
echo_console(f'wcs_fire_rate: invalid userid {userid}')

player.fire_rate = fire_rate


# wcs_reset_fire_rate <userid>
@TypedServerCommand('wcs_reset_fire_rate')
def wcs_reset_fire_rate_command(command_info, userid:int):
try:
player = player_instances.from_userid(userid)
except ValueError:
echo_console(f'wcs_reset_fire_rate: invalid userid {userid}')

player.fire_rate = 1.0


And if you want to see where your shots are going (server-side), you can use this:

Syntax: Select all

from engines.precache import Model
from entities.entity import Entity
from events import Event
from mathlib import Vector


model_light_glow = Model('sprites/light_glow03.vmt')


@Event('bullet_impact')
def bullet_impact(event):
create_sprite(
position=Vector(event['x'], event['y'], event['z']),
size=0.05,
color_str='0 255 0',
lifetime=5
)


def create_sprite(position, size, color_str, alpha=150, lifetime=0):
sprite = Entity.create('env_sprite')

sprite.model = model_light_glow
sprite.origin = position
sprite.scale = size

sprite.set_key_value_float('GlowProxySize', 0)
sprite.set_key_value_float('framerate', 15)
sprite.set_key_value_bool('disablereceiveshadows', True)
sprite.set_key_value_float('HDRColorScale', 1)
sprite.set_key_value_int('renderamt', alpha)
sprite.set_key_value_int('rendermode', 9)
sprite.set_key_value_string('rendercolor', color_str)
sprite.set_key_value_int('renderfx', 0)
sprite.set_key_value_int('spawnflags', 1)
sprite.spawn()

if lifetime > 0:
sprite.delay(lifetime, sprite.remove)
Last edited by VinciT on Tue Feb 12, 2019 4:50 pm, edited 4 times in total.
User avatar
Ayuto
Project Leader
Posts: 2023
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: Command: Weapon Speed modifier

Postby Ayuto » Sun Feb 10, 2019 10:24 pm

That looks good! I just have a few hints. First, you might want to use the OnPlayerRunCommand listener instead of hooking the function on your own. It should be a little bit faster and handles an exception in TF2. Second, this snippet could be changed as well.

Syntax: Select all

weapon.set_datamap_property_float(
'm_flNextPrimaryAttack', cur_time
)
# Notify the engine of the property change.
# Without this, the server console will get spammed with stuff like
# this: Entity 90 (class 'weapon_ak47') reported ENTITY_CHANGE_NONE
# but 'm_flNextPrimaryAttack' changed.
weapon.edict.state_changed()
You get that message, because m_flNextPrimaryAttack is a networked property. Changing it without notifying results in that message. Instead of notifying on your own, you could simply use set_network_property_float.
User avatar
VinciT
Senior Member
Posts: 114
Joined: Thu Dec 18, 2014 2:41 am

Re: Command: Weapon Speed modifier

Postby VinciT » Sun Feb 10, 2019 11:20 pm

Changing weapon.set_datamap_property_float() to weapon.set_network_property_float() was causing this error when I tried earlier:

Code: Select all

[SP] Caught an Exception:
Traceback (most recent call last):
  File "..\addons\source-python\packages\source-python\commands\typed.py", line 589, in on_command
    result = cls.on_clean_command(info, command_node, args)
  File "..\addons\source-python\packages\source-python\commands\typed.py", line 607, in on_clean_command
    return command_node.callback(command_info, *cleaned_args)
  File "..\addons\source-python\plugins\wcs_fire\wcs_fire.py", line 287, in wcs_fire_rate_command
    player.fire_rate = fire_rate
  File "..\addons\source-python\packages\source-python\entities\_base.py", line 125, in __setattr__
    object.__setattr__(self, attr, value)
  File "..\addons\source-python\plugins\wcs_fire\wcs_fire.py", line 117, in set_fire_rate
    'm_flNextPrimaryAttack', cur_time

ValueError: Unable to find property 'm_flNextPrimaryAttack'.
But now that you mention it again, I took another look at all the properties for the Weapon entity and LocalActiveWeaponData.m_flNextPrimaryAttack seems like the correct one to use. Thanks!

As for the OnPlayerRunCommand listener, since I'm using a modified Player class, would this be the correct way to use it?:

Syntax: Select all

@OnPlayerRunCommand
def on_player_run_command(player, user_cmd):
# Is the player dead?
if player.get_datamap_property_bool('pl.deadflag'):
return

# Get the PlayerModified instance.
player = player_instances[player.index]

# Does the player have their recoil enabled?
if not player.no_recoil:
return

user_cmd.random_seed = 0
Or would it be better to use a set() containing player indexes / userids to check if the player has their recoil disabled or not?
Last edited by VinciT on Mon Feb 11, 2019 3:59 pm, edited 1 time in total.
User avatar
L'In20Cible
Project Leader
Posts: 1167
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: Command: Weapon Speed modifier

Postby L'In20Cible » Mon Feb 11, 2019 12:48 am

VinciT wrote:Or would it be better to use a set() containing player indexes / userids to check if the player has their recoil disabled or not?

I would personally leave it to what you posted over using a different container. Mainly because you will end having to lookup two of them in your weapon_fire_pre (one for the player, then a second for their state) hook and that syncing would be redundant.
User avatar
VinciT
Senior Member
Posts: 114
Joined: Thu Dec 18, 2014 2:41 am

Re: Command: Weapon Speed modifier

Postby VinciT » Mon Feb 11, 2019 5:23 am

Got it. I've replaced the hook with the listener in the post above. Thanks for your input guys!
User avatar
L'In20Cible
Project Leader
Posts: 1167
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: Command: Weapon Speed modifier

Postby L'In20Cible » Mon Feb 11, 2019 3:27 pm

VinciT wrote:Got it. I've replaced the hook with the listener in the post above. Thanks for your input guys!

Noticed a typo there:

Syntax: Select all

if player.get_datamap_property_bool('pl_deadflag'):
User avatar
VinciT
Senior Member
Posts: 114
Joined: Thu Dec 18, 2014 2:41 am

Re: Command: Weapon Speed modifier

Postby VinciT » Mon Feb 11, 2019 3:59 pm

Whoops, don't know how I managed to replace that dot with an underscore, fixed!

Return to “Plugin Requests”

Who is online

Users browsing this forum: No registered users and 3 guests