Page 1 of 1

Coding a "Smokebomb"-like ability

Posted: Wed Jun 10, 2020 2:56 pm
by JustGR
This is clearly inspired from Assassin's Creed MP and ninjas in general, and is intended for a bunch of friends on my own server. I'd like to give players an ability that allows the use of a smoke grenade right where they stand (immediate detonation, allows them to retreat). Does anyone here think this would break game balance? If not, how can this be implemented?

Re: Coding a "Smokebomb"-like ability

Posted: Wed Jun 10, 2020 6:56 pm
by Kami
Hey, that sounds like a cool idea. It could propably be expanded with other ninja style abilities like backstabbing or walljumps.

For the smokes you can try this:

Syntax: Select all

from players.entity import Player
from entities.entity import Entity
from commands.typed import TypedSayCommand, TypedClientCommand


@TypedSayCommand('!smoke')
@TypedClientCommand('smoke')
def typed_say_smoke(command):
player = Player(command.index)
smoke = Entity.create('smokegrenade_projectile')
smoke.spawn()
smoke.teleport(player.origin)
smoke.detonate()

#Removing the smokegrenade to avoid getting stuck
smoke.remove()


You can simply use "bind <anykey> say !smoke" or "bind <anykey> smoke" and then ingame press the key you bound the command to.

Re: Coding a "Smokebomb"-like ability

Posted: Wed Jun 10, 2020 7:19 pm
by JustGR
On it. Is it possible to override existing binds like Inspect instead? That way I don't have to have all clients run the bind command. Also need to figure out if I can set a limit on the ability, but that's for later.

Re: Coding a "Smokebomb"-like ability

Posted: Wed Jun 10, 2020 7:32 pm
by Kami
I think you could check for any keyboard key beeing pressed. I will try and figure that out later!

Edit: This is what I came up with. No need to bind a command to a key, you can just press E and deploy your smokebomb. It has a cooldown of 5 seconds which you can change in the code.

Syntax: Select all

from players.entity import Player
from entities.entity import Entity
from players.constants import PlayerButtons
from listeners import OnPlayerRunCommand, OnClientActive
from filters.players import PlayerIter
from listeners.tick import Delay
from messages import HintText
from entities.constants import SolidType
from players.helpers import userid_from_index

smoke_button = PlayerButtons.USE
smoke_delay = 5.0
can_smoke = {}

for player in PlayerIter():
can_smoke[player.userid] = 1

@OnClientActive
def _on_client_active(index):
player = Player(index)
can_smoke[player.userid] = 1


@OnPlayerRunCommand
def _on_player_run_command(player, usercmd):
if (usercmd.buttons & smoke_button and not player.dead):
if can_smoke[player.userid] == 1:
smoke = Entity.create('smokegrenade_projectile')
smoke.spawn()
smoke.solid_type = SolidType.NONE
smoke.teleport(player.origin)
smoke.detonate()
smoke.remove()

can_smoke[player.userid] = 0
start_countdown(player.index, smoke_delay)


def start_countdown(index, time):
if (IsValidIndex(index)):
if time > 0:
HintText("Smokebomb is ready in\n %.1f seconds" % (time)).send(index)
time -= 0.1
Delay(0.1, start_countdown,(index, time))
else:
HintText("Smokebomb is ready!").send(index)
can_smoke[Player(index).userid] = 1
else:
return


def IsValidIndex(index):
try:
userid_from_index(index)
except ValueError:
return False
return True

Re: Coding a "Smokebomb"-like ability

Posted: Thu Jun 11, 2020 3:03 am
by VinciT
If you want to use the inspect key (CSGO only), you need to hook the +lookatweapon client command.

Syntax: Select all

from commands.client import ClientCommand

@ClientCommand('+lookatweapon')
def player_inspect(command, index):
# Do stuff here.
And in case you want to block the inspect animation you can import CommandReturn and return CommandReturn.BLOCK at the end of the function.

Re: Coding a "Smokebomb"-like ability

Posted: Thu Jun 11, 2020 3:42 am
by JustGR
OK, I got to test out the first script you gave me. Unfortunately, it ended up crashing the server every time, with a Segmentation fault. I can only assume something is going wrong with the Entity created, and am not entirely sure how I can go about debugging this. Once I can spawn the smokebomb correctly, I'll go after binding and setting cooldowns on it.

Re: Coding a "Smokebomb"-like ability

Posted: Thu Jun 11, 2020 5:22 am
by VinciT
Try this:

Syntax: Select all

# ../smoke_ability/smoke_ability.py

# Python
import time

# Source.Python
from commands.client import ClientCommand
from engines.sound import Sound
from entities.entity import Entity
from events import Event
from messages import HintText
from players.dictionary import PlayerDictionary
from players.entity import Player
from stringtables import string_tables


