Page 1 of 1

CSGO : Adding Parachutes (from DZ) into DM

Posted: Sun Jun 14, 2020 4:50 am
by JustGR
As title says, I would like to give all players a parachute for unlimited use in Deathmatch. This is just me wanting to mess with props and understand how they work, for the most part. I could probably get away with creating a 'prop_weapon_upgrade_chute' and having players pick them up, but was wondering if there was a more elegant solution to the problem.

Re: CSGO : Adding Parachutes (from DZ) into DM

Posted: Sun Jun 14, 2020 1:00 pm
by VinciT
I believe this should work:

Syntax: Select all

# ../chute/chute.py

# Source.Python
from engines.server import engine_server
from events import Event
from listeners import OnLevelInit
from players.dictionary import PlayerDictionary


parachute_models = (
'models/props_survival/parachute/chute.mdl',
'models/props_survival/upgrades/parachutepack.mdl',
'models/weapons/v_parachute.mdl'
)

player_instances = PlayerDictionary()


@OnLevelInit
def on_map_loaded(map_name):
"""Precaches parachute models when the map loads to avoid crashing the
server (with the following error: UTIL_SetModel: not precached)."""
for model in parachute_models:
engine_server.precache_model(model, preload=True)


@Event('player_spawn')
def player_spawn(event):
"""Gives the player a parachute when they spawn."""
player = player_instances.from_userid(event['userid'])
player.give_named_item('parachute')
Also a note in case you want to add in more Danger Zone items/weapons to non-DZ gamemodes: always precache the models for said items/weapons, otherwise you'll crash the server.

Re: CSGO : Adding Parachutes (from DZ) into DM

