EasyPlayer

Custom Packages that plugins can require for common usages.
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

EasyPlayer

Postby Mahi » Tue Jul 07, 2015 12:05 pm

EasyPlayer is an idea of a player class that automates all the player effects without having to worry about other people's plugins interfering with your plugin.
A good example of this are move types: Your plugin gives noclip to someone for 10 seconds (using player.move_type = MoveType.NOCLIP), but 1 second after gaining the noclip, someone else's plugin freezes the player (using player.move_type = MoveType.NONE), and thus removes your beautiful noclip!
What a horrible plugin you've created, only 1 second noclips :(

This was a huge issue when working on my Hero-Wars project, and EasyPlayer is my attempt to fix it. This package is free to use for everyone, as it would most likely be useful for many other plugins too.

EasyPlayer currently implements the following player effects:
  • Noclip
  • Fly
  • Freeze (movement)
  • Paralyze (movement and aim)
  • Burn
  • Godmode
It also implements a restriction system and allows objects to be instantiated directly from an userid using the EasyPlayer.from_userid() classmethod. There's also the @PlayerEffect decorator, if you ever feel like subclassing EasyPlayer and adding your own player effects.
You can read more about it on GitHub, or download the latest version from the releases page.
Last edited by Mahi on Sat Dec 18, 2021 4:43 pm, edited 1 time in total.
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Sun Jul 12, 2015 5:51 pm

Update 12.7.2015:
You no longer use player.freeze(0) to unfreeze a player, but instead you capture a _PlayerEffect instance and call .cancel() method on it. This way you can even cancel effects with a fixed duration. (Thanks to Ayuto for the suggestion :) )

Here's an example of the new method; burn/unburn a player whenever he jumps.

Syntax: Select all

from easyplayer import EasyPlayer
from events import Event

burn = None

@Event
def player_jump(game_event):
global burn
player = EasyPlayer.from_userid(game_event.get_int('userid'))
if burn is None:
burn = player.burn()
else:
burn.cancel()
burn = None
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Postby BackRaw » Sun Jul 12, 2015 10:13 pm

One suggestion: avoid checking for None and handle the effects directly within EasyPlayer:

Syntax: Select all

from easyplayer import EasyPlayer
from events import Event


@Event
def player_jump(game_event):
player = EasyPlayer.from_userid(game_event.get_int('userid'))

burn = player.get_effect('burn')

if burn.is_running():
burn.cancel()

else:
player.start_effect('burn', <duration>)
My Github repositories:

Source.Python: https://github.com/backraw
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Sun Jul 12, 2015 11:17 pm

Thanks for the suggestion! I'm not sure what you mean by this? How's it an improvement over the current style? Avoid checking for None where, I don't think we need to check for None at the moment?

Also, I don't see how your example supports multiple effects of the same type.

EDIT: Ahh, you were referring to the example code I showed :) That's just an example, and 99.9% of the time you won't be having a global variable like that. It might not have been the best example out there.
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Postby BackRaw » Tue Jul 14, 2015 12:27 am

Yeah sorry, but you're going good :D
My Github repositories:

Source.Python: https://github.com/backraw
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Sat Aug 01, 2015 8:04 am

Updated: https://github.com/MarkusMeskanen/EasyPlayer/commit/e593e0ac0589fd8bdfe1fc6174d1bd26c0d92c88

Instead of using tick_delays.delay(), I've monkey-patched the events.listener._EventListener.fire_game_event method to call EasyPlayer's events after all other events have been called.
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Tue Oct 27, 2015 2:18 pm

Version 1.0 is out, should be stable with the latest SP: https://github.com/MarkusMeskanen/EasyPlayer/releases/tag/v1.0
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Sun Nov 01, 2015 8:09 pm

Again new release is out, version 1.1: https://github.com/MarkusMeskanen/EasyPlayer/releases/tag/v1.1

I've switched to useing userIDs for caching instead of the entities' indexes, since userIDs cannot be reused like indexes can. Thanks to Ayuto for pointing this out.
Also, EasyPlayer's method shift_property() now passes the duration properly to the tick_delays.delay() function call.

And this thread should probably be in the Custom Packages forum, which didn't exist back when I made this thread. If so, any admin mind moving it? :)
User avatar
Ayuto
Project Leader
Posts: 2193
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Sun Nov 01, 2015 9:57 pm

Mahi wrote:And this thread should probably be in the Custom Packages forum, which didn't exist back when I made this thread. If so, any admin mind moving it? :)
Done! :)
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Re: EasyPlayer

Postby Mahi » Mon Dec 13, 2021 9:45 pm

Haven't been updating the thread for a while as I don't think many other people are using the package, but EasyPlayer has since been updated to v2.1.1 and has a lot of new stuff, including easier event management:

Syntax: Select all

player_dict = PlayerDictionary(easyplayer.Player)  # Can be your own custom Player class
events = easyplayer.EventManager(player_dict)