COOLDOWN = 5
MAX_USES = 2


SOUND_ERROR = Sound('ui/weapon_cant_buy.wav')
SOUND_ABILITY = Sound(
sample='weapons/smokegrenade/sg_explode.wav',
volume=0.6,
# How quickly does the sound fade based on distance?
attenuation=2.4
)


MESSAGE_COOLDOWN = HintText('Your smoke ability is on cooldown!')
MESSAGE_USED_UP = HintText('Out of ability charges!')


class PlayerSA(Player):
"""Modified Player class."""

def __init__(self, index, caching=True):
super().__init__(index, caching)
self.last_ability_use = 0
self.used_abilities = 0

def use_ability(self):
# Create the smoke effect without the screen effect (gray screen).
particle = create_smoke_particle(self.origin)
# Adjust the sound to emit from the particle's origin and play it.
SOUND_ABILITY.index = particle.index
SOUND_ABILITY.play()
# Save the last time the ability was used.
self.last_ability_use = time.time()
# Increase the ability counter.
self.used_abilities += 1


player_instances = PlayerDictionary(PlayerSA)


@ClientCommand('+lookatweapon')
def player_inspect(command, index):
player = player_instances[index]

# Has the player used up all of their ability charges?
if player.used_abilities >= MAX_USES:
MESSAGE_USED_UP.send(index)
SOUND_ERROR.play(index)
return

# Is the player's ability still on cooldown?
if time.time() - player.last_ability_use < COOLDOWN:
MESSAGE_COOLDOWN.send(index)
SOUND_ERROR.play(index)
return

player.use_ability()


def create_smoke_particle(origin):
"""Creates a smoke particle effect at the given origin."""
particle = Entity.create('info_particle_system')
particle.effect_name = 'explosion_smokegrenade'
particle.origin = origin

particle.effect_index = string_tables.ParticleEffectNames.add_string(
'explosion_smokegrenade')
particle.start()
# Remove the 'info_particle_system' after 20 seconds - the duration of the
# smoke grenade effect. NOTE: The duration is hardcoded in the particle
# effect itself, changing it here will have no effect.
particle.delay(20, particle.remove)
return particle


@Event('player_spawn')
def player_spawn(event):
# Reset the ability counter when the player spawns.
player_instances.from_userid(event['userid']).used_abilities = 0

Re: Coding a "Smokebomb"-like ability

Posted: Thu Jun 11, 2020 12:08 pm
by L'In20Cible
JustGR wrote:OK, I got to test out the first script you gave me. Unfortunately, it ended up crashing the server every time, with a Segmentation fault. I can only assume something is going wrong with the Entity created, and am not entirely sure how I can go about debugging this. Once I can spawn the smokebomb correctly, I'll go after binding and setting cooldowns on it.

My first guess would be the detonate signature being outdated for your platform.

Re: Coding a "Smokebomb"-like ability

Posted: Fri Jun 12, 2020 12:25 am
by JustGR
VinciT wrote:Try this:
<snip>


Adapted this to my script and it worked like a charm! For now I have it set up as a TypedSayCommand. Will get back to you after testing further.

EDIT: Alright, tested it out in a couple of matches today. No issues. Only confused about the smoke duration. Is there really no way to adjust it (make it faster than 15 seconds)?

Re: Coding a "Smokebomb"-like ability

Posted: Fri Jun 12, 2020 5:03 am
by VinciT
JustGR wrote:EDIT: Alright, tested it out in a couple of matches today. No issues. Only confused about the smoke duration. Is there really no way to adjust it (make it faster than 15 seconds)?
The particle effect used for the smoke grenade has its duration hard-coded. Removing the particle system before that time is up has no effect. You could try using an env_smokestack to create the effect instead.

Re: Coding a "Smokebomb"-like ability

Posted: Fri Jun 12, 2020 10:50 am
by L'In20Cible
VinciT wrote:
JustGR wrote:EDIT: Alright, tested it out in a couple of matches today. No issues. Only confused about the smoke duration. Is there really no way to adjust it (make it faster than 15 seconds)?
Removing the particle system before that time is up has no effect.

Tried to .stop() it first?

Re: Coding a "Smokebomb"-like ability

Posted: Fri Jun 12, 2020 3:54 pm
by VinciT
L'In20Cible wrote:Tried to .stop() it first?
Yep. Also tried to teleport it away before removing it. I think all the particles are spawned right away which is why no matter what we do to the particle system, the particles remain. But I did figure out a way to set the duration of the effect:

Syntax: Select all

# ../smoke_ability/smoke_ability.py

# Python
import time

# Source.Python
from commands.client import ClientCommand
from engines.sound import Sound
from entities.entity import BaseEntity, Entity
from entities.helpers import index_from_pointer, edict_from_index
from entities.hooks import EntityPreHook, EntityCondition
from events import Event
from listeners import OnEntityDeleted
from messages import HintText
from players.dictionary import PlayerDictionary
from players.entity import Player
from stringtables import string_tables


