Way to reverse the spawn?

Please post any questions about developing your plugin here. Please use the search function before posting!
battleweaver
Member
Posts: 40
Joined: Mon Oct 02, 2017 7:57 am
Location: Moscow
Contact:

Way to reverse the spawn?

Postby battleweaver » Fri Dec 01, 2017 1:21 pm

Hi, guys.
Situation:
CS:GO
I have a data structure {uniqueID : weapon_entity}, which I recieve from, say, outer space.
During the match i want to organize some sort of inventory. The prototype instance of my inventory is dict (player_id : [weapon entities he has ]).
I add weapon entites to inventory by touching them.

Some code.
Ripped from "game flow description" script

Syntax: Select all

backpack = Backpack()

#this part is about creating the set of dictionary keys
@Event('round_start')
def on_round_start(game_event):
#weapon_spawn() #created weapon entities on map
for player in PlayerIter():
backpack.add_player(index_from_userid(player.userid))

@Event('player_connect_full')
def on_player_connected(game_event):
playerID = index_from_userid(game_event['userid'])
backpack.add_player(playerID)

#the sweetest part: inventory dictionary values
@EntityPreHook(EntityCondition.equals_entity_classname(*weapon_index.keys()), 'start_touch')
def pre_start_touch(stack):
weapon = make_object(Weapon, stack[0])
#print(f'----------------------------------')
#print (f'{weapon.classname} is touched')
if weapon.classname in weapon_index.keys():
other = make_object(Entity, stack[1])
if other.is_player():
player = make_object(Player, stack[1])
print(f'Weapon {weapon.classname} was touched by {other.classname}\n')
backpack.add_item(player, weapon)
#weapon.remove() #this thing crushes server

Backpack()

Syntax: Select all

from messages import SayText2
from players._base import Player
from players.helpers import index_from_userid
from steam import SteamID

class Backpack:
def __init__(self):
self.primary_weap_dict = dict()

def add_player(self, playerID):
self.primary_weap_dict[SteamID.parse(Player(playerID).steamid).to_uint64()] = list()
print('Player added')

def add_item(self, player, item):
player_id = SteamID.parse(player.steamid).to_uint64()
player_index = index_from_userid(player.userid)
self.primary_weap_dict[player_id].append(item)
print('Item added')
SayText2(f'{player_id} has {list (map(lambda weapon: weapon.classname),self.primary_weap_dict[player_id])}').send(player_index)


To the point: when I want to pick up a weapon, I should remove it from the map. If I destroy the weapon_entity entirely, how to signal to my first data structure that something happened to the weapon_entity? OR Is there a way to reverse the spawn() method of an entity?
User avatar
Ayuto
Project Leader
Posts: 1819
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: Way to reverse the spawn?

Postby Ayuto » Fri Dec 01, 2017 3:36 pm

I guess your weapon.remove() line is crashing because you don't stop the original function from executing. Try "return False" after that line.

To find out whether an entity is deleted, you could use the OnEntityDeleted listener.
User avatar
iPlayer
Developer
Posts: 590
Joined: Sat Nov 14, 2015 8:37 am
Location: Moscow
Contact:

Re: Way to reverse the spawn?

Postby iPlayer » Fri Dec 01, 2017 3:46 pm

