Coding a "Smokebomb"-like ability

Please post any questions about developing your plugin here. Please use the search function before posting!
JustGR
Junior Member
Posts: 20
Joined: Tue Apr 28, 2020 12:39 pm

Coding a "Smokebomb"-like ability

Postby JustGR » Wed Jun 10, 2020 2:56 pm

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?
User avatar
Kami
Global Moderator
Posts: 263
Joined: Wed Aug 15, 2012 1:24 am
Location: Germany

Re: Coding a "Smokebomb"-like ability

Postby Kami » Wed Jun 10, 2020 6:56 pm

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.
JustGR
Junior Member
Posts: 20
Joined: Tue Apr 28, 2020 12:39 pm

Re: Coding a "Smokebomb"-like ability

Postby JustGR » Wed Jun 10, 2020 7:19 pm

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.
User avatar
Kami
Global Moderator
Posts: 263
Joined: Wed Aug 15, 2012 1:24 am
Location: Germany

Re: Coding a "Smokebomb"-like ability

Postby Kami » Wed Jun 10, 2020 7:32 pm

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
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: Coding a "Smokebomb"-like ability

Postby VinciT » Thu Jun 11, 2020 3:03 am

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.
ImageImageImageImageImage
JustGR
Junior Member
Posts: 20
Joined: Tue Apr 28, 2020 12:39 pm

Re: Coding a "Smokebomb"-like ability

Postby JustGR » Thu Jun 11, 2020 3:42 am

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.
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: Coding a "Smokebomb"-like ability

Postby VinciT » Thu Jun 11, 2020 5:22 am

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
ImageImageImageImageImage
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: Coding a "Smokebomb"-like ability

Postby L'In20Cible » Thu Jun 11, 2020 12:08 pm

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.
JustGR
Junior Member
Posts: 20
Joined: Tue Apr 28, 2020 12:39 pm

Re: Coding a "Smokebomb"-like ability

Postby JustGR » Fri Jun 12, 2020 12:25 am

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)?
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: Coding a "Smokebomb"-like ability

Postby VinciT » Fri Jun 12, 2020 5:03 am

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.
ImageImageImageImageImage
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: Coding a "Smokebomb"-like ability

Postby L'In20Cible » Fri Jun 12, 2020 10:50 am

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?
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: Coding a "Smokebomb"-like ability

Postby VinciT » Fri Jun 12, 2020 3:54 pm

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:.
ImageImageImageImageImage
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: Coding a "Smokebomb"-like ability

Postby L'In20Cible » Fri Jun 19, 2020 6:26 am

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.

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 16 guests