@events.on('player_victim')
def on_player_victim(player, attacker, dmg_health, **eargs): # Pack unused arguments into "eargs"
attacker.take_damage(dmg_health // 3, attacker_index=player.index)
attacker.freeze(duration=2)

Download here: https://github.com/Mahi/EasyPlayer/releases/tag/v2.1.1
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: EasyPlayer

Postby L'In20Cible » Mon Dec 20, 2021 5:22 am

Nice! Here are some stuff I noticed after a quick hovering:
  • Your Player class won't use caching unless you explicitly tell it to do so:

    Syntax: Select all

    class Player(SourcePythonPlayer):
    """A player entity class with additional functionality.
    Takes full advantage of the `Effect` class, and implements
    some other missing functionality from Source.Python's player class.
    """
    caching = True # Uses caching

    ...

  • Your Player.cs_team and Player.tf_team are the equivalence of Player.team_name.
  • Your Player.chest_location, Player.stomach_location and Player.hip_location properties will yield invalid results if the players are crouched, or if their scaling is modified. That's probably information you can retrieve through Player.model_header.
  • You should rather use self.delay into your Player.shift_property method so that the delays are cancelled if the players disconnect. Currently, you will assign a freed pointer resulting into undefined behaviours.
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Re: EasyPlayer

Postby Mahi » Mon Dec 20, 2021 7:13 am

Thanks for the feedback, I'll be fixing the issues shortly!

Why should I use caching though? Isn't that what PlayerDictionary is for?
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: EasyPlayer

Postby L'In20Cible » Mon Dec 20, 2021 9:15 am

Mahi wrote:Why should I use caching though? Isn't that what PlayerDictionary is for?

To an extent, yes. Assuming scripters using your library only query instances from a dictionary. Telling your class to uses caching makes sure all the following is True:

Syntax: Select all

from players.entity import Player
from players.dictionary import PlayerDictionary
from memory import make_object

class MyPlayer(Player):
caching = True

players = PlayerDictionary(MyPlayer)
player = players[1]

print(player is MyPlayer(1))
print(player is MyPlayer.from_userid(player.userid))
print(player is MyPlayer.from_inthandle(player.inthandle))
print(player is make_object(MyPlayer, player))
print(player is players[player.index])


Whereas not doing so, they will all be False except for the last one. For the most part, creating new instances in itself is not really heavy, but makes a huge difference for everything that is internally cached into these cached instances and reused. For example, run the following code:

Syntax: Select all

from time import time
from players.entity import Player
from players.dictionary import PlayerDictionary
from memory import get_size, make_object

class MyPlayer(Player):
caching = True

def timeit():
t = time()
for i in range(1000000):
player = MyPlayer(1)
player.index
player.server_class
player.datamap
player.factory
player.edict
player.pointer
player.inthandle
get_size(player)
player.net_info
player.raw_steamid
player.playerinfo
player.userid
player.steamid
player.client
player.base_client
player.uniqueid
player.address
player.server_classes
player.dynamic_attributes
player.properties
player.inputs
player.outputs
print(time() - t)

timeit()
MyPlayer._caching = False
timeit()
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Re: EasyPlayer

Postby Mahi » Tue Dec 21, 2021 11:41 am

I've chosen not to implement caching as I believe one should be using PlayerDictionary anyways, and subclassing to enable caching should be trivial.

I've also attempted to use model_header for the hitboxes, but only managed to do so for the pelvis bone (index 0), as all other bones seem to have nonsense positions: Any suggestions?

And finally, could you further clarify this matter:
L'In20Cible wrote:You should rather use self.delay into your Player.shift_property method so that the delays are cancelled if the players disconnect.

Are you suggesting that the delays are cancelled automatically as long as I store them on the player entityd, or should I save them and then manually cancel them upon player disconnecting?

Thank you for all the suggestions and help!
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: EasyPlayer

Postby L'In20Cible » Tue Dec 21, 2021 1:50 pm

Mahi wrote:I've chosen not to implement caching as I believe one should be using PlayerDictionary anyways, and subclassing to enable caching should be trivial.
Technically they could also pass caching=True on construction to get the instance from the cache. Not declaring it in the class (or its __init__ signature) only defaults it to False.

Mahi wrote:I've also attempted to use model_header for the hitboxes, but only managed to do so for the pelvis bone (index 0), as all other bones seem to have nonsense positions: Any suggestions?

You would have to transform and pose it to the current player's origin/sequence. Easiest way is probably to dynamically call CBaseAnimating::GetBoneTransform.

Mahi wrote:Are you suggesting that the delays are cancelled automatically as long as I store them on the player entityd, or should I save them and then manually cancel them upon player disconnecting?
Entity.delay will automatically cancels pending delays for entities that are removed (or player that disconnect) so replacing Delay to self.delay in your method would do that.
hawrebelo
Junior Member
Posts: 1
Joined: Wed Feb 23, 2022 8:41 am

Re: EasyPlayer

Postby hawrebelo » Wed Feb 23, 2022 8:43 am

the effects are amazing

Return to “Custom Packages”

Who is online

Users browsing this forum: No registered users and 8 guests