I'll start with some common things and get to your goal in the end.

  1. Use entities.dictionary.EntityDictionary class (or its derivatives like players.dictionary.PlayerDictionary or weapons.dictionary.WeaponDictionary) whenever you need a container that is used to store Entity (or Player/Weapon) instances.

    It provides you a fast access to its values by an entity index. The value, whatever it is, is automatically removed when that index becomes invalid.
    So there's no way you could accidentally access an expired Entity instance and crash the server.

    This class accepts a factory as its first argument to a constructor and also you can subclass it and override its on_automatically_removed method. These two simple facts make it the preferred way to store entities whatever reason you want to store them for. Default EntityDictionary factory creates Entity instances.

    I can see that you can at least make use of PlayerDictionary to store players.
  2. Syntax: Select all

    for player in PlayerIter():
    backpack.add_player(index_from_userid(player.userid))

    First of all, PlayerIter() yields Player instances which inherit all Entity stuff, including .index attribute. So the second line can be re-written like this:

    Syntax: Select all

    backpack.add_player(player.index)

    That will save you converting index to userid (Source.Python does it internally when you access .userid attribute) and converting this userid back to the index - the latter conversion is actually performed by getting an edict from userid and then obtaining the index from the edict.

    Secondly, you need a Player instance inside of that add_player method. Because you instantiate it:
    SteamID.parse(Player(playerID).steamid).to_uint64()
    So you'd better pass the whole instance to the method, not expect the method to create it on its own.

    These two points combined, your player iteration will then look like this:

    Syntax: Select all

    for player in PlayerIter():
    backpack.add_player(player)

  3. If I saw something called playerID, I'd be deciding between UserID and some sort of SteamID. But I would not expect it to be an entity index which is basically what it is. Just go with "index" no matter what kind of entity this index belongs to. You can always resolve conflicts if you work with multiple indexes in local context, but this is kind of problem to be solved only when it occurs.
  4. Syntax: Select all

    @EntityPreHook(EntityCondition.equals_entity_classname(*weapon_index.keys()), 'start_touch')

    Be aware that this will set a hook on a start_touch function of the first entity that passes the equals_entity_classname filter.

    Imagine this scenario in C++ terms:
    BaseClass with StartTouch method
    WeaponClass1(BaseClass) that overrides the parent StartTouch method
    WeaponClass2(BaseClass) that doesn't override that method

    WeaponClass1 and WeaponClass2 are the actual weapon classes. Let's say those weapons have classnames, like weapon_class1 and weapon_class2.

    And you try to set a hook on StartTouch method, but you come up with a filter that weapons of both classes will pass:

    Syntax: Select all

    @EntityPreHook(EntityCondition.equals_entity_classname("weapon_class1", "weapon_class2"), 'start_touch')

    It seems reasonable at first - because you want to hook the function on all weapons.

    But the fact is, you get some undetermined behavior. The actual function to be hooked will depend on what entity is spawned first.

    Let's assume that the server starts, your plugin tells Source.Python that you want to hook StartTouch and Source.Python indeed begins to monitor all created entities. As soon as a filter-passing entity is created, SP would obtain the address of its StartTouch method and set up a hook.

    1. Then a weapon with weapon_class2 classname is created. Since WeaponClass2 doesn't override StartTouch method, Source.Python will only find the base implementation (from BaseClass). So your hook gets set on BaseClass::StartTouch.

      Result
      Your hook function will be called for weapon_class2 entities.
      It will also be called for weapon_class1 if WeaponClass1::StartTouch calls the "base" method (the one from BaseClass).
      And it will also be called for any other weapons (WeaponClass3 etc) by the same rules: either if they're fine with the default StartTouch implementation or every time the base BaseClass::StartTouch is called by any means. This means you can get your hook called for entities of types that you never passed to the filter - simply because the entity you wanted to hook didn't have its own implementation.
      It makes sense to check the actual entities that you got your hook called with. Especially since the real StartTouch method is implemented in CBaseEntity, so you might end up with ANY entities calling your hook.
    2. Then a weapon with weapon_class1 classname is created. Since WeaponClass1 overrides StartTouch method, Source.Python will only find the its own implementation (WeaponClass1::StartTouch). So your hook gets set on that particular implementation.

      Result
      Your hook function will only be called for weapon_class1 entities and the entities of classes that subclass WeaponClass1 without providing their own implementation of StartTouch method.

    Entity hook decorator considers its mission completed as soon as the hook is set. So the first qualified entity to appear is the first one to serve. In two cases above, it doesn't matter how many entities of the opposite type spawn right afterwards - the consequences will remain the same.

    I'm not sure if there're any actual weapons that override StartTouch method, so you'll probably be able to avoid undetermined behavior that depends on the order of entity appearance.
    But you might get unwanted entities (not just weapons) if the closest implementation of the method is not in CBaseCombatWeapon but instead in some more generic class. In fact, I think that only triggers and some collision-specific entities override it.

    But I'd keep that enormous list of weapon classnames away from the filter...
  5. ...because StartTouch works both ways. Simply use EntityCondition.is_player filter. If a weapon is touched by a player (your current condition), the player is touched by that weapon as well. The hook would have get set on CBaseEntity::StartTouch anyways, so it just simplifies your filter and then your entity checks inside of the function. Your hook will get called for everything but triggers and collideables, so be ready to do the those checks.

    Your hook code will look like this:

    Syntax: Select all

    def pre_start_touch(stack):
    entity = make_object(Entity, stack[0]) # The player is now in stack[0], not in stack[1]
    if not entity.is_player():
    return

    other = make_object(Entity, stack[1]) # And the weapon is expected to be in stack[1]
    if other.classname not in weapon_index.keys():
    return

    # The following two lines add Player/Weapon specific stuff to the respective entities
    player = Player(entity.index)
    weapon = Weapon(other.index)
    # At this point you're sure that a player touched a weapon and you have their instances in 'player' and 'weapon' variables.


    A voice in my head (it resembles Ayuto) tells me that a more raw (and fast) BaseEntity class could be used for the sake of checking if it's a player (1st entity) and if it's a weapon (2nd entity). No need to waste resources on the Entity instances when we just abandon them in favor of Player/Weapon instances.
  6. Look into CBasePlayer::BumpWeapon function, it's exposed by Source.Python. It might prove more efficient if we're talking about player-weapon type of collisions.
  7. Now to your actual goal. You say you want the entity to be removed on touch. I'm afraid a little more introduction on the idea of your "data structure" is needed, because if you want it to be removed, it will also need to be removed from your data structure. This is essential: your data structure should not store invalidated entity instances.

    Now if what you meant by "signalling to data structure about removing the entity" is "removing the instance from data structure when the entity is removed" - well, the answer will be obvious with EntityDictionary. Not only you would be able to access the entity instance (or even your own class instance, depending on the factory) by an index, but the dictionary will take care of removing its values that are associated with the index of the removed entity. If you want to perform additional action upon such removal (but while the item is still in the dictionary), there's a method you can override for that, see the first point.
    If you don't like the idea of storing weapon instances by their indexes (I see that your data structure implies some sort of "uniqueID" for that), think of it this way: you still need to map indexes to your own uniqueIDs just so that you can detect the entity removal in OnEntityDeleted listener. And once we've established that index -> uniqueID mapping is essential, we replace it with index -> Entity instance mapping just so that EntityDictionary would take care of everything.
    It's still possible to clear your data structure clean from the invalid entity instances, but finding an entity in it will require iterating over all of your data structure and checking every instance in it for its index.

    Now if you want to keep the instance in your data structure, you should not remove the entity. In fact, removing it in a pre-hook of StartTouch is a straight way to crash the server, because the method will be called with an invalid pointer instead of a valid entity. The game doesn't expect the entity to just get removed in between two consequental lines of code (removing the entity in a pre-hook effectively does just that). You could possibly cancel the function execution at all after removal by returning non-None value from your hook, but there's still a paired StartTouch call that will not be happy.
    So how to de-spawn a weapon? I don't know what you're trying to do there, but if you really need, you can teleport the weapon outside of the playable area. Or make it non-solid and prevent it from being transmitted to clients. Just don't teleport it inside of touch-related hooks. They don't like it for some reason (in terms of crashing the server - again). To teleport the entity on touch, schedule its teleporting into a regular tick loop, outside of all the dirty hook routines. You can do that with listeners.tick.Delay and a delay value of 0 seconds. Source.Python will execute your "delayed" call as soon as possible, but not right away. It will instead get executed in a regular, safe, OnTick-based context.