COOLDOWN = 5
MAX_USES = 2
DURATION = 5


SOUND_ERROR = Sound('ui/weapon_cant_buy.wav')
SOUND_ABILITY = Sound(
sample='weapons/smokegrenade/sg_explode.wav',
volume=0.6,
# How quickly does the sound fade based on distance?
attenuation=2.4
)


MESSAGE_COOLDOWN = HintText('Your smoke ability is on cooldown!')
MESSAGE_USED_UP = HintText('Out of ability charges!')

# Edict flag used for making sure an entity gets transmitted to all clients
# no matter what. If an entity has this flag, attempting to hide it in the
# SetTransmit hook will not work.
FL_EDICT_ALWAYS = 1<<3
hidden_particles = set()
is_particle_system = EntityCondition.equals_entity_classname(
'info_particle_system')


class PlayerSA(Player):
"""Modified Player class."""

def __init__(self, index, caching=True):
super().__init__(index, caching)
self.last_ability_use = 0
self.used_abilities = 0

def use_ability(self):
# Create the smoke effect without the screen effect (gray screen).
SmokeEffect.create(self.origin, DURATION)
# Save the last time the ability was used.
self.last_ability_use = time.time()
# Increase the ability counter.
self.used_abilities += 1


player_instances = PlayerDictionary(PlayerSA)


@ClientCommand('+lookatweapon')
def player_inspect(command, index):
player = player_instances[index]

# Has the player used up all of their ability charges?
if player.used_abilities >= MAX_USES:
MESSAGE_USED_UP.send(index)
SOUND_ERROR.play(index)
return

# Is the player's ability still on cooldown?
if time.time() - player.last_ability_use < COOLDOWN:
MESSAGE_COOLDOWN.send(index)
SOUND_ERROR.play(index)
return

player.use_ability()


class SmokeEffect(Entity):
"""Basic class for creating and manipulating the smoke grenade effect."""
caching = True

@classmethod
def create(cls, origin, duration):
"""Creates a particle system that the smoke grenade uses at the given
origin.

Args:
origin (Vector): Spawn position of the effect.
duration (float): Duration (in seconds) until the effect is hidden.
"""
particle = cls(BaseEntity.create('info_particle_system').index)
particle.effect_name = 'explosion_smokegrenade'
particle.origin = origin
particle.effect_index = string_tables.ParticleEffectNames.add_string(
'explosion_smokegrenade')
particle.start()
# Hide the particle system after the given 'duration' to fake the
# duration of the effect.
particle.delay(duration, particle.hide)
# Remove the 'info_particle_system' after 20 seconds - the duration of
# the smoke grenade effect. This is just to keep things tidy.
particle.delay(20, particle.remove)

# Adjust the sound to emit from the particle's origin and play it.
SOUND_ABILITY.index = particle.index
SOUND_ABILITY.play()
return particle

def hide(self):
# Is this entity supposed to skip the SetTransmit hook?
if self.edict.state_flags & FL_EDICT_ALWAYS:
# Strip the FL_EDICT_ALWAYS flag to make the entity go through the
# hook at least once.
self.edict.state_flags = self.edict.state_flags ^ FL_EDICT_ALWAYS

hidden_particles.add(self.index)


@EntityPreHook(is_particle_system, 'set_transmit')
def set_transmit_pre(stack_data):
index = index_from_pointer(stack_data[0])

# Is this particle system supposed to be hidden?
if index in hidden_particles:
edict = edict_from_index(index)
# Check if the FL_EDICT_ALWAYS flag has been reapplied.
if edict.state_flags & FL_EDICT_ALWAYS:
# If so, strip it again.
edict.state_flags = edict.state_flags ^ FL_EDICT_ALWAYS

# Hide it.
return False


@OnEntityDeleted
def on_entity_deleted(base_entity):
try:
index = base_entity.index
except ValueError:
return

try:
hidden_particles.remove(index)
except KeyError:
pass


@Event('player_spawn')
def player_spawn(event):
# Reset the ability counter when the player spawns.
player_instances.from_userid(event['userid']).used_abilities = 0
Very crude way of doing it, but it works. I just wish we had that C++ SetTransmit implementation, for both speed and ease of use :tongue:.

Re: Coding a "Smokebomb"-like ability

Posted: Fri Jun 19, 2020 6:26 am
by L'In20Cible
VinciT wrote:I just wish we had that C++ SetTransmit implementation, for both speed and ease of use :tongue:.

No progress have been made, at least on my side I haven't looked deeper into that than brain storming about it. Been rather busy with other stuff lately so not sure when I personally will get a chance to look into that.