Posted: Sun Jun 14, 2020 9:33 pm
by JustGR
Tried it out. Would it be possible to disengage the parachute (like BOTW's paraglider), or stop ourselves from gliding down to places we aren't supposed to be (death pits for instance)?

Re: CSGO : Adding Parachutes (from DZ) into DM

Posted: Mon Jun 15, 2020 4:46 am
by VinciT
Try this for disengaging/cutting the parachute:

Syntax: Select all

# ../chute/chute.py

# Source.Python
from commands.client import ClientCommand
from engines.server import engine_server
from entities.constants import WORLD_ENTITY_INDEX
from entities.entity import Entity
from entities.helpers import index_from_pointer
from entities.hooks import EntityPreHook, EntityCondition
from events import Event
from listeners import OnLevelInit, OnEntitySpawned, OnEntityDeleted
from players.dictionary import PlayerDictionary
from players.entity import Player


parachute_models = (
'models/props_survival/parachute/chute.mdl',
'models/props_survival/upgrades/parachutepack.mdl',
'models/weapons/v_parachute.mdl'
)

# Dictionary for holding references of parachutes and their users (players).
parachutes = {}


class PlayerC(Player):
"""Extended Player class."""

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

self.cutting_parachute = False
self.has_parachute = False

def cut_parachute(self):
# Is the player currently parachuting?
if self.has_parachute:
self.cutting_parachute = True
self.has_parachute = False


player_instances = PlayerDictionary(PlayerC)


@EntityPreHook(EntityCondition.is_player, 'post_think')
def post_think_pre(stack_data):
player = player_instances[index_from_pointer(stack_data[0])]

# Bots don't need this, so skip them.
if player.is_bot():
return

# Is the player trying to get rid of their parachute?
if player.cutting_parachute:
# Trick the player into thinking they've landed.
player.ground_entity = Entity(WORLD_ENTITY_INDEX).inthandle
player.cutting_parachute = False


@OnLevelInit
def on_map_loaded(map_name):
"""Precaches parachute models when the map loads to avoid crashing the
server (with the following error: UTIL_SetModel: not precached)."""
for model in parachute_models:
engine_server.precache_model(model, preload=True)


@Event('player_spawn')
def player_spawn(event):
"""Gives the player a parachute when they spawn."""
player = player_instances.from_userid(event['userid'])
player.give_named_item('parachute')


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


@OnEntitySpawned
def on_entity_spawned(base_entity):
try:
index = base_entity.index
except ValueError:
return

# The parachute that's attached to the player is actually a 'prop_dynamic'
# entity. Let's check if any of them spawned.
if base_entity.classname == 'prop_dynamic':
entity = Entity(index)
# When an entity spawns, not all properties are setup right away, so we
# delay our property based checks for a single frame.
entity.delay(0, late_check, (entity,))


def late_check(entity):
# Is this a parachute? (models/props_survival/parachute/chute.mdl)
if 'parachute/chute.mdl' in entity.model.path:
# Get the player that the 'prop_dynamic' is tied to.
player = player_instances[entity.parent.index]
player.has_parachute = True
# Make a reference between the parachute and the player. We'll use this
# later when the parachute gets removed.
parachutes[entity.index] = player.index


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

# Check if the entity that's about to be removed is a parachute.
try:
# Notify the player that they've lost their parachute.
player_instances[parachutes.pop(index)].has_parachute = False
except KeyError:
# Nope, not a parachute. Moving on..
pass
The way it's setup - once the player presses their inspect key, if they have an active parachute, they'll get rid of it.
JustGR wrote:..stop ourselves from gliding down to places we aren't supposed to be (death pits for instance)?
Could you clarify this a bit? Do you mean some kind of a push upwards? Like a gust of wind?

Re: CSGO : Adding Parachutes (from DZ) into DM

Posted: Mon Jun 15, 2020 8:31 am
by JustGR
VinciT wrote:
JustGR wrote:..stop ourselves from gliding down to places we aren't supposed to be (death pits for instance)?
Could you clarify this a bit? Do you mean some kind of a push upwards? Like a gust of wind?


Well, "Revali's Gale" would be nice, but would break the CS:GO spirit so hard it's not even funny :tongue:
Was thinking about the case where it would be possible to glide all the way down to death pits. In my eyes, the best fix for this would be a "stamina meter", but I'm open to suggestions.

Re: CSGO : Adding Parachutes (from DZ) into DM

Posted: Tue Jun 16, 2020 12:07 am
by VinciT
Haha, I guess I was thinking about pushing players away from the deathpits. Your way seems more reasonable, here you go:

Syntax: Select all

# ../chute/chute.py

# Source.Python
from commands.client import ClientCommand
from cvars import ConVar
from engines.server import engine_server
from entities.constants import WORLD_ENTITY_INDEX
from entities.entity import Entity
from entities.helpers import index_from_pointer
from entities.hooks import EntityPreHook, EntityCondition
from events import Event
from listeners import OnLevelInit, OnEntitySpawned, OnEntityDeleted
from messages import HintText
from players.dictionary import PlayerDictionary
from players.entity import Player


# Time (in seconds) until the parachute automatically gets cut.
MAX_PARACHUTE_STAMINA = 3.0
# How quickly should the player regain their parachute stamina compared to
# losing it?
STAMINA_REGEN_MULTIPLIER = 1.75
# Should the player be able to re-open their parachute while they have stamina?
REUSABLE_PARACHUTE = True

# Find and store the 'sv_dz_parachute_reuse' convar.
CVAR_PARACHUTE_REUSE = ConVar('sv_dz_parachute_reuse')


MESSAGE_NEW_PARACHUTE = HintText('Parachute ready')
MESSAGE_LIMIT_REACHED = HintText('Out of stamina')


parachute_models = (
'models/props_survival/parachute/chute.mdl',
'models/props_survival/upgrades/parachutepack.mdl',
'models/weapons/v_parachute.mdl'
)

# Dictionary for holding references of parachutes and their users (players).
parachutes = {}


class PlayerC(Player):
"""Extended Player class."""

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

self.cutting_parachute = False
self.has_parachute = False
self.parachute_stamina = MAX_PARACHUTE_STAMINA
self.stamina_think = self.repeat(self._stamina_think)

def cut_parachute(self):
# Is the player currently parachuting?
if self.has_parachute:
self.cutting_parachute = True
self.has_parachute = False

def on_started_parachuting(self):
"""Called when the player opens their parachute."""
self.has_parachute = True
# Start calling the `_stamina_think()` function once every 250ms.
self.stamina_think.start(0.25)

def on_stopped_parachuting(self):
"""Called when the player loses their parachute."""
self.has_parachute = False

if REUSABLE_PARACHUTE and self.parachute_stamina > 0:
# Give the player a new parachute after a single frame delay.
# NOTE: Without the delay, this wouldn't work.
self.delay(0, self.give_parachute)

def give_parachute(self):
"""Gives the player a parachute."""
self.give_named_item('parachute')
MESSAGE_NEW_PARACHUTE.send(self.index)

def _stamina_think(self):
# Is the player currently parachuting?
if self.has_parachute:
# Has the player used up all of their parachute stamina?
if self.parachute_stamina <= 0:
self.parachute_stamina = 0
self.cut_parachute()
MESSAGE_LIMIT_REACHED.send(self.index)
return

# Reduce their stamina.
self.parachute_stamina -= 0.25
else:
# Has the player regained all of their parachute stamina?
if self.parachute_stamina >= MAX_PARACHUTE_STAMINA:
# Cap their stamina.
self.parachute_stamina = MAX_PARACHUTE_STAMINA
# Stop the loop.
self.stamina_think.stop()
self.give_parachute()
return

# Is the player on the ground?
if self.ground_entity > 0:
# Give them back some stamina.
self.parachute_stamina += 0.25 * STAMINA_REGEN_MULTIPLIER


player_instances = PlayerDictionary(PlayerC)


@EntityPreHook(EntityCondition.is_player, 'post_think')
def post_think_pre(stack_data):
player = player_instances[index_from_pointer(stack_data[0])]

# Bots don't need this, so skip them.
if player.is_bot():
return

# Is the player trying to get rid of their parachute?
if player.cutting_parachute:
# Trick the player into thinking they've landed.
player.ground_entity = Entity(WORLD_ENTITY_INDEX).inthandle
player.cutting_parachute = False


@OnLevelInit
def on_map_loaded(map_name):
"""Precaches parachute models when the map loads to avoid crashing the
server (with the following error: UTIL_SetModel: not precached)."""
for model in parachute_models:
engine_server.precache_model(model, preload=True)

# Don't notify the players when we change the convar.
CVAR_PARACHUTE_REUSE.remove_public()
# Change 'sv_dz_parachute_reuse' to 0.
CVAR_PARACHUTE_REUSE.set_int(0)


@Event('player_spawn')
def player_spawn(event):
"""Gives the player a parachute when they spawn."""
player_instances.from_userid(event['userid']).give_parachute()


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


@OnEntitySpawned
def on_entity_spawned(base_entity):
try:
index = base_entity.index
except ValueError:
return

# The parachute that's attached to the player is actually a 'prop_dynamic'
# entity. Let's check if any of them spawned.
if base_entity.classname == 'prop_dynamic':
entity = Entity(index)
# When an entity spawns, not all properties are setup right away, so we
# delay our property based checks for a single frame.
entity.delay(0, late_check, (entity,))


def late_check(entity):
# Is this a parachute? (models/props_survival/parachute/chute.mdl)
if 'parachute/chute.mdl' in entity.model.path:
# Get the player that the 'prop_dynamic' is tied to.
player = player_instances[entity.parent.index]
player.on_started_parachuting()
# Make a reference between the parachute and the player. We'll use this
# later when the parachute gets removed.
parachutes[entity.index] = player.index


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

# Check if the entity that's about to be removed is a parachute.
try:
# Notify the player that they've lost their parachute.
player_instances[parachutes.pop(index)].on_stopped_parachuting()
except KeyError:
# Nope, not a parachute. Moving on..
pass