Image /id/its_iPlayer
My plugins: Map Cycle • Killstreaker • DeadChat • Infinite Jumping • TripMines • AdPurge • Bot Damage • PLRBots • Entity AntiSpam

Hail, Companion. [...] Hands to yourself, sneak thief. Image
battleweaver
Member
Posts: 40
Joined: Mon Oct 02, 2017 7:57 am
Location: Moscow
Contact:

Re: Way to reverse the spawn?

Postby battleweaver » Fri Dec 01, 2017 5:16 pm

iPlayer wrote:So how to de-spawn a weapon? I don't know what you're trying to do there, but if you really need, you can teleport the weapon outside of the playable area. Or make it non-solid and prevent it from being transmitted to clients. Just don't teleport it inside of touch-related hooks. They don't like it for some reason (in terms of crashing the server - again). To teleport the entity on touch, schedule its teleporting into a regular tick loop, outside of all the dirty hook routines. You can do that with listeners.tick.Delay and a delay value of 0 seconds. Source.Python will execute your "delayed" call as soon as possible, but not right away. It will instead get executed in a regular, safe, OnTick-based context.[/list]


FIrst of all, thank you for the answer.
My goal is:
At the start of the round i recieve JSON {*uniqueID: {weapon entity parameters}}, at the end of the round i send JSON {player: [*uniqueID]}.

I am familiar with teleportation mechanics when i try to organize weapon inventory. It has some disadvantages. Here is the discussion of our work.
battleweaver
Member
Posts: 40
Joined: Mon Oct 02, 2017 7:57 am
Location: Moscow
Contact:

Re: Way to reverse the spawn?

Postby battleweaver » Tue Dec 05, 2017 1:06 pm

Syntax: Select all

@EntityPreHook(EntityCondition.equals_entity_classname(*weapon_index.keys()), 'start_touch')
def pre_start_touch(stack):
print(f'----------------------------------')
entity = make_object(Entity, stack[0])
print (f"{entity.index} touched something")
if not entity.is_player():
print (f"{entity.index} is not a player")
return
other = make_object(Entity, stack[1])
if other.classname not in weapon_index.keys():
print (f"Player {entity.index} touched {other.index} and it is not a weapon")
return
player = Player(entity.index)
weapon = Weapon(other.index)
print(f'Player {player.index} touched by {weapon.index}\n')
backpack.add_item(player, weapon_backend_ID.pop(weapon.index))

Output:

Code: Select all

backend IDs for weapons {271: 38415, 275: 38421, 278: 38422, 282: 38423, 285: 38424, 289: 38427, 292: 38428, 296: 38420, 299: 38425, 303: 38430, 306: 38418, 310: 38417, 313: 38419, 317: 38416, 320: 38429, 323: 38426}
Player's 1 inventory is  []
L 12/05/2017 - 15:47:10: World triggered "Round_Start"
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
285 touched something
285 is not a player
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
253 touched something
253 is not a player
----------------------------------
0 touched something
0 is not a player
----------------------------------
254 touched something
254 is not a player
----------------------------------
0 touched something
0 is not a player
----------------------------------
255 touched something
255 is not a player
----------------------------------
0 touched something
0 is not a player
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
303 touched something
303 is not a player
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
303 touched something
303 is not a player
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon
----------------------------------
303 touched something
303 is not a player
----------------------------------
0 touched something
0 is not a player
----------------------------------
1 touched something
Player 1 touched 0 and it is not a weapon

Is it possible that collision of player and weapon is always analysed from weapon entity's side?
User avatar
BackRaw
Senior Member
Posts: 486
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Re: Way to reverse the spawn?

Postby BackRaw » Tue Dec 05, 2017 6:48 pm

hhhmm.. maybe stack[0] and stack[1] are not always of the same type? Like, sometimes they are swapped. I'm not sure though.
User avatar
iPlayer
Developer
Posts: 590
Joined: Sat Nov 14, 2015 8:37 am
Location: Moscow
Contact:

Re: Way to reverse the spawn?

Postby iPlayer » Tue Dec 05, 2017 8:08 pm

For one, such hook should actually be split into two separate hooks. One with a EntityCondition for weapon classnames, the other one with EntityCondition.is_player

Though it wouldn't make any difference, because you hooked CBaseEntity::StartTouch anyways. It's just that if you expect players to be hooked as well, that condition doesn't make much sense.

So I guess - yes, maybe collisions of some specific combinations of entities get called only on one entity. Look into bump_weapon method though.

hhhmm.. maybe stack[0] and stack[1] are not always of the same type? Like, sometimes they are swapped. I'm not sure though.

For methods, stack[0] is the object which method is being called. In StartTouch, stack[1] is the entity that our object (entity) just touched. So the order is determined, and the only question is why this hook doesn't get called symmetrically in our case.
Image /id/its_iPlayer
My plugins: Map Cycle • Killstreaker • DeadChat • Infinite Jumping • TripMines • AdPurge • Bot Damage • PLRBots • Entity AntiSpam

Hail, Companion. [...] Hands to yourself, sneak thief. Image
battleweaver
Member
Posts: 40
Joined: Mon Oct 02, 2017 7:57 am
Location: Moscow
Contact:

Re: Way to reverse the spawn?

Postby battleweaver » Wed Dec 06, 2017 7:50 am

I swapped stack[0] and stack[1] in iPlayer's code and it works fine.

Syntax: Select all

def pre_start_touch(stack):
print(f'----------------------------------')
entity = make_object(Entity, stack[1])
print (f"{entity.index} touched something")
if not entity.is_player():
print (f"{entity.index} is not a player")
return
other = make_object(Entity, stack[0])
if other.classname not in weapon_index.keys():
print (f"Player {entity.index} touched {other.index} and it is not a weapon")
return
player = Player(entity.index)
weapon = Weapon(other.index)
print(f'Player {player.index} touched by {weapon.index}\n')
User avatar
iPlayer
Developer
Posts: 590
Joined: Sat Nov 14, 2015 8:37 am
Location: Moscow
Contact:

Re: Way to reverse the spawn?

Postby iPlayer » Wed Dec 06, 2017 9:04 am

Well, but if you swapped them, you should also edit your strings, because they have the opposite meaning now.
Image /id/its_iPlayer
My plugins: Map Cycle • Killstreaker • DeadChat • Infinite Jumping • TripMines • AdPurge • Bot Damage • PLRBots • Entity AntiSpam

Hail, Companion. [...] Hands to yourself, sneak thief. Image
User avatar
L'In20Cible
Project Leader
Posts: 1044
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: Way to reverse the spawn?

Postby L'In20Cible » Wed Dec 06, 2017 9:20 am

I'm not entirely sure why you are not looking into Player.bump_weapon like previously suggested? It might proves to be more efficient because you already know 0 will be a player and 1 will be a weapon... There is an example there: https://github.com/Source-Python-Dev-Te ... #L381-L389
User avatar
BackRaw
Senior Member
Posts: 486
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Re: Way to reverse the spawn?

Postby BackRaw » Wed Dec 06, 2017 10:26 am

You can also look at the bump_weapon hook of my Ultimate Deathmatch plugin: https://github.com/backraw/udm/blob/mas ... dm.py#L282
It basically blocks bumping into a weapon if the player is using the admin menu or if the player didn't choose the weapon as an inventory item.
battleweaver
Member
Posts: 40
Joined: Mon Oct 02, 2017 7:57 am
Location: Moscow
Contact:

Re: Way to reverse the spawn?

Postby battleweaver » Wed Dec 06, 2017 10:36 am

L'In20Cible wrote:I'm not entirely sure why you are not looking into Player.bump_weapon like previously suggested? It might proves to be more efficient because you already know 0 will be a player and 1 will be a weapon... There is an example there: https://github.com/Source-Python-Dev-Te ... #L381-L389

During the conversation with iPlayer I realised that in my case weapon entities are not the thing I need. What I really need is prop_dynamic entites with a model of weapon. At this stage of my project i plan to use weapon entites, but later i plan to change it. Meanwhile I train in describing all kinds of interactions of player with entities (pick up, scroll or choose from inventory, drop).

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 1 guest